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)) {
|
iframe->frame.hd.type)) {
|
||||||
if (!session->callbacks.unpack_extension_callback) {
|
if (!session->callbacks.unpack_extension_callback) {
|
||||||
/* Silently ignore unknown frame type. */
|
/* Silently ignore unknown frame type. */
|
||||||
|
|
||||||
busy = 1;
|
busy = 1;
|
||||||
|
|
||||||
iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
|
iframe->state = NGHTTP2_IB_IGN_PAYLOAD;
|
||||||
|
|
|
@ -39,7 +39,7 @@ link_libraries(
|
||||||
if(ENABLE_APP)
|
if(ENABLE_APP)
|
||||||
set(HELPER_OBJECTS
|
set(HELPER_OBJECTS
|
||||||
util.cc
|
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
|
# nghttp client
|
||||||
|
@ -111,6 +111,7 @@ if(ENABLE_APP)
|
||||||
shrpx_router.cc
|
shrpx_router.cc
|
||||||
shrpx_api_downstream_connection.cc
|
shrpx_api_downstream_connection.cc
|
||||||
shrpx_health_monitor_downstream_connection.cc
|
shrpx_health_monitor_downstream_connection.cc
|
||||||
|
cache_digest.cc
|
||||||
)
|
)
|
||||||
if(HAVE_SPDYLAY)
|
if(HAVE_SPDYLAY)
|
||||||
list(APPEND NGHTTPX_SRCS
|
list(APPEND NGHTTPX_SRCS
|
||||||
|
@ -159,6 +160,7 @@ if(ENABLE_APP)
|
||||||
memchunk_test.cc
|
memchunk_test.cc
|
||||||
template_test.cc
|
template_test.cc
|
||||||
base64_test.cc
|
base64_test.cc
|
||||||
|
cache_digest_test.cc
|
||||||
)
|
)
|
||||||
add_executable(nghttpx-unittest EXCLUDE_FROM_ALL
|
add_executable(nghttpx-unittest EXCLUDE_FROM_ALL
|
||||||
${NGHTTPX_UNITTEST_SOURCES}
|
${NGHTTPX_UNITTEST_SOURCES}
|
||||||
|
|
|
@ -62,6 +62,7 @@
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "ssl.h"
|
#include "ssl.h"
|
||||||
#include "template.h"
|
#include "template.h"
|
||||||
|
#include "cache_digest.h"
|
||||||
|
|
||||||
#ifndef O_BINARY
|
#ifndef O_BINARY
|
||||||
#define O_BINARY (0)
|
#define O_BINARY (0)
|
||||||
|
@ -825,10 +826,22 @@ int Http2Handler::on_write() { return write_(*this); }
|
||||||
int Http2Handler::connection_made() {
|
int Http2Handler::connection_made() {
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
r = nghttp2_session_server_new(&session_, sessions_->get_callbacks(), this);
|
nghttp2_option *opt;
|
||||||
|
r = nghttp2_option_new(&opt);
|
||||||
|
|
||||||
if (r != 0) {
|
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();
|
auto config = sessions_->get_config();
|
||||||
|
@ -852,7 +865,7 @@ int Http2Handler::connection_made() {
|
||||||
|
|
||||||
r = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, entry.data(), niv);
|
r = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, entry.data(), niv);
|
||||||
if (r != 0) {
|
if (r != 0) {
|
||||||
return r;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config->connection_window_bits != -1) {
|
if (config->connection_window_bits != -1) {
|
||||||
|
@ -860,7 +873,7 @@ int Http2Handler::connection_made() {
|
||||||
session_, NGHTTP2_FLAG_NONE, 0,
|
session_, NGHTTP2_FLAG_NONE, 0,
|
||||||
(1 << config->connection_window_bits) - 1);
|
(1 << config->connection_window_bits) - 1);
|
||||||
if (r != 0) {
|
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);
|
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,
|
ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id,
|
||||||
uint8_t *buf, size_t length, uint32_t *data_flags,
|
uint8_t *buf, size_t length, uint32_t *data_flags,
|
||||||
nghttp2_data_source *source, void *user_data) {
|
nghttp2_data_source *source, void *user_data) {
|
||||||
|
@ -1542,6 +1588,20 @@ int hd_on_frame_recv_callback(nghttp2_session *session,
|
||||||
hd->remove_settings_timer();
|
hd->remove_settings_timer();
|
||||||
}
|
}
|
||||||
break;
|
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:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1549,6 +1609,36 @@ int hd_on_frame_recv_callback(nghttp2_session *session,
|
||||||
}
|
}
|
||||||
} // namespace
|
} // 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 {
|
namespace {
|
||||||
int hd_on_frame_send_callback(nghttp2_session *session,
|
int hd_on_frame_send_callback(nghttp2_session *session,
|
||||||
const nghttp2_frame *frame, void *user_data) {
|
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
|
||||||
|
|
||||||
|
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 {
|
namespace {
|
||||||
void fill_callback(nghttp2_session_callbacks *callbacks, const Config *config) {
|
void fill_callback(nghttp2_session_callbacks *callbacks, const Config *config) {
|
||||||
nghttp2_session_callbacks_set_on_stream_close_callback(
|
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(
|
nghttp2_session_callbacks_set_select_padding_callback(
|
||||||
callbacks, 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
|
} // namespace
|
||||||
|
|
||||||
|
|
|
@ -153,6 +153,12 @@ struct Stream {
|
||||||
|
|
||||||
class Sessions;
|
class Sessions;
|
||||||
|
|
||||||
|
struct CacheDigest {
|
||||||
|
std::vector<uint64_t> keys;
|
||||||
|
uint32_t logn;
|
||||||
|
uint32_t logp;
|
||||||
|
};
|
||||||
|
|
||||||
class Http2Handler {
|
class Http2Handler {
|
||||||
public:
|
public:
|
||||||
Http2Handler(Sessions *sessions, int fd, SSL *ssl, int64_t session_id);
|
Http2Handler(Sessions *sessions, int fd, SSL *ssl, int64_t session_id);
|
||||||
|
@ -206,6 +212,15 @@ public:
|
||||||
|
|
||||||
WriteBuf *get_wb();
|
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:
|
private:
|
||||||
ev_io wev_;
|
ev_io wev_;
|
||||||
ev_io rev_;
|
ev_io rev_;
|
||||||
|
@ -213,6 +228,10 @@ private:
|
||||||
std::map<int32_t, std::unique_ptr<Stream>> id2stream_;
|
std::map<int32_t, std::unique_ptr<Stream>> id2stream_;
|
||||||
WriteBuf wb_;
|
WriteBuf wb_;
|
||||||
std::function<int(Http2Handler &)> read_, write_;
|
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_;
|
int64_t session_id_;
|
||||||
nghttp2_session *session_;
|
nghttp2_session *session_;
|
||||||
Sessions *sessions_;
|
Sessions *sessions_;
|
||||||
|
|
|
@ -64,10 +64,10 @@ if ENABLE_APP
|
||||||
bin_PROGRAMS += nghttp nghttpd nghttpx
|
bin_PROGRAMS += nghttp nghttpd nghttpx
|
||||||
|
|
||||||
HELPER_OBJECTS = util.cc \
|
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 \
|
HELPER_HFILES = util.h \
|
||||||
http2.h timegm.h app_helper.h nghttp2_config.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_OBJECTS =
|
||||||
HTML_PARSER_HFILES = HtmlParser.h
|
HTML_PARSER_HFILES = HtmlParser.h
|
||||||
|
@ -138,7 +138,8 @@ NGHTTPX_SRCS = \
|
||||||
shrpx_api_downstream_connection.cc shrpx_api_downstream_connection.h \
|
shrpx_api_downstream_connection.cc shrpx_api_downstream_connection.h \
|
||||||
shrpx_health_monitor_downstream_connection.cc \
|
shrpx_health_monitor_downstream_connection.cc \
|
||||||
shrpx_health_monitor_downstream_connection.h \
|
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
|
if HAVE_SPDYLAY
|
||||||
NGHTTPX_SRCS += shrpx_spdy_upstream.cc shrpx_spdy_upstream.h
|
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 \
|
buffer_test.cc buffer_test.h \
|
||||||
memchunk_test.cc memchunk_test.h \
|
memchunk_test.cc memchunk_test.h \
|
||||||
template_test.cc template_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} \
|
nghttpx_unittest_CPPFLAGS = ${AM_CPPFLAGS} \
|
||||||
-DNGHTTP2_SRC_DIR=\"$(top_srcdir)/src\"
|
-DNGHTTP2_SRC_DIR=\"$(top_srcdir)/src\"
|
||||||
nghttpx_unittest_LDADD = libnghttpx.a ${LDADD} @CUNIT_LIBS@ @TESTLDADD@
|
nghttpx_unittest_LDADD = libnghttpx.a ${LDADD} @CUNIT_LIBS@ @TESTLDADD@
|
||||||
|
|
|
@ -106,6 +106,8 @@ std::string strframetype(uint8_t type) {
|
||||||
return "WINDOW_UPDATE";
|
return "WINDOW_UPDATE";
|
||||||
case NGHTTP2_ALTSVC:
|
case NGHTTP2_ALTSVC:
|
||||||
return "ALTSVC";
|
return "ALTSVC";
|
||||||
|
case NGHTTP2_DRAFT_CACHE_DIGEST:
|
||||||
|
return "CACHE_DIGSET";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string s = "extension(0x";
|
std::string s = "extension(0x";
|
||||||
|
|
|
@ -41,6 +41,11 @@
|
||||||
|
|
||||||
namespace nghttp2 {
|
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,
|
int verbose_on_header_callback(nghttp2_session *session,
|
||||||
const nghttp2_frame *frame, const uint8_t *name,
|
const nghttp2_frame *frame, const uint8_t *name,
|
||||||
size_t namelen, const uint8_t *value,
|
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 "base64.h"
|
||||||
#include "ssl.h"
|
#include "ssl.h"
|
||||||
#include "template.h"
|
#include "template.h"
|
||||||
|
#include "cache_digest.h"
|
||||||
|
|
||||||
#ifndef O_BINARY
|
#ifndef O_BINARY
|
||||||
#define O_BINARY (0)
|
#define O_BINARY (0)
|
||||||
|
@ -98,6 +99,7 @@ Config::Config()
|
||||||
padding(0),
|
padding(0),
|
||||||
max_concurrent_streams(100),
|
max_concurrent_streams(100),
|
||||||
peer_max_concurrent_streams(100),
|
peer_max_concurrent_streams(100),
|
||||||
|
cache_digest_bits(7),
|
||||||
weight(NGHTTP2_DEFAULT_WEIGHT),
|
weight(NGHTTP2_DEFAULT_WEIGHT),
|
||||||
multiply(1),
|
multiply(1),
|
||||||
timeout(0.),
|
timeout(0.),
|
||||||
|
@ -544,7 +546,8 @@ HttpClient::HttpClient(const nghttp2_session_callbacks *callbacks,
|
||||||
state(ClientState::IDLE),
|
state(ClientState::IDLE),
|
||||||
upgrade_response_status_code(0),
|
upgrade_response_status_code(0),
|
||||||
fd(-1),
|
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(&wev, writecb, 0, EV_WRITE);
|
||||||
ev_io_init(&rev, readcb, 0, EV_READ);
|
ev_io_init(&rev, readcb, 0, EV_READ);
|
||||||
|
|
||||||
|
@ -1997,6 +2000,8 @@ int before_frame_send_callback(nghttp2_session *session,
|
||||||
namespace {
|
namespace {
|
||||||
int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame,
|
||||||
void *user_data) {
|
void *user_data) {
|
||||||
|
int rv;
|
||||||
|
|
||||||
if (config.verbose) {
|
if (config.verbose) {
|
||||||
verbose_on_frame_send_callback(session, frame, user_data);
|
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();
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
@ -2363,6 +2381,35 @@ ssize_t file_read_callback(nghttp2_session *session, int32_t stream_id,
|
||||||
}
|
}
|
||||||
} // namespace
|
} // 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 {
|
namespace {
|
||||||
int run(char **uris, int n) {
|
int run(char **uris, int n) {
|
||||||
nghttp2_session_callbacks *callbacks;
|
nghttp2_session_callbacks *callbacks;
|
||||||
|
@ -2407,6 +2454,9 @@ int run(char **uris, int n) {
|
||||||
callbacks, select_padding_callback);
|
callbacks, select_padding_callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nghttp2_session_callbacks_set_pack_extension_callback(
|
||||||
|
callbacks, pack_extension_callback);
|
||||||
|
|
||||||
std::string prev_scheme;
|
std::string prev_scheme;
|
||||||
std::string prev_host;
|
std::string prev_host;
|
||||||
uint16_t prev_port = 0;
|
uint16_t prev_port = 0;
|
||||||
|
@ -2632,6 +2682,13 @@ Options:
|
||||||
(up to a short timeout) until the server sends a 100
|
(up to a short timeout) until the server sends a 100
|
||||||
Continue interim response. This option is ignored unless
|
Continue interim response. This option is ignored unless
|
||||||
combined with the -d option.
|
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.
|
--version Display version information and exit.
|
||||||
-h, --help Display this help 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'},
|
{"header-table-size", required_argument, nullptr, 'c'},
|
||||||
{"padding", required_argument, nullptr, 'b'},
|
{"padding", required_argument, nullptr, 'b'},
|
||||||
{"har", required_argument, nullptr, 'r'},
|
{"har", required_argument, nullptr, 'r'},
|
||||||
|
{"cache-digest-uri", required_argument, nullptr, 'C'},
|
||||||
{"cert", required_argument, &flag, 1},
|
{"cert", required_argument, &flag, 1},
|
||||||
{"key", required_argument, &flag, 2},
|
{"key", required_argument, &flag, 2},
|
||||||
{"color", no_argument, &flag, 3},
|
{"color", no_argument, &flag, 3},
|
||||||
|
@ -2684,14 +2742,18 @@ int main(int argc, char **argv) {
|
||||||
{"no-push", no_argument, &flag, 11},
|
{"no-push", no_argument, &flag, 11},
|
||||||
{"max-concurrent-streams", required_argument, &flag, 12},
|
{"max-concurrent-streams", required_argument, &flag, 12},
|
||||||
{"expect-continue", no_argument, &flag, 13},
|
{"expect-continue", no_argument, &flag, 13},
|
||||||
|
{"cache-digest-bits", required_argument, &flag, 14},
|
||||||
{nullptr, 0, nullptr, 0}};
|
{nullptr, 0, nullptr, 0}};
|
||||||
int option_index = 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);
|
long_options, &option_index);
|
||||||
if (c == -1) {
|
if (c == -1) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
switch (c) {
|
switch (c) {
|
||||||
|
case 'C':
|
||||||
|
config.cache_digest_uris.push_back(optarg);
|
||||||
|
break;
|
||||||
case 'M':
|
case 'M':
|
||||||
// peer-max-concurrent-streams option
|
// peer-max-concurrent-streams option
|
||||||
config.peer_max_concurrent_streams = strtoul(optarg, nullptr, 10);
|
config.peer_max_concurrent_streams = strtoul(optarg, nullptr, 10);
|
||||||
|
@ -2885,6 +2947,18 @@ int main(int argc, char **argv) {
|
||||||
// expect-continue option
|
// expect-continue option
|
||||||
config.expect_continue = true;
|
config.expect_continue = true;
|
||||||
break;
|
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;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -64,6 +64,7 @@ struct Config {
|
||||||
|
|
||||||
Headers headers;
|
Headers headers;
|
||||||
Headers trailer;
|
Headers trailer;
|
||||||
|
std::vector<std::string> cache_digest_uris;
|
||||||
std::string certfile;
|
std::string certfile;
|
||||||
std::string keyfile;
|
std::string keyfile;
|
||||||
std::string datafile;
|
std::string datafile;
|
||||||
|
@ -74,6 +75,7 @@ struct Config {
|
||||||
size_t padding;
|
size_t padding;
|
||||||
size_t max_concurrent_streams;
|
size_t max_concurrent_streams;
|
||||||
ssize_t peer_max_concurrent_streams;
|
ssize_t peer_max_concurrent_streams;
|
||||||
|
uint32_t cache_digest_bits;
|
||||||
int32_t weight;
|
int32_t weight;
|
||||||
int multiply;
|
int multiply;
|
||||||
// milliseconds
|
// milliseconds
|
||||||
|
@ -283,6 +285,8 @@ struct HttpClient {
|
||||||
// true if the response message of HTTP Upgrade request is fully
|
// true if the response message of HTTP Upgrade request is fully
|
||||||
// received. It is not relevant the upgrade succeeds, or not.
|
// received. It is not relevant the upgrade succeeds, or not.
|
||||||
bool upgrade_response_complete;
|
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
|
// SETTINGS payload sent as token68 in HTTP Upgrade
|
||||||
std::array<uint8_t, 128> settings_payload;
|
std::array<uint8_t, 128> settings_payload;
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,7 @@
|
||||||
#include "shrpx_config.h"
|
#include "shrpx_config.h"
|
||||||
#include "ssl.h"
|
#include "ssl.h"
|
||||||
#include "shrpx_router_test.h"
|
#include "shrpx_router_test.h"
|
||||||
|
#include "cache_digest_test.h"
|
||||||
|
|
||||||
static int init_suite1(void) { return 0; }
|
static int init_suite1(void) { return 0; }
|
||||||
|
|
||||||
|
@ -198,7 +199,9 @@ int main(int argc, char *argv[]) {
|
||||||
!CU_add_test(pSuite, "template_string_ref",
|
!CU_add_test(pSuite, "template_string_ref",
|
||||||
nghttp2::test_template_string_ref) ||
|
nghttp2::test_template_string_ref) ||
|
||||||
!CU_add_test(pSuite, "base64_encode", nghttp2::test_base64_encode) ||
|
!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();
|
CU_cleanup_registry();
|
||||||
return CU_get_error();
|
return CU_get_error();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue