mirror of
https://github.com/moparisthebest/spdylay
synced 2024-11-11 12:05:07 -05:00
262cda86e8
We say the header block is invalid if at least one of the following condition is true: There are duplicate header names; or the header names are not encoded in US-ASCII character set and not lower cased; or the header name is zero-length string; or the header value contains multiple in-sequence NUL bytes. spdylay_frame_unpack_nv() returns SPDYLAY_ERR_INVALID_HEADER_BLOCK if the unpacking suceeded but it found the header block is invalid. This means that caller treats it as success, but do additional processing for invalid header block if it wants. The functions calling spdylay_frame_unpack_nv() also return SPDYLAY_ERR_INVALID_HEADER_BLOCK.
1097 lines
33 KiB
C
1097 lines
33 KiB
C
/*
|
|
* Spdylay - SPDY Library
|
|
*
|
|
* Copyright (c) 2012 Tatsuhiro Tsujikawa
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining
|
|
* a copy of this software and associated documentation files (the
|
|
* "Software"), to deal in the Software without restriction, including
|
|
* without limitation the rights to use, copy, modify, merge, publish,
|
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
* permit persons to whom the Software is furnished to do so, subject to
|
|
* the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
#include "spdylay_frame.h"
|
|
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
|
|
#include "spdylay_helper.h"
|
|
#include "spdylay_net.h"
|
|
|
|
size_t spdylay_frame_get_len_size(uint16_t version)
|
|
{
|
|
if(SPDYLAY_PROTO_SPDY2 == version) {
|
|
return 2;
|
|
} else if(SPDYLAY_PROTO_SPDY3 == version) {
|
|
return 4;
|
|
} else {
|
|
/* Unsupported version */
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static uint8_t spdylay_unpack_pri(const uint8_t *data)
|
|
{
|
|
return (data[0] >> 6) & 0x3;
|
|
}
|
|
|
|
static uint8_t* spdylay_pack_str(uint8_t *buf, const char *str, size_t len,
|
|
size_t len_size)
|
|
{
|
|
spdylay_frame_put_nv_len(buf, len, len_size);
|
|
buf += len_size;
|
|
memcpy(buf, str, len);
|
|
return buf+len;
|
|
}
|
|
|
|
static void spdylay_frame_pack_ctrl_hd(uint8_t* buf, const spdylay_ctrl_hd *hd)
|
|
{
|
|
spdylay_put_uint16be(&buf[0], hd->version);
|
|
buf[0] |= 1 << 7;
|
|
spdylay_put_uint16be(&buf[2], hd->type);
|
|
spdylay_put_uint32be(&buf[4], hd->length);
|
|
buf[4] = hd->flags;
|
|
}
|
|
|
|
static void spdylay_frame_unpack_ctrl_hd(spdylay_ctrl_hd *hd,
|
|
const uint8_t* buf)
|
|
{
|
|
hd->version = spdylay_get_uint16(buf) & SPDYLAY_VERSION_MASK;
|
|
hd->type = spdylay_get_uint16(&buf[2]);
|
|
hd->flags = buf[4];
|
|
hd->length = spdylay_get_uint32(&buf[4]) & SPDYLAY_LENGTH_MASK;
|
|
}
|
|
|
|
ssize_t spdylay_frame_alloc_pack_nv(uint8_t **buf_ptr,
|
|
size_t *buflen_ptr,
|
|
uint8_t **nvbuf_ptr,
|
|
size_t *nvbuflen_ptr,
|
|
char **nv, size_t nv_offset,
|
|
size_t len_size,
|
|
spdylay_zlib *deflater)
|
|
{
|
|
size_t nvspace;
|
|
size_t maxframelen;
|
|
ssize_t framelen;
|
|
int r;
|
|
nvspace = spdylay_frame_count_nv_space(nv, len_size);
|
|
r = spdylay_reserve_buffer(nvbuf_ptr, nvbuflen_ptr, nvspace);
|
|
if(r != 0) {
|
|
return SPDYLAY_ERR_NOMEM;
|
|
}
|
|
maxframelen = nv_offset+spdylay_zlib_deflate_hd_bound(deflater, nvspace);
|
|
r = spdylay_reserve_buffer(buf_ptr, buflen_ptr, maxframelen);
|
|
if(r != 0) {
|
|
return SPDYLAY_ERR_NOMEM;
|
|
}
|
|
spdylay_frame_pack_nv(*nvbuf_ptr, nv, len_size);
|
|
framelen = spdylay_zlib_deflate_hd(deflater,
|
|
(*buf_ptr)+nv_offset,
|
|
maxframelen-nv_offset,
|
|
*nvbuf_ptr, nvspace);
|
|
if(framelen < 0) {
|
|
return framelen;
|
|
}
|
|
framelen += nv_offset;
|
|
return framelen;
|
|
}
|
|
|
|
int spdylay_frame_count_unpack_nv_space
|
|
(size_t *nvlen_ptr, size_t *buflen_ptr, const uint8_t *in, size_t inlen,
|
|
size_t len_size)
|
|
{
|
|
uint32_t n;
|
|
size_t buflen = 0;
|
|
size_t nvlen = 0;
|
|
size_t off = 0;
|
|
size_t i;
|
|
if(inlen < len_size) {
|
|
return SPDYLAY_ERR_INVALID_FRAME;
|
|
}
|
|
/* TODO limit n in a reasonable number */
|
|
n = spdylay_frame_get_nv_len(in, len_size);
|
|
off += len_size;
|
|
for(i = 0; i < n; ++i) {
|
|
uint32_t len;
|
|
size_t j;
|
|
for(j = 0; j < 2; ++j) {
|
|
if(inlen-off < len_size) {
|
|
return SPDYLAY_ERR_INVALID_FRAME;
|
|
}
|
|
len = spdylay_frame_get_nv_len(in+off, len_size);
|
|
off += len_size;
|
|
if(inlen-off < len) {
|
|
return SPDYLAY_ERR_INVALID_FRAME;
|
|
}
|
|
buflen += len+1;
|
|
off += len;
|
|
}
|
|
for(j = off, off -= len; off != j; ++off) {
|
|
if(in[off] == '\0') {
|
|
++nvlen;
|
|
}
|
|
}
|
|
++nvlen;
|
|
}
|
|
if(inlen == off) {
|
|
*nvlen_ptr = nvlen;
|
|
|
|
*buflen_ptr = buflen+(nvlen*2+1)*sizeof(char*);
|
|
return 0;
|
|
} else {
|
|
return SPDYLAY_ERR_INVALID_FRAME;
|
|
}
|
|
}
|
|
|
|
static int spdylay_length_prefix_str_compar2(const void *lhs, const void *rhs)
|
|
{
|
|
ssize_t lhslen, rhslen, complen;
|
|
int r;
|
|
lhslen = spdylay_get_uint16(*(uint8_t**)lhs);
|
|
rhslen = spdylay_get_uint16(*(uint8_t**)rhs);
|
|
complen = spdylay_min(lhslen, rhslen);
|
|
r = memcmp(*(uint8_t**)lhs+2, *(uint8_t**)rhs+2, complen);
|
|
if(r == 0) {
|
|
return lhslen-rhslen;
|
|
} else {
|
|
return r;
|
|
}
|
|
}
|
|
|
|
static int spdylay_length_prefix_str_compar4(const void *lhs, const void *rhs)
|
|
{
|
|
ssize_t lhslen, rhslen, complen;
|
|
int r;
|
|
/* Assuming the returned value does not exceed the maximum value of
|
|
ssize_t */
|
|
lhslen = spdylay_get_uint32(*(uint8_t**)lhs);
|
|
rhslen = spdylay_get_uint32(*(uint8_t**)rhs);
|
|
complen = spdylay_min(lhslen, rhslen);
|
|
r = memcmp(*(uint8_t**)lhs+4, *(uint8_t**)rhs+4, complen);
|
|
if(r == 0) {
|
|
return lhslen-rhslen;
|
|
} else {
|
|
return r;
|
|
}
|
|
}
|
|
|
|
int spdylay_frame_unpack_nv_check_name(uint8_t *buf, size_t buflen,
|
|
const uint8_t *in, size_t inlen,
|
|
size_t len_size)
|
|
{
|
|
uint32_t n;
|
|
size_t i;
|
|
const uint8_t **index;
|
|
n = spdylay_frame_get_nv_len(in, len_size);
|
|
assert(n*sizeof(char*) <= buflen);
|
|
in += len_size;
|
|
index = (const uint8_t**)buf;
|
|
for(i = 0; i < n; ++i) {
|
|
uint32_t len;
|
|
size_t j;
|
|
len = spdylay_frame_get_nv_len(in, len_size);
|
|
if(len == 0) {
|
|
return SPDYLAY_ERR_INVALID_HEADER_BLOCK;
|
|
}
|
|
*index++ = in;
|
|
in += len_size;
|
|
for(j = 0; j < len; ++j) {
|
|
unsigned char c = in[j];
|
|
if(c < 0x20 || c > 0x7e || ('A' <= c && c <= 'Z')) {
|
|
return SPDYLAY_ERR_INVALID_HEADER_BLOCK;
|
|
}
|
|
}
|
|
in += len;
|
|
len = spdylay_frame_get_nv_len(in, len_size);
|
|
in += len_size+len;
|
|
}
|
|
qsort(buf, n, sizeof(uint8_t*),
|
|
len_size == 2 ?
|
|
spdylay_length_prefix_str_compar2 : spdylay_length_prefix_str_compar4);
|
|
index = (const uint8_t**)buf;
|
|
for(i = 1; i < n; ++i) {
|
|
uint32_t len1 = spdylay_frame_get_nv_len(*(index+i-1), len_size);
|
|
uint32_t len2 = spdylay_frame_get_nv_len(*(index+i), len_size);
|
|
if(len1 == len2 && memcmp(*(index+i-1)+len_size, *(index+i)+len_size,
|
|
len_size) == 0) {
|
|
return SPDYLAY_ERR_INVALID_HEADER_BLOCK;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int spdylay_frame_unpack_nv(char ***nv_ptr, const uint8_t *in, size_t inlen,
|
|
size_t len_size)
|
|
{
|
|
size_t nvlen, buflen;
|
|
int r;
|
|
size_t i;
|
|
char *buf, **index, *data;
|
|
uint32_t n;
|
|
int invalid_header_block = 0;
|
|
r = spdylay_frame_count_unpack_nv_space(&nvlen, &buflen, in, inlen, len_size);
|
|
if(r != 0) {
|
|
return r;
|
|
}
|
|
buf = malloc(buflen);
|
|
if(buf == NULL) {
|
|
return SPDYLAY_ERR_NOMEM;
|
|
}
|
|
r = spdylay_frame_unpack_nv_check_name((uint8_t*)buf, buflen, in, inlen,
|
|
len_size);
|
|
if(r == SPDYLAY_ERR_INVALID_HEADER_BLOCK) {
|
|
invalid_header_block = 1;
|
|
r = 0;
|
|
} else if(r != 0) {
|
|
free(buf);
|
|
return r;
|
|
}
|
|
index = (char**)buf;
|
|
data = buf+(nvlen*2+1)*sizeof(char*);
|
|
n = spdylay_frame_get_nv_len(in, len_size);
|
|
in += len_size;
|
|
for(i = 0; i < n; ++i) {
|
|
uint32_t len;
|
|
char *name, *val;
|
|
char *stop;
|
|
len = spdylay_frame_get_nv_len(in, len_size);
|
|
in += len_size;
|
|
name = data;
|
|
memcpy(data, in, len);
|
|
data += len;
|
|
*data = '\0';
|
|
++data;
|
|
in += len;
|
|
|
|
len = spdylay_frame_get_nv_len(in, len_size);
|
|
in += len_size;
|
|
val = data;
|
|
memcpy(data, in, len);
|
|
|
|
for(stop = data+len; data != stop; ++data) {
|
|
if(*data == '\0') {
|
|
*index++ = name;
|
|
*index++ = val;
|
|
if(val == data) {
|
|
invalid_header_block = 1;
|
|
}
|
|
val = data+1;
|
|
}
|
|
}
|
|
*data = '\0';
|
|
++data;
|
|
in += len;
|
|
|
|
*index++ = name;
|
|
*index++ = val;
|
|
}
|
|
*index = NULL;
|
|
assert((size_t)((char*)index - buf) == (nvlen*2)*sizeof(char*));
|
|
*nv_ptr = (char**)buf;
|
|
return invalid_header_block ? SPDYLAY_ERR_INVALID_HEADER_BLOCK : 0;
|
|
}
|
|
|
|
int spdylay_frame_alloc_unpack_nv(char ***nv_ptr,
|
|
spdylay_buffer *inflatebuf,
|
|
uint8_t **nvbuf_ptr,
|
|
size_t *nvbuflen_ptr,
|
|
const uint8_t *in, size_t inlen,
|
|
size_t len_size,
|
|
spdylay_zlib *inflater)
|
|
{
|
|
ssize_t nvspace;
|
|
int r;
|
|
nvspace = spdylay_zlib_inflate_hd(inflater, inflatebuf, in, inlen);
|
|
if(nvspace < 0) {
|
|
return nvspace;
|
|
} else {
|
|
r = spdylay_reserve_buffer(nvbuf_ptr, nvbuflen_ptr, nvspace);
|
|
if(r != 0) {
|
|
return SPDYLAY_ERR_NOMEM;
|
|
}
|
|
spdylay_buffer_serialize(inflatebuf, *nvbuf_ptr);
|
|
r = spdylay_frame_unpack_nv(nv_ptr, *nvbuf_ptr, nvspace, len_size);
|
|
return r;
|
|
}
|
|
}
|
|
|
|
size_t spdylay_frame_count_nv_space(char **nv, size_t len_size)
|
|
{
|
|
size_t sum = len_size;
|
|
int i;
|
|
const char *prev = "";
|
|
size_t prevlen = 0;
|
|
for(i = 0; nv[i]; i += 2) {
|
|
const char *key = nv[i];
|
|
const char *val = nv[i+1];
|
|
size_t keylen = strlen(key);
|
|
size_t vallen = strlen(val);
|
|
if(prevlen == keylen && memcmp(prev, key, keylen) == 0) {
|
|
/* Join previous value, with NULL character */
|
|
sum += vallen+1;
|
|
} else {
|
|
prev = key;
|
|
prevlen = keylen;
|
|
/* SPDY NV header does not include terminating NULL byte */
|
|
sum += keylen+vallen+len_size*2;
|
|
}
|
|
}
|
|
return sum;
|
|
}
|
|
|
|
ssize_t spdylay_frame_pack_nv(uint8_t *buf, char **nv, size_t len_size)
|
|
{
|
|
int i;
|
|
uint8_t *bufp = buf+len_size;
|
|
uint32_t num_nv = 0;
|
|
/* TODO Join values with same keys, using '\0' as a delimiter */
|
|
const char *prev = "";
|
|
uint8_t *prev_vallen_buf = NULL;
|
|
uint32_t prev_vallen = 0;
|
|
for(i = 0; nv[i]; i += 2) {
|
|
const char *key = nv[i];
|
|
const char *val = nv[i+1];
|
|
size_t keylen = strlen(key);
|
|
size_t vallen = strlen(val);
|
|
if(strcmp(prev, key) == 0) {
|
|
prev_vallen += vallen+1;
|
|
spdylay_frame_put_nv_len(prev_vallen_buf, prev_vallen, len_size);
|
|
*bufp = '\0';
|
|
++bufp;
|
|
memcpy(bufp, val, vallen);
|
|
bufp += vallen;
|
|
} else {
|
|
++num_nv;
|
|
bufp = spdylay_pack_str(bufp, key, keylen, len_size);
|
|
prev = key;
|
|
prev_vallen_buf = bufp;
|
|
prev_vallen = vallen;
|
|
bufp = spdylay_pack_str(bufp, val, vallen, len_size);
|
|
}
|
|
}
|
|
spdylay_frame_put_nv_len(buf, num_nv, len_size);
|
|
return bufp-buf;
|
|
}
|
|
|
|
int spdylay_frame_is_ctrl_frame(uint8_t first_byte)
|
|
{
|
|
return first_byte & 0x80;
|
|
}
|
|
|
|
void spdylay_frame_nv_del(char **nv)
|
|
{
|
|
free(nv);
|
|
}
|
|
|
|
char** spdylay_frame_nv_copy(const char **nv)
|
|
{
|
|
int i;
|
|
char *buf;
|
|
char **index, *data;
|
|
size_t buflen = 0;
|
|
for(i = 0; nv[i]; ++i) {
|
|
buflen += strlen(nv[i])+1;
|
|
}
|
|
buflen += (i+1)*sizeof(char*);
|
|
buf = malloc(buflen);
|
|
if(buf == NULL) {
|
|
return NULL;
|
|
}
|
|
index = (char**)buf;
|
|
data = buf+(i+1)*sizeof(char*);
|
|
|
|
for(i = 0; nv[i]; ++i) {
|
|
size_t len = strlen(nv[i])+1;
|
|
memcpy(data, nv[i], len);
|
|
*index++ = data;
|
|
data += len;
|
|
}
|
|
*index = NULL;
|
|
return (char**)buf;
|
|
}
|
|
|
|
static int spdylay_string_compar(const void *lhs, const void *rhs)
|
|
{
|
|
return strcmp(*(char **)lhs, *(char **)rhs);
|
|
}
|
|
|
|
void spdylay_frame_nv_sort(char **nv)
|
|
{
|
|
int n;
|
|
for(n = 0; nv[n]; ++n);
|
|
qsort(nv, n/2, 2*sizeof(char*), spdylay_string_compar);
|
|
}
|
|
|
|
void spdylay_frame_nv_downcase(char **nv)
|
|
{
|
|
int i, j;
|
|
for(i = 0; nv[i]; i += 2) {
|
|
for(j = 0; nv[i][j] != '\0'; ++j) {
|
|
if('A' <= nv[i][j] && nv[i][j] <= 'Z') {
|
|
nv[i][j] += 'a'-'A';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
char** spdylay_frame_nv_norm_copy(const char **nv)
|
|
{
|
|
char **nv_copy;
|
|
nv_copy = spdylay_frame_nv_copy(nv);
|
|
if(nv_copy != NULL) {
|
|
spdylay_frame_nv_downcase(nv_copy);
|
|
spdylay_frame_nv_sort(nv_copy);
|
|
}
|
|
return nv_copy;
|
|
}
|
|
|
|
/* Table to translate SPDY/3 header names to SPDY/2. */
|
|
static char *spdylay_nv_3to2[] = {
|
|
":host", "host",
|
|
":method", "method",
|
|
":path", "url",
|
|
":scheme", "scheme",
|
|
":status", "status",
|
|
":version", "version",
|
|
NULL
|
|
};
|
|
|
|
void spdylay_frame_nv_3to2(char **nv)
|
|
{
|
|
int i, j;
|
|
for(i = 0; nv[i]; i += 2) {
|
|
for(j = 0; spdylay_nv_3to2[j]; j += 2) {
|
|
if(strcmp(nv[i], spdylay_nv_3to2[j]) == 0) {
|
|
nv[i] = spdylay_nv_3to2[j+1];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void spdylay_frame_nv_2to3(char **nv)
|
|
{
|
|
int i, j;
|
|
for(i = 0; nv[i]; i += 2) {
|
|
for(j = 0; spdylay_nv_3to2[j]; j += 2) {
|
|
if(strcmp(nv[i], spdylay_nv_3to2[j+1]) == 0) {
|
|
nv[i] = spdylay_nv_3to2[j];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void spdylay_frame_syn_stream_init(spdylay_syn_stream *frame,
|
|
uint16_t version, uint8_t flags,
|
|
int32_t stream_id, int32_t assoc_stream_id,
|
|
uint8_t pri, char **nv)
|
|
{
|
|
memset(frame, 0, sizeof(spdylay_syn_stream));
|
|
frame->hd.version = version;
|
|
frame->hd.type = SPDYLAY_SYN_STREAM;
|
|
frame->hd.flags = flags;
|
|
frame->stream_id = stream_id;
|
|
frame->assoc_stream_id = assoc_stream_id;
|
|
frame->pri = pri;
|
|
frame->nv = nv;
|
|
}
|
|
|
|
void spdylay_frame_syn_stream_free(spdylay_syn_stream *frame)
|
|
{
|
|
spdylay_frame_nv_del(frame->nv);
|
|
}
|
|
|
|
void spdylay_frame_syn_reply_init(spdylay_syn_reply *frame,
|
|
uint16_t version, uint8_t flags,
|
|
int32_t stream_id, char **nv)
|
|
{
|
|
memset(frame, 0, sizeof(spdylay_syn_reply));
|
|
frame->hd.version = version;
|
|
frame->hd.type = SPDYLAY_SYN_REPLY;
|
|
frame->hd.flags = flags;
|
|
frame->stream_id = stream_id;
|
|
frame->nv = nv;
|
|
}
|
|
|
|
void spdylay_frame_syn_reply_free(spdylay_syn_reply *frame)
|
|
{
|
|
spdylay_frame_nv_del(frame->nv);
|
|
}
|
|
|
|
void spdylay_frame_ping_init(spdylay_ping *frame,
|
|
uint16_t version, uint32_t unique_id)
|
|
{
|
|
memset(frame, 0, sizeof(spdylay_ping));
|
|
frame->hd.version = version;
|
|
frame->hd.type = SPDYLAY_PING;
|
|
frame->hd.flags = SPDYLAY_CTRL_FLAG_NONE;
|
|
frame->hd.length = 4;
|
|
frame->unique_id = unique_id;
|
|
}
|
|
|
|
void spdylay_frame_ping_free(spdylay_ping *frame)
|
|
{}
|
|
|
|
void spdylay_frame_goaway_init(spdylay_goaway *frame,
|
|
uint16_t version, int32_t last_good_stream_id,
|
|
uint32_t status_code)
|
|
{
|
|
memset(frame, 0, sizeof(spdylay_goaway));
|
|
frame->hd.version = version;
|
|
frame->hd.type = SPDYLAY_GOAWAY;
|
|
if(version == SPDYLAY_PROTO_SPDY2) {
|
|
frame->hd.length = 4;
|
|
} else if(version == SPDYLAY_PROTO_SPDY3) {
|
|
frame->hd.length = 8;
|
|
frame->status_code = status_code;
|
|
} else {
|
|
frame->hd.length = 0;
|
|
}
|
|
frame->last_good_stream_id = last_good_stream_id;
|
|
}
|
|
|
|
void spdylay_frame_goaway_free(spdylay_goaway *frame)
|
|
{}
|
|
|
|
void spdylay_frame_headers_init(spdylay_headers *frame,
|
|
uint16_t version, uint8_t flags,
|
|
int32_t stream_id, char **nv)
|
|
{
|
|
memset(frame, 0, sizeof(spdylay_headers));
|
|
frame->hd.version = version;
|
|
frame->hd.type = SPDYLAY_HEADERS;
|
|
frame->hd.flags = flags;
|
|
frame->stream_id = stream_id;
|
|
frame->nv = nv;
|
|
}
|
|
|
|
void spdylay_frame_headers_free(spdylay_headers *frame)
|
|
{
|
|
spdylay_frame_nv_del(frame->nv);
|
|
}
|
|
|
|
void spdylay_frame_rst_stream_init(spdylay_rst_stream *frame,
|
|
uint16_t version,
|
|
int32_t stream_id, uint32_t status_code)
|
|
{
|
|
memset(frame, 0, sizeof(spdylay_rst_stream));
|
|
frame->hd.version = version;
|
|
frame->hd.type = SPDYLAY_RST_STREAM;
|
|
frame->hd.flags = 0;
|
|
frame->hd.length = 8;
|
|
frame->stream_id = stream_id;
|
|
frame->status_code = status_code;
|
|
}
|
|
|
|
void spdylay_frame_rst_stream_free(spdylay_rst_stream *frame)
|
|
{}
|
|
|
|
void spdylay_frame_window_update_init(spdylay_window_update *frame,
|
|
uint16_t version,
|
|
int32_t stream_id,
|
|
int32_t delta_window_size)
|
|
{
|
|
memset(frame, 0, sizeof(spdylay_window_update));
|
|
frame->hd.version = version;
|
|
frame->hd.type = SPDYLAY_WINDOW_UPDATE;
|
|
frame->hd.flags = 0;
|
|
frame->hd.length = 8;
|
|
frame->stream_id = stream_id;
|
|
frame->delta_window_size = delta_window_size;
|
|
}
|
|
|
|
void spdylay_frame_window_update_free(spdylay_window_update *frame)
|
|
{}
|
|
|
|
void spdylay_frame_settings_init(spdylay_settings *frame,
|
|
uint16_t version, uint8_t flags,
|
|
spdylay_settings_entry *iv, size_t niv)
|
|
{
|
|
memset(frame, 0, sizeof(spdylay_settings));
|
|
frame->hd.version = version;
|
|
frame->hd.type = SPDYLAY_SETTINGS;
|
|
frame->hd.flags = flags;
|
|
frame->hd.length = 4+niv*8;
|
|
frame->niv = niv;
|
|
frame->iv = iv;
|
|
}
|
|
|
|
void spdylay_frame_settings_free(spdylay_settings *frame)
|
|
{
|
|
free(frame->iv);
|
|
}
|
|
|
|
void spdylay_frame_data_init(spdylay_data *frame, int32_t stream_id,
|
|
uint8_t flags,
|
|
const spdylay_data_provider *data_prd)
|
|
{
|
|
memset(frame, 0, sizeof(spdylay_data));
|
|
frame->stream_id = stream_id;
|
|
frame->flags = flags;
|
|
frame->data_prd = *data_prd;
|
|
}
|
|
|
|
void spdylay_frame_data_free(spdylay_data *frame)
|
|
{}
|
|
|
|
#define SPDYLAY_SYN_STREAM_NV_OFFSET 18
|
|
|
|
ssize_t spdylay_frame_pack_syn_stream(uint8_t **buf_ptr,
|
|
size_t *buflen_ptr,
|
|
uint8_t **nvbuf_ptr,
|
|
size_t *nvbuflen_ptr,
|
|
spdylay_syn_stream *frame,
|
|
spdylay_zlib *deflater)
|
|
{
|
|
ssize_t framelen;
|
|
size_t len_size = spdylay_frame_get_len_size(frame->hd.version);
|
|
if(len_size == 0) {
|
|
return SPDYLAY_ERR_UNSUPPORTED_VERSION;
|
|
}
|
|
framelen = spdylay_frame_alloc_pack_nv(buf_ptr, buflen_ptr,
|
|
nvbuf_ptr, nvbuflen_ptr,
|
|
frame->nv,
|
|
SPDYLAY_SYN_STREAM_NV_OFFSET,
|
|
len_size,
|
|
deflater);
|
|
if(framelen < 0) {
|
|
return framelen;
|
|
}
|
|
frame->hd.length = framelen-SPDYLAY_FRAME_HEAD_LENGTH;
|
|
memset(*buf_ptr, 0, SPDYLAY_SYN_STREAM_NV_OFFSET);
|
|
/* pack ctrl header after length is determined */
|
|
spdylay_frame_pack_ctrl_hd(*buf_ptr, &frame->hd);
|
|
spdylay_put_uint32be(&(*buf_ptr)[8], frame->stream_id);
|
|
spdylay_put_uint32be(&(*buf_ptr)[12], frame->assoc_stream_id);
|
|
(*buf_ptr)[16] = (frame->pri << 6);
|
|
if(frame->hd.version == SPDYLAY_PROTO_SPDY3) {
|
|
(*buf_ptr)[17] = frame->slot;
|
|
}
|
|
return framelen;
|
|
}
|
|
|
|
int spdylay_frame_unpack_syn_stream(spdylay_syn_stream *frame,
|
|
spdylay_buffer *inflatebuf,
|
|
uint8_t **nvbuf_ptr,
|
|
size_t *nvbuflen_ptr,
|
|
const uint8_t *head, size_t headlen,
|
|
const uint8_t *payload, size_t payloadlen,
|
|
spdylay_zlib *inflater)
|
|
{
|
|
int r;
|
|
size_t len_size;
|
|
if(payloadlen < 12) {
|
|
return SPDYLAY_ERR_INVALID_FRAME;
|
|
}
|
|
spdylay_frame_unpack_ctrl_hd(&frame->hd, head);
|
|
len_size = spdylay_frame_get_len_size(frame->hd.version);
|
|
if(len_size == 0) {
|
|
return SPDYLAY_ERR_UNSUPPORTED_VERSION;
|
|
}
|
|
frame->stream_id = spdylay_get_uint32(payload) & SPDYLAY_STREAM_ID_MASK;
|
|
frame->assoc_stream_id =
|
|
spdylay_get_uint32(payload+4) & SPDYLAY_STREAM_ID_MASK;
|
|
frame->pri = spdylay_unpack_pri(payload+8);
|
|
if(frame->hd.version == SPDYLAY_PROTO_SPDY3) {
|
|
frame->slot = payload[9];
|
|
} else {
|
|
frame->slot = 0;
|
|
}
|
|
r = spdylay_frame_alloc_unpack_nv(&frame->nv, inflatebuf,
|
|
nvbuf_ptr, nvbuflen_ptr,
|
|
payload+10, payloadlen-10,
|
|
len_size,
|
|
inflater);
|
|
return r;
|
|
}
|
|
|
|
#define SPDYLAY_SPDY2_SYN_REPLY_NV_OFFSET 14
|
|
#define SPDYLAY_SPDY3_SYN_REPLY_NV_OFFSET 12
|
|
|
|
ssize_t spdylay_frame_pack_syn_reply(uint8_t **buf_ptr,
|
|
size_t *buflen_ptr,
|
|
uint8_t **nvbuf_ptr,
|
|
size_t *nvbuflen_ptr,
|
|
spdylay_syn_reply *frame,
|
|
spdylay_zlib *deflater)
|
|
{
|
|
ssize_t framelen;
|
|
size_t len_size;
|
|
size_t nv_offset;
|
|
len_size = spdylay_frame_get_len_size(frame->hd.version);
|
|
if(len_size == 0) {
|
|
return SPDYLAY_ERR_UNSUPPORTED_VERSION;
|
|
}
|
|
nv_offset = frame->hd.version == SPDYLAY_PROTO_SPDY2 ?
|
|
SPDYLAY_SPDY2_SYN_REPLY_NV_OFFSET : SPDYLAY_SPDY3_SYN_REPLY_NV_OFFSET;
|
|
|
|
framelen = spdylay_frame_alloc_pack_nv(buf_ptr, buflen_ptr,
|
|
nvbuf_ptr, nvbuflen_ptr,
|
|
frame->nv, nv_offset,
|
|
len_size, deflater);
|
|
if(framelen < 0) {
|
|
return framelen;
|
|
}
|
|
frame->hd.length = framelen-SPDYLAY_FRAME_HEAD_LENGTH;
|
|
memset(*buf_ptr, 0, nv_offset);
|
|
spdylay_frame_pack_ctrl_hd(*buf_ptr, &frame->hd);
|
|
spdylay_put_uint32be(&(*buf_ptr)[8], frame->stream_id);
|
|
return framelen;
|
|
}
|
|
|
|
int spdylay_frame_unpack_syn_reply(spdylay_syn_reply *frame,
|
|
spdylay_buffer *inflatebuf,
|
|
uint8_t **nvbuf_ptr,
|
|
size_t *nvbuflen_ptr,
|
|
const uint8_t *head, size_t headlen,
|
|
const uint8_t *payload, size_t payloadlen,
|
|
spdylay_zlib *inflater)
|
|
{
|
|
int r;
|
|
size_t len_size;
|
|
size_t nv_offset;
|
|
if(payloadlen < 8) {
|
|
return SPDYLAY_ERR_INVALID_FRAME;
|
|
}
|
|
spdylay_frame_unpack_ctrl_hd(&frame->hd, head);
|
|
len_size = spdylay_frame_get_len_size(frame->hd.version);
|
|
if(len_size == 0) {
|
|
return SPDYLAY_ERR_UNSUPPORTED_VERSION;
|
|
}
|
|
nv_offset = frame->hd.version == SPDYLAY_PROTO_SPDY2 ? 6 : 4;
|
|
frame->stream_id = spdylay_get_uint32(payload) & SPDYLAY_STREAM_ID_MASK;
|
|
r = spdylay_frame_alloc_unpack_nv(&frame->nv, inflatebuf,
|
|
nvbuf_ptr, nvbuflen_ptr,
|
|
payload+nv_offset, payloadlen-nv_offset,
|
|
len_size, inflater);
|
|
return r;
|
|
}
|
|
|
|
ssize_t spdylay_frame_pack_ping(uint8_t **buf_ptr, size_t *buflen_ptr,
|
|
spdylay_ping *frame)
|
|
{
|
|
ssize_t framelen = 12;
|
|
int r;
|
|
r = spdylay_reserve_buffer(buf_ptr, buflen_ptr, framelen);
|
|
if(r != 0) {
|
|
return r;
|
|
}
|
|
memset(*buf_ptr, 0, framelen);
|
|
spdylay_frame_pack_ctrl_hd(*buf_ptr, &frame->hd);
|
|
spdylay_put_uint32be(&(*buf_ptr)[8], frame->unique_id);
|
|
return framelen;
|
|
}
|
|
|
|
int spdylay_frame_unpack_ping(spdylay_ping *frame,
|
|
const uint8_t *head, size_t headlen,
|
|
const uint8_t *payload, size_t payloadlen)
|
|
{
|
|
if(payloadlen != 4) {
|
|
return SPDYLAY_ERR_INVALID_FRAME;
|
|
}
|
|
spdylay_frame_unpack_ctrl_hd(&frame->hd, head);
|
|
frame->unique_id = spdylay_get_uint32(payload);
|
|
return 0;
|
|
}
|
|
|
|
ssize_t spdylay_frame_pack_goaway(uint8_t **buf_ptr, size_t *buflen_ptr,
|
|
spdylay_goaway *frame)
|
|
{
|
|
ssize_t framelen;
|
|
int r;
|
|
if(frame->hd.version == SPDYLAY_PROTO_SPDY2) {
|
|
framelen = 12;
|
|
} else if(frame->hd.version == SPDYLAY_PROTO_SPDY3) {
|
|
framelen = 16;
|
|
} else {
|
|
return SPDYLAY_ERR_UNSUPPORTED_VERSION;
|
|
}
|
|
r = spdylay_reserve_buffer(buf_ptr, buflen_ptr, framelen);
|
|
if(r != 0) {
|
|
return r;
|
|
}
|
|
memset(*buf_ptr, 0, framelen);
|
|
spdylay_frame_pack_ctrl_hd(*buf_ptr, &frame->hd);
|
|
spdylay_put_uint32be(&(*buf_ptr)[8], frame->last_good_stream_id);
|
|
if(frame->hd.version == SPDYLAY_PROTO_SPDY3) {
|
|
spdylay_put_uint32be(&(*buf_ptr)[12], frame->status_code);
|
|
}
|
|
return framelen;
|
|
}
|
|
|
|
int spdylay_frame_unpack_goaway(spdylay_goaway *frame,
|
|
const uint8_t *head, size_t headlen,
|
|
const uint8_t *payload, size_t payloadlen)
|
|
{
|
|
spdylay_frame_unpack_ctrl_hd(&frame->hd, head);
|
|
if(frame->hd.version == SPDYLAY_PROTO_SPDY2) {
|
|
if(payloadlen != 4) {
|
|
return SPDYLAY_ERR_INVALID_FRAME;
|
|
}
|
|
} else if(frame->hd.version == SPDYLAY_PROTO_SPDY3) {
|
|
if(payloadlen != 8) {
|
|
return SPDYLAY_ERR_INVALID_FRAME;
|
|
}
|
|
} else {
|
|
return SPDYLAY_ERR_UNSUPPORTED_VERSION;
|
|
}
|
|
frame->last_good_stream_id = spdylay_get_uint32(payload) &
|
|
SPDYLAY_STREAM_ID_MASK;
|
|
if(frame->hd.version == SPDYLAY_PROTO_SPDY3) {
|
|
frame->status_code = spdylay_get_uint32(payload+4);
|
|
} else {
|
|
frame->status_code = 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#define SPDYLAY_SPDY2_HEADERS_NV_OFFSET 14
|
|
#define SPDYLAY_SPDY3_HEADERS_NV_OFFSET 12
|
|
|
|
ssize_t spdylay_frame_pack_headers(uint8_t **buf_ptr, size_t *buflen_ptr,
|
|
uint8_t **nvbuf_ptr, size_t *nvbuflen_ptr,
|
|
spdylay_headers *frame,
|
|
spdylay_zlib *deflater)
|
|
{
|
|
ssize_t framelen;
|
|
size_t len_size;
|
|
size_t nv_offset;
|
|
len_size = spdylay_frame_get_len_size(frame->hd.version);
|
|
if(len_size == 0) {
|
|
return SPDYLAY_ERR_UNSUPPORTED_VERSION;
|
|
}
|
|
nv_offset = frame->hd.version == SPDYLAY_PROTO_SPDY2 ?
|
|
SPDYLAY_SPDY2_HEADERS_NV_OFFSET : SPDYLAY_SPDY3_HEADERS_NV_OFFSET;
|
|
framelen = spdylay_frame_alloc_pack_nv(buf_ptr, buflen_ptr,
|
|
nvbuf_ptr, nvbuflen_ptr,
|
|
frame->nv, nv_offset,
|
|
len_size, deflater);
|
|
if(framelen < 0) {
|
|
return framelen;
|
|
}
|
|
frame->hd.length = framelen-SPDYLAY_FRAME_HEAD_LENGTH;
|
|
memset(*buf_ptr, 0, nv_offset);
|
|
spdylay_frame_pack_ctrl_hd(*buf_ptr, &frame->hd);
|
|
spdylay_put_uint32be(&(*buf_ptr)[8], frame->stream_id);
|
|
return framelen;
|
|
}
|
|
|
|
int spdylay_frame_unpack_headers(spdylay_headers *frame,
|
|
spdylay_buffer *inflatebuf,
|
|
uint8_t **nvbuf_ptr,
|
|
size_t *nvbuflen_ptr,
|
|
const uint8_t *head, size_t headlen,
|
|
const uint8_t *payload, size_t payloadlen,
|
|
spdylay_zlib *inflater)
|
|
{
|
|
int r;
|
|
size_t len_size;
|
|
size_t nv_offset;
|
|
if(payloadlen < 8) {
|
|
return SPDYLAY_ERR_INVALID_FRAME;
|
|
}
|
|
spdylay_frame_unpack_ctrl_hd(&frame->hd, head);
|
|
len_size = spdylay_frame_get_len_size(frame->hd.version);
|
|
if(len_size == 0) {
|
|
return SPDYLAY_ERR_UNSUPPORTED_VERSION;
|
|
}
|
|
nv_offset = frame->hd.version == SPDYLAY_PROTO_SPDY2 ? 6 : 4;
|
|
frame->stream_id = spdylay_get_uint32(payload) & SPDYLAY_STREAM_ID_MASK;
|
|
r = spdylay_frame_alloc_unpack_nv(&frame->nv, inflatebuf,
|
|
nvbuf_ptr, nvbuflen_ptr,
|
|
payload+nv_offset, payloadlen-nv_offset,
|
|
len_size, inflater);
|
|
return r;
|
|
}
|
|
|
|
ssize_t spdylay_frame_pack_rst_stream(uint8_t **buf_ptr, size_t *buflen_ptr,
|
|
spdylay_rst_stream *frame)
|
|
{
|
|
ssize_t framelen = 16;
|
|
int r;
|
|
r = spdylay_reserve_buffer(buf_ptr, buflen_ptr, framelen);
|
|
if(r != 0) {
|
|
return r;
|
|
}
|
|
memset(*buf_ptr, 0, framelen);
|
|
spdylay_frame_pack_ctrl_hd(*buf_ptr, &frame->hd);
|
|
spdylay_put_uint32be(&(*buf_ptr)[8], frame->stream_id);
|
|
spdylay_put_uint32be(&(*buf_ptr)[12], frame->status_code);
|
|
return framelen;
|
|
}
|
|
|
|
int spdylay_frame_unpack_rst_stream(spdylay_rst_stream *frame,
|
|
const uint8_t *head, size_t headlen,
|
|
const uint8_t *payload, size_t payloadlen)
|
|
{
|
|
if(payloadlen != 8) {
|
|
return SPDYLAY_ERR_INVALID_FRAME;
|
|
}
|
|
spdylay_frame_unpack_ctrl_hd(&frame->hd, head);
|
|
frame->stream_id = spdylay_get_uint32(payload) & SPDYLAY_STREAM_ID_MASK;
|
|
frame->status_code = spdylay_get_uint32(payload+4);
|
|
return 0;
|
|
}
|
|
|
|
ssize_t spdylay_frame_pack_window_update(uint8_t **buf_ptr, size_t *buflen_ptr,
|
|
spdylay_window_update *frame)
|
|
{
|
|
ssize_t framelen = 16;
|
|
int r;
|
|
r = spdylay_reserve_buffer(buf_ptr, buflen_ptr, framelen);
|
|
if(r != 0) {
|
|
return r;
|
|
}
|
|
memset(*buf_ptr, 0, framelen);
|
|
spdylay_frame_pack_ctrl_hd(*buf_ptr, &frame->hd);
|
|
spdylay_put_uint32be(&(*buf_ptr)[8], frame->stream_id);
|
|
spdylay_put_uint32be(&(*buf_ptr)[12], frame->delta_window_size);
|
|
return framelen;
|
|
}
|
|
|
|
int spdylay_frame_unpack_window_update(spdylay_window_update *frame,
|
|
const uint8_t *head, size_t headlen,
|
|
const uint8_t *payload,
|
|
size_t payloadlen)
|
|
{
|
|
if(payloadlen != 8) {
|
|
return SPDYLAY_ERR_INVALID_FRAME;
|
|
}
|
|
spdylay_frame_unpack_ctrl_hd(&frame->hd, head);
|
|
frame->stream_id = spdylay_get_uint32(payload) & SPDYLAY_STREAM_ID_MASK;
|
|
frame->delta_window_size = spdylay_get_uint32(&payload[4]) &
|
|
SPDYLAY_DELTA_WINDOW_SIZE_MASK;
|
|
return 0;
|
|
}
|
|
|
|
ssize_t spdylay_frame_pack_settings(uint8_t **buf_ptr, size_t *buflen_ptr,
|
|
spdylay_settings *frame)
|
|
{
|
|
ssize_t framelen = SPDYLAY_FRAME_HEAD_LENGTH+frame->hd.length;
|
|
size_t i;
|
|
int r;
|
|
if(frame->hd.version != SPDYLAY_PROTO_SPDY2 &&
|
|
frame->hd.version != SPDYLAY_PROTO_SPDY3) {
|
|
return SPDYLAY_ERR_UNSUPPORTED_VERSION;
|
|
}
|
|
r = spdylay_reserve_buffer(buf_ptr, buflen_ptr, framelen);
|
|
if(r != 0) {
|
|
return r;
|
|
}
|
|
memset(*buf_ptr, 0, framelen);
|
|
spdylay_frame_pack_ctrl_hd(*buf_ptr, &frame->hd);
|
|
spdylay_put_uint32be(&(*buf_ptr)[8], frame->niv);
|
|
if(frame->hd.version == SPDYLAY_PROTO_SPDY2) {
|
|
for(i = 0; i < frame->niv; ++i) {
|
|
int off = i*8;
|
|
/* spdy/2 spec says ID is network byte order, but publicly
|
|
deployed server sends little endian host byte order. */
|
|
char *id_ptr = (char*)(&frame->iv[i].settings_id);
|
|
#ifdef WORDS_BIGENDIAN
|
|
(*buf_ptr)[12+off] = id_ptr[3];
|
|
(*buf_ptr)[12+off+1] = id_ptr[2];
|
|
(*buf_ptr)[12+off+2] = id_ptr[1];
|
|
#else /* !WORDS_BIGENDIAN */
|
|
memcpy(&(*buf_ptr)[12+off], id_ptr, 3);
|
|
#endif /* !WORDS_BIGENDIAN */
|
|
(*buf_ptr)[15+off] = frame->iv[i].flags;
|
|
spdylay_put_uint32be(&(*buf_ptr)[16+off], frame->iv[i].value);
|
|
}
|
|
} else {
|
|
for(i = 0; i < frame->niv; ++i) {
|
|
int off = i*8;
|
|
spdylay_put_uint32be(&(*buf_ptr)[12+off], frame->iv[i].settings_id);
|
|
(*buf_ptr)[12+off] = frame->iv[i].flags;
|
|
spdylay_put_uint32be(&(*buf_ptr)[16+off], frame->iv[i].value);
|
|
}
|
|
}
|
|
return framelen;
|
|
}
|
|
|
|
int spdylay_frame_unpack_settings(spdylay_settings *frame,
|
|
const uint8_t *head, size_t headlen,
|
|
const uint8_t *payload, size_t payloadlen)
|
|
{
|
|
size_t i;
|
|
if(payloadlen < 4) {
|
|
return SPDYLAY_ERR_INVALID_FRAME;
|
|
}
|
|
spdylay_frame_unpack_ctrl_hd(&frame->hd, head);
|
|
if(frame->hd.version != SPDYLAY_PROTO_SPDY2 &&
|
|
frame->hd.version != SPDYLAY_PROTO_SPDY3) {
|
|
return SPDYLAY_ERR_UNSUPPORTED_VERSION;
|
|
}
|
|
frame->niv = spdylay_get_uint32(payload);
|
|
if(payloadlen != 4+frame->niv*8) {
|
|
return SPDYLAY_ERR_INVALID_FRAME;
|
|
}
|
|
frame->iv = malloc(frame->niv*sizeof(spdylay_settings_entry));
|
|
if(frame->iv == NULL) {
|
|
return SPDYLAY_ERR_NOMEM;
|
|
}
|
|
if(frame->hd.version == SPDYLAY_PROTO_SPDY2) {
|
|
for(i = 0; i < frame->niv; ++i) {
|
|
size_t off = i*8;
|
|
/* ID is little endian. See comments in
|
|
spdylay_frame_pack_settings(). */
|
|
frame->iv[i].settings_id = 0;
|
|
#ifdef WORDS_BIGENDIAN
|
|
*(char*)(&frame->iv[i].settings_id[1]) = &payload[4+off+2];
|
|
*(char*)(&frame->iv[i].settings_id[2]) = &payload[4+off+1];
|
|
*(char*)(&frame->iv[i].settings_id[3]) = &payload[4+off+0];
|
|
#else /* !WORDS_BIGENDIAN */
|
|
memcpy(&frame->iv[i].settings_id, &payload[4+off], 3);
|
|
#endif /* !WORDS_BIGENDIAN */
|
|
frame->iv[i].flags = payload[7+off];
|
|
frame->iv[i].value = spdylay_get_uint32(&payload[8+off]);
|
|
}
|
|
} else {
|
|
for(i = 0; i < frame->niv; ++i) {
|
|
size_t off = i*8;
|
|
frame->iv[i].settings_id = spdylay_get_uint32(&payload[4+off]) &
|
|
SPDYLAY_SETTINGS_ID_MASK;
|
|
frame->iv[i].flags = payload[4+off];
|
|
frame->iv[i].value = spdylay_get_uint32(&payload[8+off]);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
spdylay_settings_entry* spdylay_frame_iv_copy(const spdylay_settings_entry *iv,
|
|
size_t niv)
|
|
{
|
|
spdylay_settings_entry *iv_copy;
|
|
size_t len = niv*sizeof(spdylay_settings_entry);
|
|
iv_copy = malloc(len);
|
|
if(iv_copy == NULL) {
|
|
return NULL;
|
|
}
|
|
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);
|
|
}
|