Added handling of received PING

This commit is contained in:
Tatsuhiro Tsujikawa 2012-01-27 23:05:29 +09:00
parent 9461147968
commit d1c4c59aad
7 changed files with 202 additions and 7 deletions

View File

@ -51,6 +51,8 @@ AC_CHECK_LIB([cunit], [CU_initialize_registry],
[have_cunit=yes], [have_cunit=no]) [have_cunit=yes], [have_cunit=no])
AM_CONDITIONAL([HAVE_CUNIT], [ test "x${have_cunit}" = "xyes" ]) AM_CONDITIONAL([HAVE_CUNIT], [ test "x${have_cunit}" = "xyes" ])
AC_SEARCH_LIBS([clock_gettime], [rt])
# Checks for header files. # Checks for header files.
AC_CHECK_HEADERS([ \ AC_CHECK_HEADERS([ \
arpa/inet.h \ arpa/inet.h \
@ -58,6 +60,7 @@ AC_CHECK_HEADERS([ \
stdint.h \ stdint.h \
stdlib.h \ stdlib.h \
string.h \ string.h \
time.h \
unistd.h \ unistd.h \
]) ])

View File

@ -179,11 +179,18 @@ typedef void (*spdylay_on_invalid_ctrl_recv_callback)
(spdylay_session *session, spdylay_frame_type type, spdylay_frame *frame, (spdylay_session *session, spdylay_frame_type type, spdylay_frame *frame,
void *user_data); void *user_data);
/*
* Callback function invoked when PING reply is received from peer.
*/
typedef void (*spdylay_on_ping_recv_callback)
(spdylay_session *session, const struct timespec *rtt, void *user_data);
typedef struct { typedef struct {
spdylay_send_callback send_callback; spdylay_send_callback send_callback;
spdylay_recv_callback recv_callback; spdylay_recv_callback recv_callback;
spdylay_on_ctrl_recv_callback on_ctrl_recv_callback; spdylay_on_ctrl_recv_callback on_ctrl_recv_callback;
spdylay_on_invalid_ctrl_recv_callback on_invalid_ctrl_recv_callback; spdylay_on_invalid_ctrl_recv_callback on_invalid_ctrl_recv_callback;
spdylay_on_ping_recv_callback on_ping_recv_callback;
} spdylay_session_callbacks; } spdylay_session_callbacks;
int spdylay_session_client_new(spdylay_session **session_ptr, int spdylay_session_client_new(spdylay_session **session_ptr,
@ -215,6 +222,8 @@ int spdylay_reply_submit(spdylay_session *session,
int32_t stream_id, const char **nv, int32_t stream_id, const char **nv,
spdylay_data_provider *data_prd); spdylay_data_provider *data_prd);
int spdylay_submit_ping(spdylay_session *session);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@ -71,8 +71,14 @@ int spdylay_session_client_new(spdylay_session **session_ptr,
return SPDYLAY_ERR_NOMEM; return SPDYLAY_ERR_NOMEM;
} }
memset(*session_ptr, 0, sizeof(spdylay_session)); memset(*session_ptr, 0, sizeof(spdylay_session));
/* IDs for use in client */
(*session_ptr)->next_stream_id = 1; (*session_ptr)->next_stream_id = 1;
(*session_ptr)->last_recv_stream_id = 0; (*session_ptr)->last_recv_stream_id = 0;
(*session_ptr)->next_unique_id = 1;
(*session_ptr)->last_ping_unique_id = 0;
memset(&(*session_ptr)->last_ping_time, 0, sizeof(struct timespec));
r = spdylay_zlib_deflate_hd_init(&(*session_ptr)->hd_deflater); r = spdylay_zlib_deflate_hd_init(&(*session_ptr)->hd_deflater);
if(r != 0) { if(r != 0) {
@ -131,10 +137,12 @@ static void spdylay_outbound_item_free(spdylay_outbound_item *item)
case SPDYLAY_RST_STREAM: case SPDYLAY_RST_STREAM:
spdylay_frame_rst_stream_free(&item->frame->rst_stream); spdylay_frame_rst_stream_free(&item->frame->rst_stream);
break; break;
case SPDYLAY_PING:
spdylay_frame_ping_free(&item->frame->ping);
break;
case SPDYLAY_HEADERS: case SPDYLAY_HEADERS:
/* Currently we don't have any API to send HEADERS frame, so this spdylay_frame_headers_free(&item->frame->headers);
is unreachable. */ break;
abort();
case SPDYLAY_DATA: case SPDYLAY_DATA:
spdylay_frame_data_free(&item->frame->data); spdylay_frame_data_free(&item->frame->data);
break; break;
@ -195,6 +203,10 @@ int spdylay_session_add_frame(spdylay_session *session,
} }
break; break;
} }
case SPDYLAY_PING:
/* Ping has "height" priority. Give it -1. */
item->pri = -1;
break;
case SPDYLAY_HEADERS: case SPDYLAY_HEADERS:
/* Currently we don't have any API to send HEADERS frame, so this /* Currently we don't have any API to send HEADERS frame, so this
is unreachable. */ is unreachable. */
@ -339,6 +351,12 @@ ssize_t spdylay_session_prep_frame(spdylay_session *session,
} }
break; break;
} }
case SPDYLAY_PING:
framebuflen = spdylay_frame_pack_ping(&framebuf, &item->frame->ping);
if(framebuflen < 0) {
return framebuflen;
}
break;
case SPDYLAY_HEADERS: case SPDYLAY_HEADERS:
/* Currently we don't have any API to send HEADERS frame, so this /* Currently we don't have any API to send HEADERS frame, so this
is unreachable. */ is unreachable. */
@ -370,7 +388,7 @@ static void spdylay_active_outbound_item_reset
memset(aob, 0, sizeof(spdylay_active_outbound_item)); memset(aob, 0, sizeof(spdylay_active_outbound_item));
} }
static spdylay_outbound_item* spdylay_session_get_ob_pq_top spdylay_outbound_item* spdylay_session_get_ob_pq_top
(spdylay_session *session) (spdylay_session *session)
{ {
return (spdylay_outbound_item*)spdylay_pq_top(&session->ob_pq); return (spdylay_outbound_item*)spdylay_pq_top(&session->ob_pq);
@ -401,6 +419,13 @@ static int spdylay_session_after_frame_sent(spdylay_session *session)
case SPDYLAY_RST_STREAM: case SPDYLAY_RST_STREAM:
spdylay_session_close_stream(session, frame->rst_stream.stream_id); spdylay_session_close_stream(session, frame->rst_stream.stream_id);
break; break;
case SPDYLAY_PING:
/* We record the time now and show application code RTT when
reply PING is received. */
session->last_ping_unique_id = frame->ping.unique_id;
/* TODO If clock_gettime() fails, what should we do? */
clock_gettime(CLOCK_MONOTONIC, &session->last_ping_time);
break;
case SPDYLAY_HEADERS: case SPDYLAY_HEADERS:
/* Currently we don't have any API to send HEADERS frame, so this /* Currently we don't have any API to send HEADERS frame, so this
is unreachable. */ is unreachable. */
@ -688,6 +713,39 @@ int spdylay_session_on_syn_reply_received(spdylay_session *session,
return r; return r;
} }
int spdylay_session_on_ping_received(spdylay_session *session,
spdylay_frame *frame)
{
int r = 0;
if(frame->ping.unique_id != 0) {
if(session->last_ping_unique_id == frame->ping.unique_id) {
/* This is ping reply from peer */
struct timespec rtt;
clock_gettime(CLOCK_MONOTONIC, &rtt);
rtt.tv_nsec -= session->last_ping_time.tv_nsec;
if(rtt.tv_nsec < 0) {
rtt.tv_nsec += 1000000000;
--rtt.tv_sec;
}
rtt.tv_sec -= session->last_ping_time.tv_sec;
/* Assign 0 to last_ping_unique_id so that we can ignore same
ID. */
session->last_ping_unique_id = 0;
if(session->callbacks.on_ping_recv_callback) {
session->callbacks.on_ping_recv_callback(session, &rtt,
session->user_data);
}
spdylay_session_call_on_ctrl_frame_received(session, SPDYLAY_PING, frame);
} else if((session->server && frame->ping.unique_id % 2 == 1) ||
(!session->server && frame->ping.unique_id % 2 == 0)) {
/* Peer sent ping, so ping it back */
r = spdylay_session_add_ping(session, frame->ping.unique_id);
spdylay_session_call_on_ctrl_frame_received(session, SPDYLAY_PING, frame);
}
}
return r;
}
int spdylay_session_on_headers_received(spdylay_session *session, int spdylay_session_on_headers_received(spdylay_session *session,
spdylay_frame *frame) spdylay_frame *frame)
{ {
@ -770,6 +828,17 @@ int spdylay_session_process_ctrl_frame(spdylay_session *session)
spdylay_frame_syn_reply_free(&frame.syn_reply); spdylay_frame_syn_reply_free(&frame.syn_reply);
} }
break; break;
case SPDYLAY_PING:
r = spdylay_frame_unpack_ping(&frame.ping,
session->iframe.headbuf,
sizeof(session->iframe.headbuf),
session->iframe.buf,
session->iframe.len);
if(r == 0) {
r = spdylay_session_on_ping_received(session, &frame);
spdylay_frame_ping_free(&frame.ping);
}
break;
case SPDYLAY_HEADERS: case SPDYLAY_HEADERS:
r = spdylay_frame_unpack_headers(&frame.headers, r = spdylay_frame_unpack_headers(&frame.headers,
session->iframe.headbuf, session->iframe.headbuf,
@ -890,13 +959,35 @@ int spdylay_session_want_write(spdylay_session *session)
return session->aob.item != NULL || !spdylay_pq_empty(&session->ob_pq); return session->aob.item != NULL || !spdylay_pq_empty(&session->ob_pq);
} }
int spdylay_session_add_ping(spdylay_session *session, uint32_t unique_id)
{
int r;
spdylay_frame *frame;
frame = malloc(sizeof(spdylay_frame));
if(frame == NULL) {
return SPDYLAY_ERR_NOMEM;
}
spdylay_frame_ping_init(&frame->ping, unique_id);
r = spdylay_session_add_frame(session, SPDYLAY_PING, frame);
if(r != 0) {
spdylay_frame_ping_free(&frame->ping);
free(frame);
}
return r;
}
int spdylay_submit_ping(spdylay_session *session)
{
return spdylay_session_add_ping(session,
spdylay_session_get_next_unique_id(session));
}
int spdylay_reply_submit(spdylay_session *session, int spdylay_reply_submit(spdylay_session *session,
int32_t stream_id, const char **nv, int32_t stream_id, const char **nv,
spdylay_data_provider *data_prd) spdylay_data_provider *data_prd)
{ {
int r; int r;
spdylay_frame *frame; spdylay_frame *frame;
spdylay_frame *data_frame = NULL;
char **nv_copy; char **nv_copy;
uint8_t flags = 0; uint8_t flags = 0;
frame = malloc(sizeof(spdylay_frame)); frame = malloc(sizeof(spdylay_frame));
@ -920,6 +1011,7 @@ int spdylay_reply_submit(spdylay_session *session,
return r; return r;
} }
if(data_prd != NULL) { if(data_prd != NULL) {
spdylay_frame *data_frame;
/* TODO If error is not FATAL, we should add RST_STREAM frame to /* TODO If error is not FATAL, we should add RST_STREAM frame to
cancel this stream. */ cancel this stream. */
data_frame = malloc(sizeof(spdylay_frame)); data_frame = malloc(sizeof(spdylay_frame));
@ -1001,3 +1093,15 @@ ssize_t spdylay_session_pack_data_overwrite(spdylay_session *session,
frame->flags = flags; frame->flags = flags;
return r+8; return r+8;
} }
uint32_t spdylay_session_get_next_unique_id(spdylay_session *session)
{
if(session->next_unique_id > SPDYLAY_MAX_UNIQUE_ID) {
if(session->server) {
session->next_unique_id = 2;
} else {
session->next_unique_id = 1;
}
}
return session->next_unique_id++;
}

View File

@ -29,6 +29,8 @@
# include <config.h> # include <config.h>
#endif /* HAVE_CONFIG_H */ #endif /* HAVE_CONFIG_H */
#include <time.h>
#include <spdylay/spdylay.h> #include <spdylay/spdylay.h>
#include "spdylay_pq.h" #include "spdylay_pq.h"
#include "spdylay_map.h" #include "spdylay_map.h"
@ -62,6 +64,10 @@ typedef enum {
#define SPDYLAY_HEAD_LEN 8 #define SPDYLAY_HEAD_LEN 8
/* Maximum unique ID in use for PING. If unique ID exeeds this number,
it wraps to 1 (client) or 2 (server) */
#define SPDYLAY_MAX_UNIQUE_ID ((1u << 31)-1)
typedef struct { typedef struct {
spdylay_inbound_state state; spdylay_inbound_state state;
uint8_t headbuf[SPDYLAY_HEAD_LEN]; uint8_t headbuf[SPDYLAY_HEAD_LEN];
@ -77,6 +83,9 @@ typedef struct spdylay_session {
uint8_t server; uint8_t server;
int32_t next_stream_id; int32_t next_stream_id;
int32_t last_recv_stream_id; int32_t last_recv_stream_id;
/* Counter of unique ID of PING. Wraps when it exceeds
SPDYLAY_MAX_UNIQUE_ID */
uint32_t next_unique_id;
spdylay_map /* <spdylay_stream*> */ streams; spdylay_map /* <spdylay_stream*> */ streams;
spdylay_pq /* <spdylay_outbound_item*> */ ob_pq; spdylay_pq /* <spdylay_outbound_item*> */ ob_pq;
@ -89,6 +98,11 @@ typedef struct spdylay_session {
spdylay_zlib hd_deflater; spdylay_zlib hd_deflater;
spdylay_zlib hd_inflater; spdylay_zlib hd_inflater;
/* The last unique ID sent to the peer. */
uint32_t last_ping_unique_id;
/* Time stamp when last ping is sent. */
struct timespec last_ping_time;
spdylay_session_callbacks callbacks; spdylay_session_callbacks callbacks;
void *user_data; void *user_data;
} spdylay_session; } spdylay_session;
@ -102,6 +116,8 @@ int spdylay_session_add_frame(spdylay_session *session,
int spdylay_session_add_rst_stream(spdylay_session *session, int spdylay_session_add_rst_stream(spdylay_session *session,
int32_t stream_id, uint32_t status_code); int32_t stream_id, uint32_t status_code);
int spdylay_session_add_ping(spdylay_session *session, uint32_t unique_id);
/* /*
* Creates new stream in |session| with stream ID |stream_id|, * Creates new stream in |session| with stream ID |stream_id|,
* priority |pri| and flags |flags|. Currently, |flags| & * priority |pri| and flags |flags|. Currently, |flags| &
@ -173,4 +189,15 @@ ssize_t spdylay_session_pack_data_overwrite(spdylay_session *session,
uint8_t *buf, size_t len, uint8_t *buf, size_t len,
spdylay_data *frame); spdylay_data *frame);
/*
* Returns next unique ID which can be used with PING.
*/
uint32_t spdylay_session_get_next_unique_id(spdylay_session *session);
/*
* Returns top of outbound frame queue. This function returns NULL if
* queue is empty.
*/
spdylay_outbound_item* spdylay_session_get_ob_pq_top(spdylay_session *session);
#endif /* SPDYLAY_SESSION_H */ #endif /* SPDYLAY_SESSION_H */

View File

@ -84,6 +84,8 @@ int main()
test_spdylay_session_reply_fail) || test_spdylay_session_reply_fail) ||
!CU_add_test(pSuite, "session_on_headers_received", !CU_add_test(pSuite, "session_on_headers_received",
test_spdylay_session_on_headers_received) || test_spdylay_session_on_headers_received) ||
!CU_add_test(pSuite, "session_on_ping_received",
test_spdylay_session_on_ping_received) ||
!CU_add_test(pSuite, "frame_unpack_nv", test_spdylay_frame_unpack_nv) || !CU_add_test(pSuite, "frame_unpack_nv", test_spdylay_frame_unpack_nv) ||
!CU_add_test(pSuite, "frame_count_nv_space", !CU_add_test(pSuite, "frame_count_nv_space",
test_spdylay_frame_count_nv_space) || test_spdylay_frame_count_nv_space) ||

View File

@ -49,7 +49,7 @@ typedef struct {
typedef struct { typedef struct {
accumulator *acc; accumulator *acc;
scripted_data_feed *df; scripted_data_feed *df;
int valid, invalid; int valid, invalid, ping_recv;
size_t data_source_length; size_t data_source_length;
} my_user_data; } my_user_data;
@ -122,6 +122,14 @@ static void on_invalid_ctrl_recv_callback(spdylay_session *session,
++ud->invalid; ++ud->invalid;
} }
static void on_ping_recv_callback(spdylay_session *session,
const struct timespec *rtt,
void *user_data)
{
my_user_data *ud = (my_user_data*)user_data;
++ud->ping_recv;
}
static ssize_t fixed_length_data_source_read_callback static ssize_t fixed_length_data_source_read_callback
(spdylay_session *session, uint8_t *buf, size_t len, int *eof, (spdylay_session *session, uint8_t *buf, size_t len, int *eof,
spdylay_data_source *source, void *user_data) spdylay_data_source *source, void *user_data)
@ -533,3 +541,44 @@ void test_spdylay_session_on_headers_received()
spdylay_frame_headers_free(&frame.headers); spdylay_frame_headers_free(&frame.headers);
spdylay_session_del(session); spdylay_session_del(session);
} }
void test_spdylay_session_on_ping_received()
{
spdylay_session *session;
spdylay_session_callbacks callbacks = {
NULL,
NULL,
on_ctrl_recv_callback,
on_invalid_ctrl_recv_callback,
on_ping_recv_callback
};
my_user_data user_data;
const char *nv[] = { NULL };
spdylay_frame frame;
spdylay_outbound_item *top;
uint32_t unique_id;
user_data.valid = 0;
user_data.invalid = 0;
user_data.ping_recv = 0;
spdylay_session_client_new(&session, &callbacks, &user_data);
unique_id = 2;
spdylay_frame_ping_init(&frame.ping, unique_id);
CU_ASSERT(0 == spdylay_session_on_ping_received(session, &frame));
CU_ASSERT(1 == user_data.valid);
CU_ASSERT(0 == user_data.ping_recv);
top = spdylay_session_get_ob_pq_top(session);
CU_ASSERT(SPDYLAY_PING == top->frame_type);
CU_ASSERT(unique_id == top->frame->ping.unique_id);
session->last_ping_unique_id = 1;
frame.ping.unique_id = 1;
CU_ASSERT(0 == spdylay_session_on_ping_received(session, &frame));
CU_ASSERT(2 == user_data.valid);
CU_ASSERT(1 == user_data.ping_recv);
spdylay_frame_ping_free(&frame.ping);
spdylay_session_del(session);
}

View File

@ -35,5 +35,6 @@ void test_spdylay_session_send_syn_reply();
void test_spdylay_reply_submit(); void test_spdylay_reply_submit();
void test_spdylay_session_reply_fail(); void test_spdylay_session_reply_fail();
void test_spdylay_session_on_headers_received(); void test_spdylay_session_on_headers_received();
void test_spdylay_session_on_ping_received();
#endif // SPDYLAY_SESSION_TEST_H #endif // SPDYLAY_SESSION_TEST_H