diff --git a/configure.ac b/configure.ac index 97ebbc1..e794d0a 100644 --- a/configure.ac +++ b/configure.ac @@ -51,6 +51,8 @@ AC_CHECK_LIB([cunit], [CU_initialize_registry], [have_cunit=yes], [have_cunit=no]) AM_CONDITIONAL([HAVE_CUNIT], [ test "x${have_cunit}" = "xyes" ]) +AC_SEARCH_LIBS([clock_gettime], [rt]) + # Checks for header files. AC_CHECK_HEADERS([ \ arpa/inet.h \ @@ -58,6 +60,7 @@ AC_CHECK_HEADERS([ \ stdint.h \ stdlib.h \ string.h \ + time.h \ unistd.h \ ]) diff --git a/lib/includes/spdylay/spdylay.h b/lib/includes/spdylay/spdylay.h index 73b628c..93fed61 100644 --- a/lib/includes/spdylay/spdylay.h +++ b/lib/includes/spdylay/spdylay.h @@ -179,11 +179,18 @@ typedef void (*spdylay_on_invalid_ctrl_recv_callback) (spdylay_session *session, spdylay_frame_type type, spdylay_frame *frame, 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 { spdylay_send_callback send_callback; spdylay_recv_callback recv_callback; spdylay_on_ctrl_recv_callback on_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; 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, spdylay_data_provider *data_prd); +int spdylay_submit_ping(spdylay_session *session); + #ifdef __cplusplus } #endif diff --git a/lib/spdylay_session.c b/lib/spdylay_session.c index fbc605b..7fb6cfa 100644 --- a/lib/spdylay_session.c +++ b/lib/spdylay_session.c @@ -71,8 +71,14 @@ int spdylay_session_client_new(spdylay_session **session_ptr, return SPDYLAY_ERR_NOMEM; } memset(*session_ptr, 0, sizeof(spdylay_session)); + + /* IDs for use in client */ (*session_ptr)->next_stream_id = 1; (*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); if(r != 0) { @@ -131,10 +137,12 @@ static void spdylay_outbound_item_free(spdylay_outbound_item *item) case SPDYLAY_RST_STREAM: spdylay_frame_rst_stream_free(&item->frame->rst_stream); break; + case SPDYLAY_PING: + spdylay_frame_ping_free(&item->frame->ping); + break; case SPDYLAY_HEADERS: - /* Currently we don't have any API to send HEADERS frame, so this - is unreachable. */ - abort(); + spdylay_frame_headers_free(&item->frame->headers); + break; case SPDYLAY_DATA: spdylay_frame_data_free(&item->frame->data); break; @@ -195,6 +203,10 @@ int spdylay_session_add_frame(spdylay_session *session, } break; } + case SPDYLAY_PING: + /* Ping has "height" priority. Give it -1. */ + item->pri = -1; + break; case SPDYLAY_HEADERS: /* Currently we don't have any API to send HEADERS frame, so this is unreachable. */ @@ -339,6 +351,12 @@ ssize_t spdylay_session_prep_frame(spdylay_session *session, } break; } + case SPDYLAY_PING: + framebuflen = spdylay_frame_pack_ping(&framebuf, &item->frame->ping); + if(framebuflen < 0) { + return framebuflen; + } + break; case SPDYLAY_HEADERS: /* Currently we don't have any API to send HEADERS frame, so this is unreachable. */ @@ -370,7 +388,7 @@ static void spdylay_active_outbound_item_reset 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) { 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: spdylay_session_close_stream(session, frame->rst_stream.stream_id); 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: /* Currently we don't have any API to send HEADERS frame, so this is unreachable. */ @@ -688,6 +713,39 @@ int spdylay_session_on_syn_reply_received(spdylay_session *session, 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, spdylay_frame *frame) { @@ -770,6 +828,17 @@ int spdylay_session_process_ctrl_frame(spdylay_session *session) spdylay_frame_syn_reply_free(&frame.syn_reply); } 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: r = spdylay_frame_unpack_headers(&frame.headers, 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); } +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, int32_t stream_id, const char **nv, spdylay_data_provider *data_prd) { int r; spdylay_frame *frame; - spdylay_frame *data_frame = NULL; char **nv_copy; uint8_t flags = 0; frame = malloc(sizeof(spdylay_frame)); @@ -920,6 +1011,7 @@ int spdylay_reply_submit(spdylay_session *session, return r; } if(data_prd != NULL) { + spdylay_frame *data_frame; /* TODO If error is not FATAL, we should add RST_STREAM frame to cancel this stream. */ data_frame = malloc(sizeof(spdylay_frame)); @@ -1001,3 +1093,15 @@ ssize_t spdylay_session_pack_data_overwrite(spdylay_session *session, frame->flags = flags; 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++; +} diff --git a/lib/spdylay_session.h b/lib/spdylay_session.h index 90f72de..e606ab8 100644 --- a/lib/spdylay_session.h +++ b/lib/spdylay_session.h @@ -29,6 +29,8 @@ # include #endif /* HAVE_CONFIG_H */ +#include + #include #include "spdylay_pq.h" #include "spdylay_map.h" @@ -62,6 +64,10 @@ typedef enum { #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 { spdylay_inbound_state state; uint8_t headbuf[SPDYLAY_HEAD_LEN]; @@ -77,7 +83,10 @@ typedef struct spdylay_session { uint8_t server; int32_t next_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 /* */ streams; spdylay_pq /* */ ob_pq; @@ -89,6 +98,11 @@ typedef struct spdylay_session { spdylay_zlib hd_deflater; 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; void *user_data; } spdylay_session; @@ -102,6 +116,8 @@ int spdylay_session_add_frame(spdylay_session *session, int spdylay_session_add_rst_stream(spdylay_session *session, 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|, * 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, 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 */ diff --git a/tests/main.c b/tests/main.c index 531c737..fbd4d14 100644 --- a/tests/main.c +++ b/tests/main.c @@ -84,6 +84,8 @@ int main() test_spdylay_session_reply_fail) || !CU_add_test(pSuite, "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_count_nv_space", test_spdylay_frame_count_nv_space) || diff --git a/tests/spdylay_session_test.c b/tests/spdylay_session_test.c index cf63edc..8507d03 100644 --- a/tests/spdylay_session_test.c +++ b/tests/spdylay_session_test.c @@ -49,7 +49,7 @@ typedef struct { typedef struct { accumulator *acc; scripted_data_feed *df; - int valid, invalid; + int valid, invalid, ping_recv; size_t data_source_length; } my_user_data; @@ -122,6 +122,14 @@ static void on_invalid_ctrl_recv_callback(spdylay_session *session, ++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 (spdylay_session *session, uint8_t *buf, size_t len, int *eof, 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_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); +} diff --git a/tests/spdylay_session_test.h b/tests/spdylay_session_test.h index cf4da3b..153ef68 100644 --- a/tests/spdylay_session_test.h +++ b/tests/spdylay_session_test.h @@ -35,5 +35,6 @@ void test_spdylay_session_send_syn_reply(); void test_spdylay_reply_submit(); void test_spdylay_session_reply_fail(); void test_spdylay_session_on_headers_received(); +void test_spdylay_session_on_ping_received(); #endif // SPDYLAY_SESSION_TEST_H