/*
 * Copyright (c) Likewise Software.  All rights Reserved.
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the license, or (at
 * your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser
 * General Public License for more details.  You should have received a copy
 * of the GNU Lesser General Public License along with this program.  If
 * not, see <http://www.gnu.org/licenses/>.
 *
 * LIKEWISE SOFTWARE MAKES THIS SOFTWARE AVAILABLE UNDER OTHER LICENSING
 * TERMS AS WELL.  IF YOU HAVE ENTERED INTO A SEPARATE LICENSE AGREEMENT
 * WITH LIKEWISE SOFTWARE, THEN YOU MAY ELECT TO USE THE SOFTWARE UNDER THE
 * TERMS OF THAT SOFTWARE LICENSE AGREEMENT INSTEAD OF THE TERMS OF THE GNU
 * LESSER GENERAL PUBLIC LICENSE, NOTWITHSTANDING THE ABOVE NOTICE.  IF YOU
 * HAVE QUESTIONS, OR WISH TO REQUEST A COPY OF THE ALTERNATE LICENSING
 * TERMS OFFERED BY LIKEWISE SOFTWARE, PLEASE CONTACT LIKEWISE SOFTWARE AT
 * license@likewisesoftware.com
 */

/*
 * Module Name:
 *
 *        peer-direct.c
 *
 * Abstract:
 *
 *        Peer direct call support
 *
 * Authors: Brian Koropoff (bkoropoff@likewisesoftware.com)
 *
 */

#include <config.h>

#include <lwmsg/data.h>

#include "peer-private.h"
#include "call-private.h"
#include "util-private.h"
#include "connection-private.h"

#define LOCK_ENDPOINTS() LOCK_MUTEX(&endpoint_lock)
#define UNLOCK_ENDPOINTS() UNLOCK_MUTEX(&endpoint_lock)
#define LOCK_SESSION(s) LOCK_MUTEX(&(s)->session->lock)
#define UNLOCK_SESSION(s) UNLOCK_MUTEX(&(s)->session->lock)

static pthread_mutex_t endpoint_lock = PTHREAD_MUTEX_INITIALIZER;
static LWMsgRing endpoints = {&endpoints, &endpoints};

static
void
lwmsg_peer_direct_disconnect_in_lock(
    PeerDirectSession* session
    );

static
void
lwmsg_peer_direct_call_invoke_complete(
    PeerDirectCall* call,
    LWMsgStatus status,
    LWMsgBool do_complete
    );

static
void
lwmsg_peer_direct_session_complete_task(
    LWMsgTask* task,
    void* context,
    LWMsgTaskTrigger wake,
    LWMsgTaskTrigger* wait,
    LWMsgTaskTime* timeout
    );

static
PeerDirectEndpoint*
lwmsg_peer_direct_find(
    const char* name
)
{
    LWMsgRing* iter = NULL;
    PeerDirectEndpoint* endpoint = NULL;

    for (iter = endpoints.next; iter != &endpoints; iter = iter->next)
    {
        endpoint = LWMSG_OBJECT_FROM_MEMBER(iter, PeerDirectEndpoint, ring);

        if (!strcmp(name, endpoint->name))
        {
            return endpoint;
        }
    }

    return NULL;
}

LWMsgStatus
lwmsg_peer_direct_endpoint_listen(
    const char* name,
    LWMsgPeer* server
    )
{
    LWMsgStatus status = LWMSG_STATUS_SUCCESS;
    PeerDirectEndpoint* endpoint = NULL;

    LOCK_ENDPOINTS();

    endpoint = lwmsg_peer_direct_find(name);

    if (endpoint)
    {
        /* FIXME: better status code */
        BAIL_ON_ERROR(status = LWMSG_STATUS_BUSY);
    }

    BAIL_ON_ERROR(status = LWMSG_ALLOC(&endpoint));

    lwmsg_ring_init(&endpoint->ring);
    lwmsg_ring_init(&endpoint->sessions);

    endpoint->name = name;
    endpoint->server = server;

    lwmsg_ring_insert_before(&endpoints, &endpoint->ring);

error:

    UNLOCK_ENDPOINTS();

    return status;
}

static
void
lwmsg_peer_direct_call_cancel_in_lock(
    PeerDirectCall* call
    )
{
    if (!(call->state & DIRECT_CALL_CANCEL_START) &&
        !(call->state & DIRECT_CALL_COMPLETED))
    {
        call->state |= DIRECT_CALL_CANCEL_START;
        if (call->state & DIRECT_CALL_DISPATCHED)
        {
            if (call->cancel)
            {
                UNLOCK_SESSION(call->session);
                call->cancel(&call->callee, call->cancel_data);
                LOCK_SESSION(call->session);
            }
            call->state |= DIRECT_CALL_CANCEL_DONE;
        }

        if ((call->state & DIRECT_CALL_DISPATCHED) &&
            (call->state & DIRECT_CALL_COMPLETED))
        {
            /* Calling the completion callback here might cause lock reentrancy
             * problems for the client application.  Post the call to a deferred
             * completion queue instead.
             */
            lwmsg_ring_remove(&call->ring);
            lwmsg_ring_enqueue(&call->session->deferred, &call->ring);
            lwmsg_task_wake(call->session->complete_task);
        }
    }
}

static
void
lwmsg_peer_direct_session_rundown_in_lock(
    PeerDirectSession* session
    )
{
    LWMsgRing* iter = NULL;
    PeerDirectCall* call = NULL;
    LWMsgRing calls;

    lwmsg_ring_init(&calls);

    if (!session->rundown)
    {
        session->rundown = LWMSG_TRUE;

        while (!lwmsg_ring_is_empty(&session->calls))
        {
            /* Move call onto local ring.
             * This is necessary because we release the
             * session lock while running a cancel function,
             * during which session->calls can change.  By
             * always dequeuing from the front of the list,
             * we can visit every call safely.
             */
            lwmsg_ring_dequeue(&session->calls, &iter);
            call = LWMSG_OBJECT_FROM_MEMBER(iter, PeerDirectCall, ring);
            lwmsg_ring_enqueue(&calls, &call->ring);
            lwmsg_peer_direct_call_cancel_in_lock(call);
        }

        /* Move remaining calls back */
        lwmsg_ring_move(&calls, &session->calls);
    }

    /* Wait for calls to finish */
    while (session->call_count)
    {
        pthread_cond_wait(&session->session->event, &session->session->lock);
    }

    /* Null out endpoint */
    session->endpoint = NULL;

    /* Now we can run down all handles on the main session */
    lwmsg_peer_session_reset(session->session);

    /* Release session data */
    if (session->data && session->destruct)
    {
        session->destruct(session->token, session->data);
        session->data = NULL;
    }
}

void
lwmsg_peer_direct_endpoint_shutdown(
    const char* name,
    LWMsgPeer* server
    )
{
    PeerDirectEndpoint* endpoint = NULL;
    LWMsgRing sessions = {&sessions, &sessions};
    LWMsgRing* iter = NULL;
    PeerDirectSession* session = NULL;

    LOCK_ENDPOINTS();

    /* Find and remove endpoint from list to prevent further connections */
    endpoint = lwmsg_peer_direct_find(name);
    LWMSG_ASSERT(endpoint && endpoint->server == server);
    lwmsg_ring_remove(&endpoint->ring);

    /* Move sessions to temporary list */
    while (!lwmsg_ring_is_empty(&endpoint->sessions))
    {
        lwmsg_ring_dequeue(&endpoint->sessions, &iter);
        session = LWMSG_OBJECT_FROM_MEMBER(iter, PeerDirectSession, ring);
        lwmsg_ring_enqueue(&sessions, &session->temp_ring);
    }

    UNLOCK_ENDPOINTS();

    /* Run down all remaining sessions */
    for (iter = sessions.next; iter != &sessions; iter = iter->next)
    {
        session = LWMSG_OBJECT_FROM_MEMBER(iter, PeerDirectSession, temp_ring);

        LOCK_SESSION(session);
        lwmsg_peer_direct_session_rundown_in_lock(session);
        UNLOCK_SESSION(session);
        lwmsg_peer_direct_session_release(session);
    }

    free(endpoint);
}

LWMsgStatus
lwmsg_peer_direct_session_connect(
    const char* name,
    PeerDirectSession* session
    )
{
    LWMsgStatus status = LWMSG_STATUS_SUCCESS;
    PeerDirectEndpoint* endpoint = NULL;

    LOCK_ENDPOINTS();

    endpoint = lwmsg_peer_direct_find(name);

    if (!endpoint)
    {
        /* FIXME: better status code */
        UNLOCK_ENDPOINTS();
        BAIL_ON_ERROR(status = LWMSG_STATUS_NOT_FOUND);
    }

    lwmsg_ring_insert_before(&endpoint->sessions, &session->ring);
    session->endpoint = endpoint;
    /* Add reference from endpoint back to session.  This circularity will be
     * broken by an explicit disconnect by the client or an explicit shutdown
     * by the server.
     */
    session->refs++;
    session->rundown = FALSE;

    UNLOCK_ENDPOINTS();

    BAIL_ON_ERROR(status = lwmsg_task_new(
            session->session->peer->task_manager,
            NULL,
            lwmsg_peer_direct_session_complete_task,
            session,
            &session->complete_task));

    /* Add reference from task back to session.  Cycle is broken by session
     * being disconnected (either by client or server shutdown).
     */
    session->refs++;

    if (endpoint->server->session_construct)
    {
        BAIL_ON_ERROR(status = endpoint->server->session_construct(
            session->token,
            endpoint->server->session_construct_data,
            &session->data));
    }

    session->destruct = endpoint->server->session_destruct;

error:

    if (status && endpoint)
    {
        /* Back out of the connection */
        lwmsg_peer_direct_disconnect_in_lock(session);
    }

    return status;
}

static
void
lwmsg_peer_direct_disconnect_in_lock(
    PeerDirectSession* session
    )
{
    lwmsg_peer_direct_session_rundown_in_lock(session);
    if (session->complete_task)
    {
        lwmsg_task_cancel(session->complete_task);
    }
    LOCK_ENDPOINTS();
    /* If we haven't been removed from the endpoint's
     * session list, remove it now and drop the endpoint's
     * reference to us.  If we are already removed, the
     * server will drop the reference itself in
     * lwmsg_peer_direct_endpoint_shutdown().
     */
    if (!lwmsg_ring_is_empty(&session->ring))
    {
        lwmsg_ring_remove(&session->ring);
        /* This won't be the last reference */
        session->refs--;
    }
    UNLOCK_ENDPOINTS();
}

void
lwmsg_peer_direct_session_disconnect(
    PeerDirectSession* session
    )
{
    LOCK_SESSION(session);
    lwmsg_peer_direct_disconnect_in_lock(session);
    UNLOCK_SESSION(session);
}

static
LWMsgSecurityToken*
direct_get_security_token (
    LWMsgSession* session
    )
{
    PeerDirectSession* dsession = (PeerDirectSession*) session;

    return dsession->token;
}

static
LWMsgStatus
direct_register_handle_local(
    LWMsgSession* session,
    const char* type,
    void* data,
    void (*cleanup)(void* ptr),
    LWMsgHandle** handle
    )
{
    PeerDirectSession* dsession = (PeerDirectSession*) session;

    return dsession->session->base.sclass->register_handle_local(
        (LWMsgSession*) dsession->session,
        type,
        data,
        cleanup,
        handle);
}

static
void
direct_retain_handle(
    LWMsgSession* session,
    LWMsgHandle* handle
    )
{
    PeerDirectSession* dsession = (PeerDirectSession*) session;

    return dsession->session->base.sclass->retain_handle(
        (LWMsgSession*) dsession->session,
        handle);
}

static
void
direct_release_handle(
    LWMsgSession* session,
    LWMsgHandle* handle
    )
{
    PeerDirectSession* dsession = (PeerDirectSession*) session;

    return dsession->session->base.sclass->release_handle(
        (LWMsgSession*) dsession->session,
        handle);
}

static
LWMsgStatus
direct_unregister_handle(
    LWMsgSession* session,
    LWMsgHandle* handle
    )
{
    PeerDirectSession* dsession = (PeerDirectSession*) session;

    return dsession->session->base.sclass->unregister_handle(
        (LWMsgSession*) dsession->session,
        handle);
}

static
LWMsgStatus
direct_resolve_handle_to_id(
    LWMsgSession* session,
    LWMsgHandle* handle,
    const char** type,
    LWMsgHandleType* htype,
    LWMsgHandleID* hid
    )
{
    PeerDirectSession* dsession = (PeerDirectSession*) session;

    return dsession->session->base.sclass->resolve_handle_to_id(
        (LWMsgSession*) dsession->session,
        handle,
        type,
        htype,
        hid);
}

static
LWMsgStatus
direct_get_handle_data(
    LWMsgSession* session,
    LWMsgHandle* handle,
    void** data
    )
{
    PeerDirectSession* dsession = (PeerDirectSession*) session;

    return dsession->session->base.sclass->get_handle_data(
        (LWMsgSession*) dsession->session,
        handle,
        data);
}

static
void*
direct_get_data (
    LWMsgSession* session
    )
{
    PeerDirectSession* dsession = (PeerDirectSession*) session;

    return dsession->data;
}

static
LWMsgStatus
direct_acquire_call(
    LWMsgSession* session,
    LWMsgCall** call
    )
{
    return lwmsg_peer_direct_call_new((PeerDirectSession*) session, LWMSG_TRUE, (PeerDirectCall**)(void*) call);
}

static LWMsgSessionClass direct_session_class =
{
    .register_handle_local = direct_register_handle_local,
    .retain_handle = direct_retain_handle,
    .release_handle = direct_release_handle,
    .unregister_handle = direct_unregister_handle,
    .resolve_handle_to_id = direct_resolve_handle_to_id,
    .get_handle_data = direct_get_handle_data,
    .get_data = direct_get_data,
    .get_peer_security_token = direct_get_security_token,
    .acquire_call = direct_acquire_call
};

static
LWMsgStatus
lwmsg_peer_direct_call_invoke_dispatch(
    PeerDirectCall* call,
    LWMsgPeerCallFunction func
    )
{
    LWMsgPeer* source_peer = !call->is_callback ? call->session->session->peer : call->session->endpoint->server;
    LWMsgPeer* target_peer = call->is_callback ? call->session->session->peer : call->session->endpoint->server;

    if (source_peer->trace_begin)
    {
        source_peer->trace_begin(&call->caller, call->in, LWMSG_STATUS_SUCCESS, source_peer->trace_data);
    }

    if (target_peer->trace_begin)
    {
        target_peer->trace_begin(&call->callee, call->in, LWMSG_STATUS_SUCCESS, target_peer->trace_data);
    }

    return func(&call->callee, call->in, call->out, target_peer->dispatch_data);
}

static
void
lwmsg_peer_direct_call_invoke_complete(
    PeerDirectCall* call,
    LWMsgStatus status,
    LWMsgBool do_complete
    )
{
    LWMsgPeer* source_peer = !call->is_callback ? call->session->session->peer : call->session->endpoint->server;
    LWMsgPeer* target_peer = call->is_callback ? call->session->session->peer : call->session->endpoint->server;

    if (source_peer->trace_end)
    {
        source_peer->trace_end(&call->caller, call->out, status, source_peer->trace_data);
    }

    if (target_peer->trace_end)
    {
        target_peer->trace_end(&call->callee, call->out, status, target_peer->trace_data);
    }

    if (do_complete && call->complete)
    {
        call->complete(&call->caller, status, call->complete_data);
    }
}

static
void
lwmsg_peer_direct_call_release(
    LWMsgCall* call
    )
{
    PeerDirectCall* dcall = LWMSG_OBJECT_FROM_MEMBER(call, PeerDirectCall, caller);
    PeerDirectSession* session = dcall->session;
    uint32_t refs = 0;

    LOCK_SESSION(session);
    refs = --dcall->refs;
    UNLOCK_SESSION(session);

    if (refs == 0)
    {
        lwmsg_ring_remove(&dcall->ring);
        lwmsg_peer_direct_session_release(dcall->session);
        free(dcall);
    }
}

/* Remove an in-progress call from session */
static
void
lwmsg_peer_direct_call_remove(
    PeerDirectCall* call
    )
{
    PeerDirectSession* session = call->session;

    /* First, remove the call from in-progress list */
    LOCK_SESSION(session);
    lwmsg_ring_remove(&call->ring);
    /* We need to decrement in-progress call count later */
    UNLOCK_SESSION(session);

    /* Release the call */
    lwmsg_peer_direct_call_release(&call->caller);

    // Decrement in-progress call count and notify waiters.
    // This must be done *after* releasing the call as
    // the session rundown logic needs to guarantee that
    // the session won't be accessed after call_count is 0
    LOCK_SESSION(session);
    session->call_count--;
    if (session->rundown)
    {
        pthread_cond_broadcast(&session->session->event);
    }
    UNLOCK_SESSION(session);
}

static
void
lwmsg_peer_direct_call_worker(
    void* data
    )
{
    LWMsgStatus status = LWMSG_STATUS_SUCCESS;
    PeerDirectCall* dcall = data;
    PeerDirectSession* session = dcall->session;
    PeerDirectEndpoint* endpoint = dcall->session->endpoint;
    LWMsgPeerCallFunction func = NULL;
    LWMsgPeer* target_peer = NULL;
    LWMsgBool completed = LWMSG_FALSE;
    LWMsgBool cancel_start = LWMSG_FALSE;
    LWMsgBool cancel_done = LWMSG_FALSE;
    LWMsgBool waiting = LWMSG_FALSE;
    LWMsgBool remove = LWMSG_FALSE;

    target_peer = dcall->is_callback ? session->session->peer : endpoint->server;
    func = target_peer->dispatch.vector[dcall->in->tag]->data;

    status = lwmsg_peer_direct_call_invoke_dispatch(dcall, func);

    LOCK_SESSION(session);
    dcall->state |= DIRECT_CALL_DISPATCHED;

    cancel_start = dcall->state & DIRECT_CALL_CANCEL_START;
    cancel_done = dcall->state & DIRECT_CALL_CANCEL_DONE;
    completed = dcall->state & DIRECT_CALL_COMPLETED;
    waiting = dcall->state & DIRECT_CALL_WAITING;

    // Unacknowledged cancel
    if (cancel_start && !cancel_done)
    {
        if (!completed && dcall->cancel)
        {
            UNLOCK_SESSION(session);
            dcall->cancel(&dcall->callee, dcall->cancel_data);
            LOCK_SESSION(session);
            completed = dcall->state & DIRECT_CALL_COMPLETED;
            waiting = dcall->state & DIRECT_CALL_WAITING;
        }
        dcall->state |= DIRECT_CALL_CANCEL_DONE;
    }

    if (completed || status != LWMSG_STATUS_PENDING)
    {
        if (status == LWMSG_STATUS_PENDING)
        {
            status = dcall->status;
        }
    }
    UNLOCK_SESSION(session);

    switch (status)
    {
    case LWMSG_STATUS_PENDING:
        break;
    default:
        if (dcall->complete)
        {
            lwmsg_peer_direct_call_invoke_complete(dcall, status, LWMSG_TRUE);
            remove = LWMSG_TRUE;
        }

        if (waiting)
        {
            pthread_cond_broadcast(&dcall->session->session->event);
        }

        if (remove)
        {
            lwmsg_peer_direct_call_remove(dcall);
        }
    }
}

static
LWMsgStatus
lwmsg_peer_direct_call_dispatch(
    LWMsgCall* call,
    const LWMsgParams* in,
    LWMsgParams* out,
    LWMsgCompleteFunction complete,
    void* data
    )
{
    LWMsgStatus status = LWMSG_STATUS_SUCCESS;
    PeerDirectCall* dcall = LWMSG_OBJECT_FROM_MEMBER(call, PeerDirectCall, caller);
    PeerDirectSession* session = dcall->session;
    LWMsgPeerCallFunction func = NULL;
    LWMsgBool rundown = LWMSG_FALSE;
    LWMsgBool canceled = LWMSG_FALSE;
    LWMsgBool cancel_done = LWMSG_FALSE;
    LWMsgBool completed = LWMSG_FALSE;
    LWMsgPeer* target_peer = NULL;
    LWMsgDispatchSpec* spec = NULL;
    LWMsgBool remove = LWMSG_FALSE;

    LOCK_SESSION(session);
    rundown = session->rundown;
    if (!rundown)
    {
        dcall->state = 0;
        dcall->complete = complete;
        dcall->complete_data = data;
        dcall->in = in;
        dcall->out = out;
        lwmsg_ring_enqueue(&session->calls, &dcall->ring);
        session->call_count++;
        dcall->refs++;
    }
    UNLOCK_SESSION(session);

    if (rundown)
    {
        BAIL_ON_ERROR(status = LWMSG_STATUS_CANCELLED);
    }

    /* The call is now in the in-progress list, so we need to remove it
     * if anything goes wrong */
    remove = LWMSG_TRUE;

    target_peer = dcall->is_callback ? session->session->peer : session->endpoint->server;

    if (in->tag >= target_peer->dispatch.vector_length)
    {
        BAIL_ON_ERROR(status = LWMSG_STATUS_UNIMPLEMENTED);
    }

    spec = target_peer->dispatch.vector[in->tag];
    func = spec->data;

    if (!func)
    {
        BAIL_ON_ERROR(status = LWMSG_STATUS_UNIMPLEMENTED);
    }

    if (complete && spec->type == LWMSG_DISPATCH_TYPE_BLOCK)
    {
        /*
         * The caller doesn't want to block but the callee does,
         * so dispatch a work item and return LWMSG_STATUS_PENDING
         */

        BAIL_ON_ERROR(status = lwmsg_task_dispatch_work_item(
            target_peer->task_manager,
            lwmsg_peer_direct_call_worker,
            dcall));
        // Work item is responsible for removing call now
        remove = LWMSG_FALSE;
        BAIL_ON_ERROR(status = LWMSG_STATUS_PENDING);
    }

    // We won't be responsible for removing the call after dispatch
    // except in certain conditions handled below
    remove = LWMSG_FALSE;
    status = lwmsg_peer_direct_call_invoke_dispatch(dcall, func);

    switch (status)
    {
    case LWMSG_STATUS_PENDING:

        LOCK_SESSION(session);
        completed = dcall->state & DIRECT_CALL_COMPLETED;
        canceled = dcall->state & DIRECT_CALL_CANCEL_START;
        cancel_done = dcall->state & DIRECT_CALL_CANCEL_DONE;
        dcall->state |= DIRECT_CALL_DISPATCHED;

        if (canceled && !cancel_done)
        {
            if (!completed && dcall->cancel)
            {
                UNLOCK_SESSION(session);
                dcall->cancel(&dcall->caller, dcall->cancel_data);
                LOCK_SESSION(session);
                completed = dcall->state & DIRECT_CALL_COMPLETED;
            }

            dcall->state |= DIRECT_CALL_CANCEL_DONE;
        }

        /* If sync call, we have to wait for completion */
        if (!dcall->complete)
        {
            dcall->state |= DIRECT_CALL_WAITING;

            while (!completed)
            {
                pthread_cond_wait(&session->session->event, &session->session->lock);
                completed = dcall->state & DIRECT_CALL_COMPLETED;
            }
        }

        if (completed)
        {
            // Call was pended but immediately completed, so we are in charge of removal
            remove = LWMSG_TRUE;
            status = dcall->status;
        }
        UNLOCK_SESSION(session);

        BAIL_ON_ERROR(status);
        break;
    default:
        // Synchronous completion -- we are in charge of removal
        remove = LWMSG_TRUE;
        BAIL_ON_ERROR(status);
    }

    if (status != LWMSG_STATUS_PENDING)
    {
        lwmsg_peer_direct_call_invoke_complete(dcall, status, LWMSG_FALSE);
    }

error:

    if (remove)
    {
        lwmsg_peer_direct_call_remove(dcall);
    }

    return status;
}

static
LWMsgStatus
lwmsg_peer_direct_call_pend(
    LWMsgCall* call,
    LWMsgCancelFunction cancel,
    void* data
    )
{
    PeerDirectCall* dcall = LWMSG_OBJECT_FROM_MEMBER(call, PeerDirectCall, callee);

    dcall->cancel = cancel;
    dcall->cancel_data = data;

    return LWMSG_STATUS_SUCCESS;
}

static
LWMsgStatus
lwmsg_peer_direct_call_complete(
    LWMsgCall* call,
    LWMsgStatus status
    )
{
    PeerDirectCall* dcall = LWMSG_OBJECT_FROM_MEMBER(call, PeerDirectCall, callee);
    LWMsgBool dispatched = LWMSG_FALSE;
    LWMsgBool waiting = LWMSG_FALSE;
    LWMsgBool remove = LWMSG_FALSE;
    LWMsgBool cancel_start = LWMSG_FALSE;
    LWMsgBool cancel_done = LWMSG_FALSE;

    LOCK_SESSION(dcall->session);
    dcall->state |= DIRECT_CALL_COMPLETED;
    dcall->status = status;
    cancel_start = dcall->state & DIRECT_CALL_CANCEL_START;
    cancel_done = dcall->state & DIRECT_CALL_CANCEL_DONE;
    dispatched = dcall->state & DIRECT_CALL_DISPATCHED;
    waiting = dcall->state & DIRECT_CALL_WAITING;
    UNLOCK_SESSION(dcall->session);

    if (dispatched &&
        !(cancel_start && !cancel_done) &&
        dcall->complete)
    {
        lwmsg_peer_direct_call_invoke_complete(dcall, status, TRUE);
        remove = LWMSG_TRUE;
    }

    if (waiting)
    {
        pthread_cond_broadcast(&dcall->session->session->event);
    }

    if (remove)
    {
        lwmsg_peer_direct_call_remove(dcall);
    }

    return LWMSG_STATUS_SUCCESS;
}

static
LWMsgStatus
lwmsg_peer_direct_call_cancel(
    LWMsgCall* call
    )
{
    PeerDirectCall* dcall = LWMSG_OBJECT_FROM_MEMBER(call, PeerDirectCall, caller);

    LOCK_SESSION(dcall->session);
    lwmsg_peer_direct_call_cancel_in_lock(dcall);
    UNLOCK_SESSION(dcall->session);

    return LWMSG_STATUS_SUCCESS;

}

static
LWMsgStatus
lwmsg_peer_direct_call_wait(
    LWMsgCall* call
    )
{
    LWMsgStatus status = LWMSG_STATUS_SUCCESS;
    PeerDirectCall* dcall = LWMSG_OBJECT_FROM_MEMBER(call, PeerDirectCall, caller);

    LOCK_SESSION(dcall->session);
    dcall->state |= DIRECT_CALL_WAITING;

    while (!(dcall->state & DIRECT_CALL_COMPLETED))
    {
        pthread_cond_wait(&dcall->session->session->event, &dcall->session->session->lock);
    }
    status = dcall->status;

    UNLOCK_SESSION(dcall->session);

    return status;
}


static
LWMsgStatus
lwmsg_peer_direct_call_destroy_params(
    LWMsgCall* call,
    LWMsgParams* params
    )
{
    LWMsgStatus status = LWMSG_STATUS_SUCCESS;
    PeerDirectCall* dcall = LWMSG_OBJECT_FROM_MEMBER(call, PeerDirectCall, caller);
    LWMsgTypeSpec* spec = NULL;

    if (params->data && params->tag != LWMSG_TAG_INVALID)
    {
        BAIL_ON_ERROR(status = lwmsg_protocol_get_message_type(
            dcall->session->session->peer->protocol,
            params->tag,
            &spec));

        lwmsg_data_free_graph_cleanup(
            dcall->session->session->peer->context,
            spec,
            params->data);

        params->tag = LWMSG_TAG_INVALID;
        params->data = NULL;
    }

error:

    return status;
}

static
LWMsgSession*
lwmsg_peer_direct_call_get_session_caller(
    LWMsgCall* call
    )
{
    PeerDirectCall* dcall = LWMSG_OBJECT_FROM_MEMBER(call, PeerDirectCall, caller);

    return (LWMsgSession*) dcall->session->session;
}

static
LWMsgSession*
lwmsg_peer_direct_call_get_session_callee(
    LWMsgCall* call
    )
{
    PeerDirectCall* dcall = LWMSG_OBJECT_FROM_MEMBER(call, PeerDirectCall, callee);

    return (LWMsgSession*) dcall->session;
}

static LWMsgCallClass direct_caller_class =
{
    .release = lwmsg_peer_direct_call_release,
    .dispatch = lwmsg_peer_direct_call_dispatch,
    .cancel = lwmsg_peer_direct_call_cancel,
    .wait = lwmsg_peer_direct_call_wait,
    .destroy_params = lwmsg_peer_direct_call_destroy_params,
    .get_session = lwmsg_peer_direct_call_get_session_caller
};

static LWMsgCallClass direct_callee_class =
{
    .pend = lwmsg_peer_direct_call_pend,
    .complete = lwmsg_peer_direct_call_complete,
    .get_session = lwmsg_peer_direct_call_get_session_callee
};

LWMsgStatus
lwmsg_peer_direct_call_new(
    PeerDirectSession* session,
    LWMsgBool is_callback,
    PeerDirectCall** call
    )
{
    LWMsgStatus status = LWMSG_STATUS_SUCCESS;
    PeerDirectCall* dcall = NULL;

    BAIL_ON_ERROR(status = LWMSG_ALLOC(&dcall));

    dcall->caller.vtbl = &direct_caller_class;
    dcall->callee.vtbl = &direct_callee_class;
    lwmsg_ring_init(&dcall->ring);
    dcall->session = session;
    dcall->refs = 1;
    dcall->is_callback = is_callback;
    dcall->caller.is_outgoing = LWMSG_TRUE;
    dcall->callee.is_outgoing = LWMSG_FALSE;

error:

    *call = dcall;

    return status;
}

static
void
lwmsg_peer_direct_session_delete(
    PeerDirectSession* my_session
    )
{
    lwmsg_peer_session_release(my_session->session);

    if (my_session->token)
    {
        lwmsg_security_token_delete(my_session->token);
    }

    if (my_session->complete_task)
    {
        lwmsg_task_release(my_session->complete_task);
    }

    free(my_session);
}

void
lwmsg_peer_direct_session_release(
    PeerDirectSession* session
    )
{
    uint32_t refs = 0;

    LOCK_SESSION(session);
    refs = --session->refs;
    UNLOCK_SESSION(session);

    if (refs == 0)
    {
        lwmsg_peer_direct_session_delete(session);
    }
}

static
void
lwmsg_peer_direct_session_complete_task(
    LWMsgTask* task,
    void* context,
    LWMsgTaskTrigger wake,
    LWMsgTaskTrigger* wait,
    LWMsgTaskTime* timeout
    )
{
    PeerDirectSession* session = (PeerDirectSession*) context;
    LWMsgRing* ring = NULL;
    PeerDirectCall* call = NULL;

    LOCK_SESSION(session);

    if (wake & LWMSG_TASK_TRIGGER_CANCEL && lwmsg_ring_is_empty(&session->deferred))
    {
        *wait = 0;
    }
    else if (!lwmsg_ring_is_empty(&session->deferred))
    {
        lwmsg_ring_dequeue(&session->deferred, &ring);
        call = LWMSG_OBJECT_FROM_MEMBER(ring, PeerDirectCall, ring);
        *wait = LWMSG_TASK_TRIGGER_YIELD;
    }
    else
    {
        *wait = LWMSG_TASK_TRIGGER_EXPLICIT;
    }

    UNLOCK_SESSION(session);

    if (call)
    {
        lwmsg_peer_direct_call_invoke_complete(call, call->status, TRUE);
        lwmsg_peer_direct_call_remove(call);
    }
}

LWMsgStatus
lwmsg_peer_direct_session_new(
    PeerSession* session,
    PeerDirectSession** direct_session
    )
{
    LWMsgStatus status = LWMSG_STATUS_SUCCESS;
    PeerDirectSession* my_session = NULL;
    LWMsgBool attr_destroy = LWMSG_FALSE;
    pthread_mutexattr_t attr;

    BAIL_ON_ERROR(status = LWMSG_ALLOC(&my_session));

    my_session->base.sclass = &direct_session_class;

    my_session->session = session;
    my_session->refs = 1;

    lwmsg_ring_init(&my_session->calls);
    lwmsg_ring_init(&my_session->deferred);
    lwmsg_ring_init(&my_session->ring);
    lwmsg_ring_init(&my_session->temp_ring);

    BAIL_ON_ERROR(status = lwmsg_local_token_new(geteuid(), getegid(), getpid(), &my_session->token));

done:

    *direct_session = my_session;

    if (attr_destroy)
    {
        pthread_mutexattr_destroy(&attr);
    }

    return status;

error:

    if (my_session)
    {
        lwmsg_peer_direct_session_delete(my_session);
        my_session = NULL;
    }

    goto done;
}

