diff --git a/lib/includes/spdylay/spdylay.h b/lib/includes/spdylay/spdylay.h index 7362a67..8874cd6 100644 --- a/lib/includes/spdylay/spdylay.h +++ b/lib/includes/spdylay/spdylay.h @@ -662,7 +662,7 @@ void* spdylay_session_get_stream_user_data(spdylay_session *session, * This function returns 0 if it succeeds, or one of the following * negative error codes: * - * SPDYLAY_ERR_INVALID_FRAME + * SPDYLAY_ERR_INVALID_ARGUMENT * |pri| is invalid. * SPDYLAY_ERR_NOMEM * Out of memory. @@ -728,7 +728,7 @@ int spdylay_submit_response(spdylay_session *session, * This function returns 0 if it succeeds, or one of the following * negative error codes: * - * SPDYLAY_ERR_INVALID_FRAME + * SPDYLAY_ERR_INVALID_ARGUMENT * |pri| is invalid. * SPDYLAY_ERR_NOMEM * Out of memory. @@ -830,6 +830,23 @@ int spdylay_submit_ping(spdylay_session *session); */ int spdylay_submit_goaway(spdylay_session *session, uint32_t status_code); +/* + * Stores local settings and submits SETTINGS frame. The |iv| is the + * pointer to the array of spdylay_settings_entry. The |niv| indicates + * the number of spdylay_settings_entry. The |flags| is bitwise-OR of + * one or more values from spdylay_settings_flag. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * SPDYLAY_ERR_INVALID_ARGUMENT + * The |iv| contains duplicate settings ID or invalid value. + * SPDYLAY_ERR_NOMEM + * Out of memory. + */ +int spdylay_submit_settings(spdylay_session *session, uint8_t flags, + const spdylay_settings_entry *iv, size_t niv); + /* * A helper function for dealing with NPN in client side. * |in| contains server's protocol in preferable order. diff --git a/lib/spdylay_frame.c b/lib/spdylay_frame.c index 293375a..8331f31 100644 --- a/lib/spdylay_frame.c +++ b/lib/spdylay_frame.c @@ -1000,3 +1000,14 @@ spdylay_settings_entry* spdylay_frame_iv_copy(const spdylay_settings_entry *iv, memcpy(iv_copy, iv, len); return iv_copy; } + +static int spdylay_settings_entry_compar(const void *lhs, const void *rhs) +{ + return ((spdylay_settings_entry *)lhs)->settings_id + -((spdylay_settings_entry *)rhs)->settings_id; +} + +void spdylay_frame_iv_sort(spdylay_settings_entry *iv, size_t niv) +{ + qsort(iv, niv, sizeof(spdylay_settings_entry), spdylay_settings_entry_compar); +} diff --git a/lib/spdylay_frame.h b/lib/spdylay_frame.h index faa6b15..03e0dc5 100644 --- a/lib/spdylay_frame.h +++ b/lib/spdylay_frame.h @@ -618,4 +618,11 @@ void spdylay_frame_nv_2to3(char **nv); spdylay_settings_entry* spdylay_frame_iv_copy(const spdylay_settings_entry *iv, size_t niv); +/* + * Sorts the |iv| with the ascending order of the settings_id member. + * The number of the element in the array pointed by the |iv| is given + * by the |niv|. + */ +void spdylay_frame_iv_sort(spdylay_settings_entry *iv, size_t niv); + #endif /* SPDYLAY_FRAME_H */ diff --git a/lib/spdylay_session.c b/lib/spdylay_session.c index 6818d48..444021f 100644 --- a/lib/spdylay_session.c +++ b/lib/spdylay_session.c @@ -328,6 +328,7 @@ int spdylay_session_add_frame(spdylay_session *session, } case SPDYLAY_SETTINGS: /* Should SPDYLAY_SETTINGS have higher priority? */ + item->pri = -1; break; case SPDYLAY_NOOP: /* We don't have any public API to add NOOP, so here is @@ -1542,6 +1543,17 @@ static void spdylay_session_update_initial_window_size vals); } +void spdylay_session_update_local_settings(spdylay_session *session, + spdylay_settings_entry *iv, + size_t niv) +{ + int i; + for(i = 0; i < niv; ++i) { + assert(iv[i].settings_id > 0 && iv[i].settings_id <= SPDYLAY_SETTINGS_MAX); + session->local_settings[iv[i].settings_id] = iv[i].value; + } +} + int spdylay_session_on_settings_received(spdylay_session *session, spdylay_frame *frame) { diff --git a/lib/spdylay_session.h b/lib/spdylay_session.h index 994e9ad..f6070ab 100644 --- a/lib/spdylay_session.h +++ b/lib/spdylay_session.h @@ -483,4 +483,14 @@ spdylay_outbound_item* spdylay_session_get_next_ob_item */ uint8_t spdylay_session_get_pri_lowest(spdylay_session *session); +/* + * Updates local settings with the |iv|. The number of elements in the + * array pointed by the |iv| is given by the |niv|. This function + * assumes that the all settings_id member in |iv| are in range 1 to + * SPDYLAY_SETTINGS_MAX, inclusive. + */ +void spdylay_session_update_local_settings(spdylay_session *session, + spdylay_settings_entry *iv, + size_t niv); + #endif /* SPDYLAY_SESSION_H */ diff --git a/lib/spdylay_submit.c b/lib/spdylay_submit.c index c907893..7671e3a 100644 --- a/lib/spdylay_submit.c +++ b/lib/spdylay_submit.c @@ -24,6 +24,8 @@ */ #include "spdylay_submit.h" +#include + #include "spdylay_session.h" #include "spdylay_frame.h" @@ -183,6 +185,44 @@ int spdylay_submit_goaway(spdylay_session *session, uint32_t status_code) status_code); } +int spdylay_submit_settings(spdylay_session *session, uint8_t flags, + const spdylay_settings_entry *iv, size_t niv) +{ + spdylay_frame *frame; + spdylay_settings_entry *iv_copy; + int check[SPDYLAY_SETTINGS_MAX+1]; + int i, r; + memset(check, 0, sizeof(check)); + for(i = 0; i < niv; ++i) { + if(iv[i].settings_id > SPDYLAY_SETTINGS_MAX || iv[i].settings_id == 0 || + check[iv[i].settings_id] == 1) { + return SPDYLAY_ERR_INVALID_ARGUMENT; + } else { + check[iv[i].settings_id] = 1; + } + } + frame = malloc(sizeof(spdylay_frame)); + if(frame == NULL) { + return SPDYLAY_ERR_NOMEM; + } + iv_copy = spdylay_frame_iv_copy(iv, niv); + if(iv_copy == NULL) { + free(frame); + return SPDYLAY_ERR_NOMEM; + } + spdylay_frame_iv_sort(iv_copy, niv); + spdylay_frame_settings_init(&frame->settings, session->version, + flags, iv_copy, niv); + r = spdylay_session_add_frame(session, SPDYLAY_SETTINGS, frame, NULL); + if(r == 0) { + spdylay_session_update_local_settings(session, iv_copy, niv); + } else { + spdylay_frame_settings_free(&frame->settings); + free(frame); + } + return r; +} + int spdylay_submit_request(spdylay_session *session, uint8_t pri, const char **nv, const spdylay_data_provider *data_prd, diff --git a/tests/main.c b/tests/main.c index 439e833..25a95e8 100644 --- a/tests/main.c +++ b/tests/main.c @@ -140,6 +140,8 @@ int main(int argc, char* argv[]) test_spdylay_session_on_ctrl_not_send) || !CU_add_test(pSuite, "session_on_settings_received", test_spdylay_session_on_settings_received) || + !CU_add_test(pSuite, "session_submit_settings", + test_spdylay_submit_settings) || !CU_add_test(pSuite, "frame_unpack_nv_spdy2", test_spdylay_frame_unpack_nv_spdy2) || !CU_add_test(pSuite, "frame_unpack_nv_spdy3", diff --git a/tests/spdylay_session_test.c b/tests/spdylay_session_test.c index 308cc4a..afe890f 100644 --- a/tests/spdylay_session_test.c +++ b/tests/spdylay_session_test.c @@ -1898,3 +1898,77 @@ void test_spdylay_session_on_settings_received() spdylay_session_del(session); } + +void test_spdylay_submit_settings() +{ + spdylay_session *session; + spdylay_session_callbacks callbacks; + my_user_data ud; + spdylay_outbound_item *item; + spdylay_frame *frame; + spdylay_settings_entry iv[3]; + + iv[0].settings_id = SPDYLAY_SETTINGS_MAX_CONCURRENT_STREAMS; + iv[0].value = 50; + iv[0].flags = SPDYLAY_ID_FLAG_SETTINGS_NONE; + + iv[1].settings_id = SPDYLAY_SETTINGS_INITIAL_WINDOW_SIZE; + iv[1].value = 16*1024; + iv[1].flags = SPDYLAY_ID_FLAG_SETTINGS_NONE; + + /* This is duplicate entry */ + iv[2].settings_id = SPDYLAY_SETTINGS_MAX_CONCURRENT_STREAMS; + iv[2].value = 150; + iv[2].flags = SPDYLAY_ID_FLAG_SETTINGS_NONE; + + memset(&callbacks, 0, sizeof(spdylay_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_ctrl_send_callback = on_ctrl_send_callback; + spdylay_session_server_new(&session, SPDYLAY_PROTO_SPDY3, &callbacks, &ud); + + CU_ASSERT(SPDYLAY_ERR_INVALID_ARGUMENT == + spdylay_submit_settings(session, SPDYLAY_FLAG_SETTINGS_NONE, + iv, 3)); + + /* Make sure that local settings are not changed */ + CU_ASSERT(SPDYLAY_CONCURRENT_STREAMS_MAX == + session->local_settings[SPDYLAY_SETTINGS_MAX_CONCURRENT_STREAMS]); + CU_ASSERT(SPDYLAY_INITIAL_WINDOW_SIZE == + session->local_settings[SPDYLAY_SETTINGS_INITIAL_WINDOW_SIZE]); + + CU_ASSERT(0 == spdylay_submit_settings(session, + SPDYLAY_FLAG_SETTINGS_CLEAR_SETTINGS, + iv, 2)); + + /* Make sure that local settings are changed */ + CU_ASSERT(50 == + session->local_settings[SPDYLAY_SETTINGS_MAX_CONCURRENT_STREAMS]); + CU_ASSERT(16*1024 == + session->local_settings[SPDYLAY_SETTINGS_INITIAL_WINDOW_SIZE]); + + item = spdylay_session_get_next_ob_item(session); + + CU_ASSERT(SPDYLAY_SETTINGS == item->frame_type); + + frame = item->frame; + CU_ASSERT(2 == frame->settings.niv); + CU_ASSERT(SPDYLAY_FLAG_SETTINGS_CLEAR_SETTINGS == frame->settings.hd.flags); + + CU_ASSERT(50 == frame->settings.iv[0].value); + CU_ASSERT(SPDYLAY_SETTINGS_MAX_CONCURRENT_STREAMS == + frame->settings.iv[0].settings_id); + CU_ASSERT(SPDYLAY_FLAG_SETTINGS_NONE == + frame->settings.iv[0].flags); + + CU_ASSERT(16*1024 == frame->settings.iv[1].value); + CU_ASSERT(SPDYLAY_SETTINGS_INITIAL_WINDOW_SIZE == + frame->settings.iv[1].settings_id); + CU_ASSERT(SPDYLAY_FLAG_SETTINGS_NONE == + frame->settings.iv[1].flags); + + ud.ctrl_send_cb_called = 0; + CU_ASSERT(0 == spdylay_session_send(session)); + CU_ASSERT(1 == ud.ctrl_send_cb_called); + + spdylay_session_del(session); +} diff --git a/tests/spdylay_session_test.h b/tests/spdylay_session_test.h index c248406..134f126 100644 --- a/tests/spdylay_session_test.h +++ b/tests/spdylay_session_test.h @@ -62,5 +62,6 @@ void test_spdylay_session_defer_data(); void test_spdylay_session_flow_control(); void test_spdylay_session_on_ctrl_not_send(); void test_spdylay_session_on_settings_received(); +void test_spdylay_submit_settings(); #endif // SPDYLAY_SESSION_TEST_H