/*
 * 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-private.h
 *
 * Abstract:
 *
 *        Multi-threaded peer API (private header)
 *
 * Authors: Brian Koropoff (bkoropoff@likewisesoftware.com)
 *
 */
#ifndef __LWMSG_PEER_PRIVATE_H__
#define __LWMSG_PEER_PRIVATE_H__

#include <lwmsg/peer.h>
#include <lwmsg/protocol.h>
#include <lwmsg/connection.h>
#include <lwmsg/assoc.h>
#include <lwmsg/session.h>
#include "context-private.h"
#include "util-private.h"
#include "call-private.h"
#include "session-private.h"
#include "task-private.h"

#include <pthread.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>

/* Key for looking up handle by ID in table */
typedef struct PeerHandleKey
{
    LWMsgHandleType type;
    LWMsgHandleID id;
} PeerHandleKey;

/* State of listeners for a peer */
typedef enum PeerListenState
{
    PEER_STATE_STOPPED = 0,
    PEER_STATE_STARTING,
    PEER_STATE_STARTED,
    PEER_STATE_STOPPING,
    PEER_STATE_ERROR
} PeerListenState;

/* An endpoint address in a list of endpoints to listen on or connect to */
typedef struct PeerEndpointAddress
{
    LWMsgEndpointType type;
    char* endpoint;
    mode_t permissions;
    int fd;
    LWMsgRing ring;
} PeerEndpointAddress;

/* Tracks a call over an assoc session */
typedef struct PeerAssocCall
{
    LWMsgCall base;
    /* Links call into hash table indexed by cookie in PeerAssocSession */
    LWMsgRing hash_ring;
    /* Links call into queue of active calls in PeerAssocSession */
    LWMsgRing queue_ring;
    /* Owning session */
    struct PeerAssocSession* session;
    /* State flags */
    enum
    {
        PEER_CALL_NONE = 0x0,
        PEER_CALL_DISPATCHED = 0x1,
        PEER_CALL_PENDED = 0x2,
        PEER_CALL_COMPLETED = 0x4,
        PEER_CALL_CANCEL_START = 0x8,
        PEER_CALL_CANCEL_DONE = 0x10,
        PEER_CALL_WAITING = 0x20
    } volatile state;
    /* Reference count
     *
     * Incoming calls - 1 reference, held by session
     * Outgoing calls - 1 reference, held by application
     *                  +1 held by session while call is in progress
     */
    int volatile refs;

    /* Call cookie for coordinating request/response messages over association */
    LWMsgCookie cookie;
    /* Status of call */
    LWMsgStatus volatile status;

    union
    {
        /* Tracks incoming call state */
        struct
        {
            LWMsgCancelFunction cancel;
            void* cancel_data;
            LWMsgDispatchSpec* spec;
            void* dispatch_data;
            LWMsgParams in;
            LWMsgParams out;
        } incoming;
        /* Tracks outgoing call state */
        struct
        {
            LWMsgCompleteFunction complete;
            void* complete_data;
            const LWMsgParams* in;
            LWMsgParams* out;
        } outgoing;
    } params;
} PeerAssocCall;

/* Listens for incoming assoc sessions */
typedef struct PeerAssocEndpoint
{
    LWMsgPeer* peer;
    LWMsgTask* event_task;
    LWMsgEndpointType type;
    char* endpoint;
    mode_t perms;
    int fd;
} PeerAssocEndpoint;

/* Current state in PeerAssocSession state machine */
typedef enum PeerAssocSessionState
{
    PEER_TASK_BEGIN_ACCEPT,
    PEER_TASK_FINISH_ACCEPT,
    PEER_TASK_BEGIN_CONNECT,
    PEER_TASK_FINISH_CONNECT,
    PEER_TASK_DISPATCH,
    PEER_TASK_BEGIN_CLOSE,
    PEER_TASK_FINISH_CLOSE,
    PEER_TASK_BEGIN_RESET,
    PEER_TASK_FINISH_RESET,
    PEER_TASK_DROP,
    PEER_TASK_DONE
} PeerAssocSessionState;

/* Used by a PeerSession to establish a session over an association. */
typedef struct PeerAssocSession
{
    /* Thread pool task that coordinates calls and assoc I/O */
    LWMsgTask* event_task;
    /* State machine state */
    PeerAssocSessionState state;
    /* Association with other peer */
    LWMsgAssoc* assoc;
    /* Owning abstract session */
    struct PeerSession* session;
    /* Outstanding incoming/outgoing calls indexed by cookie */
    LWMsgHashTable incoming_calls;
    LWMsgHashTable outgoing_calls;
    /* Outstanding incoming/outgoing calls that have "woken up" due
     * to completion, cancellation, etc. and need processing */
    LWMsgRing active_incoming_calls;
    LWMsgRing active_outgoing_calls;
    /* Current incoming/outgoing message on assoc */
    LWMsgMessage incoming_message;
    LWMsgMessage outgoing_message;
    /* Whether there is an incoming/outgoing message in progress */
    unsigned incoming:1;
    unsigned outgoing:1;
    /* Whether the assoc is blocked on sending/receiving */
    unsigned send_blocked:1;
    unsigned recv_blocked:1;
    /* Whether we have started but not finished receiving a message */
    unsigned recv_partial:1;
    /* Whether the outgoing message should be destroyed after it is sent.
     * Only call replies should be destroyed.  Call request parameters are
     * owned by the caller. */
    unsigned destroy_outgoing:1;
    /* Next cookie to use for an outgoing call */
    LWMsgCookie volatile next_cookie;
    /* Ref count */
    uint32_t volatile refs;
    /* Status of session */
    LWMsgStatus volatile status;
} PeerAssocSession;

/* Listens for incoming direct sessions */
typedef struct PeerDirectEndpoint
{
    /* Endpoint name */
    const char* name;
    /* Peer that registered this endpoint */
    struct LWMsgPeer* server;
    /* List of active sessions */
    LWMsgRing sessions;
    /* Links endpoint into global table of direct endpoints */
    LWMsgRing ring;
} PeerDirectEndpoint;

/* This structure is used by a PeerSession to maintain
 * a connection to a PeerDirectEndpoint.  It also represents
 * server-side session state:
 * - User data
 * - Security token
 * - LWMsgSession* returned by callee-side lwmsg_call_get_session()
 *
 * All handles are allocated and managed by the PeerSession.  This
 * is so that if server shuts down and starts back up in another
 * process, the PeerSession can still transparently reconnect
 * without any of its handles becoming dangling pointers (although
 * they will be invalidated). */
typedef struct PeerDirectSession
{
    /* Base */
    LWMsgSession base;
    /* Actual session */
    struct PeerSession* session;
    /* Outstanding calls */
    LWMsgRing calls;
    /* Deferred completion calls */
    LWMsgRing deferred;
    /* Links session into list in PeerDirectEndpoint */
    LWMsgRing ring;
    /* Links session into temporary list used by lwmsg_peer_direct_endpoint_shutdown() */
    LWMsgRing temp_ring;
    /* The direct endpoint the session is connected to.  NULLing this out prevents
     * further calls from being dispatched. */
    PeerDirectEndpoint* endpoint;
    /* Session destructor */
    LWMsgSessionDestructFunction destruct;
    /* User data from session constructor */
    void* data;
    /* Trivial security token representing ourselves */
    LWMsgSecurityToken* token;
    /* Task to run deferred completions */
    LWMsgTask* complete_task;
    /* Ref count */
    uint32_t volatile refs;
    /* Count of in-progress calls */
    uint32_t volatile call_count;
    /* Are we running down session? */
    LWMsgBool volatile rundown;
} PeerDirectSession;

/* Tracks a call over a direct session.
 * This struct actually contains two LWMsgCall substructures representing
 * the caller and callee "views" of the call.  This is so that functions
 * like lwmsg_call_get_session() return the correct result depending on
 * which side calls it. */
typedef struct PeerDirectCall
{
    /* Caller/callee handles */
    LWMsgCall caller;
    LWMsgCall callee;
    /* Owning direct session state */
    PeerDirectSession* session;
    /* Call state flags */
    enum
    {
        DIRECT_CALL_DISPATCHED = 0x1,
        DIRECT_CALL_CANCEL_START = 0x2,
        DIRECT_CALL_CANCEL_DONE = 0x4,
        DIRECT_CALL_COMPLETED = 0x8,
        DIRECT_CALL_WAITING = 0x10
    } volatile state;
    /* Basic call parameters */
    const LWMsgParams* in;
    LWMsgParams* out;
    LWMsgStatus status;
    LWMsgCompleteFunction complete;
    void* complete_data;
    LWMsgCancelFunction cancel;
    void* cancel_data;
    /* Links call into list on PeerDirectSession */
    LWMsgRing ring;
    /* Ref count */
    uint8_t volatile refs;
    /* Is this a call from the server back to the client? */
    unsigned is_callback:1;
} PeerDirectCall;

/* Tracks an abstract session with another peer.
 * The session can be established or re-established on demand
 * via the direct_session or assoc_session members.  Handles
 * are preserved across reconnects (although they become invalid). */
typedef struct PeerSession
{
    /* Base session struct */
    LWMsgSession base;
    /* Session identifier */
    LWMsgSessionID id;
    /* Security token of session creator */
    LWMsgSecurityToken* sec_token;
    /* Reference count */
    size_t volatile refs;
    /* Hash of handles by id */
    LWMsgHashTable handle_by_id;
    /* Lock */
    pthread_mutex_t lock;
    unsigned lock_destroy:1;
    /* Event */
    pthread_cond_t event;
    unsigned event_destroy:1;
    /* Next handle ID */
    unsigned long volatile next_hid;
    /* User data pointer and destructor */
    void* data;
    LWMsgSessionDestructFunction destruct;
    /* Owning peer */
    struct LWMsgPeer* peer;
    /* Direct session state */
    PeerDirectSession* direct_session;
    /* Assoc session state */
    PeerAssocSession* assoc_session;
    /* Explicit endpoint to use in lieu of list in peer.
     * This is used by lwmsg_peer_accept_fd() and lwmsg_peer_connect_fd(). */
    PeerEndpointAddress* endpoint;
    /* String representation of connected endpoint.  Simplifies logging */
    char* endpoint_str;
    /* Is session outgoing? */
    unsigned is_outgoing:1;
} PeerSession;

/* Handle implementation */
struct LWMsgHandle
{
    /* Key */
    PeerHandleKey key;
    /* Validity bit */
    LWMsgBool volatile valid;
    /* Reference count */
    size_t volatile refs;
    /* Handle type */
    const char* type;
    /* Handle pointer */
    void* pointer;
    /* Handle cleanup function */
    void (*cleanup)(void*);
    /* Link in hash table by id */
    LWMsgRing id_ring;
};

/* The actual peer structure */
struct LWMsgPeer
{
    /* Context */
    const LWMsgContext* context;
    /* Protocol */
    LWMsgProtocol* protocol;
    /* Wrapper around RTL thread pool */
    LWMsgTaskManager* task_manager;
    /* Resource limit settings */
    size_t max_clients;
    size_t max_dispatch;
    size_t max_backlog;
    /* Timeout settings */
    struct
    {
        LWMsgTime message;
        LWMsgTime establish;
        LWMsgTime idle;
    } timeout;
    /* User data passed to call dispatch functions */
    void* dispatch_data;

    /* Session setup/teardown functions */
    LWMsgSessionConstructFunction session_construct;
    LWMsgSessionDestructFunction session_destruct;
    void* session_construct_data;

    /* Invoked when listener dies unexpectedly */
    LWMsgPeerExceptionFunction except;
    void* except_data;

    /* Trace functions */
    LWMsgPeerTraceFunction trace_begin;
    LWMsgPeerTraceFunction trace_end;
    void* trace_data;

    /* Dispatch function table */
    struct
    {
        LWMsgDispatchSpec** vector;
        size_t vector_length;
    } dispatch;

    /* All tasks for listeners or accepted associations (incoming) */
    LWMsgTaskGroup* listen_tasks;
    /* All tasks for connected associations (outgoing) */
    LWMsgTaskGroup* connect_tasks;

    /* Configured endpoints to listen on */
    LWMsgRing listen_endpoints;

    /* Configured endpoints to attempt to connect to in order */
    LWMsgRing connect_endpoints;
    /* Current outgoing session (API currently allows only one at a time) */
    PeerSession* connect_session;
    /* Stores status code if outgoing session fails */
    LWMsgStatus connect_status;

    /* Total number of accepted associations */
    size_t num_clients;

    /* Lock, event, state management */
    pthread_mutex_t lock;
    unsigned lock_init:1;
    pthread_cond_t event;
    unsigned event_init:1;
    PeerListenState state;
    LWMsgStatus status;
};

#define LOCK_MUTEX(_lock_) \
    do \
    { \
        int __err__ = pthread_mutex_lock((_lock_)); \
        LWMSG_ASSERT(__err__ == 0); \
    } while (0)

#define UNLOCK_MUTEX(_lock_) \
    do \
    { \
        int __err__ = pthread_mutex_unlock((_lock_)); \
        LWMSG_ASSERT(__err__ == 0); \
    } while (0)

#define PEER_RAISE_ERROR(_peer_, _status_, ...) \
    RAISE_ERROR((_peer_), (_status_), __VA_ARGS__)

#define PEER_LOCK(_peer_, _lock_)           \
    do                                          \
    {                                           \
        if (!(_lock_))                          \
        {                                       \
            (_lock_) = 1;                       \
            lwmsg_peer_lock((_peer_));      \
        }                                       \
    } while (0)

#define PEER_UNLOCK(_peer_, _lock_)         \
    do                                          \
    {                                           \
        if ((_lock_))                           \
        {                                       \
            (_lock_) = 0;                       \
            lwmsg_peer_unlock((_peer_));    \
        }                                       \
    } while (0)


#define PEER_ASSOC_CALL(h) ((PeerAssocCall*) (h))

void
lwmsg_peer_lock(
    LWMsgPeer* peer
    );

void
lwmsg_peer_unlock(
    LWMsgPeer* peer
    );

LWMsgStatus
lwmsg_peer_session_new(
    LWMsgPeer* peer,
    PeerSession** session
    );

void
lwmsg_peer_session_reset(
    PeerSession* session
    );

void
lwmsg_peer_session_release(
    PeerSession* session
    );

void
lwmsg_peer_session_retain(
    PeerSession* session
    );

void
lwmsg_peer_session_disconnect(
    PeerSession* session
    );

LWMsgStatus
lwmsg_peer_session_acquire_call(
    PeerSession* session,
    LWMsgCall** call
    );

LWMsgStatus
lwmsg_peer_assoc_session_new_connect(
    PeerSession* session,
    LWMsgAssoc* assoc,
    PeerAssocSession** assoc_session
    );

LWMsgStatus
lwmsg_peer_assoc_session_new_accept(
    PeerSession* session,
    LWMsgAssoc* assoc,
    PeerAssocSession** assoc_session
    );

LWMsgStatus
lwmsg_peer_assoc_endpoint_new(
    LWMsgPeer* peer,
    LWMsgEndpointType type,
    const char* endpoint,
    mode_t perms,
    int fd,
    PeerAssocEndpoint** assoc_endpoint
    );

void
lwmsg_peer_assoc_endpoint_delete(
    PeerAssocEndpoint* assoc_endpoint
    );

void
lwmsg_peer_assoc_session_release(
    PeerAssocSession* assoc_session
    );

LWMsgBool
lwmsg_peer_acquire_client_slot(
    LWMsgPeer* peer
    );

void
lwmsg_peer_release_client_slot(
    LWMsgPeer* peer
    );

size_t
lwmsg_peer_get_num_clients(
    LWMsgPeer* peer
    );

LWMsgStatus
lwmsg_peer_accept_fd_internal(
    LWMsgPeer* peer,
    LWMsgEndpointType type,
    int fd,
    const char* endpoint
    );

void
lwmsg_peer_assoc_call_release(
    PeerAssocCall* call
    );

LWMsgStatus
lwmsg_peer_assoc_call_new(
    PeerAssocSession* session,
    PeerAssocCall** call
    );

LWMsgStatus
lwmsg_peer_assoc_call_dispatch_incoming(
    PeerAssocCall* call,
    LWMsgDispatchSpec* spec,
    void* dispatch_data,
    LWMsgMessage* incoming_message
    );

LWMsgStatus
lwmsg_peer_assoc_call_complete_outgoing(
    PeerAssocCall* call,
    LWMsgMessage* incoming_message
    );

LWMsgStatus
lwmsg_peer_assoc_call_cancel_incoming(
    PeerAssocCall* call
    );

LWMsgStatus
lwmsg_peer_direct_session_new(
    PeerSession* session,
    PeerDirectSession** direct_session
    );

void
lwmsg_peer_direct_session_release(
    PeerDirectSession* session
    );

LWMsgStatus
lwmsg_peer_direct_session_connect(
    const char* name,
    PeerDirectSession* session
    );

void
lwmsg_peer_direct_session_disconnect(
    PeerDirectSession* session
    );

LWMsgStatus
lwmsg_peer_direct_endpoint_listen(
    const char* name,
    LWMsgPeer* server
    );

void
lwmsg_peer_direct_endpoint_shutdown(
    const char* name,
    LWMsgPeer* server
    );

LWMsgStatus
lwmsg_peer_direct_call_new(
    PeerDirectSession* session,
    LWMsgBool is_callback,
    PeerDirectCall** call
    );

void
lwmsg_peer_session_string_for_session(
    LWMsgSession* session,
    LWMsgSessionString string
    );

LWMsgStatus
lwmsg_peer_assoc_session_log_message(
    PeerAssocSession* session,
    LWMsgMessage* message,
    LWMsgBool is_outgoing
    );

LWMsgStatus
lwmsg_peer_assoc_session_log_accept(
    PeerAssocSession* session
    );

LWMsgStatus
lwmsg_peer_assoc_session_log_connect(
    PeerAssocSession* session
    );

LWMsgStatus
lwmsg_peer_assoc_session_log_state(
    PeerAssocSession* session
    );

#endif
