Implement compressed DATA
The library interface supports compressed DATA. The library does not deflate nor inflate data payload. When sending data, an application has to compress data and set NGHTTP2_DATA_FLAG_COMPRESSED to data_flags parameter in nghttp2_data_source_read_callback. On receiving, flags parameter in nghttp2_on_data_chunk_recv_callback includes NGHTTP2_FLAG_COMPRESSED. An application should check the flags and inflate data as necessary. Since compression context is per frame, when DATA is seen in nghttp2_on_frame_recv_callback, an application should reset compression context.
This commit is contained in:
parent
6bb410d603
commit
052be3296c
|
@ -488,7 +488,11 @@ typedef enum {
|
||||||
/**
|
/**
|
||||||
* The PRIORITY flag.
|
* The PRIORITY flag.
|
||||||
*/
|
*/
|
||||||
NGHTTP2_FLAG_PRIORITY = 0x20
|
NGHTTP2_FLAG_PRIORITY = 0x20,
|
||||||
|
/**
|
||||||
|
* THE COMPRESSED flag.
|
||||||
|
*/
|
||||||
|
NGHTTP2_FLAG_COMPRESSED = 0x20
|
||||||
} nghttp2_flag;
|
} nghttp2_flag;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -512,10 +516,14 @@ typedef enum {
|
||||||
* SETTINGS_INITIAL_WINDOW_SIZE
|
* SETTINGS_INITIAL_WINDOW_SIZE
|
||||||
*/
|
*/
|
||||||
NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE = 4,
|
NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE = 4,
|
||||||
|
/**
|
||||||
|
* SETTINGS_COMPRESS_DATA
|
||||||
|
*/
|
||||||
|
NGHTTP2_SETTINGS_COMPRESS_DATA = 5,
|
||||||
/**
|
/**
|
||||||
* Maximum ID of :type:`nghttp2_settings_id`.
|
* Maximum ID of :type:`nghttp2_settings_id`.
|
||||||
*/
|
*/
|
||||||
NGHTTP2_SETTINGS_MAX = 4
|
NGHTTP2_SETTINGS_MAX = 5
|
||||||
} nghttp2_settings_id;
|
} nghttp2_settings_id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -638,7 +646,11 @@ typedef enum {
|
||||||
/**
|
/**
|
||||||
* Indicates EOF was sensed.
|
* Indicates EOF was sensed.
|
||||||
*/
|
*/
|
||||||
NGHTTP2_DATA_FLAG_EOF = 0x01
|
NGHTTP2_DATA_FLAG_EOF = 0x01,
|
||||||
|
/**
|
||||||
|
* Indicates data was compressed.
|
||||||
|
*/
|
||||||
|
NGHTTP2_DATA_FLAG_COMPRESSED = 0x02
|
||||||
} nghttp2_data_flag;
|
} nghttp2_data_flag;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -649,7 +661,11 @@ typedef enum {
|
||||||
* The implementation of this function must read at most |length|
|
* The implementation of this function must read at most |length|
|
||||||
* bytes of data from |source| (or possibly other places) and store
|
* bytes of data from |source| (or possibly other places) and store
|
||||||
* them in |buf| and return number of data stored in |buf|. If EOF is
|
* them in |buf| and return number of data stored in |buf|. If EOF is
|
||||||
* reached, set :enum:`NGHTTP2_DATA_FLAG_EOF` flag in |*data_falgs|.
|
* reached, set :enum:`NGHTTP2_DATA_FLAG_EOF` flag in |*data_flags|.
|
||||||
|
*
|
||||||
|
* To send compressed data payload without affecting content-length,
|
||||||
|
* set :enum:`NGHTTP2_DATA_FLAG_COMPRESSED` flag in |*data_flags|.
|
||||||
|
*
|
||||||
* If the application wants to postpone DATA frames (e.g.,
|
* If the application wants to postpone DATA frames (e.g.,
|
||||||
* asynchronous I/O, or reading data blocks for long time), it is
|
* asynchronous I/O, or reading data blocks for long time), it is
|
||||||
* achieved by returning :enum:`NGHTTP2_ERR_DEFERRED` without reading
|
* achieved by returning :enum:`NGHTTP2_ERR_DEFERRED` without reading
|
||||||
|
|
|
@ -938,6 +938,7 @@ int nghttp2_iv_check(const nghttp2_settings_entry *iv, size_t niv)
|
||||||
case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS:
|
case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS:
|
||||||
break;
|
break;
|
||||||
case NGHTTP2_SETTINGS_ENABLE_PUSH:
|
case NGHTTP2_SETTINGS_ENABLE_PUSH:
|
||||||
|
case NGHTTP2_SETTINGS_COMPRESS_DATA:
|
||||||
if(iv[i].value != 0 && iv[i].value != 1) {
|
if(iv[i].value != 0 && iv[i].value != 1) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -225,6 +225,7 @@ static void init_settings(uint32_t *settings)
|
||||||
NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS;
|
NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS;
|
||||||
settings[NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE] =
|
settings[NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE] =
|
||||||
NGHTTP2_INITIAL_WINDOW_SIZE;
|
NGHTTP2_INITIAL_WINDOW_SIZE;
|
||||||
|
settings[NGHTTP2_SETTINGS_COMPRESS_DATA] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void nghttp2_active_outbound_item_reset
|
static void nghttp2_active_outbound_item_reset
|
||||||
|
@ -3464,6 +3465,12 @@ int nghttp2_session_on_settings_received(nghttp2_session *session,
|
||||||
(session, frame, NGHTTP2_FLOW_CONTROL_ERROR);
|
(session, frame, NGHTTP2_FLOW_CONTROL_ERROR);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case NGHTTP2_SETTINGS_COMPRESS_DATA:
|
||||||
|
if(entry->value != 0 && entry->value != 1) {
|
||||||
|
return nghttp2_session_handle_invalid_connection
|
||||||
|
(session, frame, NGHTTP2_PROTOCOL_ERROR);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
session->remote_settings[entry->settings_id] = entry->value;
|
session->remote_settings[entry->settings_id] = entry->value;
|
||||||
}
|
}
|
||||||
|
@ -4024,7 +4031,12 @@ static int nghttp2_session_on_data_received_fail_fast(nghttp2_session *session)
|
||||||
{
|
{
|
||||||
int rv;
|
int rv;
|
||||||
nghttp2_stream *stream;
|
nghttp2_stream *stream;
|
||||||
int32_t stream_id = session->iframe.frame.hd.stream_id;
|
nghttp2_inbound_frame *iframe;
|
||||||
|
int32_t stream_id;
|
||||||
|
|
||||||
|
iframe = &session->iframe;
|
||||||
|
stream_id = iframe->frame.hd.stream_id;
|
||||||
|
|
||||||
if(stream_id == 0) {
|
if(stream_id == 0) {
|
||||||
/* The spec says that if a DATA frame is received whose stream ID
|
/* The spec says that if a DATA frame is received whose stream ID
|
||||||
is 0, the recipient MUST respond with a connection error of
|
is 0, the recipient MUST respond with a connection error of
|
||||||
|
@ -4041,6 +4053,12 @@ static int nghttp2_session_on_data_received_fail_fast(nghttp2_session *session)
|
||||||
if(stream->shut_flags & NGHTTP2_SHUT_RD) {
|
if(stream->shut_flags & NGHTTP2_SHUT_RD) {
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if((iframe->frame.hd.flags & NGHTTP2_FLAG_COMPRESSED) &&
|
||||||
|
session->local_settings[NGHTTP2_SETTINGS_COMPRESS_DATA] == 0) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
if(nghttp2_session_is_my_stream_id(session, stream_id)) {
|
if(nghttp2_session_is_my_stream_id(session, stream_id)) {
|
||||||
if(stream->state == NGHTTP2_STREAM_CLOSING) {
|
if(stream->state == NGHTTP2_STREAM_CLOSING) {
|
||||||
return NGHTTP2_ERR_IGN_PAYLOAD;
|
return NGHTTP2_ERR_IGN_PAYLOAD;
|
||||||
|
@ -4111,6 +4129,7 @@ static int inbound_frame_set_settings_entry(nghttp2_inbound_frame *iframe)
|
||||||
case NGHTTP2_SETTINGS_ENABLE_PUSH:
|
case NGHTTP2_SETTINGS_ENABLE_PUSH:
|
||||||
case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS:
|
case NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS:
|
||||||
case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE:
|
case NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE:
|
||||||
|
case NGHTTP2_SETTINGS_COMPRESS_DATA:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -4260,7 +4279,8 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
|
||||||
iframe->frame.hd.flags &= (NGHTTP2_FLAG_END_STREAM |
|
iframe->frame.hd.flags &= (NGHTTP2_FLAG_END_STREAM |
|
||||||
NGHTTP2_FLAG_END_SEGMENT |
|
NGHTTP2_FLAG_END_SEGMENT |
|
||||||
NGHTTP2_FLAG_PAD_LOW |
|
NGHTTP2_FLAG_PAD_LOW |
|
||||||
NGHTTP2_FLAG_PAD_HIGH);
|
NGHTTP2_FLAG_PAD_HIGH |
|
||||||
|
NGHTTP2_FLAG_COMPRESSED);
|
||||||
/* Check stream is open. If it is not open or closing,
|
/* Check stream is open. If it is not open or closing,
|
||||||
ignore payload. */
|
ignore payload. */
|
||||||
busy = 1;
|
busy = 1;
|
||||||
|
@ -5559,6 +5579,10 @@ int nghttp2_session_pack_data(nghttp2_session *session,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(data_flags & NGHTTP2_DATA_FLAG_COMPRESSED) {
|
||||||
|
flags |= NGHTTP2_FLAG_COMPRESSED;
|
||||||
|
}
|
||||||
|
|
||||||
/* The primary reason of data_frame is pass to the user callback */
|
/* The primary reason of data_frame is pass to the user callback */
|
||||||
data_frame.hd.length = payloadlen;
|
data_frame.hd.length = payloadlen;
|
||||||
data_frame.hd.stream_id = frame->hd.stream_id;
|
data_frame.hd.stream_id = frame->hd.stream_id;
|
||||||
|
|
|
@ -234,6 +234,12 @@ void print_flags(const nghttp2_frame_hd& hd)
|
||||||
}
|
}
|
||||||
s += "PAD_HIGH";
|
s += "PAD_HIGH";
|
||||||
}
|
}
|
||||||
|
if(hd.flags & NGHTTP2_FLAG_COMPRESSED) {
|
||||||
|
if(!s.empty()) {
|
||||||
|
s += " | ";
|
||||||
|
}
|
||||||
|
s += "COMPRESSED";
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case NGHTTP2_HEADERS:
|
case NGHTTP2_HEADERS:
|
||||||
if(hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
if(hd.flags & NGHTTP2_FLAG_END_STREAM) {
|
||||||
|
|
|
@ -632,6 +632,15 @@ void test_nghttp2_iv_check(void)
|
||||||
iv[1].value = 3;
|
iv[1].value = 3;
|
||||||
CU_ASSERT(!nghttp2_iv_check(iv, 2));
|
CU_ASSERT(!nghttp2_iv_check(iv, 2));
|
||||||
|
|
||||||
|
/* COMPRESSED_DATA only allows 0 or 1 */
|
||||||
|
iv[1].settings_id = NGHTTP2_SETTINGS_COMPRESS_DATA;
|
||||||
|
iv[1].value = 0;
|
||||||
|
CU_ASSERT(nghttp2_iv_check(iv, 2));
|
||||||
|
iv[1].value = 1;
|
||||||
|
CU_ASSERT(nghttp2_iv_check(iv, 2));
|
||||||
|
iv[1].value = 3;
|
||||||
|
CU_ASSERT(!nghttp2_iv_check(iv, 2));
|
||||||
|
|
||||||
/* Undefined SETTINGS ID */
|
/* Undefined SETTINGS ID */
|
||||||
iv[1].settings_id = 1000000009;
|
iv[1].settings_id = 1000000009;
|
||||||
iv[1].value = 0;
|
iv[1].value = 0;
|
||||||
|
|
|
@ -75,6 +75,7 @@ typedef struct {
|
||||||
nghttp2_nv nv;
|
nghttp2_nv nv;
|
||||||
size_t data_chunk_len;
|
size_t data_chunk_len;
|
||||||
size_t padding_boundary;
|
size_t padding_boundary;
|
||||||
|
int compress_data;
|
||||||
} my_user_data;
|
} my_user_data;
|
||||||
|
|
||||||
static void scripted_data_feed_init2(scripted_data_feed *df,
|
static void scripted_data_feed_init2(scripted_data_feed *df,
|
||||||
|
@ -249,6 +250,24 @@ static ssize_t fixed_length_data_source_read_callback
|
||||||
return wlen;
|
return wlen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static ssize_t compressed_fixed_length_data_source_read_callback
|
||||||
|
(nghttp2_session *session, int32_t stream_id,
|
||||||
|
uint8_t *buf, size_t len, uint32_t *data_flags,
|
||||||
|
nghttp2_data_source *source, void *user_data)
|
||||||
|
{
|
||||||
|
my_user_data *ud = (my_user_data*)user_data;
|
||||||
|
size_t wlen;
|
||||||
|
|
||||||
|
wlen = fixed_length_data_source_read_callback
|
||||||
|
(session, stream_id, buf, len, data_flags, source, user_data);
|
||||||
|
|
||||||
|
if(ud->compress_data) {
|
||||||
|
*data_flags |= NGHTTP2_DATA_FLAG_COMPRESSED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return wlen;
|
||||||
|
}
|
||||||
|
|
||||||
static ssize_t temporal_failure_data_source_read_callback
|
static ssize_t temporal_failure_data_source_read_callback
|
||||||
(nghttp2_session *session, int32_t stream_id,
|
(nghttp2_session *session, int32_t stream_id,
|
||||||
uint8_t *buf, size_t len, uint32_t *data_flags,
|
uint8_t *buf, size_t len, uint32_t *data_flags,
|
||||||
|
@ -703,6 +722,57 @@ void test_nghttp2_session_recv_data(void)
|
||||||
CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == OB_CTRL(item)->goaway.error_code);
|
CU_ASSERT(NGHTTP2_PROTOCOL_ERROR == OB_CTRL(item)->goaway.error_code);
|
||||||
|
|
||||||
nghttp2_session_del(session);
|
nghttp2_session_del(session);
|
||||||
|
|
||||||
|
/* Receiving compressed DATA while SETTINGS_COMPRESS_DATA == 0 is
|
||||||
|
subject to connection error */
|
||||||
|
|
||||||
|
nghttp2_session_client_new(&session, &callbacks, &ud);
|
||||||
|
|
||||||
|
hd.length = 4096;
|
||||||
|
hd.type = NGHTTP2_DATA;
|
||||||
|
hd.flags = NGHTTP2_FLAG_COMPRESSED;
|
||||||
|
hd.stream_id = 1;
|
||||||
|
nghttp2_frame_pack_frame_hd(data, &hd);
|
||||||
|
|
||||||
|
stream = nghttp2_session_open_stream(session, 1,
|
||||||
|
NGHTTP2_STREAM_FLAG_NONE,
|
||||||
|
&pri_spec_default,
|
||||||
|
NGHTTP2_STREAM_OPENED, NULL);
|
||||||
|
|
||||||
|
ud.data_chunk_recv_cb_called = 0;
|
||||||
|
ud.frame_recv_cb_called = 0;
|
||||||
|
rv = nghttp2_session_mem_recv(session, data, 8+4096);
|
||||||
|
CU_ASSERT(8+4096 == rv);
|
||||||
|
|
||||||
|
CU_ASSERT(0 == ud.data_chunk_recv_cb_called);
|
||||||
|
CU_ASSERT(0 == ud.frame_recv_cb_called);
|
||||||
|
item = nghttp2_session_get_next_ob_item(session);
|
||||||
|
CU_ASSERT(NGHTTP2_GOAWAY == OB_CTRL_TYPE(item));
|
||||||
|
|
||||||
|
nghttp2_session_del(session);
|
||||||
|
|
||||||
|
/* Receiving compressed DATA while SETTINGS_COMPRESS_DATA == 1 is
|
||||||
|
allowed */
|
||||||
|
|
||||||
|
nghttp2_session_client_new(&session, &callbacks, &ud);
|
||||||
|
|
||||||
|
session->local_settings[NGHTTP2_SETTINGS_COMPRESS_DATA] = 1;
|
||||||
|
|
||||||
|
stream = nghttp2_session_open_stream(session, 1,
|
||||||
|
NGHTTP2_STREAM_FLAG_NONE,
|
||||||
|
&pri_spec_default,
|
||||||
|
NGHTTP2_STREAM_OPENED, NULL);
|
||||||
|
|
||||||
|
ud.data_chunk_recv_cb_called = 0;
|
||||||
|
ud.frame_recv_cb_called = 0;
|
||||||
|
rv = nghttp2_session_mem_recv(session, data, 8+4096);
|
||||||
|
CU_ASSERT(8+4096 == rv);
|
||||||
|
|
||||||
|
CU_ASSERT(1 == ud.data_chunk_recv_cb_called);
|
||||||
|
CU_ASSERT(1 == ud.frame_recv_cb_called);
|
||||||
|
CU_ASSERT(NULL == nghttp2_session_get_next_ob_item(session));
|
||||||
|
|
||||||
|
nghttp2_session_del(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
void test_nghttp2_session_recv_continuation(void)
|
void test_nghttp2_session_recv_continuation(void)
|
||||||
|
@ -2613,12 +2683,14 @@ void test_nghttp2_submit_data(void)
|
||||||
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
|
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
|
||||||
callbacks.send_callback = block_count_send_callback;
|
callbacks.send_callback = block_count_send_callback;
|
||||||
|
|
||||||
data_prd.read_callback = fixed_length_data_source_read_callback;
|
data_prd.read_callback = compressed_fixed_length_data_source_read_callback;
|
||||||
ud.data_source_length = NGHTTP2_DATA_PAYLOADLEN * 2;
|
ud.data_source_length = NGHTTP2_DATA_PAYLOADLEN * 2;
|
||||||
CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud));
|
CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, &ud));
|
||||||
aob = &session->aob;
|
aob = &session->aob;
|
||||||
framebufs = &aob->framebufs;
|
framebufs = &aob->framebufs;
|
||||||
|
|
||||||
|
session->remote_settings[NGHTTP2_SETTINGS_COMPRESS_DATA] = 1;
|
||||||
|
|
||||||
nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
|
nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE,
|
||||||
&pri_spec_default, NGHTTP2_STREAM_OPENING,
|
&pri_spec_default, NGHTTP2_STREAM_OPENING,
|
||||||
NULL);
|
NULL);
|
||||||
|
@ -2626,6 +2698,8 @@ void test_nghttp2_submit_data(void)
|
||||||
NGHTTP2_FLAG_END_STREAM |
|
NGHTTP2_FLAG_END_STREAM |
|
||||||
NGHTTP2_FLAG_END_SEGMENT, 1, &data_prd));
|
NGHTTP2_FLAG_END_SEGMENT, 1, &data_prd));
|
||||||
|
|
||||||
|
/* First, no compression */
|
||||||
|
ud.compress_data = 0;
|
||||||
ud.block_count = 0;
|
ud.block_count = 0;
|
||||||
CU_ASSERT(0 == nghttp2_session_send(session));
|
CU_ASSERT(0 == nghttp2_session_send(session));
|
||||||
data_frame = nghttp2_outbound_item_get_data_frame(aob->item);
|
data_frame = nghttp2_outbound_item_get_data_frame(aob->item);
|
||||||
|
@ -2638,15 +2712,19 @@ void test_nghttp2_submit_data(void)
|
||||||
CU_ASSERT((NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_SEGMENT) ==
|
CU_ASSERT((NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_SEGMENT) ==
|
||||||
data_frame->hd.flags);
|
data_frame->hd.flags);
|
||||||
|
|
||||||
|
/* Now compression enabled */
|
||||||
|
ud.compress_data = 1;
|
||||||
ud.block_count = 1;
|
ud.block_count = 1;
|
||||||
CU_ASSERT(0 == nghttp2_session_send(session));
|
CU_ASSERT(0 == nghttp2_session_send(session));
|
||||||
data_frame = nghttp2_outbound_item_get_data_frame(aob->item);
|
data_frame = nghttp2_outbound_item_get_data_frame(aob->item);
|
||||||
nghttp2_frame_unpack_frame_hd(&hd, buf->pos);
|
nghttp2_frame_unpack_frame_hd(&hd, buf->pos);
|
||||||
|
|
||||||
/* This is the last frame, so we must have following flags */
|
/* This is the last frame, so we must have following flags */
|
||||||
CU_ASSERT((NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_SEGMENT) == hd.flags);
|
CU_ASSERT((NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_SEGMENT |
|
||||||
|
NGHTTP2_FLAG_COMPRESSED) == hd.flags);
|
||||||
/* frame->hd.flags has these flags */
|
/* frame->hd.flags has these flags */
|
||||||
CU_ASSERT((NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_SEGMENT) ==
|
CU_ASSERT((NGHTTP2_FLAG_END_STREAM | NGHTTP2_FLAG_END_SEGMENT |
|
||||||
|
NGHTTP2_FLAG_COMPRESSED) ==
|
||||||
data_frame->hd.flags);
|
data_frame->hd.flags);
|
||||||
|
|
||||||
nghttp2_session_del(session);
|
nghttp2_session_del(session);
|
||||||
|
|
Loading…
Reference in New Issue