Commit dfab04cd authored by Philip Withnall's avatar Philip Withnall Committed by Olivier Crête

socket: Add vectored I/O support for receiving on sockets

Replace the recv() API with a recv_messages() API, which supports
receiving multiple messages, each with multiple buffers rather than a
single monolithic buffer.

This doesn’t break API, as the socket API is not exposed outside
libnice. It does introduce a new struct: NiceInputMessage, which is
analogous to struct mmsghdr.

This includes updates to the test-bsd test to cover the changed API.
parent bc527dcf
...@@ -181,4 +181,10 @@ component_io_cb ( ...@@ -181,4 +181,10 @@ component_io_cb (
gssize agent_recv_locked (NiceAgent *agent, Stream *stream, gssize agent_recv_locked (NiceAgent *agent, Stream *stream,
Component *component, NiceSocket *socket, guint8 *buf, gsize buf_len); Component *component, NiceSocket *socket, guint8 *buf, gsize buf_len);
gsize
memcpy_buffer_to_input_message (NiceInputMessage *message,
const guint8 *buffer, gsize buffer_length);
guint8 *
compact_input_message (NiceInputMessage *message, gsize *buffer_length);
#endif /*_NICE_AGENT_PRIV_H */ #endif /*_NICE_AGENT_PRIV_H */
...@@ -2438,13 +2438,19 @@ agent_recv_locked ( ...@@ -2438,13 +2438,19 @@ agent_recv_locked (
GList *item; GList *item;
guint8 local_buf[MAX_BUFFER_SIZE]; guint8 local_buf[MAX_BUFFER_SIZE];
gsize local_buf_len = MAX_BUFFER_SIZE; gsize local_buf_len = MAX_BUFFER_SIZE;
GInputVector local_bufs = { local_buf, local_buf_len };
NiceInputMessage local_messages = { &local_bufs, 1, &from, 0 };
gint n_valid_messages;
/* Returns -1 on error, 0 on EWOULDBLOCK, and > 0 on success. /* Returns -1 on error, 0 on EWOULDBLOCK, and > 0 on success.
* *
* FIXME: We have to receive into a local buffer then copy out because * FIXME: We have to receive into a local buffer then copy out because
* otherwise, if @buf is too small, we could lose data, even when in * otherwise, if @buf is too small, we could lose data, even when in
* reliable mode (because reliable streams are packetised). */ * reliable mode (because reliable streams are packetised). */
len = nice_socket_recv (socket, &from, local_buf_len, (gchar *) local_buf); n_valid_messages = nice_socket_recv_messages (socket, &local_messages, 1);
len = (n_valid_messages == 1) ?
(gssize) local_messages.length : n_valid_messages;
if (len == 0) { if (len == 0) {
return 0; return 0;
...@@ -2555,6 +2561,99 @@ agent_recv_locked ( ...@@ -2555,6 +2561,99 @@ agent_recv_locked (
return len; return len;
} }
/* Print the composition of an array of messages. No-op if debugging is
* disabled. */
static void
nice_debug_message_composition (NiceInputMessage *messages, guint n_messages)
{
#ifndef NDEBUG
guint i;
for (i = 0; i < n_messages; i++) {
NiceInputMessage *message = &messages[i];
guint j;
nice_debug ("Message %p (from: %p, length: %" G_GSIZE_FORMAT ")", message,
message->from, message->length);
for (j = 0;
(message->n_buffers >= 0 && j < (guint) message->n_buffers) ||
(message->n_buffers < 0 && message->buffers[j].buffer != NULL);
j++) {
GInputVector *buffer = &message->buffers[j];
nice_debug ("\tBuffer %p (length: %" G_GSIZE_FORMAT ")", buffer->buffer,
buffer->size);
}
}
#endif
}
/* Concatenate all the buffers in the given @recv_message into a single, newly
* allocated, monolithic buffer which is returned. The length of the new buffer
* is returned in @buffer_length, and should be equal to the length field of
* @recv_message.
*
* The return value must be freed with g_free(). */
guint8 *
compact_input_message (NiceInputMessage *message, gsize *buffer_length)
{
guint8 *buffer;
gsize offset = 0;
guint i;
nice_debug ("%s: **WARNING: SLOW PATH**", G_STRFUNC);
nice_debug_message_composition (message, 1);
*buffer_length = message->length;
buffer = g_malloc (*buffer_length);
for (i = 0;
(message->n_buffers >= 0 && i < (guint) message->n_buffers) ||
(message->n_buffers < 0 && message->buffers[i].buffer != NULL);
i++) {
gsize len = MIN (*buffer_length - offset, message->buffers[i].size);
memcpy (buffer + offset, message->buffers[i].buffer, len);
offset += len;
}
return buffer;
}
/* Returns the number of bytes copied. Silently drops any data from @buffer
* which doesn’t fit in @message. */
gsize
memcpy_buffer_to_input_message (NiceInputMessage *message,
const guint8 *buffer, gsize buffer_length)
{
guint i;
nice_debug ("%s: **WARNING: SLOW PATH**", G_STRFUNC);
message->length = 0;
for (i = 0;
buffer_length > 0 &&
((message->n_buffers >= 0 && i < (guint) message->n_buffers) ||
(message->n_buffers < 0 && message->buffers[i].buffer != NULL));
i++) {
gsize len;
len = MIN (message->buffers[i].size, buffer_length);
memcpy (message->buffers[i].buffer, buffer, len);
buffer += len;
buffer_length -= len;
message->buffers[i].size = len;
message->length += len;
}
nice_debug_message_composition (message, 1);
return message->length;
}
static gboolean static gboolean
nice_agent_recv_cancelled_cb (GCancellable *cancellable, gpointer user_data) nice_agent_recv_cancelled_cb (GCancellable *cancellable, gpointer user_data)
{ {
......
...@@ -137,6 +137,40 @@ typedef struct _NiceAgent NiceAgent; ...@@ -137,6 +137,40 @@ typedef struct _NiceAgent NiceAgent;
G_BEGIN_DECLS G_BEGIN_DECLS
/**
* NiceInputMessage:
* @buffers: (array length=n_buffers): unowned array of #GInputVector buffers to
* store data in for this message
* @n_buffers: number of #GInputVectors in @buffers, or -1 to indicate @buffers
* is %NULL-terminated
* @from: (allow-none): return location to store the address of the peer who
* transmitted the message, or %NULL
* @length: total number of valid bytes contiguously stored in @buffers
*
* Represents a single message received off the network. For reliable
* connections, this is essentially just an array of buffers (specifically,
* @from can be ignored). for non-reliable connections, it represents a single
* packet as received from the OS.
*
* @n_buffers may be -1 to indicate that @buffers is terminated by a
* #GInputVector with a %NULL buffer pointer.
*
* By providing arrays of #NiceInputMessages to functions like
* nice_agent_recv_messages(), multiple messages may be received with a single
* call, which is more efficient than making multiple calls in a loop. In this
* manner, nice_agent_recv_messages() is analogous to recvmmsg(); and
* #NiceInputMessage to struct mmsghdr.
*
* Since: 0.1.5
*/
typedef struct {
GInputVector *buffers;
gint n_buffers; /* may be -1 to indicate @buffers is NULL-terminated */
NiceAddress *from; /* return location for address of message sender */
gsize length; /* sum of the lengths of @buffers */
} NiceInputMessage;
#define NICE_TYPE_AGENT nice_agent_get_type() #define NICE_TYPE_AGENT nice_agent_get_type()
#define NICE_AGENT(obj) \ #define NICE_AGENT(obj) \
......
...@@ -7,6 +7,7 @@ NiceComponentType ...@@ -7,6 +7,7 @@ NiceComponentType
NiceProxyType NiceProxyType
NiceCompatibility NiceCompatibility
NiceAgentRecvFunc NiceAgentRecvFunc
NiceInputMessage
NICE_AGENT_MAX_REMOTE_CANDIDATES NICE_AGENT_MAX_REMOTE_CANDIDATES
nice_agent_new nice_agent_new
nice_agent_new_reliable nice_agent_new_reliable
......
...@@ -70,9 +70,15 @@ typedef struct { ...@@ -70,9 +70,15 @@ typedef struct {
gchar *username; gchar *username;
gchar *password; gchar *password;
GQueue send_queue; GQueue send_queue;
gchar *recv_buf;
gint recv_len; /* Ring buffer for receiving HTTP headers into before they’re parsed. */
gint content_length; guint8 *recv_buf;
gsize recv_buf_length; /* allocation size of @recv_buf */
gsize recv_buf_pos; /* offset from @recv_buf of the 0th byte in the buffer */
gsize recv_buf_fill; /* number of bytes occupied in the buffer */
/* Parsed from the Content-Length header provided by the other endpoint. */
gsize content_length;
} HttpPriv; } HttpPriv;
...@@ -84,8 +90,8 @@ struct to_be_sent { ...@@ -84,8 +90,8 @@ struct to_be_sent {
static void socket_close (NiceSocket *sock); static void socket_close (NiceSocket *sock);
static gint socket_recv (NiceSocket *sock, NiceAddress *from, static gint socket_recv_messages (NiceSocket *sock,
guint len, gchar *buf); NiceInputMessage *recv_messages, guint n_recv_messages);
static gboolean socket_send (NiceSocket *sock, const NiceAddress *to, static gboolean socket_send (NiceSocket *sock, const NiceAddress *to,
guint len, const gchar *buf); guint len, const gchar *buf);
static gboolean socket_is_reliable (NiceSocket *sock); static gboolean socket_is_reliable (NiceSocket *sock);
...@@ -111,14 +117,16 @@ nice_http_socket_new (NiceSocket *base_socket, ...@@ -111,14 +117,16 @@ nice_http_socket_new (NiceSocket *base_socket,
priv->username = g_strdup (username); priv->username = g_strdup (username);
priv->password = g_strdup (password); priv->password = g_strdup (password);
priv->recv_buf = NULL; priv->recv_buf = NULL;
priv->recv_len = 0; priv->recv_buf_length = 0;
priv->recv_buf_pos = 0;
priv->recv_buf_fill = 0;
priv->content_length = 0; priv->content_length = 0;
sock->fileno = priv->base_socket->fileno; sock->fileno = priv->base_socket->fileno;
sock->addr = priv->base_socket->addr; sock->addr = priv->base_socket->addr;
sock->send = socket_send; sock->send = socket_send;
sock->recv = socket_recv; sock->recv_messages = socket_recv_messages;
sock->is_reliable = socket_is_reliable; sock->is_reliable = socket_is_reliable;
sock->close = socket_close; sock->close = socket_close;
...@@ -183,152 +191,349 @@ socket_close (NiceSocket *sock) ...@@ -183,152 +191,349 @@ socket_close (NiceSocket *sock)
g_slice_free(HttpPriv, sock->priv); g_slice_free(HttpPriv, sock->priv);
} }
static void
assert_ring_buffer_valid (HttpPriv *priv)
{
g_assert (priv->recv_buf_fill <= priv->recv_buf_length);
g_assert (priv->recv_buf_pos == 0 ||
priv->recv_buf_pos < priv->recv_buf_length);
g_assert (priv->recv_buf_length == 0 || priv->recv_buf != NULL);
}
/* Pops up to @buffer_length bytes off the ring buffer and copies them into
* @buffer. Returns the number of bytes copied. */
static gsize
memcpy_ring_buffer_to_buffer (HttpPriv *priv,
guint8 *buffer, gsize buffer_length)
{
gsize len, consumed = 0;
gboolean has_wrapped;
has_wrapped =
(priv->recv_buf_pos + priv->recv_buf_fill) > priv->recv_buf_length;
if (has_wrapped) {
len = MIN (priv->recv_buf_length - priv->recv_buf_pos, buffer_length);
memcpy (buffer, priv->recv_buf + priv->recv_buf_pos, len);
consumed += len;
buffer += len;
buffer_length -= len;
len = MIN (priv->recv_buf_fill - len, buffer_length);
memcpy (buffer, priv->recv_buf, len);
consumed += len;
} else {
len = MIN (priv->recv_buf_fill, buffer_length);
memcpy (buffer, priv->recv_buf + priv->recv_buf_pos, len);
consumed += len;
}
priv->recv_buf_pos =
(priv->recv_buf_pos + consumed) % priv->recv_buf_length;
priv->recv_buf_fill -= consumed;
return consumed;
}
/* Returns the number of messages touched. Silently drops any data from @buffer
* which doesn’t fit in @messages. Updates the ring buffer to pop the copied
* data off it. Treats all #GInputVectors in @messages the same; there is no
* differentiation between different #NiceInputMessages. */
static gint static gint
socket_recv (NiceSocket *sock, NiceAddress *from, guint len, gchar *buf) memcpy_ring_buffer_to_input_messages (HttpPriv *priv,
NiceInputMessage *messages, guint n_messages)
{
guint i, j;
for (i = 0; priv->recv_buf_fill > 0 && i < n_messages; i++) {
NiceInputMessage *message = &messages[i];
for (j = 0;
priv->recv_buf_fill > 0 &&
((message->n_buffers >= 0 && j < (guint) message->n_buffers) ||
(message->n_buffers < 0 && message->buffers[j].buffer != NULL));
j++) {
message->buffers[j].size =
memcpy_ring_buffer_to_buffer (priv,
message->buffers[j].buffer, message->buffers[j].size);
message->length += message->buffers[j].size;
}
}
return i;
}
/* FIXME: The current implementation of socket_recv_message() is a fast
* pass-through to nice_socket_recv_message() if the HTTP socket is connected,
* but is a slow state machine otherwise, using multiple memcpy()s. Spruce it up
* to better to use the recv_messages to avoid the memcpy()s. */
static gint
socket_recv_messages (NiceSocket *sock,
NiceInputMessage *recv_messages, guint n_recv_messages)
{ {
HttpPriv *priv = sock->priv; HttpPriv *priv = sock->priv;
gint read = -1; gint ret = -1;
if (from) if (priv->state == HTTP_STATE_CONNECTED) {
*from = priv->addr; guint i;
if (priv->base_socket) /* Fast path: pass through to the base socket once we’re connected. */
read = nice_socket_recv (priv->base_socket, NULL, len, buf); if (priv->base_socket) {
ret = nice_socket_recv_messages (priv->base_socket,
recv_messages, n_recv_messages);
}
if (ret <= 0)
return ret;
/* After successfully receiving into at least one NiceInputMessage,
* update the from address in each valid NiceInputMessage. */
for (i = 0; i < (guint) ret; i++) {
if (recv_messages[i].from != NULL)
*recv_messages[i].from = priv->addr;
}
if (read <= 0 || priv->state == HTTP_STATE_CONNECTED) { return ret;
return read;
} else { } else {
priv->recv_buf = g_realloc (priv->recv_buf, priv->recv_len + read); /* Slow path: read into a local ring buffer until we’re parsed enough of the
memcpy (priv->recv_buf + priv->recv_len, buf, read); * headers. Double the buffer in size every time it fills up. */
priv->recv_len += read; gboolean has_wrapped;
GInputVector local_recv_bufs[2];
NiceInputMessage local_recv_message = { local_recv_bufs, 2, NULL, 0 };
/* Has the buffer filled up? Start with an initial buffer of 1KB, which
* should cover the average size of HTTP response headers. Source:
* http://dev.chromium.org/spdy/spdy-whitepaper */
if (priv->recv_buf_fill == priv->recv_buf_length) {
priv->recv_buf_length = MAX (priv->recv_buf_length * 2, 1024);
priv->recv_buf = g_realloc (priv->recv_buf, priv->recv_buf_length);
}
assert_ring_buffer_valid (priv);
/* Read some data into the buffer. Use two GInputVectors: one for the tail
* of the buffer and one for the head. */
has_wrapped =
(priv->recv_buf_pos + priv->recv_buf_fill) > priv->recv_buf_length;
if (has_wrapped) {
local_recv_bufs[0].buffer =
priv->recv_buf + (priv->recv_buf_pos + priv->recv_buf_fill) %
priv->recv_buf_length;
local_recv_bufs[0].size = priv->recv_buf_length - priv->recv_buf_fill;
local_recv_bufs[1].buffer = NULL;
local_recv_bufs[1].size = 0;
} else {
local_recv_bufs[0].buffer =
priv->recv_buf + priv->recv_buf_pos + priv->recv_buf_fill;
local_recv_bufs[0].size =
priv->recv_buf_length - (priv->recv_buf_pos + priv->recv_buf_fill);
local_recv_bufs[1].buffer = priv->recv_buf;
local_recv_bufs[1].size = priv->recv_buf_pos;
}
if (priv->base_socket) {
ret = nice_socket_recv_messages (priv->base_socket,
&local_recv_message, 1);
}
if (ret <= 0)
return ret;
/* Update the buffer’s metadata. */
priv->recv_buf_fill += local_recv_message.length;
assert_ring_buffer_valid (priv);
/* Fall through and try parsing the newly received data. */
} }
retry: #define GET_BYTE(pos) \
nice_debug ("Receiving from HTTP proxy (state %d) : %d \n'%s'", priv->state, priv->recv_len, priv->recv_buf); priv->recv_buf[(pos + priv->recv_buf_pos) % priv->recv_buf_length]
#define EAT_WHITESPACE(pos) \
while (pos < priv->recv_buf_fill && GET_BYTE(pos) == ' ') \
pos++; \
if (pos >= priv->recv_buf_fill) \
goto not_enough_data;
retry:
nice_debug ("Receiving from HTTP proxy (state %d) : %" G_GSSIZE_FORMAT " \n"
"'%s'", priv->state, priv->recv_buf_fill,
priv->recv_buf + priv->recv_buf_pos);
switch (priv->state) { switch (priv->state) {
case HTTP_STATE_INIT: case HTTP_STATE_INIT:
{ {
gint pos = 0; /* This is a logical position in the recv_buf; add
* (priv->recv_buf + priv->recv_buf_pos) to get the actual byte in
* memory. */
guint pos = 0;
/* Remove any leading spaces (could happen!) */ /* Eat leading whitespace and check we have enough data. */
while (pos < priv->recv_len && priv->recv_buf[pos] == ' ') EAT_WHITESPACE (pos);
pos++;
/* Make sure we have enough data */ if (pos + 7 > priv->recv_buf_fill)
if (pos >= priv->recv_len)
goto not_enough_data; goto not_enough_data;
if (GET_BYTE (pos + 0) != 'H' ||
if (pos + 7 > priv->recv_len) GET_BYTE (pos + 1) != 'T' ||
goto not_enough_data; GET_BYTE (pos + 2) != 'T' ||
if (strncmp (priv->recv_buf + pos, "HTTP/1.", 7) != 0) GET_BYTE (pos + 3) != 'P' ||
GET_BYTE (pos + 4) != '/' ||
GET_BYTE (pos + 5) != '1' ||
GET_BYTE (pos + 6) != '.')
goto error; goto error;
pos += 7; pos += 7;
if (pos >= priv->recv_len) if (pos >= priv->recv_buf_fill)
goto not_enough_data; goto not_enough_data;
if(priv->recv_buf[pos] != '0' && priv->recv_buf[pos] != '1') if (GET_BYTE (pos) != '0' && GET_BYTE (pos) != '1')
goto error; goto error;
pos++; pos++;
/* Make sure we have a space after the HTTP version */ /* Make sure we have a space after the HTTP version */
if (pos >= priv->recv_len) if (pos >= priv->recv_buf_fill)
goto not_enough_data; goto not_enough_data;
if (priv->recv_buf[pos] != ' ') if (GET_BYTE (pos) != ' ')
goto error; goto error;
/* Skip all spaces (could be more than one!) */ EAT_WHITESPACE (pos);
while (pos < priv->recv_len && priv->recv_buf[pos] == ' ')
pos++;
if (pos >= priv->recv_len)
goto not_enough_data;
/* Check for a successfull 2xx code */ /* Check for a successful 2xx code */
if (pos + 3 > priv->recv_len) if (pos + 3 > priv->recv_buf_fill)
goto not_enough_data; goto not_enough_data;
if (priv->recv_buf[pos] != '2' || if (GET_BYTE (pos) != '2' ||
priv->recv_buf[pos+1] < '0' || priv->recv_buf[pos+1] > '9' || GET_BYTE (pos + 1) < '0' || GET_BYTE (pos + 1) > '9' ||
priv->recv_buf[pos+2] < '0' || priv->recv_buf[pos+2] > '9') GET_BYTE (pos + 2) < '0' || GET_BYTE (pos + 2) > '9')
goto error; goto error;
/* Clear any trailing chars */ /* Clear any trailing chars */
while (pos + 1 < priv->recv_len && while (pos + 1 < priv->recv_buf_fill &&
priv->recv_buf[pos] != '\r' && priv->recv_buf[pos+1] != '\n') GET_BYTE (pos) != '\r' && GET_BYTE (pos + 1) != '\n')
pos++; pos++;
if (pos + 1 >= priv->recv_len) if (pos + 1 >= priv->recv_buf_fill)
goto not_enough_data; goto not_enough_data;
pos += 2; pos += 2;
/* consume the data we just parsed */ /* Consume the data we just parsed. */
priv->recv_len -= pos; priv->recv_buf_pos = (priv->recv_buf_pos + pos) % priv->recv_buf_length;
memmove (priv->recv_buf, priv->recv_buf + pos, priv->recv_len); priv->recv_buf_fill -= pos;
priv->recv_buf = g_realloc (priv->recv_buf, priv->recv_len);
priv->content_length = 0; priv->content_length = 0;
priv->state = HTTP_STATE_HEADERS; priv->state = HTTP_STATE_HEADERS;
goto retry; goto retry;
} }
break; break;
case HTTP_STATE_HEADERS: case HTTP_STATE_HEADERS:
{ {
gint pos = 0; guint pos = 0;
if (pos + 15 < priv->recv_buf_fill &&
(GET_BYTE (pos + 0) == 'C' || GET_BYTE (pos + 0) == 'c') &&
(GET_BYTE (pos + 1) == 'o' || GET_BYTE (pos + 1) == 'O') &&
(GET_BYTE (pos + 2) == 'n' || GET_BYTE (pos + 2) == 'N') &&
(GET_BYTE (pos + 3) == 't' || GET_BYTE (pos + 3) == 'T') &&
(GET_BYTE (pos + 4) == 'e' || GET_BYTE (pos + 4) == 'E') &&
(GET_BYTE (pos + 5) == 'n' || GET_BYTE (pos + 5) == 'N') &&
(GET_BYTE (pos + 6) == 't' || GET_BYTE (pos + 6) == 'T') &&
GET_BYTE (pos + 7) == '-' &&
(GET_BYTE (pos + 8) == 'L' || GET_BYTE (pos + 8) == 'l') &&
(GET_BYTE (pos + 9) == 'e' || GET_BYTE (pos + 9) == 'E') &&
(GET_BYTE (pos + 10) == 'n' || GET_BYTE (pos + 10) == 'N') &&
(GET_BYTE (pos + 11) == 'g' || GET_BYTE (pos + 11) == 'G') &&
(GET_BYTE (pos + 12) == 't' || GET_BYTE (pos + 12) == 'T') &&
(GET_BYTE (pos + 13) == 'h' || GET_BYTE (pos + 13) == 'H') &&
GET_BYTE (pos + 14) == ':') {
/* Found a Content-Length header. Parse and store the value. Note that
* the HTTP standard allows for arbitrarily-big content lengths. We
* limit it to G_MAXSIZE for sanity’s sake.
*
* The code below is equivalent to strtoul(input, NULL, 10), but
* operates on a ring buffer. */
pos += 15;
EAT_WHITESPACE (pos);
priv->content_length = 0;
while (TRUE) {
guint8 byte = GET_BYTE (pos);
gint val = g_ascii_digit_value (byte);
if (pos + 15 < priv->recv_len && if (byte == '\r') {
g_ascii_strncasecmp (priv->recv_buf, "Content-Length:", 15) == 0) { /* Reached the end of the value; fall out to the code below which
priv->content_length = atoi(priv->recv_buf + 15); * will grab the \n. */
break;
} else if (val == -1) {
priv->content_length = 0;
goto error;
} }
while (pos + 1 < priv->recv_len &&
priv->recv_buf[pos] != '\r' && priv->recv_buf[pos+1] != '\n') /* Check for overflow. Don’t flag it as an error; just fall through
* to the code below which will skip to the \r\n. */
if (priv->content_length > G_MAXSIZE / 10 ||
priv->content_length * 10 > G_MAXSIZE - val) {
priv->content_length = 0;
break;
}
priv->content_length = (priv->content_length * 10) + val;
if (pos + 1 > priv->recv_buf_fill)
goto not_enough_data;
pos++;
}
}
/* Skip over the header. */
while (pos + 1 < priv->recv_buf_fill &&
GET_BYTE (pos) != '\r' && GET_BYTE (pos + 1) != '\n')
pos++; pos++;
nice_debug ("pos = %d, len = %d", pos, priv->recv_len);
if (pos + 1 >= priv->recv_len) nice_debug ("pos = %u, fill = %" G_GSSIZE_FORMAT,
pos, priv->recv_buf_fill);
if (pos + 1 >= priv->recv_buf_fill)
goto not_enough_data; goto not_enough_data;
pos += 2; pos += 2;
/* consume the data we just parsed */ /* Consume the data we just parsed. */
priv->recv_len -= pos; priv->recv_buf_pos = (priv->recv_buf_pos + pos) % priv->recv_buf_length;
memmove (priv->recv_buf, priv->recv_buf + pos, priv->recv_len); priv->recv_buf_fill -= pos;
priv->recv_buf = g_realloc (priv->recv_buf, priv->recv_len);
if (pos == 2) if (pos == 2)
priv->state = HTTP_STATE_BODY; priv->state = HTTP_STATE_BODY;
goto retry; goto retry;
} }
break; break;
case HTTP_STATE_BODY: case HTTP_STATE_BODY:
{ {
gint consumed = priv->content_length; gsize consumed;
if (priv->content_length == 0) { if (priv->content_length == 0) {
priv->state = HTTP_STATE_CONNECTED; priv->state = HTTP_STATE_CONNECTED;
goto retry; goto retry;
} }
if (priv->recv_len == 0)
if (priv->recv_buf_fill == 0)
goto not_enough_data; goto not_enough_data;
if (priv->content_length > priv->recv_len) consumed = MIN (priv->content_length, priv->recv_buf_fill);
consumed = priv->recv_len;
priv->recv_len -= consumed; priv->recv_buf_pos =
(priv->recv_buf_pos + consumed) % priv->recv_buf_length;
priv->recv_buf_fill -= consumed;
priv->content_length -= consumed; priv->content_length -= consumed;
memmove (priv->recv_buf, priv->recv_buf + consumed, priv->recv_len);
priv->recv_buf = g_realloc (priv->recv_buf, priv->recv_len);
goto retry; goto retry;
} }
break; break;
case HTTP_STATE_CONNECTED: case HTTP_STATE_CONNECTED:
{ {
guint recv_len = priv->recv_len; gsize len;
struct to_be_sent *tbs = NULL; struct to_be_sent *tbs = NULL;
if (recv_len > len) len = memcpy_ring_buffer_to_input_messages (priv,
recv_len = len; recv_messages, n_recv_messages);
memcpy (buf, priv->recv_buf, recv_len);
/* consume the data we returned */
priv->recv_len -= recv_len;
memmove (priv->recv_buf, priv->recv_buf + recv_len, priv->recv_len);
priv->recv_buf = g_realloc (priv->recv_buf, priv->recv_len);
/* Send the pending data */ /* Send the pending data */
while ((tbs = g_queue_pop_head (&priv->send_queue))) { while ((tbs = g_queue_pop_head (&priv->send_queue))) {
...@@ -338,7 +543,7 @@ socket_recv (NiceSocket *sock, NiceAddress *from, guint len, gchar *buf) ...@@ -338,7 +543,7 @@ socket_recv (NiceSocket *sock, NiceAddress *from, guint len, gchar *buf)
g_slice_free (struct to_be_sent, tbs); g_slice_free (struct to_be_sent, tbs);
} }
return recv_len; return len;
} }
break; break;
case HTTP_STATE_ERROR: case HTTP_STATE_ERROR:
......
...@@ -89,8 +89,8 @@ static const gchar SSL_CLIENT_HANDSHAKE[] = { ...@@ -89,8 +89,8 @@ static const gchar SSL_CLIENT_HANDSHAKE[] = {
static void socket_close (NiceSocket *sock); static void socket_close (NiceSocket *sock);
static gint socket_recv (NiceSocket *sock, NiceAddress *from, static gint socket_recv_messages (NiceSocket *sock,
guint len, gchar *buf); NiceInputMessage *recv_messages, guint n_recv_messages);
static gboolean socket_send (NiceSocket *sock, const NiceAddress *to, static gboolean socket_send (NiceSocket *sock, const NiceAddress *to,
guint len, const gchar *buf); guint len, const gchar *buf);
static gboolean socket_is_reliable (NiceSocket *sock); static gboolean socket_is_reliable (NiceSocket *sock);
...@@ -113,7 +113,7 @@ nice_pseudossl_socket_new (NiceSocket *base_socket) ...@@ -113,7 +113,7 @@ nice_pseudossl_socket_new (NiceSocket *base_socket)
sock->fileno = priv->base_socket->fileno; sock->fileno = priv->base_socket->fileno;
sock->addr = priv->base_socket->addr; sock->addr = priv->base_socket->addr;
sock->send = socket_send; sock->send = socket_send;
sock->recv = socket_recv; sock->recv_messages = socket_recv_messages;
sock->is_reliable = socket_is_reliable; sock->is_reliable = socket_is_reliable;
sock->close = socket_close; sock->close = socket_close;
...@@ -142,23 +142,33 @@ socket_close (NiceSocket *sock) ...@@ -142,23 +142,33 @@ socket_close (NiceSocket *sock)
static gint static gint
socket_recv (NiceSocket *sock, NiceAddress *from, guint len, gchar *buf) socket_recv_messages (NiceSocket *sock,
NiceInputMessage *recv_messages, guint n_recv_messages)
{ {
PseudoSSLPriv *priv = sock->priv; PseudoSSLPriv *priv = sock->priv;
if (priv->handshaken) { if (priv->handshaken) {
if (priv->base_socket) if (priv->base_socket) {
return nice_socket_recv (priv->base_socket, from, len, buf); /* Fast path: once we’ve done the handshake, pass straight through to the
* base socket. */
return nice_socket_recv_messages (priv->base_socket,
recv_messages, n_recv_messages);
}
} else { } else {
gchar data[sizeof(SSL_SERVER_HANDSHAKE)]; guint8 data[sizeof(SSL_SERVER_HANDSHAKE)];
gint ret = -1; gint ret = -1;
GInputVector local_recv_buf = { data, sizeof (data) };
NiceInputMessage local_recv_message = { &local_recv_buf, 1, NULL, 0 };
if (priv->base_socket) if (priv->base_socket) {
ret = nice_socket_recv (priv->base_socket, from, sizeof(data), data); ret = nice_socket_recv_messages (priv->base_socket,
&local_recv_message, 1);
}
if (ret <= 0) { if (ret <= 0) {
return ret; return ret;
} else if ((guint) ret == sizeof(SSL_SERVER_HANDSHAKE) && } else if (ret == 1 &&
local_recv_buf.size == sizeof (SSL_SERVER_HANDSHAKE) &&
memcmp(SSL_SERVER_HANDSHAKE, data, sizeof(SSL_SERVER_HANDSHAKE)) == 0) { memcmp(SSL_SERVER_HANDSHAKE, data, sizeof(SSL_SERVER_HANDSHAKE)) == 0) {
struct to_be_sent *tbs = NULL; struct to_be_sent *tbs = NULL;
priv->handshaken = TRUE; priv->handshaken = TRUE;
......
...@@ -43,10 +43,54 @@ ...@@ -43,10 +43,54 @@
#include "socket.h" #include "socket.h"
/**
* nice_socket_recv_messages:
* @sock: a #NiceSocket
* @recv_messages: (array length=n_recv_messages) (out caller-allocates):
* array of #NiceInputMessages to return received messages in
* @n_recv_messages: number of elements in the @recv_messages array
*
* Receive up to @n_recv_messages message on the socket, in a non-reliable,
* non-blocking fashion. The total size of the buffers in each #NiceInputMessage
* must be big enough to contain an entire message (65536 bytes), or excess
* bytes will be silently dropped.
*
* On success, the number of messages received into @recv_messages is returned,
* which may be less than @n_recv_messages if the call would have blocked
* part-way through. If the socket would have blocked to begin with, or if
* @n_recv_messages is zero, zero is returned. On failure, a negative value is
* returned, but no further error information is available. Calling this
* function on a socket which has closed is an error, and a negative value is
* returned.
*
* If a positive N is returned, the first N messages in @recv_messages are
* valid. Each valid message is guaranteed to have a non-zero
* #NiceInputMessage::length, and its buffers are guaranteed to be filled
* sequentially up to that number of bytes If #NiceInputMessage::from was
* non-%NULL for a valid message, it may be set to the address of the sender of
* that received message.
*
* If the return value is zero or negative, the from return address and length
* in every #NiceInputMessage in @recv_messages are guaranteed to be unmodified.
* The buffers may have been modified.
*
* The base addresses and sizes of the buffers in a #NiceInputMessage are never
* modified. Neither is the base address of #NiceInputMessage::from, nor the
* base address and length of the #NiceInputMessage::buffers array.
*
* Returns: number of valid messages returned in @recv_messages, or a negative
* value on error
*
* Since: 0.1.5
*/
gint gint
nice_socket_recv (NiceSocket *sock, NiceAddress *from, guint len, gchar *buf) nice_socket_recv_messages (NiceSocket *sock,
NiceInputMessage *recv_messages, guint n_recv_messages)
{ {
return sock->recv (sock, from, len, buf); g_return_val_if_fail (sock != NULL, -1);
g_return_val_if_fail (n_recv_messages == 0 || recv_messages != NULL, -1);
return sock->recv_messages (sock, recv_messages, n_recv_messages);
} }
gboolean gboolean
......
...@@ -37,6 +37,7 @@ ...@@ -37,6 +37,7 @@
#ifndef _SOCKET_H #ifndef _SOCKET_H
#define _SOCKET_H #define _SOCKET_H
#include "agent.h"
#include "address.h" #include "address.h"
#include <gio/gio.h> #include <gio/gio.h>
...@@ -58,8 +59,10 @@ struct _NiceSocket ...@@ -58,8 +59,10 @@ struct _NiceSocket
{ {
NiceAddress addr; NiceAddress addr;
GSocket *fileno; GSocket *fileno;
gint (*recv) (NiceSocket *sock, NiceAddress *from, guint len, /* Implementations must handle any value of n_recv_messages, including 0. Iff
gchar *buf); * n_recv_messages is 0, recv_messages may be NULL. */
gint (*recv_messages) (NiceSocket *sock,
NiceInputMessage *recv_messages, guint n_recv_messages);
gboolean (*send) (NiceSocket *sock, const NiceAddress *to, guint len, gboolean (*send) (NiceSocket *sock, const NiceAddress *to, guint len,
const gchar *buf); const gchar *buf);
gboolean (*is_reliable) (NiceSocket *sock); gboolean (*is_reliable) (NiceSocket *sock);
...@@ -69,7 +72,8 @@ struct _NiceSocket ...@@ -69,7 +72,8 @@ struct _NiceSocket
G_GNUC_WARN_UNUSED_RESULT G_GNUC_WARN_UNUSED_RESULT
gint gint
nice_socket_recv (NiceSocket *sock, NiceAddress *from, guint len, gchar *buf); nice_socket_recv_messages (NiceSocket *sock,
NiceInputMessage *recv_messages, guint n_recv_messages);
gboolean gboolean
nice_socket_send (NiceSocket *sock, const NiceAddress *to, nice_socket_send (NiceSocket *sock, const NiceAddress *to,
......
...@@ -76,8 +76,8 @@ struct to_be_sent { ...@@ -76,8 +76,8 @@ struct to_be_sent {
static void socket_close (NiceSocket *sock); static void socket_close (NiceSocket *sock);
static gint socket_recv (NiceSocket *sock, NiceAddress *from, static gint socket_recv_messages (NiceSocket *sock,
guint len, gchar *buf); NiceInputMessage *recv_messages, guint n_recv_messages);
static gboolean socket_send (NiceSocket *sock, const NiceAddress *to, static gboolean socket_send (NiceSocket *sock, const NiceAddress *to,
guint len, const gchar *buf); guint len, const gchar *buf);
static gboolean socket_is_reliable (NiceSocket *sock); static gboolean socket_is_reliable (NiceSocket *sock);
...@@ -106,7 +106,7 @@ nice_socks5_socket_new (NiceSocket *base_socket, ...@@ -106,7 +106,7 @@ nice_socks5_socket_new (NiceSocket *base_socket,
sock->fileno = priv->base_socket->fileno; sock->fileno = priv->base_socket->fileno;
sock->addr = priv->base_socket->addr; sock->addr = priv->base_socket->addr;
sock->send = socket_send; sock->send = socket_send;
sock->recv = socket_recv; sock->recv_messages = socket_recv_messages;
sock->is_reliable = socket_is_reliable; sock->is_reliable = socket_is_reliable;
sock->close = socket_close; sock->close = socket_close;
...@@ -160,31 +160,50 @@ socket_close (NiceSocket *sock) ...@@ -160,31 +160,50 @@ socket_close (NiceSocket *sock)
static gint static gint
socket_recv (NiceSocket *sock, NiceAddress *from, guint buf_len, gchar *buf) socket_recv_messages (NiceSocket *sock,
NiceInputMessage *recv_messages, guint n_recv_messages)
{ {
Socks5Priv *priv = sock->priv; Socks5Priv *priv = sock->priv;
guint i;
if (from) gint ret = -1;
*from = priv->addr;
switch (priv->state) { switch (priv->state) {
case SOCKS_STATE_CONNECTED: case SOCKS_STATE_CONNECTED:
if (priv->base_socket) /* Common case: fast pass-through to the base socket once we’re
return nice_socket_recv (priv->base_socket, NULL, buf_len, buf); * connected. */
break; if (priv->base_socket) {
ret = nice_socket_recv_messages (priv->base_socket,
recv_messages, n_recv_messages);
}
if (ret <= 0)
return ret;
/* After successfully receiving into at least one NiceInputMessage,
* update the from address in each valid NiceInputMessage. */
for (i = 0; i < (guint) ret; i++) {
if (recv_messages[i].from != NULL)
*recv_messages[i].from = priv->addr;
}
return ret;
case SOCKS_STATE_INIT: case SOCKS_STATE_INIT:
{ {
gchar data[2]; guint8 data[2];
gint ret = -1; GInputVector local_recv_buf = { data, sizeof (data) };
NiceInputMessage local_recv_message = { &local_recv_buf, 1, NULL, 0 };
nice_debug ("Socks5 state Init"); nice_debug ("Socks5 state Init");
if (priv->base_socket) if (priv->base_socket) {
ret = nice_socket_recv (priv->base_socket, NULL, sizeof(data), data); ret = nice_socket_recv_messages (priv->base_socket,
&local_recv_message, 1);
}
if (ret <= 0) { if (ret <= 0) {
return ret; return ret;
} else if(ret == sizeof(data)) { } else if (ret == 1 && local_recv_buf.size == sizeof(data)) {
if (data[0] == 0x05) { if (data[0] == 0x05) {
if (data[1] == 0x02) { if (data[1] == 0x02) {
gchar msg[515]; gchar msg[515];
...@@ -242,16 +261,19 @@ socket_recv (NiceSocket *sock, NiceAddress *from, guint buf_len, gchar *buf) ...@@ -242,16 +261,19 @@ socket_recv (NiceSocket *sock, NiceAddress *from, guint buf_len, gchar *buf)
break; break;
case SOCKS_STATE_AUTH: case SOCKS_STATE_AUTH:
{ {
gchar data[2]; guint8 data[2];
gint ret = -1; GInputVector local_recv_buf = { data, sizeof (data) };
NiceInputMessage local_recv_message = { &local_recv_buf, 1, NULL, 0 };
nice_debug ("Socks5 state auth"); nice_debug ("Socks5 state auth");
if (priv->base_socket) if (priv->base_socket) {
ret = nice_socket_recv (priv->base_socket, NULL, sizeof(data), data); ret = nice_socket_recv_messages (priv->base_socket,
&local_recv_message, 1);
}
if (ret <= 0) { if (ret <= 0) {
return ret; return ret;
} else if(ret == sizeof(data)) { } else if (ret == 1 && local_recv_buf.size == sizeof(data)) {
if (data[0] == 0x01 && data[1] == 0x00) { if (data[0] == 0x01 && data[1] == 0x00) {
/* Authenticated */ /* Authenticated */
goto send_connect; goto send_connect;
...@@ -264,16 +286,20 @@ socket_recv (NiceSocket *sock, NiceAddress *from, guint buf_len, gchar *buf) ...@@ -264,16 +286,20 @@ socket_recv (NiceSocket *sock, NiceAddress *from, guint buf_len, gchar *buf)
break; break;
case SOCKS_STATE_CONNECT: case SOCKS_STATE_CONNECT:
{ {
gchar data[22]; guint8 data[22];
gint ret = -1; GInputVector local_recv_buf = { data, sizeof (data) };
NiceInputMessage local_recv_message = { &local_recv_buf, 1, NULL, 0 };
nice_debug ("Socks5 state connect"); nice_debug ("Socks5 state connect");
if (priv->base_socket) if (priv->base_socket) {
ret = nice_socket_recv (priv->base_socket, NULL, 4, data); local_recv_buf.size = 4;
ret = nice_socket_recv_messages (priv->base_socket,
&local_recv_message, 1);
}
if (ret <= 0) { if (ret <= 0) {
return ret; return ret;
} else if(ret == 4) { } else if (ret == 1 && local_recv_buf.size == 4) {
if (data[0] == 0x05) { if (data[0] == 0x05) {
switch (data[1]) { switch (data[1]) {
case 0x00: case 0x00:
...@@ -281,15 +307,19 @@ socket_recv (NiceSocket *sock, NiceAddress *from, guint buf_len, gchar *buf) ...@@ -281,15 +307,19 @@ socket_recv (NiceSocket *sock, NiceAddress *from, guint buf_len, gchar *buf)
struct to_be_sent *tbs = NULL; struct to_be_sent *tbs = NULL;
switch (data[3]) { switch (data[3]) {
case 0x01: /* IPV4 bound address */ case 0x01: /* IPV4 bound address */
ret = nice_socket_recv (priv->base_socket, NULL, 6, data); local_recv_buf.size = 6;
if (ret != 6) { ret = nice_socket_recv_messages (priv->base_socket,
&local_recv_message, 1);
if (ret != 1 || local_recv_buf.size != 6) {
/* Could not read server bound address */ /* Could not read server bound address */
goto error; goto error;
} }
break; break;
case 0x04: /* IPV6 bound address */ case 0x04: /* IPV6 bound address */
ret = nice_socket_recv (priv->base_socket, NULL, 18, data); local_recv_buf.size = 18;
if (ret != 18) { ret = nice_socket_recv_messages (priv->base_socket,
&local_recv_message, 1);
if (ret != 1 || local_recv_buf.size != 18) {
/* Could not read server bound address */ /* Could not read server bound address */
goto error; goto error;
} }
......
...@@ -70,8 +70,8 @@ struct to_be_sent { ...@@ -70,8 +70,8 @@ struct to_be_sent {
#define MAX_QUEUE_LENGTH 20 #define MAX_QUEUE_LENGTH 20
static void socket_close (NiceSocket *sock); static void socket_close (NiceSocket *sock);
static gint socket_recv (NiceSocket *sock, NiceAddress *from, static gint socket_recv_messages (NiceSocket *sock,
guint len, gchar *buf); NiceInputMessage *recv_messages, guint n_recv_messages);
static gboolean socket_send (NiceSocket *sock, const NiceAddress *to, static gboolean socket_send (NiceSocket *sock, const NiceAddress *to,
guint len, const gchar *buf); guint len, const gchar *buf);
static gboolean socket_is_reliable (NiceSocket *sock); static gboolean socket_is_reliable (NiceSocket *sock);
...@@ -170,7 +170,7 @@ nice_tcp_bsd_socket_new (GMainContext *ctx, NiceAddress *addr) ...@@ -170,7 +170,7 @@ nice_tcp_bsd_socket_new (GMainContext *ctx, NiceAddress *addr)
sock->fileno = gsock; sock->fileno = gsock;
sock->send = socket_send; sock->send = socket_send;
sock->recv = socket_recv; sock->recv_messages = socket_recv_messages;
sock->is_reliable = socket_is_reliable; sock->is_reliable = socket_is_reliable;
sock->close = socket_close; sock->close = socket_close;
...@@ -202,36 +202,54 @@ socket_close (NiceSocket *sock) ...@@ -202,36 +202,54 @@ socket_close (NiceSocket *sock)
} }
static gint static gint
socket_recv (NiceSocket *sock, NiceAddress *from, guint len, gchar *buf) socket_recv_messages (NiceSocket *sock,
NiceInputMessage *recv_messages, guint n_recv_messages)
{ {
TcpPriv *priv = sock->priv; TcpPriv *priv = sock->priv;
int ret; guint i;
GError *gerr = NULL;
/* Don't try to access the socket if it had an error */ /* Don't try to access the socket if it had an error */
if (priv->error) if (priv->error)
return -1; return -1;
ret = g_socket_receive (sock->fileno, buf, len, NULL, &gerr); for (i = 0; i < n_recv_messages; i++) {
gint flags = G_SOCKET_MSG_NONE;
GError *gerr = NULL;
gssize len;
len = g_socket_receive_message (sock->fileno, NULL,
recv_messages[i].buffers, recv_messages[i].n_buffers,
NULL, NULL, &flags, NULL, &gerr);
/* recv returns 0 when the peer performed a shutdown.. we must return -1 here recv_messages[i].length = MAX (len, 0);
* so that the agent destroys the g_source */
if (ret == 0) { /* recv returns 0 when the peer performed a shutdown.. we must return -1
* here so that the agent destroys the g_source */
if (len == 0) {
priv->error = TRUE; priv->error = TRUE;
return -1; break;
} }
if (ret < 0) { if (len < 0) {
if(g_error_matches (gerr, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) if (g_error_matches (gerr, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
ret = 0; len = 0;
g_error_free (gerr); g_error_free (gerr);
return ret; return len;
} }
if (from) if (recv_messages[i].from)
*from = priv->server_addr; *recv_messages[i].from = priv->server_addr;
return ret;
if (len <= 0)
break;
}
/* Was there an error processing the first message? */
if (priv->error && i == 0)
return -1;
return i;
} }
/* Data sent to this function must be a single entity because buffers can be /* Data sent to this function must be a single entity because buffers can be
......
...@@ -43,6 +43,7 @@ ...@@ -43,6 +43,7 @@
#endif #endif
#include "tcp-turn.h" #include "tcp-turn.h"
#include "agent-priv.h"
#include <string.h> #include <string.h>
#include <errno.h> #include <errno.h>
...@@ -66,8 +67,8 @@ typedef struct { ...@@ -66,8 +67,8 @@ typedef struct {
#define MAX_UDP_MESSAGE_SIZE 65535 #define MAX_UDP_MESSAGE_SIZE 65535
static void socket_close (NiceSocket *sock); static void socket_close (NiceSocket *sock);
static gint socket_recv (NiceSocket *sock, NiceAddress *from, static gint socket_recv_messages (NiceSocket *sock,
guint len, gchar *buf); NiceInputMessage *recv_messages, guint n_recv_messages);
static gboolean socket_send (NiceSocket *sock, const NiceAddress *to, static gboolean socket_send (NiceSocket *sock, const NiceAddress *to,
guint len, const gchar *buf); guint len, const gchar *buf);
static gboolean socket_is_reliable (NiceSocket *sock); static gboolean socket_is_reliable (NiceSocket *sock);
...@@ -86,7 +87,7 @@ nice_tcp_turn_socket_new (NiceSocket *base_socket, ...@@ -86,7 +87,7 @@ nice_tcp_turn_socket_new (NiceSocket *base_socket,
sock->fileno = priv->base_socket->fileno; sock->fileno = priv->base_socket->fileno;
sock->addr = priv->base_socket->addr; sock->addr = priv->base_socket->addr;
sock->send = socket_send; sock->send = socket_send;
sock->recv = socket_recv; sock->recv_messages = socket_recv_messages;
sock->is_reliable = socket_is_reliable; sock->is_reliable = socket_is_reliable;
sock->close = socket_close; sock->close = socket_close;
...@@ -105,13 +106,14 @@ socket_close (NiceSocket *sock) ...@@ -105,13 +106,14 @@ socket_close (NiceSocket *sock)
g_slice_free(TurnTcpPriv, sock->priv); g_slice_free(TurnTcpPriv, sock->priv);
} }
static gssize
static gint socket_recv_message (NiceSocket *sock, NiceInputMessage *recv_message)
socket_recv (NiceSocket *sock, NiceAddress *from, guint len, gchar *buf)
{ {
TurnTcpPriv *priv = sock->priv; TurnTcpPriv *priv = sock->priv;
int ret; gssize ret;
guint padlen; guint padlen;
GInputVector local_recv_buf;
NiceInputMessage local_recv_message;
if (priv->expecting_len == 0) { if (priv->expecting_len == 0) {
guint headerlen = 0; guint headerlen = 0;
...@@ -124,13 +126,18 @@ socket_recv (NiceSocket *sock, NiceAddress *from, guint len, gchar *buf) ...@@ -124,13 +126,18 @@ socket_recv (NiceSocket *sock, NiceAddress *from, guint len, gchar *buf)
else else
return -1; return -1;
ret = nice_socket_recv (priv->base_socket, from, local_recv_buf.buffer = priv->recv_buf.u8 + priv->recv_buf_len;
headerlen - priv->recv_buf_len, local_recv_buf.size = headerlen - priv->recv_buf_len;
(gchar *) priv->recv_buf.u8 + priv->recv_buf_len); local_recv_message.buffers = &local_recv_buf;
local_recv_message.n_buffers = 1;
local_recv_message.from = recv_message->from;
local_recv_message.length = 0;
ret = nice_socket_recv_messages (priv->base_socket, &local_recv_message, 1);
if (ret < 0) if (ret < 0)
return ret; return ret;
priv->recv_buf_len += ret; priv->recv_buf_len += local_recv_message.length;
if (priv->recv_buf_len < headerlen) if (priv->recv_buf_len < headerlen)
return 0; return 0;
...@@ -161,27 +168,60 @@ socket_recv (NiceSocket *sock, NiceAddress *from, guint len, gchar *buf) ...@@ -161,27 +168,60 @@ socket_recv (NiceSocket *sock, NiceAddress *from, guint len, gchar *buf)
else else
padlen = 0; padlen = 0;
ret = nice_socket_recv (priv->base_socket, from, local_recv_buf.buffer = priv->recv_buf.u8 + priv->recv_buf_len;
priv->expecting_len + padlen - priv->recv_buf_len, local_recv_buf.size = priv->expecting_len + padlen - priv->recv_buf_len;
(gchar *) priv->recv_buf.u8 + priv->recv_buf_len); local_recv_message.buffers = &local_recv_buf;
local_recv_message.n_buffers = 1;
local_recv_message.from = recv_message->from;
local_recv_message.length = 0;
ret = nice_socket_recv_messages (priv->base_socket, &local_recv_message, 1);
if (ret < 0) if (ret < 0)
return ret; return ret;
priv->recv_buf_len += ret; priv->recv_buf_len += local_recv_message.length;
if (priv->recv_buf_len == priv->expecting_len + padlen) { if (priv->recv_buf_len == priv->expecting_len + padlen) {
guint copy_len = MIN (len, priv->recv_buf_len); /* FIXME: Eliminate this memcpy(). */
memcpy (buf, priv->recv_buf.u8, copy_len); ret = memcpy_buffer_to_input_message (recv_message,
priv->recv_buf.u8, priv->recv_buf_len);
priv->expecting_len = 0; priv->expecting_len = 0;
priv->recv_buf_len = 0; priv->recv_buf_len = 0;
return copy_len; return ret;
} }
return 0; return 0;
} }
static gint
socket_recv_messages (NiceSocket *socket,
NiceInputMessage *recv_messages, guint n_recv_messages)
{
guint i;
gboolean error = FALSE;
for (i = 0; i < n_recv_messages; i++) {
gssize len;
len = socket_recv_message (socket, &recv_messages[i]);
recv_messages[i].length = MAX (len, 0);
if (len < 0)
error = TRUE;
if (len <= 0)
break;
}
/* Was there an error processing the first message? */
if (error && i == 0)
return -1;
return i;
}
static gboolean static gboolean
socket_send (NiceSocket *sock, const NiceAddress *to, socket_send (NiceSocket *sock, const NiceAddress *to,
guint len, const gchar *buf) guint len, const gchar *buf)
......
...@@ -114,8 +114,8 @@ typedef struct { ...@@ -114,8 +114,8 @@ typedef struct {
} SendData; } SendData;
static void socket_close (NiceSocket *sock); static void socket_close (NiceSocket *sock);
static gint socket_recv (NiceSocket *sock, NiceAddress *from, static gint socket_recv_messages (NiceSocket *sock,
guint len, gchar *buf); NiceInputMessage *recv_messages, guint n_recv_messages);
static gboolean socket_send (NiceSocket *sock, const NiceAddress *to, static gboolean socket_send (NiceSocket *sock, const NiceAddress *to,
guint len, const gchar *buf); guint len, const gchar *buf);
static gboolean socket_is_reliable (NiceSocket *sock); static gboolean socket_is_reliable (NiceSocket *sock);
...@@ -230,7 +230,7 @@ nice_turn_socket_new (GMainContext *ctx, NiceAddress *addr, ...@@ -230,7 +230,7 @@ nice_turn_socket_new (GMainContext *ctx, NiceAddress *addr,
sock->addr = *addr; sock->addr = *addr;
sock->fileno = base_socket->fileno; sock->fileno = base_socket->fileno;
sock->send = socket_send; sock->send = socket_send;
sock->recv = socket_recv; sock->recv_messages = socket_recv_messages;
sock->is_reliable = socket_is_reliable; sock->is_reliable = socket_is_reliable;
sock->close = socket_close; sock->close = socket_close;
sock->priv = (void *) priv; sock->priv = (void *) priv;
...@@ -304,25 +304,89 @@ socket_close (NiceSocket *sock) ...@@ -304,25 +304,89 @@ socket_close (NiceSocket *sock)
} }
static gint static gint
socket_recv (NiceSocket *sock, NiceAddress *from, guint len, gchar *buf) socket_recv_messages (NiceSocket *sock,
NiceInputMessage *recv_messages, guint n_recv_messages)
{ {
TurnPriv *priv = (TurnPriv *) sock->priv; TurnPriv *priv = (TurnPriv *) sock->priv;
uint8_t recv_buf[STUN_MAX_MESSAGE_SIZE]; gint n_messages;
gint recv_len; guint i;
NiceAddress recv_from; gboolean error = FALSE;
NiceSocket *dummy; guint n_valid_messages;
nice_debug ("received message on TURN socket"); nice_debug ("received message on TURN socket");
recv_len = nice_socket_recv (priv->base_socket, &recv_from, n_messages = nice_socket_recv_messages (priv->base_socket,
sizeof(recv_buf), (gchar *) recv_buf); recv_messages, n_recv_messages);
if (n_messages < 0)
return n_messages;
/* Process all the messages. Those which fail parsing are re-used for the next
* message.
*
* FIXME: This needs a fast path which avoids allocations or memcpy()s.
* Implementing such a path means rewriting the TURN parser (and hence the
* STUN message code) to operate on vectors of buffers, rather than a
* monolithic buffer. */
for (i = 0; i < (guint) n_messages; i += n_valid_messages) {
NiceInputMessage *message = &recv_messages[i];
NiceSocket *dummy;
NiceAddress from;
guint8 *buffer;
gsize buffer_length;
gint parsed_buffer_length;
gboolean allocated_buffer = FALSE;
n_valid_messages = 1;
/* Compact the message’s buffers into a single one for parsing. Avoid this
* in the (hopefully) common case of a single-element buffer vector. */
if (message->n_buffers == 1 ||
(message->n_buffers == -1 &&
message->buffers[0].buffer != NULL &&
message->buffers[1].buffer == NULL)) {
buffer = message->buffers[0].buffer;
buffer_length = message->buffers[0].size;
} else {
nice_debug ("%s: **WARNING: SLOW PATH**", G_STRFUNC);
buffer = compact_input_message (message, &buffer_length);
allocated_buffer = TRUE;
}
/* Parse in-place. */
parsed_buffer_length = nice_turn_socket_parse_recv (sock, &dummy,
&from, buffer_length, (gchar *) buffer,
message->from, (gchar *) buffer, buffer_length);
message->length = MAX (parsed_buffer_length, 0);
if (parsed_buffer_length < 0) {
error = TRUE;
} else if (parsed_buffer_length == 0) {
/* A TURN control message which needs ignoring. Re-use this
* NiceInputMessage in the next loop iteration. */
n_valid_messages = 0;
}
/* Split up the monolithic buffer again into the caller-provided buffers. */
if (parsed_buffer_length > 0 && allocated_buffer) {
parsed_buffer_length =
memcpy_buffer_to_input_message (message, buffer,
parsed_buffer_length);
}
if (allocated_buffer)
g_free (buffer);
if (error)
break;
}
/* Was there an error processing the first message? */
if (error && i == 0)
return -1;
if (recv_len > 0) return i;
return nice_turn_socket_parse_recv (sock, &dummy, from, len, buf,
&recv_from, (gchar *) recv_buf,
(guint) recv_len);
else
return recv_len;
} }
static GSource * static GSource *
......
...@@ -57,8 +57,8 @@ ...@@ -57,8 +57,8 @@
static void socket_close (NiceSocket *sock); static void socket_close (NiceSocket *sock);
static gint socket_recv (NiceSocket *sock, NiceAddress *from, static gint socket_recv_messages (NiceSocket *sock,
guint len, gchar *buf); NiceInputMessage *recv_messages, guint n_recv_messages);
static gboolean socket_send (NiceSocket *sock, const NiceAddress *to, static gboolean socket_send (NiceSocket *sock, const NiceAddress *to,
guint len, const gchar *buf); guint len, const gchar *buf);
static gboolean socket_is_reliable (NiceSocket *sock); static gboolean socket_is_reliable (NiceSocket *sock);
...@@ -143,7 +143,7 @@ nice_udp_bsd_socket_new (NiceAddress *addr) ...@@ -143,7 +143,7 @@ nice_udp_bsd_socket_new (NiceAddress *addr)
sock->fileno = gsock; sock->fileno = gsock;
sock->send = socket_send; sock->send = socket_send;
sock->recv = socket_recv; sock->recv_messages = socket_recv_messages;
sock->is_reliable = socket_is_reliable; sock->is_reliable = socket_is_reliable;
sock->close = socket_close; sock->close = socket_close;
...@@ -168,36 +168,60 @@ socket_close (NiceSocket *sock) ...@@ -168,36 +168,60 @@ socket_close (NiceSocket *sock)
} }
static gint static gint
socket_recv (NiceSocket *sock, NiceAddress *from, guint len, gchar *buf) socket_recv_messages (NiceSocket *sock,
NiceInputMessage *recv_messages, guint n_recv_messages)
{ {
guint i;
gboolean error = FALSE;
/* Read messages into recv_messages until one fails or would block, or we
* reach the end. */
for (i = 0; i < n_recv_messages; i++) {
NiceInputMessage *recv_message = &recv_messages[i];
GSocketAddress *gaddr = NULL; GSocketAddress *gaddr = NULL;
GError *gerr = NULL; GError *gerr = NULL;
gint recvd; gssize recvd;
gint flags = G_SOCKET_MSG_NONE;
recvd = g_socket_receive_message (sock->fileno,
(recv_message->from != NULL) ? &gaddr : NULL,
recv_message->buffers, recv_message->n_buffers, NULL, NULL,
&flags, NULL, &gerr);
recvd = g_socket_receive_from (sock->fileno, &gaddr, buf, len, NULL, &gerr); recv_message->length = MAX (recvd, 0);
if (recvd < 0) { if (recvd < 0) {
if (g_error_matches(gerr, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK) if (g_error_matches (gerr, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK))
|| g_error_matches(gerr, G_IO_ERROR, G_IO_ERROR_FAILED))
recvd = 0; recvd = 0;
else
error = TRUE;
g_error_free (gerr); g_error_free (gerr);
} }
if (recvd > 0 && from != NULL && gaddr != NULL) { if (recvd > 0 && recv_message->from != NULL && gaddr != NULL) {
union { union {
struct sockaddr_storage storage; struct sockaddr_storage storage;
struct sockaddr addr; struct sockaddr addr;
} sa; } sa;
g_socket_address_to_native (gaddr, &sa.addr, sizeof (sa), NULL); g_socket_address_to_native (gaddr, &sa.addr, sizeof (sa), NULL);
nice_address_set_from_sockaddr (from, &sa.addr); nice_address_set_from_sockaddr (recv_message->from, &sa.addr);
} }
if (gaddr != NULL) if (gaddr != NULL)
g_object_unref (gaddr); g_object_unref (gaddr);
return recvd; /* Return early on error or EWOULDBLOCK. */
if (recvd <= 0)
break;
}
/* Was there an error processing the first message? */
if (error && i == 0)
return -1;
return i;
} }
static gboolean static gboolean
......
/* /*
* This file is part of the Nice GLib ICE library. * This file is part of the Nice GLib ICE library.
* *
* (C) 2006, 2007 Collabora Ltd. * (C) 2006, 2007, 2014 Collabora Ltd.
* Contact: Dafydd Harries * Contact: Dafydd Harries
* (C) 2006, 2007 Nokia Corporation. All rights reserved. * (C) 2006, 2007 Nokia Corporation. All rights reserved.
* Contact: Kai Vehmanen * Contact: Kai Vehmanen
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
* *
* Contributors: * Contributors:
* Dafydd Harries, Collabora Ltd. * Dafydd Harries, Collabora Ltd.
* Philip Withnall, Collabora Ltd.
* *
* Alternatively, the contents of this file may be used under the terms of the * Alternatively, the contents of this file may be used under the terms of the
* the GNU Lesser General Public License Version 2.1 (the "LGPL"), in which * the GNU Lesser General Public License Version 2.1 (the "LGPL"), in which
...@@ -42,51 +43,314 @@ ...@@ -42,51 +43,314 @@
#include "socket.h" #include "socket.h"
int static gssize
main (void) socket_recv (NiceSocket *sock, NiceAddress *addr, gsize buf_len, gchar *buf)
{
GInputVector local_buf = { buf, buf_len };
NiceInputMessage local_message = { &local_buf, 1, addr, 0 };
gint ret;
ret = nice_socket_recv_messages (sock, &local_message, 1);
if (ret <= 0)
return ret;
return local_buf.size;
}
static void
test_socket_initial_properties (void)
{
NiceSocket *sock;
sock = nice_udp_bsd_socket_new (NULL);
g_assert (sock != NULL);
// not bound to a particular interface
g_assert_cmpint (sock->addr.s.ip4.sin_addr.s_addr, ==, 0);
// is bound to a particular port
g_assert_cmpuint (nice_address_get_port (&sock->addr), !=, 0);
}
static void
test_socket_address_properties (void)
{
NiceSocket *sock;
NiceAddress tmp;
sock = nice_udp_bsd_socket_new (NULL);
g_assert (sock != NULL);
g_assert (nice_address_set_from_string (&tmp, "127.0.0.1"));
g_assert_cmpuint (nice_address_get_port (&sock->addr), !=, 0);
nice_address_set_port (&tmp, nice_address_get_port (&sock->addr));
g_assert_cmpuint (nice_address_get_port (&tmp), !=, 0);
}
static void
test_simple_send_recv (void)
{ {
NiceSocket *server; NiceSocket *server;
NiceSocket *client; NiceSocket *client;
NiceAddress tmp; NiceAddress tmp;
gchar buf[5]; gchar buf[5];
g_type_init ();
server = nice_udp_bsd_socket_new (NULL); server = nice_udp_bsd_socket_new (NULL);
if (!server) g_assert (server != NULL);
g_assert_not_reached();
// not bound to a particular interface client = nice_udp_bsd_socket_new (NULL);
g_assert (server->addr.s.ip4.sin_addr.s_addr == 0); g_assert (client != NULL);
// is bound to a particular port
g_assert (nice_address_get_port (&server->addr) != 0);
g_assert ((client = nice_udp_bsd_socket_new (NULL)) != NULL); g_assert (nice_address_set_from_string (&tmp, "127.0.0.1"));
// not bound to a particular interface nice_address_set_port (&tmp, nice_address_get_port (&server->addr));
g_assert (client->addr.s.ip4.sin_addr.s_addr == 0);
// is bound to a particular port /* Send and receive stuff. */
g_assert (nice_address_get_port (&client->addr) != 0); g_assert (nice_socket_send (client, &tmp, 5, "hello"));
g_assert_cmpint (socket_recv (server, &tmp, 5, buf), ==, 5);
g_assert_cmpint (strncmp (buf, "hello", 5), ==, 0);
g_assert (nice_socket_send (server, &tmp, 5, "uryyb"));
g_assert_cmpint (socket_recv (client, &tmp, 5, buf), ==, 5);
g_assert_cmpint (strncmp (buf, "uryyb", 5), ==, 0);
nice_socket_free (client);
nice_socket_free (server);
}
/* Check that sending and receiving to/from zero-length buffers returns
* immediately. */
static void
test_zero_send_recv (void)
{
NiceSocket *sock;
NiceAddress tmp;
gchar buf[5];
sock = nice_udp_bsd_socket_new (NULL);
g_assert (sock != NULL);
g_assert (nice_address_set_from_string (&tmp, "127.0.0.1"));
g_assert_cmpuint (nice_address_get_port (&sock->addr), !=, 0);
nice_address_set_port (&tmp, nice_address_get_port (&sock->addr));
g_assert_cmpuint (nice_address_get_port (&tmp), !=, 0);
g_assert (nice_socket_send (sock, &tmp, 0, "some-buffer-to-be-ignored"));
g_assert (nice_socket_send (sock, &tmp, 0, NULL));
g_assert_cmpint (socket_recv (sock, &tmp, 0, buf), ==, 0);
g_assert_cmpint (socket_recv (sock, &tmp, 0, NULL), ==, 0);
nice_socket_free (sock);
}
/* Test receiving into multiple tiny buffers. */
static void
test_multi_buffer_recv (void)
{
NiceSocket *server;
NiceSocket *client;
NiceAddress tmp;
guint8 buf[20];
guint8 dummy_buf[9];
if (!nice_address_set_from_string (&tmp, "127.0.0.1")) server = nice_udp_bsd_socket_new (NULL);
g_assert_not_reached(); g_assert (server != NULL);
g_assert (nice_address_get_port (&server->addr) != 0);
client = nice_udp_bsd_socket_new (NULL);
g_assert (client != NULL);
g_assert (nice_address_set_from_string (&tmp, "127.0.0.1"));
nice_address_set_port (&tmp, nice_address_get_port (&server->addr)); nice_address_set_port (&tmp, nice_address_get_port (&server->addr));
g_assert (nice_address_get_port (&tmp) != 0);
nice_socket_send (client, &tmp, 5, "hello"); /* Send and receive stuff. */
{
GInputVector bufs[7] = {
{ &buf[0], 1 },
{ &buf[1], 4 },
{ &buf[1], 0 }, /* should be unused (zero-length) */
{ &buf[5], 1 },
{ &buf[6], 5 },
{ &buf[11], 9 }, /* should be unused (message fits in prior buffers) */
{ &buf[11], 0 }, /* should be unused (zero-length) */
};
NiceInputMessage message = { bufs, G_N_ELEMENTS (bufs), NULL, 0 };
/* Initialise the buffers so we can try and catch out-of-bounds accesses. */
memset (buf, 0xaa, sizeof (buf));
memset (dummy_buf, 0xaa, sizeof (dummy_buf));
g_assert (5 == nice_socket_recv (server, &tmp, 5, buf)); /* Send and receive. */
g_assert (0 == strncmp (buf, "hello", 5)); g_assert (nice_socket_send (client, &tmp, 11, "hello-world"));
g_assert (nice_address_get_port (&tmp) g_assert_cmpuint (nice_socket_recv_messages (server, &message, 1), ==, 1);
== nice_address_get_port (&client->addr)); g_assert_cmpuint (message.length, ==, 11);
nice_socket_send (server, &tmp, 5, "uryyb"); /* Check all of the things. The sizes should not have been modified. */
g_assert (5 == nice_socket_recv (client, &tmp, 5, buf)); g_assert_cmpuint (bufs[0].size, ==, 1);
g_assert (0 == strncmp (buf, "uryyb", 5)); g_assert_cmpuint (bufs[1].size, ==, 4);
g_assert (nice_address_get_port (&tmp) g_assert_cmpuint (bufs[2].size, ==, 0);
== nice_address_get_port (&server->addr)); g_assert_cmpuint (bufs[3].size, ==, 1);
g_assert_cmpuint (bufs[4].size, ==, 5);
g_assert_cmpuint (bufs[5].size, ==, 9);
g_assert_cmpuint (bufs[6].size, ==, 0);
g_assert_cmpint (strncmp ((gchar *) buf, "hello-world", 11), ==, 0);
g_assert_cmpint (memcmp (buf + 11, dummy_buf, 9), ==, 0);
}
nice_socket_free (client); nice_socket_free (client);
nice_socket_free (server); nice_socket_free (server);
}
/* Fill a buffer with deterministic but non-repeated data, so that transmission
* and reception corruption is more likely to be detected. */
static void
fill_send_buf (guint8 *buf, gsize buf_len, guint seed)
{
gsize i;
for (i = 0; i < buf_len; i++) {
buf[i] = '0' + (seed % 10);
seed++;
}
}
/* Test receiving multiple messages in a single call. */
static void
test_multi_message_recv (guint n_sends, guint n_receives,
guint n_bufs_per_message, gsize send_buf_size, gsize recv_buf_size,
guint expected_n_received_messages)
{
NiceSocket *server;
NiceSocket *client;
NiceAddress tmp;
server = nice_udp_bsd_socket_new (NULL);
g_assert (server != NULL);
client = nice_udp_bsd_socket_new (NULL);
g_assert (client != NULL);
g_assert (nice_address_set_from_string (&tmp, "127.0.0.1"));
nice_address_set_port (&tmp, nice_address_get_port (&server->addr));
/* Send and receive stuff. */
{
GInputVector *bufs;
NiceInputMessage *messages;
guint i, j;
guint8 *_expected_recv_buf, *send_buf;
gsize expected_recv_buf_len;
/* Set up the receive buffers. Yay for dynamic tests! */
bufs = g_malloc0_n (n_receives * n_bufs_per_message, sizeof (GInputVector));
messages = g_malloc0_n (n_receives, sizeof (NiceInputMessage));
for (i = 0; i < n_receives; i++) {
for (j = 0; j < n_bufs_per_message; j++) {
bufs[i * n_bufs_per_message + j].buffer = g_slice_alloc (recv_buf_size);
bufs[i * n_bufs_per_message + j].size = recv_buf_size;
/* Initialise the buffer to try to catch out-of-bounds accesses. */
memset (bufs[i * n_bufs_per_message + j].buffer, 0xaa, recv_buf_size);
}
messages[i].buffers = bufs + i * n_bufs_per_message;
messages[i].n_buffers = n_bufs_per_message;
messages[i].from = NULL;
messages[i].length = 0;
}
/* Send multiple packets. */
send_buf = g_slice_alloc (send_buf_size);
for (i = 0; i < n_sends; i++) {
fill_send_buf (send_buf, send_buf_size, i);
g_assert (nice_socket_send (client, &tmp, send_buf_size,
(gchar *) send_buf));
}
g_slice_free1 (send_buf_size, send_buf);
/* Receive things. */
g_assert_cmpuint (
nice_socket_recv_messages (server, messages, n_receives), ==,
expected_n_received_messages);
/* Check all of the things. The sizes should not have been modified. */
expected_recv_buf_len = recv_buf_size * n_bufs_per_message;
_expected_recv_buf = g_slice_alloc (expected_recv_buf_len);
for (i = 0; i < expected_n_received_messages; i++) {
NiceInputMessage *message = &messages[i];
guint8 *expected_recv_buf = _expected_recv_buf;
gsize expected_len;
expected_len = MIN (send_buf_size, expected_recv_buf_len);
g_assert_cmpuint (message->length, ==, expected_len);
/* Build the expected buffer as a concatenation of the expected values of
* all receive buffers in the message. */
fill_send_buf (expected_recv_buf, expected_len, i);
if (expected_recv_buf_len > send_buf_size) {
memset (expected_recv_buf + expected_len, 0xaa,
expected_recv_buf_len - send_buf_size);
}
for (j = 0; j < n_bufs_per_message; j++) {
g_assert_cmpuint (message->buffers[j].size, ==, recv_buf_size);
g_assert_cmpint (
memcmp (message->buffers[j].buffer, expected_recv_buf,
recv_buf_size), ==, 0);
expected_recv_buf += recv_buf_size;
}
}
g_slice_free1 (expected_recv_buf_len, _expected_recv_buf);
}
nice_socket_free (client);
nice_socket_free (server);
}
int
main (void)
{
g_type_init ();
test_socket_initial_properties ();
test_socket_address_properties ();
test_simple_send_recv ();
test_zero_send_recv ();
test_multi_buffer_recv ();
/* Multi-message testing. Serious business. */
{
guint i;
struct {
guint n_sends;
guint n_receives;
guint n_bufs_per_message;
gsize send_buf_size;
gsize recv_buf_size;
guint expected_n_received_messages;
} test_cases[] = {
{ 2, 2, 1, 100, 100, 2 }, /* same number of sends and receives */
{ 4, 2, 2, 100, 77, 2 }, /* more sends than receives */
{ 1, 4, 4, 10, 100, 1 }, /* more receives than sends */
{ 100, 100, 1, 100, 64, 100 }, /* small receive buffer (data loss) */
{ 100, 100, 10, 100, 8, 100 }, /* small receive buffers (data loss) */
};
for (i = 0; i < G_N_ELEMENTS (test_cases); i++) {
test_multi_message_recv (test_cases[i].n_sends, test_cases[i].n_receives,
test_cases[i].n_bufs_per_message, test_cases[i].send_buf_size,
test_cases[i].recv_buf_size,
test_cases[i].expected_n_received_messages);
}
}
return 0; return 0;
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment