Implement CACHE_DIGEST frame for nghttp and nghttpd
We use https://tools.ietf.org/html/draft-kazuho-h2-cache-digest-01 as specification. We haven't implemented flags handling yet.
This commit is contained in:
parent
d2addbc1ed
commit
bcc97b8699
|
@ -5653,7 +5653,6 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in,
|
|||
iframe->frame.hd.type)) {
|
||||
if (!session->callbacks.unpack_extension_callback) {
|
||||
/* Silently ignore unknown frame type. */
|
||||
|
||||
busy = 1;
|
||||
|
||||
iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
|
||||
|
|
|
@ -39,7 +39,7 @@ link_libraries(
|
|||
if(ENABLE_APP)
|
||||
set(HELPER_OBJECTS
|
||||
util.cc
|
||||
http2.cc timegm.c app_helper.cc nghttp2_gzip.c
|
||||
http2.cc timegm.c app_helper.cc nghttp2_gzip.c cache_digest.cc
|
||||
)
|
||||
|
||||
# nghttp client
|
||||
|
@ -111,6 +111,7 @@ if(ENABLE_APP)
|
|||
shrpx_router.cc
|
||||
shrpx_api_downstream_connection.cc
|
||||
shrpx_health_monitor_downstream_connection.cc
|
||||
cache_digest.cc
|
||||
)
|
||||
if(HAVE_SPDYLAY)
|
||||
list(APPEND NGHTTPX_SRCS
|
||||
|
@ -159,6 +160,7 @@ if(ENABLE_APP)
|
|||
memchunk_test.cc
|
||||
template_test.cc
|
||||
base64_test.cc
|
||||
cache_digest_test.cc
|
||||
)
|
||||
add_executable(nghttpx-unittest EXCLUDE_FROM_ALL
|
||||
${NGHTTPX_UNITTEST_SOURCES}
|
||||
|
|
|
@ -62,6 +62,7 @@
|
|||
#include "util.h"
|
||||
#include "ssl.h"
|
||||
#include "template.h"
|
||||
#include "cache_digest.h"
|
||||
|
||||
#ifndef O_BINARY
|
||||
#define O_BINARY (0)
|
||||
|
@ -825,10 +826,22 @@ int Http2Handler::on_write() { return write_(*this); }
|
|||
int Http2Handler::connection_made() {
|
||||
int r;
|
||||
|
||||
r = nghttp2_session_server_new(&session_, sessions_->get_callbacks(), this);
|
||||
nghttp2_option *opt;
|
||||
r = nghttp2_option_new(&opt);
|
||||
|
||||
if (r != 0) {
|
||||
return r;
|
||||
return -1;
|
||||
}
|
||||
|
||||
nghttp2_option_set_user_recv_extension_type(opt, NGHTTP2_DRAFT_CACHE_DIGEST);
|
||||
|
||||
r = nghttp2_session_server_new2(&session_, sessions_->get_callbacks(), this,
|
||||
opt);
|
||||
|
||||
nghttp2_option_del(opt);
|
||||
|
||||
if (r != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto config = sessions_->get_config();
|
||||
|
@ -852,7 +865,7 @@ int Http2Handler::connection_made() {
|
|||
|
||||
r = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, entry.data(), niv);
|
||||
if (r != 0) {
|
||||
return r;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (config->connection_window_bits != -1) {
|
||||
|
@ -860,7 +873,7 @@ int Http2Handler::connection_made() {
|
|||
session_, NGHTTP2_FLAG_NONE, 0,
|
||||
(1 << config->connection_window_bits) - 1);
|
||||
if (r != 0) {
|
||||
return r;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1064,6 +1077,39 @@ void Http2Handler::terminate_session(uint32_t error_code) {
|
|||
nghttp2_session_terminate_session(session_, error_code);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> &Http2Handler::get_extbuf() { return extbuf_; }
|
||||
|
||||
void Http2Handler::set_cache_digest(const StringRef &authority,
|
||||
std::unique_ptr<CacheDigest> cache_digest) {
|
||||
origin_cache_digest_[authority.str()] = std::move(cache_digest);
|
||||
}
|
||||
|
||||
bool Http2Handler::cache_digest_includes(const StringRef &authority,
|
||||
const StringRef &uri) const {
|
||||
uint64_t key;
|
||||
int rv;
|
||||
|
||||
auto it = origin_cache_digest_.find(authority.str());
|
||||
|
||||
if (it == std::end(origin_cache_digest_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto &cache_digest = (*it).second;
|
||||
|
||||
auto key_nbits = cache_digest->logn + cache_digest->logp;
|
||||
|
||||
rv = cache_digest_hash(key, key_nbits, uri);
|
||||
|
||||
if (rv != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto &keys = cache_digest->keys;
|
||||
|
||||
return std::binary_search(std::begin(keys), std::end(keys), key);
|
||||
}
|
||||
|
||||
ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id,
|
||||
uint8_t *buf, size_t length, uint32_t *data_flags,
|
||||
nghttp2_data_source *source, void *user_data) {
|
||||
|
@ -1542,6 +1588,20 @@ int hd_on_frame_recv_callback(nghttp2_session *session,
|
|||
hd->remove_settings_timer();
|
||||
}
|
||||
break;
|
||||
case NGHTTP2_DRAFT_CACHE_DIGEST: {
|
||||
auto stream = hd->get_stream(frame->hd.stream_id);
|
||||
if (!stream) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto &header = stream->header;
|
||||
|
||||
hd->set_cache_digest(header.authority,
|
||||
std::unique_ptr<CacheDigest>(
|
||||
static_cast<CacheDigest *>(frame->ext.payload)));
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -1549,6 +1609,36 @@ int hd_on_frame_recv_callback(nghttp2_session *session,
|
|||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
int before_frame_send_callback(nghttp2_session *session,
|
||||
const nghttp2_frame *frame, void *user_data) {
|
||||
auto hd = static_cast<Http2Handler *>(user_data);
|
||||
|
||||
if (frame->hd.type != NGHTTP2_PUSH_PROMISE) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto promised_stream = hd->get_stream(frame->push_promise.promised_stream_id);
|
||||
|
||||
if (promised_stream == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto &header = promised_stream->header;
|
||||
|
||||
auto uri = header.scheme.str();
|
||||
uri += "://";
|
||||
uri += header.authority;
|
||||
uri += header.path;
|
||||
|
||||
if (hd->cache_digest_includes(header.authority, StringRef{uri})) {
|
||||
return NGHTTP2_ERR_CANCEL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
int hd_on_frame_send_callback(nghttp2_session *session,
|
||||
const nghttp2_frame *frame, void *user_data) {
|
||||
|
@ -1721,6 +1811,46 @@ int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
|
|||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
int on_extension_chunk_recv_callback(nghttp2_session *session,
|
||||
const nghttp2_frame_hd *frame_hd,
|
||||
const uint8_t *data, size_t len,
|
||||
void *user_data) {
|
||||
auto hd = static_cast<Http2Handler *>(user_data);
|
||||
|
||||
auto &buf = hd->get_extbuf();
|
||||
buf.insert(std::end(buf), data, data + len);
|
||||
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
int unpack_extension_callback(nghttp2_session *session, void **payload,
|
||||
const nghttp2_frame_hd *frame_hd,
|
||||
void *user_data) {
|
||||
int rv;
|
||||
auto hd = static_cast<Http2Handler *>(user_data);
|
||||
|
||||
auto cache_digest = make_unique<CacheDigest>();
|
||||
|
||||
auto &buf = hd->get_extbuf();
|
||||
|
||||
rv = cache_digest_decode(cache_digest->keys, cache_digest->logn,
|
||||
cache_digest->logp, buf.data(), buf.size());
|
||||
|
||||
buf.clear();
|
||||
|
||||
if (rv != 0) {
|
||||
return NGHTTP2_ERR_CANCEL;
|
||||
}
|
||||
|
||||
*payload = cache_digest.release();
|
||||
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
void fill_callback(nghttp2_session_callbacks *callbacks, const Config *config) {
|
||||
nghttp2_session_callbacks_set_on_stream_close_callback(
|
||||
|
@ -1756,6 +1886,15 @@ void fill_callback(nghttp2_session_callbacks *callbacks, const Config *config) {
|
|||
nghttp2_session_callbacks_set_select_padding_callback(
|
||||
callbacks, select_padding_callback);
|
||||
}
|
||||
|
||||
nghttp2_session_callbacks_set_unpack_extension_callback(
|
||||
callbacks, unpack_extension_callback);
|
||||
|
||||
nghttp2_session_callbacks_set_on_extension_chunk_recv_callback(
|
||||
callbacks, on_extension_chunk_recv_callback);
|
||||
|
||||
nghttp2_session_callbacks_set_before_frame_send_callback(
|
||||
callbacks, before_frame_send_callback);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
|
|
@ -153,6 +153,12 @@ struct Stream {
|
|||
|
||||
class Sessions;
|
||||
|
||||
struct CacheDigest {
|
||||
std::vector<uint64_t> keys;
|
||||
uint32_t logn;
|
||||
uint32_t logp;
|
||||
};
|
||||
|
||||
class Http2Handler {
|
||||
public:
|
||||
Http2Handler(Sessions *sessions, int fd, SSL *ssl, int64_t session_id);
|
||||
|
@ -206,6 +212,15 @@ public:
|
|||
|
||||
WriteBuf *get_wb();
|
||||
|
||||
std::vector<uint8_t> &get_extbuf();
|
||||
|
||||
// Sets given cache digest. Overwrites existing one if any.
|
||||
void set_cache_digest(const StringRef &origin,
|
||||
std::unique_ptr<CacheDigest> cache_digest);
|
||||
// Returns true if |uri| is included in cache digest.
|
||||
bool cache_digest_includes(const StringRef &origin,
|
||||
const StringRef &uri) const;
|
||||
|
||||
private:
|
||||
ev_io wev_;
|
||||
ev_io rev_;
|
||||
|
@ -213,6 +228,10 @@ private:
|
|||
std::map<int32_t, std::unique_ptr<Stream>> id2stream_;
|
||||
WriteBuf wb_;
|
||||
std::function<int(Http2Handler &)> read_, write_;
|
||||
// Received cache digest hash keys per origin
|
||||
std::map<std::string, std::unique_ptr<CacheDigest>> origin_cache_digest_;
|
||||
// Buffer for extension frame payload
|
||||
std::vector<uint8_t> extbuf_;
|
||||
int64_t session_id_;
|
||||
nghttp2_session *session_;
|
||||
Sessions *sessions_;
|
||||
|
|
|
@ -64,10 +64,10 @@ if ENABLE_APP
|
|||
bin_PROGRAMS += nghttp nghttpd nghttpx
|
||||
|
||||
HELPER_OBJECTS = util.cc \
|
||||
http2.cc timegm.c app_helper.cc nghttp2_gzip.c
|
||||
http2.cc timegm.c app_helper.cc nghttp2_gzip.c cache_digest.cc
|
||||
HELPER_HFILES = util.h \
|
||||
http2.h timegm.h app_helper.h nghttp2_config.h \
|
||||
nghttp2_gzip.h network.h
|
||||
nghttp2_gzip.h network.h cache_digest.h
|
||||
|
||||
HTML_PARSER_OBJECTS =
|
||||
HTML_PARSER_HFILES = HtmlParser.h
|
||||
|
@ -138,7 +138,8 @@ NGHTTPX_SRCS = \
|
|||
shrpx_api_downstream_connection.cc shrpx_api_downstream_connection.h \
|
||||
shrpx_health_monitor_downstream_connection.cc \
|
||||
shrpx_health_monitor_downstream_connection.h \
|
||||
buffer.h memchunk.h template.h allocator.h
|
||||
buffer.h memchunk.h template.h allocator.h \
|
||||
cache_digest.cc cache_digest.h
|
||||
|
||||
if HAVE_SPDYLAY
|
||||
NGHTTPX_SRCS += shrpx_spdy_upstream.cc shrpx_spdy_upstream.h
|
||||
|
@ -188,7 +189,8 @@ nghttpx_unittest_SOURCES = shrpx-unittest.cc \
|
|||
buffer_test.cc buffer_test.h \
|
||||
memchunk_test.cc memchunk_test.h \
|
||||
template_test.cc template_test.h \
|
||||
base64_test.cc base64_test.h
|
||||
base64_test.cc base64_test.h \
|
||||
cache_digest_test.cc cache_digest_test.h
|
||||
nghttpx_unittest_CPPFLAGS = ${AM_CPPFLAGS} \
|
||||
-DNGHTTP2_SRC_DIR=\"$(top_srcdir)/src\"
|
||||
nghttpx_unittest_LDADD = libnghttpx.a ${LDADD} @CUNIT_LIBS@ @TESTLDADD@
|
||||
|
|
|
@ -106,6 +106,8 @@ std::string strframetype(uint8_t type) {
|
|||
return "WINDOW_UPDATE";
|
||||
case NGHTTP2_ALTSVC:
|
||||
return "ALTSVC";
|
||||
case NGHTTP2_DRAFT_CACHE_DIGEST:
|
||||
return "CACHE_DIGSET";
|
||||
}
|
||||
|
||||
std::string s = "extension(0x";
|
||||
|
|
|
@ -41,6 +41,11 @@
|
|||
|
||||
namespace nghttp2 {
|
||||
|
||||
enum nghttp2_draft_frame_type {
|
||||
// draft-kazuho-h2-cache-digest-01
|
||||
NGHTTP2_DRAFT_CACHE_DIGEST = 0xf1
|
||||
};
|
||||
|
||||
int verbose_on_header_callback(nghttp2_session *session,
|
||||
const nghttp2_frame *frame, const uint8_t *name,
|
||||
size_t namelen, const uint8_t *value,
|
||||
|
|
|
@ -0,0 +1,406 @@
|
|||
/*
|
||||
* nghttp2 - HTTP/2 C Library
|
||||
*
|
||||
* Copyright (c) 2016 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 "cache_digest.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <array>
|
||||
#include <limits>
|
||||
|
||||
#include <openssl/evp.h>
|
||||
|
||||
namespace nghttp2 {
|
||||
|
||||
namespace {
|
||||
int compute_hash_values(std::vector<uint64_t> &hash_values,
|
||||
const std::vector<std::string> &uris, uint32_t nbits) {
|
||||
int rv;
|
||||
|
||||
if (nbits > 62) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint64_t mask = (static_cast<uint64_t>(1) << nbits) - 1;
|
||||
|
||||
auto ctx = EVP_MD_CTX_create();
|
||||
|
||||
hash_values.resize(uris.size());
|
||||
|
||||
std::array<uint8_t, 32> md;
|
||||
|
||||
auto p = std::begin(hash_values);
|
||||
for (auto &u : uris) {
|
||||
rv = EVP_DigestInit_ex(ctx, EVP_sha256(), nullptr);
|
||||
if (rv != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
rv = EVP_DigestUpdate(ctx, u.c_str(), u.size());
|
||||
if (rv != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
unsigned int len = md.size();
|
||||
|
||||
rv = EVP_DigestFinal_ex(ctx, md.data(), &len);
|
||||
if (rv != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
assert(len == 32);
|
||||
|
||||
uint64_t v;
|
||||
|
||||
v = (static_cast<uint64_t>(md[24]) << 56) +
|
||||
(static_cast<uint64_t>(md[25]) << 48) +
|
||||
(static_cast<uint64_t>(md[26]) << 40) +
|
||||
(static_cast<uint64_t>(md[27]) << 32) + (md[28] << 24) +
|
||||
(md[29] << 16) + (md[30] << 8) + md[31];
|
||||
v &= mask;
|
||||
|
||||
*p++ = v;
|
||||
}
|
||||
|
||||
EVP_MD_CTX_destroy(ctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
std::pair<uint8_t *, size_t> append_uint32(uint8_t *p, size_t b, uint32_t v,
|
||||
size_t nbits) {
|
||||
v &= (1 << nbits) - 1;
|
||||
|
||||
if (8 > b + nbits) {
|
||||
*p |= (v << (8 - b - nbits));
|
||||
return {p, b + nbits};
|
||||
}
|
||||
|
||||
if (8 == b + nbits) {
|
||||
*p++ |= v;
|
||||
return {p, 0};
|
||||
}
|
||||
|
||||
auto h = 8 - b;
|
||||
auto left = nbits - h;
|
||||
|
||||
*p++ |= (v >> left);
|
||||
b = 0;
|
||||
|
||||
for (; left >= 8; left -= 8) {
|
||||
*p++ = (v >> (left - 8)) & 0xff;
|
||||
}
|
||||
|
||||
if (left > 0) {
|
||||
*p = (v & ((1 << left) - 1)) << (8 - left);
|
||||
}
|
||||
|
||||
return {p, left};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
std::pair<uint8_t *, size_t> append_0bit(uint8_t *p, size_t b, size_t nbits) {
|
||||
if (8 > b + nbits) {
|
||||
return {p, b + nbits};
|
||||
}
|
||||
|
||||
if (8 == b + nbits) {
|
||||
return {++p, 0};
|
||||
}
|
||||
|
||||
nbits -= 8 - b;
|
||||
++p;
|
||||
|
||||
p += nbits / 8;
|
||||
|
||||
return {p, nbits % 8};
|
||||
}
|
||||
|
||||
std::pair<uint8_t *, size_t> append_single_1bit(uint8_t *p, size_t b) {
|
||||
if (8 > b + 1) {
|
||||
*p |= (1 << (7 - b));
|
||||
return {p, b + 1};
|
||||
}
|
||||
|
||||
*p++ |= 1;
|
||||
|
||||
return {p, 0};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
ssize_t cache_digest_encode(uint8_t *data, size_t datalen,
|
||||
const std::vector<std::string> &uris,
|
||||
uint32_t logp) {
|
||||
uint32_t n = 1;
|
||||
uint32_t logn = 0;
|
||||
|
||||
if (logp > 31) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint32_t p = 1;
|
||||
for (auto i = 0; i < logp; ++i, p *= 2)
|
||||
;
|
||||
|
||||
for (; n < uris.size(); n *= 2, ++logn)
|
||||
;
|
||||
|
||||
if (n - uris.size() > uris.size() - n / 2) {
|
||||
n /= 2;
|
||||
--logn;
|
||||
}
|
||||
|
||||
auto maxlen = 2 * n + n * logp;
|
||||
if (maxlen > datalen) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::vector<uint64_t> hash_values;
|
||||
|
||||
if (compute_hash_values(hash_values, uris, logn + logp) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::sort(std::begin(hash_values), std::end(hash_values));
|
||||
|
||||
auto last = data;
|
||||
|
||||
size_t b = 0;
|
||||
|
||||
std::fill_n(data, maxlen, 0);
|
||||
|
||||
std::tie(last, b) = append_uint32(last, b, logn, 5);
|
||||
std::tie(last, b) = append_uint32(last, b, logp, 5);
|
||||
|
||||
auto c = std::numeric_limits<uint64_t>::max();
|
||||
|
||||
for (auto v : hash_values) {
|
||||
if (v == c) {
|
||||
continue;
|
||||
}
|
||||
auto d = v - c - 1;
|
||||
auto q = d / p;
|
||||
auto r = d % p;
|
||||
|
||||
std::tie(last, b) = append_0bit(last, b, q);
|
||||
std::tie(last, b) = append_single_1bit(last, b);
|
||||
std::tie(last, b) = append_uint32(last, b, r, logp);
|
||||
|
||||
c = v;
|
||||
}
|
||||
|
||||
if (b != 0) {
|
||||
// we already zero-filled.
|
||||
++last;
|
||||
}
|
||||
|
||||
return last - data;
|
||||
}
|
||||
|
||||
int cache_digest_hash(uint64_t &key, size_t nbits, const StringRef &s) {
|
||||
int rv;
|
||||
uint64_t mask = (static_cast<uint64_t>(1) << nbits) - 1;
|
||||
|
||||
std::array<uint8_t, 32> md;
|
||||
|
||||
auto ctx = EVP_MD_CTX_create();
|
||||
|
||||
rv = EVP_DigestInit_ex(ctx, EVP_sha256(), nullptr);
|
||||
if (rv != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
rv = EVP_DigestUpdate(ctx, s.c_str(), s.size());
|
||||
if (rv != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
unsigned int len = md.size();
|
||||
|
||||
rv = EVP_DigestFinal_ex(ctx, md.data(), &len);
|
||||
if (rv != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
assert(len == 32);
|
||||
|
||||
EVP_MD_CTX_destroy(ctx);
|
||||
|
||||
key = (static_cast<uint64_t>(md[24]) << 56) +
|
||||
(static_cast<uint64_t>(md[25]) << 48) +
|
||||
(static_cast<uint64_t>(md[26]) << 40) +
|
||||
(static_cast<uint64_t>(md[27]) << 32) + (md[28] << 24) +
|
||||
(md[29] << 16) + (md[30] << 8) + md[31];
|
||||
|
||||
key &= mask;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
namespace {
|
||||
std::pair<const uint8_t *, size_t> read_uint32(uint32_t &res, size_t nbits,
|
||||
const uint8_t *p, size_t b) {
|
||||
if (b + nbits < 8) {
|
||||
res = (*p >> (8 - b - nbits)) & ((1 << nbits) - 1);
|
||||
return {p, b + nbits};
|
||||
}
|
||||
|
||||
if (b + nbits == 8) {
|
||||
res = *p & ((1 << nbits) - 1);
|
||||
return {++p, 0};
|
||||
}
|
||||
|
||||
res = *p & ((1 << (8 - b)) - 1);
|
||||
|
||||
++p;
|
||||
nbits -= 8 - b;
|
||||
|
||||
for (; nbits >= 8; nbits -= 8) {
|
||||
res <<= 8;
|
||||
res += *p++;
|
||||
}
|
||||
|
||||
if (nbits) {
|
||||
res <<= nbits;
|
||||
res += *p >> (8 - nbits);
|
||||
}
|
||||
|
||||
return {p, nbits};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
size_t leading_zero(uint8_t c) {
|
||||
for (size_t i = 0; i < 8; ++i) {
|
||||
if (c & (1 << (7 - i))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return 8;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
std::pair<const uint8_t *, size_t>
|
||||
read_until_1bit(uint32_t &res, const uint8_t *p, size_t b, const uint8_t *end) {
|
||||
uint8_t mask = (1 << (8 - b)) - 1;
|
||||
|
||||
if (*p & mask) {
|
||||
res = leading_zero(*p & mask) - b;
|
||||
b += res + 1;
|
||||
if (b == 8) {
|
||||
return {++p, 0};
|
||||
}
|
||||
return {p, b};
|
||||
}
|
||||
|
||||
res = 8 - b;
|
||||
|
||||
++p;
|
||||
|
||||
for (; p != end; ++p, res += 8) {
|
||||
if (!*p) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto nlz = leading_zero(*p);
|
||||
|
||||
res += nlz;
|
||||
b = nlz + 1;
|
||||
|
||||
if (b == 8) {
|
||||
return {++p, 0};
|
||||
}
|
||||
return {p, b};
|
||||
}
|
||||
|
||||
return {end, 0};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int cache_digest_decode(std::vector<uint64_t> &keys, uint32_t &logn,
|
||||
uint32_t &logp, const uint8_t *data, size_t datalen) {
|
||||
auto last = data;
|
||||
size_t b = 0;
|
||||
|
||||
auto end = data + datalen;
|
||||
|
||||
if ((end - data) * 8 < 10) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
keys.resize(0);
|
||||
|
||||
logn = 0;
|
||||
logp = 0;
|
||||
|
||||
std::tie(last, b) = read_uint32(logn, 5, last, b);
|
||||
std::tie(last, b) = read_uint32(logp, 5, last, b);
|
||||
|
||||
uint32_t n = 1, p = 1;
|
||||
|
||||
for (auto i = 0; i < logn; n *= 2, ++i)
|
||||
;
|
||||
|
||||
for (auto i = 0; i < logp; p *= 2, ++i)
|
||||
;
|
||||
|
||||
uint64_t c = std::numeric_limits<uint64_t>::max();
|
||||
|
||||
for (;;) {
|
||||
uint32_t q, r;
|
||||
|
||||
auto may_end = end - last == 1 && b > 0;
|
||||
std::tie(last, b) = read_until_1bit(q, last, b, end);
|
||||
|
||||
if (last == end) {
|
||||
if (may_end) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((end - last) * 8 < b + logp) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::tie(last, b) = read_uint32(r, logp, last, b);
|
||||
|
||||
auto d = static_cast<uint64_t>(q) * p + r;
|
||||
|
||||
c += d + 1;
|
||||
|
||||
keys.push_back(c);
|
||||
|
||||
if (last == end) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace nghttp2
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* nghttp2 - HTTP/2 C Library
|
||||
*
|
||||
* Copyright (c) 2016 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.
|
||||
*/
|
||||
#ifndef CACHE_DIGEST_H
|
||||
#define CACHE_DIGEST_H
|
||||
|
||||
#include "nghttp2_config.h"
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include "template.h"
|
||||
|
||||
namespace nghttp2 {
|
||||
|
||||
ssize_t cache_digest_encode(uint8_t *data, size_t datalen,
|
||||
const std::vector<std::string> &uris,
|
||||
uint32_t logp);
|
||||
|
||||
int cache_digest_decode(std::vector<uint64_t> &keys, uint32_t &logn,
|
||||
uint32_t &logp, const uint8_t *data, size_t datalen);
|
||||
|
||||
int cache_digest_hash(uint64_t &key, size_t nbits, const StringRef &s);
|
||||
|
||||
} // namespace nghttp2
|
||||
|
||||
#endif // CACHE_DIGEST_H
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* nghttp2 - HTTP/2 C Library
|
||||
*
|
||||
* Copyright (c) 2016 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 "cache_digest_test.h"
|
||||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
#include <CUnit/CUnit.h>
|
||||
|
||||
#include "cache_digest.h"
|
||||
#include "template.h"
|
||||
|
||||
namespace nghttp2 {
|
||||
|
||||
void test_cache_digest_encode_decode(void) {
|
||||
int rv;
|
||||
|
||||
auto uris = std::vector<std::string>{"https://nghttp2.org/foo",
|
||||
"https://nghttp2.org/bar",
|
||||
"https://nghttp2.org/buzz"};
|
||||
|
||||
auto pbits = 31;
|
||||
std::array<uint8_t, 16_k> cdbuf;
|
||||
auto cdlen = cache_digest_encode(cdbuf.data(), cdbuf.size(), uris, pbits);
|
||||
|
||||
std::vector<uint64_t> keys;
|
||||
uint32_t logn, logp;
|
||||
|
||||
rv = cache_digest_decode(keys, logn, logp, cdbuf.data(), cdlen);
|
||||
|
||||
CU_ASSERT(0 == rv);
|
||||
|
||||
auto query_keys = std::vector<uint64_t>(uris.size());
|
||||
for (size_t i = 0; i < uris.size(); ++i) {
|
||||
auto &uri = uris[i];
|
||||
|
||||
uint64_t key;
|
||||
|
||||
rv = cache_digest_hash(key, logn + logp, StringRef{uri});
|
||||
|
||||
CU_ASSERT(0 == rv);
|
||||
|
||||
query_keys[i] = key;
|
||||
}
|
||||
|
||||
CU_ASSERT(
|
||||
std::binary_search(std::begin(keys), std::end(keys), query_keys[0]));
|
||||
CU_ASSERT(
|
||||
std::binary_search(std::begin(keys), std::end(keys), query_keys[1]));
|
||||
CU_ASSERT(
|
||||
std::binary_search(std::begin(keys), std::end(keys), query_keys[2]));
|
||||
}
|
||||
|
||||
} // namespace nghttp2
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* nghttp2 - HTTP/2 C Library
|
||||
*
|
||||
* Copyright (c) 2016 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.
|
||||
*/
|
||||
#ifndef CACHE_DIGEST_TEST_H
|
||||
#define CACHE_DIGEST_TEST_H
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include <config.h>
|
||||
#endif // HAVE_CONFIG_H
|
||||
|
||||
namespace nghttp2 {
|
||||
|
||||
void test_cache_digest_encode_decode(void);
|
||||
|
||||
} // namespace nghttp2
|
||||
|
||||
#endif // CACHE_DIGEST_TEST_H
|
|
@ -59,6 +59,7 @@
|
|||
#include "base64.h"
|
||||
#include "ssl.h"
|
||||
#include "template.h"
|
||||
#include "cache_digest.h"
|
||||
|
||||
#ifndef O_BINARY
|
||||
#define O_BINARY (0)
|
||||
|
@ -98,6 +99,7 @@ Config::Config()
|
|||
padding(0),
|
||||
max_concurrent_streams(100),
|
||||
peer_max_concurrent_streams(100),
|
||||
cache_digest_bits(7),
|
||||
weight(NGHTTP2_DEFAULT_WEIGHT),
|
||||
multiply(1),
|
||||
timeout(0.),
|
||||
|
@ -544,7 +546,8 @@ HttpClient::HttpClient(const nghttp2_session_callbacks *callbacks,
|
|||
state(ClientState::IDLE),
|
||||
upgrade_response_status_code(0),
|
||||
fd(-1),
|
||||
upgrade_response_complete(false) {
|
||||
upgrade_response_complete(false),
|
||||
cache_digest_sent(false) {
|
||||
ev_io_init(&wev, writecb, 0, EV_WRITE);
|
||||
ev_io_init(&rev, readcb, 0, EV_READ);
|
||||
|
||||
|
@ -1997,6 +2000,8 @@ int before_frame_send_callback(nghttp2_session *session,
|
|||
namespace {
|
||||
int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
||||
void *user_data) {
|
||||
int rv;
|
||||
|
||||
if (config.verbose) {
|
||||
verbose_on_frame_send_callback(session, frame, user_data);
|
||||
}
|
||||
|
@ -2017,6 +2022,19 @@ int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
|||
req->continue_timer->start();
|
||||
}
|
||||
|
||||
auto client = get_client(user_data);
|
||||
|
||||
if (!client->cache_digest_sent && !config.cache_digest_uris.empty()) {
|
||||
client->cache_digest_sent = true;
|
||||
|
||||
rv = nghttp2_submit_extension(session, NGHTTP2_DRAFT_CACHE_DIGEST,
|
||||
NGHTTP2_FLAG_NONE, frame->hd.stream_id, NULL);
|
||||
|
||||
if (nghttp2_is_fatal(rv)) {
|
||||
return NGHTTP2_ERR_CALLBACK_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
@ -2363,6 +2381,35 @@ ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id,
|
|||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
ssize_t pack_cache_digest_frame(nghttp2_session *session, uint8_t *buf,
|
||||
size_t len, const nghttp2_frame *frame,
|
||||
void *user_data) {
|
||||
ssize_t encodedlen;
|
||||
|
||||
encodedlen = cache_digest_encode(buf, len, config.cache_digest_uris,
|
||||
config.cache_digest_bits);
|
||||
|
||||
if (encodedlen == -1) {
|
||||
return NGHTTP2_ERR_CANCEL;
|
||||
}
|
||||
|
||||
return encodedlen;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
ssize_t pack_extension_callback(nghttp2_session *session, uint8_t *buf,
|
||||
size_t len, const nghttp2_frame *frame,
|
||||
void *user_data) {
|
||||
if (frame->hd.type != NGHTTP2_DRAFT_CACHE_DIGEST) {
|
||||
return NGHTTP2_ERR_CANCEL;
|
||||
}
|
||||
|
||||
return pack_cache_digest_frame(session, buf, len, frame, user_data);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
int run(char **uris, int n) {
|
||||
nghttp2_session_callbacks *callbacks;
|
||||
|
@ -2407,6 +2454,9 @@ int run(char **uris, int n) {
|
|||
callbacks, select_padding_callback);
|
||||
}
|
||||
|
||||
nghttp2_session_callbacks_set_pack_extension_callback(
|
||||
callbacks, pack_extension_callback);
|
||||
|
||||
std::string prev_scheme;
|
||||
std::string prev_host;
|
||||
uint16_t prev_port = 0;
|
||||
|
@ -2632,6 +2682,13 @@ Options:
|
|||
(up to a short timeout) until the server sends a 100
|
||||
Continue interim response. This option is ignored unless
|
||||
combined with the -d option.
|
||||
-C, --cache-digest-uri=<URI>
|
||||
Add <URI> to cache digest. Use this option multiple
|
||||
times to add more than 1 URI to cache digest.
|
||||
--cache-digest-bits=<N>
|
||||
Set the number of bits to specify the probability of a
|
||||
false positive that is acceptable, expressed as "1/<N>".
|
||||
Default: 7
|
||||
--version Display version information and exit.
|
||||
-h, --help Display this help and exit.
|
||||
|
||||
|
@ -2672,6 +2729,7 @@ int main(int argc, char **argv) {
|
|||
{"header-table-size", required_argument, nullptr, 'c'},
|
||||
{"padding", required_argument, nullptr, 'b'},
|
||||
{"har", required_argument, nullptr, 'r'},
|
||||
{"cache-digest-uri", required_argument, nullptr, 'C'},
|
||||
{"cert", required_argument, &flag, 1},
|
||||
{"key", required_argument, &flag, 2},
|
||||
{"color", no_argument, &flag, 3},
|
||||
|
@ -2684,14 +2742,18 @@ int main(int argc, char **argv) {
|
|||
{"no-push", no_argument, &flag, 11},
|
||||
{"max-concurrent-streams", required_argument, &flag, 12},
|
||||
{"expect-continue", no_argument, &flag, 13},
|
||||
{"cache-digest-bits", required_argument, &flag, 14},
|
||||
{nullptr, 0, nullptr, 0}};
|
||||
int option_index = 0;
|
||||
int c = getopt_long(argc, argv, "M:Oab:c:d:gm:np:r:hH:vst:uw:W:",
|
||||
int c = getopt_long(argc, argv, "C:M:Oab:c:d:gm:np:r:hH:vst:uw:W:",
|
||||
long_options, &option_index);
|
||||
if (c == -1) {
|
||||
break;
|
||||
}
|
||||
switch (c) {
|
||||
case 'C':
|
||||
config.cache_digest_uris.push_back(optarg);
|
||||
break;
|
||||
case 'M':
|
||||
// peer-max-concurrent-streams option
|
||||
config.peer_max_concurrent_streams = strtoul(optarg, nullptr, 10);
|
||||
|
@ -2885,6 +2947,18 @@ int main(int argc, char **argv) {
|
|||
// expect-continue option
|
||||
config.expect_continue = true;
|
||||
break;
|
||||
case 14: {
|
||||
// cache-digest-bits
|
||||
auto n = strtoul(optarg, nullptr, 10);
|
||||
if (n <= 0 || n >= 32) {
|
||||
std::cerr
|
||||
<< "--cache-digest-bits: specify in the range [1, 31], inclusive"
|
||||
<< std::endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
config.cache_digest_bits = n;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
|
|
@ -64,6 +64,7 @@ struct Config {
|
|||
|
||||
Headers headers;
|
||||
Headers trailer;
|
||||
std::vector<std::string> cache_digest_uris;
|
||||
std::string certfile;
|
||||
std::string keyfile;
|
||||
std::string datafile;
|
||||
|
@ -74,6 +75,7 @@ struct Config {
|
|||
size_t padding;
|
||||
size_t max_concurrent_streams;
|
||||
ssize_t peer_max_concurrent_streams;
|
||||
uint32_t cache_digest_bits;
|
||||
int32_t weight;
|
||||
int multiply;
|
||||
// milliseconds
|
||||
|
@ -283,6 +285,8 @@ struct HttpClient {
|
|||
// true if the response message of HTTP Upgrade request is fully
|
||||
// received. It is not relevant the upgrade succeeds, or not.
|
||||
bool upgrade_response_complete;
|
||||
// true if cache digest was sent or there is no need to send it.
|
||||
bool cache_digest_sent;
|
||||
// SETTINGS payload sent as token68 in HTTP Upgrade
|
||||
std::array<uint8_t, 128> settings_payload;
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
#include "shrpx_config.h"
|
||||
#include "ssl.h"
|
||||
#include "shrpx_router_test.h"
|
||||
#include "cache_digest_test.h"
|
||||
|
||||
static int init_suite1(void) { return 0; }
|
||||
|
||||
|
@ -198,7 +199,9 @@ int main(int argc, char *argv[]) {
|
|||
!CU_add_test(pSuite, "template_string_ref",
|
||||
nghttp2::test_template_string_ref) ||
|
||||
!CU_add_test(pSuite, "base64_encode", nghttp2::test_base64_encode) ||
|
||||
!CU_add_test(pSuite, "base64_decode", nghttp2::test_base64_decode)) {
|
||||
!CU_add_test(pSuite, "base64_decode", nghttp2::test_base64_decode) ||
|
||||
!CU_add_test(pSuite, "cache_digest_encode_decode",
|
||||
nghttp2::test_cache_digest_encode_decode)) {
|
||||
CU_cleanup_registry();
|
||||
return CU_get_error();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue