/*
* 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 .
*
* 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:
*
* connection-state.c
*
* Abstract:
*
* Connection API
* State machine
*
* Authors: Brian Koropoff (bkoropoff@likewisesoftware.com)
*
*/
#include "assoc-private.h"
#include "connection-private.h"
#include "util-private.h"
#include
#include
#include
#include
#include
static inline
LWMsgStatus
lwmsg_connection_state_start(
LWMsgAssoc* assoc,
ConnectionState* state,
ConnectionEvent* event
);
static inline
LWMsgStatus
lwmsg_connection_state_begin_connect(
LWMsgAssoc* assoc,
ConnectionState* state,
ConnectionEvent* event
);
static inline
LWMsgStatus
lwmsg_connection_state_finish_connect(
LWMsgAssoc* assoc,
ConnectionState* state,
ConnectionEvent* event
);
static inline
LWMsgStatus
lwmsg_connection_state_begin_send_handshake(
LWMsgAssoc* assoc,
ConnectionState* state,
ConnectionEvent* event
);
static inline
LWMsgStatus
lwmsg_connection_state_finish_send_handshake(
LWMsgAssoc* assoc,
ConnectionState* state,
ConnectionEvent* event
);
static inline
LWMsgStatus
lwmsg_connection_state_begin_recv_handshake(
LWMsgAssoc* assoc,
ConnectionState* state,
ConnectionEvent* event
);
static inline
LWMsgStatus
lwmsg_connection_state_finish_recv_handshake(
LWMsgAssoc* assoc,
ConnectionState* state,
ConnectionEvent* event
);
static inline
LWMsgStatus
lwmsg_connection_state_established(
LWMsgAssoc* assoc,
ConnectionState* state,
ConnectionEvent* event
);
static inline
LWMsgStatus
lwmsg_connection_state_begin_close(
LWMsgAssoc* assoc,
ConnectionState* state,
ConnectionEvent* event
);
static inline
LWMsgStatus
lwmsg_connection_state_finish_close(
LWMsgAssoc* assoc,
ConnectionState* state,
ConnectionEvent* event
);
static inline
LWMsgStatus
lwmsg_connection_state_begin_reset(
LWMsgAssoc* assoc,
ConnectionState* state,
ConnectionEvent* event
);
static inline
LWMsgStatus
lwmsg_connection_state_finish_reset(
LWMsgAssoc* assoc,
ConnectionState* state,
ConnectionEvent* event
);
static inline
LWMsgStatus
lwmsg_connection_state_closed(
LWMsgAssoc* assoc,
ConnectionState* state,
ConnectionEvent* event
);
static inline
LWMsgStatus
lwmsg_connection_state_error(
LWMsgAssoc* assoc,
ConnectionState* state,
ConnectionEvent* event
);
static
LWMsgStatus
lwmsg_connection_finish_close(
LWMsgAssoc* assoc
);
LWMsgStatus
lwmsg_connection_run(
LWMsgAssoc* assoc,
ConnectionEvent event
)
{
LWMsgStatus status = LWMSG_STATUS_SUCCESS;
ConnectionPrivate* priv = CONNECTION_PRIVATE(assoc);
ConnectionState state = priv->state;
/* Run state machine until there is nothing to do */
while (event != CONNECTION_EVENT_NONE)
{
switch (state)
{
case CONNECTION_STATE_START:
BAIL_ON_ERROR(status = lwmsg_connection_state_start(assoc, &state, &event));
break;
case CONNECTION_STATE_BEGIN_CONNECT:
BAIL_ON_ERROR(status = lwmsg_connection_state_begin_connect(assoc, &state, &event));
break;
case CONNECTION_STATE_FINISH_CONNECT:
BAIL_ON_ERROR(status = lwmsg_connection_state_finish_connect(assoc, &state, &event));
break;
case CONNECTION_STATE_BEGIN_SEND_HANDSHAKE:
BAIL_ON_ERROR(status = lwmsg_connection_state_begin_send_handshake(assoc, &state, &event));
break;
case CONNECTION_STATE_FINISH_SEND_HANDSHAKE:
BAIL_ON_ERROR(status = lwmsg_connection_state_finish_send_handshake(assoc, &state, &event));
break;
case CONNECTION_STATE_BEGIN_RECV_HANDSHAKE:
BAIL_ON_ERROR(status = lwmsg_connection_state_begin_recv_handshake(assoc, &state, &event));
break;
case CONNECTION_STATE_FINISH_RECV_HANDSHAKE:
BAIL_ON_ERROR(status = lwmsg_connection_state_finish_recv_handshake(assoc, &state, &event));
break;
case CONNECTION_STATE_ESTABLISHED:
BAIL_ON_ERROR(status = lwmsg_connection_state_established(assoc, &state, &event));
break;
case CONNECTION_STATE_BEGIN_CLOSE:
BAIL_ON_ERROR(status = lwmsg_connection_state_begin_close(assoc, &state, &event));
break;
case CONNECTION_STATE_FINISH_CLOSE:
BAIL_ON_ERROR(status = lwmsg_connection_state_finish_close(assoc, &state, &event));
break;
case CONNECTION_STATE_BEGIN_RESET:
BAIL_ON_ERROR(status = lwmsg_connection_state_begin_reset(assoc, &state, &event));
break;
case CONNECTION_STATE_FINISH_RESET:
BAIL_ON_ERROR(status = lwmsg_connection_state_finish_reset(assoc, &state, &event));
break;
case CONNECTION_STATE_CLOSED:
BAIL_ON_ERROR(status = lwmsg_connection_state_closed(assoc, &state, &event));
break;
case CONNECTION_STATE_ERROR:
BAIL_ON_ERROR(status = lwmsg_connection_state_error(assoc, &state, &event));
break;
case CONNECTION_STATE_NONE:
BAIL_ON_ERROR(status = LWMSG_STATUS_INVALID_STATE);
}
}
done:
priv->state = state;
return status;
error:
switch (status)
{
case LWMSG_STATUS_SUCCESS:
case LWMSG_STATUS_PENDING:
case LWMSG_STATUS_BUSY:
case LWMSG_STATUS_MALFORMED:
case LWMSG_STATUS_INVALID_HANDLE:
case LWMSG_STATUS_OVERFLOW:
case LWMSG_STATUS_UNDERFLOW:
break;
default:
state = CONNECTION_STATE_ERROR;
break;
}
goto done;
}
static inline
LWMsgStatus
lwmsg_connection_state_start(
LWMsgAssoc* assoc,
ConnectionState* state,
ConnectionEvent* event
)
{
LWMsgStatus status = LWMSG_STATUS_SUCCESS;
ConnectionPrivate* priv = CONNECTION_PRIVATE(assoc);
switch (*event)
{
case CONNECTION_EVENT_ESTABLISH:
if (priv->fd != -1)
{
/* Set up pre-connected fd */
BAIL_ON_ERROR(status = lwmsg_connection_connect_existing(assoc));
/* Go straight to the handshake */
*state = CONNECTION_STATE_BEGIN_SEND_HANDSHAKE;
*event = CONNECTION_EVENT_FINISH;
}
else if (priv->endpoint != NULL)
{
/* If we were given an endpoint, we need to establish a connection */
*state = CONNECTION_STATE_BEGIN_CONNECT;
*event = CONNECTION_EVENT_FINISH;
}
else
{
/* Otherwise, we have a problem */
ASSOC_RAISE_ERROR(assoc, status = LWMSG_STATUS_INVALID_STATE,
"Cannot initialize connection: no file descriptor or endpoint specified");
}
break;
default:
/* We can't do anything else until we have established the connection */
BAIL_ON_ERROR(status = LWMSG_STATUS_INVALID_STATE);
break;
}
done:
return status;
error:
goto done;
}
static inline
LWMsgStatus
lwmsg_connection_state_begin_connect(
LWMsgAssoc* assoc,
ConnectionState* state,
ConnectionEvent* event
)
{
LWMsgStatus status = LWMSG_STATUS_SUCCESS;
BAIL_ON_ERROR(status = lwmsg_connection_begin_connect(assoc));
done:
if (status == LWMSG_STATUS_PENDING)
{
*state = CONNECTION_STATE_FINISH_CONNECT;
*event = CONNECTION_EVENT_FINISH;
}
else if (status == LWMSG_STATUS_SUCCESS)
{
*state = CONNECTION_STATE_BEGIN_SEND_HANDSHAKE;
}
return status;
error:
goto done;
}
static inline
LWMsgStatus
lwmsg_connection_state_finish_connect(
LWMsgAssoc* assoc,
ConnectionState* state,
ConnectionEvent* event
)
{
LWMsgStatus status = LWMSG_STATUS_SUCCESS;
switch (*event)
{
case CONNECTION_EVENT_FINISH:
BAIL_ON_ERROR(status = lwmsg_connection_finish_connect(assoc));
*state = CONNECTION_STATE_BEGIN_SEND_HANDSHAKE;
break;
case CONNECTION_EVENT_CLOSE:
*state = CONNECTION_STATE_BEGIN_CLOSE;
break;
case CONNECTION_EVENT_RESET:
*state = CONNECTION_STATE_BEGIN_RESET;
break;
case CONNECTION_EVENT_ABORT:
*state = CONNECTION_STATE_BEGIN_CLOSE;
break;
default:
BAIL_ON_ERROR(status = LWMSG_STATUS_BUSY);
break;
}
done:
return status;
error:
goto done;
}
static inline
LWMsgStatus
lwmsg_connection_state_begin_send_handshake(
LWMsgAssoc* assoc,
ConnectionState* state,
ConnectionEvent* event
)
{
LWMsgStatus status = LWMSG_STATUS_SUCCESS;
BAIL_ON_ERROR(status = lwmsg_connection_begin_send_handshake(assoc));
done:
if (status == LWMSG_STATUS_SUCCESS || status == LWMSG_STATUS_PENDING)
{
*state = CONNECTION_STATE_FINISH_SEND_HANDSHAKE;
*event = CONNECTION_EVENT_FINISH;
}
return status;
error:
goto done;
}
static inline
LWMsgStatus
lwmsg_connection_state_finish_send_handshake(
LWMsgAssoc* assoc,
ConnectionState* state,
ConnectionEvent* event
)
{
LWMsgStatus status = LWMSG_STATUS_SUCCESS;
switch (*event)
{
case CONNECTION_EVENT_FINISH:
BAIL_ON_ERROR(status = lwmsg_connection_finish_send_handshake(assoc));
*state = CONNECTION_STATE_BEGIN_RECV_HANDSHAKE;
break;
case CONNECTION_EVENT_CLOSE:
*state = CONNECTION_STATE_BEGIN_CLOSE;
break;
case CONNECTION_EVENT_RESET:
*state = CONNECTION_STATE_BEGIN_RESET;
break;
case CONNECTION_EVENT_ABORT:
*state = CONNECTION_STATE_BEGIN_CLOSE;
break;
default:
BAIL_ON_ERROR(status = LWMSG_STATUS_BUSY);
break;
}
done:
return status;
error:
goto done;
}
static inline
LWMsgStatus
lwmsg_connection_state_begin_recv_handshake(
LWMsgAssoc* assoc,
ConnectionState* state,
ConnectionEvent* event
)
{
LWMsgStatus status = LWMSG_STATUS_SUCCESS;
BAIL_ON_ERROR(status = lwmsg_connection_begin_recv_handshake(assoc));
done:
if (status == LWMSG_STATUS_SUCCESS || status == LWMSG_STATUS_PENDING)
{
*state = CONNECTION_STATE_FINISH_RECV_HANDSHAKE;
*event = CONNECTION_EVENT_FINISH;
}
return status;
error:
goto done;
}
static inline
LWMsgStatus
lwmsg_connection_state_finish_recv_handshake(
LWMsgAssoc* assoc,
ConnectionState* state,
ConnectionEvent* event
)
{
LWMsgStatus status = LWMSG_STATUS_SUCCESS;
switch (*event)
{
case CONNECTION_EVENT_FINISH:
BAIL_ON_ERROR(status = lwmsg_connection_finish_recv_handshake(assoc));
*state = CONNECTION_STATE_ESTABLISHED;
*event = CONNECTION_EVENT_NONE;
break;
case CONNECTION_EVENT_CLOSE:
*state = CONNECTION_STATE_BEGIN_CLOSE;
break;
case CONNECTION_EVENT_RESET:
*state = CONNECTION_STATE_BEGIN_RESET;
break;
case CONNECTION_EVENT_ABORT:
*state = CONNECTION_STATE_BEGIN_CLOSE;
break;
default:
BAIL_ON_ERROR(status = LWMSG_STATUS_BUSY);
}
done:
return status;
error:
goto done;
}
static inline
LWMsgStatus
lwmsg_connection_state_established(
LWMsgAssoc* assoc,
ConnectionState* state,
ConnectionEvent* event
)
{
LWMsgStatus status = LWMSG_STATUS_SUCCESS;
ConnectionPrivate* priv = CONNECTION_PRIVATE(assoc);
switch (*event)
{
case CONNECTION_EVENT_NONE:
BAIL_ON_ERROR(status = LWMSG_STATUS_INTERNAL);
break;
case CONNECTION_EVENT_ESTABLISH:
*event = CONNECTION_EVENT_NONE;
break;
case CONNECTION_EVENT_FINISH:
*event = CONNECTION_EVENT_NONE;
if (priv->outgoing && (status = lwmsg_connection_finish_send_message(assoc)) == LWMSG_STATUS_SUCCESS)
{
priv->params.message = priv->outgoing;
priv->outgoing = NULL;
}
else if (priv->incoming)
{
/* If we are still pending on a send, try a receive before giving up */
if (status == LWMSG_STATUS_PENDING)
{
status = LWMSG_STATUS_SUCCESS;
}
BAIL_ON_ERROR(status);
BAIL_ON_ERROR(status = lwmsg_connection_finish_recv_message(assoc));
priv->params.message = priv->incoming;
priv->incoming = NULL;
}
break;
case CONNECTION_EVENT_SEND:
if (priv->outgoing)
{
BAIL_ON_ERROR(status = LWMSG_STATUS_BUSY);
}
*event = CONNECTION_EVENT_FINISH;
priv->outgoing = priv->params.message;
status = lwmsg_connection_begin_send_message(assoc);
switch (status)
{
case LWMSG_STATUS_SUCCESS:
break;
case LWMSG_STATUS_INVALID_HANDLE:
case LWMSG_STATUS_MALFORMED:
case LWMSG_STATUS_OVERFLOW:
case LWMSG_STATUS_UNDERFLOW:
/* Recover from attempts to send bad messages */
priv->outgoing = NULL;
BAIL_ON_ERROR(status);
default:
BAIL_ON_ERROR(status);
}
break;
case CONNECTION_EVENT_RECV:
if (priv->incoming)
{
BAIL_ON_ERROR(status = LWMSG_STATUS_BUSY);
}
*event = CONNECTION_EVENT_FINISH;
priv->incoming = priv->params.message;
BAIL_ON_ERROR(status = lwmsg_connection_begin_recv_message(assoc));
break;
case CONNECTION_EVENT_CLOSE:
*state = CONNECTION_STATE_BEGIN_CLOSE;
break;
case CONNECTION_EVENT_RESET:
*state = CONNECTION_STATE_BEGIN_RESET;
break;
case CONNECTION_EVENT_ABORT:
*state = CONNECTION_STATE_BEGIN_CLOSE;
break;
}
done:
return status;
error:
goto done;
}
static inline
LWMsgStatus
lwmsg_connection_state_begin_close(
LWMsgAssoc* assoc,
ConnectionState* state,
ConnectionEvent* event
)
{
LWMsgStatus status = LWMSG_STATUS_SUCCESS;
ConnectionPrivate* priv = CONNECTION_PRIVATE(assoc);
if (priv->fd != -1)
{
BAIL_ON_ERROR(status = lwmsg_connection_begin_send_shutdown(assoc, LWMSG_STATUS_PEER_CLOSE));
}
done:
if (status == LWMSG_STATUS_SUCCESS || status == LWMSG_STATUS_PENDING)
{
*state = CONNECTION_STATE_FINISH_CLOSE;
*event = CONNECTION_EVENT_FINISH;
}
return status;
error:
goto done;
}
static inline
LWMsgStatus
lwmsg_connection_state_finish_close(
LWMsgAssoc* assoc,
ConnectionState* state,
ConnectionEvent* event
)
{
LWMsgStatus status = LWMSG_STATUS_SUCCESS;
ConnectionPrivate* priv = CONNECTION_PRIVATE(assoc);
switch (*event)
{
case CONNECTION_EVENT_FINISH:
if (priv->fd != -1)
{
status = lwmsg_connection_finish_send_shutdown(assoc);
switch (status)
{
case LWMSG_STATUS_PEER_CLOSE:
case LWMSG_STATUS_PEER_ABORT:
case LWMSG_STATUS_PEER_RESET:
/* Don't raise an error if the peer beat us to closing the connection */
status = LWMSG_STATUS_SUCCESS;
break;
default:
BAIL_ON_ERROR(status);
break;
}
}
BAIL_ON_ERROR(status = lwmsg_connection_finish_close(assoc));
*state = CONNECTION_STATE_CLOSED;
*event = CONNECTION_EVENT_NONE;
break;
default:
BAIL_ON_ERROR(status = LWMSG_STATUS_BUSY);
}
done:
return status;
error:
goto done;
}
static inline
LWMsgStatus
lwmsg_connection_state_begin_reset(
LWMsgAssoc* assoc,
ConnectionState* state,
ConnectionEvent* event
)
{
LWMsgStatus status = LWMSG_STATUS_SUCCESS;
BAIL_ON_ERROR(status = lwmsg_connection_begin_send_shutdown(assoc, LWMSG_STATUS_PEER_RESET));
done:
if (status == LWMSG_STATUS_SUCCESS || status == LWMSG_STATUS_PENDING)
{
*state = CONNECTION_STATE_FINISH_RESET;
*event = CONNECTION_EVENT_FINISH;
}
return status;
error:
goto done;
}
static inline
LWMsgStatus
lwmsg_connection_state_finish_reset(
LWMsgAssoc* assoc,
ConnectionState* state,
ConnectionEvent* event
)
{
LWMsgStatus status = LWMSG_STATUS_SUCCESS;
switch (*event)
{
case CONNECTION_EVENT_FINISH:
status = lwmsg_connection_finish_send_shutdown(assoc);
switch (status)
{
case LWMSG_STATUS_PEER_CLOSE:
case LWMSG_STATUS_PEER_ABORT:
case LWMSG_STATUS_PEER_RESET:
/* Don't raise an error if the peer beat us to closing the connection */
status = LWMSG_STATUS_SUCCESS;
break;
default:
BAIL_ON_ERROR(status);
break;
}
BAIL_ON_ERROR(status = lwmsg_connection_finish_close(assoc));
*state = CONNECTION_STATE_START;
*event = CONNECTION_EVENT_NONE;
break;
default:
BAIL_ON_ERROR(status = LWMSG_STATUS_BUSY);
}
done:
return status;
error:
goto done;
}
static inline
LWMsgStatus
lwmsg_connection_state_closed(
LWMsgAssoc* assoc,
ConnectionState* state,
ConnectionEvent* event
)
{
LWMsgStatus status = LWMSG_STATUS_SUCCESS;
switch (*event)
{
case CONNECTION_EVENT_FINISH:
*event = CONNECTION_EVENT_NONE;
break;
case CONNECTION_EVENT_RESET:
*state = CONNECTION_STATE_START;
*event = CONNECTION_EVENT_NONE;
break;
default:
BAIL_ON_ERROR(status = LWMSG_STATUS_INVALID_STATE);
}
done:
return status;
error:
goto done;
}
static inline
LWMsgStatus
lwmsg_connection_state_error(
LWMsgAssoc* assoc,
ConnectionState* state,
ConnectionEvent* event
)
{
LWMsgStatus status = LWMSG_STATUS_SUCCESS;
switch(*event)
{
case CONNECTION_EVENT_CLOSE:
*state = CONNECTION_STATE_BEGIN_CLOSE;
break;
case CONNECTION_EVENT_RESET:
*state = CONNECTION_STATE_BEGIN_RESET;
break;
case CONNECTION_EVENT_ABORT:
*state = CONNECTION_STATE_BEGIN_CLOSE;
break;
default:
BAIL_ON_ERROR(status = LWMSG_STATUS_INVALID_STATE);
}
done:
return status;
error:
goto done;
}
static
LWMsgStatus
lwmsg_connection_finish_close(
LWMsgAssoc* assoc
)
{
LWMsgStatus status = LWMSG_STATUS_SUCCESS;
ConnectionPrivate* priv = CONNECTION_PRIVATE(assoc);
LWMsgSessionManager* manager = NULL;
if (priv->fd != -1)
{
close(priv->fd);
priv->fd = -1;
}
if (priv->sec_token)
{
lwmsg_security_token_delete(priv->sec_token);
priv->sec_token = NULL;
}
if (priv->session)
{
BAIL_ON_ERROR(status = lwmsg_assoc_get_session_manager(assoc, &manager));
BAIL_ON_ERROR(status = lwmsg_session_manager_leave_session(manager, priv->session));
priv->session = NULL;
}
lwmsg_connection_buffer_destruct(&priv->recvbuffer);
BAIL_ON_ERROR(status = lwmsg_connection_buffer_construct(&priv->recvbuffer));
lwmsg_connection_buffer_destruct(&priv->sendbuffer);
BAIL_ON_ERROR(status = lwmsg_connection_buffer_construct(&priv->sendbuffer));
error:
return status;
}