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:
Tatsuhiro Tsujikawa 2016-06-30 23:26:12 +09:00
parent d2addbc1ed
commit bcc97b8699
14 changed files with 830 additions and 13 deletions

View File

@ -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;

View File

@ -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}

View File

@ -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

View File

@ -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_;

View File

@ -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@

View File

@ -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";

View File

@ -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,

406
src/cache_digest.cc Normal file
View File

@ -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

48
src/cache_digest.h Normal file
View File

@ -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

76
src/cache_digest_test.cc Normal file
View File

@ -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

38
src/cache_digest_test.h Normal file
View File

@ -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

View File

@ -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:

View File

@ -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;

View File

@ -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();
} }