1 /* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
20 #include <apr_strings.h>
25 #include <openssl/sha.h>
29 #include <http_core.h>
32 #include "h2_private.h"
36 #include "h2_request.h"
37 #include "h2_headers.h"
38 #include "h2_session.h"
39 #include "h2_stream.h"
41 /*******************************************************************************
42 * link header handling
43 ******************************************************************************/
45 static const char *policy_str(h2_push_policy policy)
50 case H2_PUSH_FAST_LOAD:
60 const h2_request *req;
63 apr_array_header_t *pushes;
73 static int attr_char(char c)
90 return apr_isalnum(c);
94 static int ptoken_char(char c)
126 return apr_isalnum(c);
130 static int skip_ws(link_ctx *ctx)
133 while (ctx->i < ctx->slen
134 && (((c = ctx->s[ctx->i]) == ' ') || (c == '\t'))) {
137 return (ctx->i < ctx->slen);
140 static int find_chr(link_ctx *ctx, char c, size_t *pidx)
143 for (j = ctx->i; j < ctx->slen; ++j) {
144 if (ctx->s[j] == c) {
152 static int read_chr(link_ctx *ctx, char c)
154 if (ctx->i < ctx->slen && ctx->s[ctx->i] == c) {
161 static char *mk_str(link_ctx *ctx, size_t end)
164 return apr_pstrndup(ctx->pool, ctx->s + ctx->i, end - ctx->i);
169 static int read_qstring(link_ctx *ctx, const char **ps)
171 if (skip_ws(ctx) && read_chr(ctx, '\"')) {
173 if (find_chr(ctx, '\"', &end)) {
174 *ps = mk_str(ctx, end);
182 static int read_ptoken(link_ctx *ctx, const char **ps)
186 for (i = ctx->i; i < ctx->slen && ptoken_char(ctx->s[i]); ++i) {
190 *ps = mk_str(ctx, i);
199 static int read_link(link_ctx *ctx)
201 if (skip_ws(ctx) && read_chr(ctx, '<')) {
203 if (find_chr(ctx, '>', &end)) {
204 ctx->link = mk_str(ctx, end);
212 static int read_pname(link_ctx *ctx, const char **pname)
216 for (i = ctx->i; i < ctx->slen && attr_char(ctx->s[i]); ++i) {
220 *pname = mk_str(ctx, i);
228 static int read_pvalue(link_ctx *ctx, const char **pvalue)
230 if (skip_ws(ctx) && read_chr(ctx, '=')) {
231 if (read_qstring(ctx, pvalue) || read_ptoken(ctx, pvalue)) {
238 static int read_param(link_ctx *ctx)
240 if (skip_ws(ctx) && read_chr(ctx, ';')) {
241 const char *name, *value = "";
242 if (read_pname(ctx, &name)) {
243 read_pvalue(ctx, &value); /* value is optional */
244 apr_table_setn(ctx->params, name, value);
251 static int read_sep(link_ctx *ctx)
253 if (skip_ws(ctx) && read_chr(ctx, ',')) {
259 static void init_params(link_ctx *ctx)
262 ctx->params = apr_table_make(ctx->pool, 5);
265 apr_table_clear(ctx->params);
269 static int same_authority(const h2_request *req, const apr_uri_t *uri)
271 if (uri->scheme != NULL && strcmp(uri->scheme, req->scheme)) {
274 if (uri->hostinfo != NULL && strcmp(uri->hostinfo, req->authority)) {
280 static int set_push_header(void *ctx, const char *key, const char *value)
282 size_t klen = strlen(key);
283 if (H2_HD_MATCH_LIT("User-Agent", key, klen)
284 || H2_HD_MATCH_LIT("Accept", key, klen)
285 || H2_HD_MATCH_LIT("Accept-Encoding", key, klen)
286 || H2_HD_MATCH_LIT("Accept-Language", key, klen)
287 || H2_HD_MATCH_LIT("Cache-Control", key, klen)) {
288 apr_table_setn(ctx, key, value);
293 static int has_param(link_ctx *ctx, const char *param)
295 const char *p = apr_table_get(ctx->params, param);
299 static int has_relation(link_ctx *ctx, const char *rel)
301 const char *s, *val = apr_table_get(ctx->params, "rel");
303 if (!strcmp(rel, val)) {
306 s = ap_strstr_c(val, rel);
307 if (s && (s == val || s[-1] == ' ')) {
309 if (!*s || *s == ' ') {
317 static int add_push(link_ctx *ctx)
319 /* so, we have read a Link header and need to decide
320 * if we transform it into a push.
322 if (has_relation(ctx, "preload") && !has_param(ctx, "nopush")) {
324 if (apr_uri_parse(ctx->pool, ctx->link, &uri) == APR_SUCCESS) {
325 if (uri.path && same_authority(ctx->req, &uri)) {
328 apr_table_t *headers;
332 /* We only want to generate pushes for resources in the
333 * same authority than the original request.
334 * icing: i think that is wise, otherwise we really need to
335 * check that the vhost/server is available and uses the same
336 * TLS (if any) parameters.
338 path = apr_uri_unparse(ctx->pool, &uri, APR_URI_UNP_OMITSITEPART);
339 push = apr_pcalloc(ctx->pool, sizeof(*push));
340 switch (ctx->push_policy) {
348 headers = apr_table_make(ctx->pool, 5);
349 apr_table_do(set_push_header, headers, ctx->req->headers, NULL);
350 req = h2_req_create(0, ctx->pool, method, ctx->req->scheme,
351 ctx->req->authority, path, headers,
352 ctx->req->serialize);
353 /* atm, we do not push on pushes */
354 h2_request_end_headers(req, ctx->pool, 1);
356 if (has_param(ctx, "critical")) {
357 h2_priority *prio = apr_pcalloc(ctx->pool, sizeof(*prio));
358 prio->dependency = H2_DEPENDANT_BEFORE;
359 push->priority = prio;
362 ctx->pushes = apr_array_make(ctx->pool, 5, sizeof(h2_push*));
364 APR_ARRAY_PUSH(ctx->pushes, h2_push*) = push;
371 static void inspect_link(link_ctx *ctx, const char *s, size_t slen)
373 /* RFC 5988 <https://tools.ietf.org/html/rfc5988#section-6.2.1>
374 Link = "Link" ":" #link-value
375 link-value = "<" URI-Reference ">" *( ";" link-param )
376 link-param = ( ( "rel" "=" relation-types )
377 | ( "anchor" "=" <"> URI-Reference <"> )
378 | ( "rev" "=" relation-types )
379 | ( "hreflang" "=" Language-Tag )
380 | ( "media" "=" ( MediaDesc | ( <"> MediaDesc <"> ) ) )
381 | ( "title" "=" quoted-string )
382 | ( "title*" "=" ext-value )
383 | ( "type" "=" ( media-type | quoted-mt ) )
384 | ( link-extension ) )
385 link-extension = ( parmname [ "=" ( ptoken | quoted-string ) ] )
386 | ( ext-name-star "=" ext-value )
387 ext-name-star = parmname "*" ; reserved for RFC2231-profiled
388 ; extensions. Whitespace NOT
389 ; allowed in between.
390 ptoken = 1*ptokenchar
391 ptokenchar = "!" | "#" | "$" | "%" | "&" | "'" | "("
392 | ")" | "*" | "+" | "-" | "." | "/" | DIGIT
393 | ":" | "<" | "=" | ">" | "?" | "@" | ALPHA
394 | "[" | "]" | "^" | "_" | "`" | "{" | "|"
396 media-type = type-name "/" subtype-name
397 quoted-mt = <"> media-type <">
398 relation-types = relation-type
399 | <"> relation-type *( 1*SP relation-type ) <">
400 relation-type = reg-rel-type | ext-rel-type
401 reg-rel-type = LOALPHA *( LOALPHA | DIGIT | "." | "-" )
404 and from <https://tools.ietf.org/html/rfc5987>
405 parmname = 1*attr-char
406 attr-char = ALPHA / DIGIT
407 / "!" / "#" / "$" / "&" / "+" / "-" / "."
408 / "^" / "_" / "`" / "|" / "~"
415 while (read_link(ctx)) {
417 while (read_param(ctx)) {
421 if (!read_sep(ctx)) {
427 static int head_iter(void *ctx, const char *key, const char *value)
429 if (!apr_strnatcasecmp("link", key)) {
430 inspect_link(ctx, value, strlen(value));
435 apr_array_header_t *h2_push_collect(apr_pool_t *p, const h2_request *req,
436 int push_policy, const h2_headers *res)
438 if (req && push_policy != H2_PUSH_NONE) {
439 /* Collect push candidates from the request/response pair.
441 * One source for pushes are "rel=preload" link headers
444 * TODO: This may be extended in the future by hooks or callbacks
445 * where other modules can provide push information directly.
450 memset(&ctx, 0, sizeof(ctx));
452 ctx.push_policy = push_policy;
455 apr_table_do(head_iter, &ctx, res->headers, NULL);
457 apr_table_setn(res->headers, "push-policy",
458 policy_str(push_policy));
466 /*******************************************************************************
469 * - The push diary keeps track of resources already PUSHed via HTTP/2 on this
470 * connection. It records a hash value from the absolute URL of the resource
472 * - Lacking openssl, it uses 'apr_hashfunc_default' for the value
473 * - with openssl, it uses SHA256 to calculate the hash value
474 * - whatever the method to generate the hash, the diary keeps a maximum of 64
475 * bits per hash, limiting the memory consumption to about
476 * H2PushDiarySize * 8
477 * bytes. Entries are sorted by most recently used and oldest entries are
479 * - Clients can initialize/replace the push diary by sending a 'Cache-Digest'
480 * header. Currently, this is the base64url encoded value of the cache digest
481 * as specified in https://datatracker.ietf.org/doc/draft-kazuho-h2-cache-digest/
482 * This draft can be expected to evolve and the definition of the header
483 * will be added there and refined.
484 * - The cache digest header is a Golomb Coded Set of hash values, but it may
485 * limit the amount of bits per hash value even further. For a good description
487 * http://giovanni.bajo.it/post/47119962313/golomb-coded-sets-smaller-than-bloom-filters
488 * - The means that the push diary might be initialized with hash values of much
489 * less than 64 bits, leading to more false positives, but smaller digest size.
490 ******************************************************************************/
493 #define GCSLOG_LEVEL APLOG_TRACE1
495 typedef struct h2_push_diary_entry {
497 } h2_push_diary_entry;
501 static void sha256_update(SHA256_CTX *ctx, const char *s)
503 SHA256_Update(ctx, s, strlen(s));
506 static void calc_sha256_hash(h2_push_diary *diary, apr_uint64_t *phash, h2_push *push)
510 unsigned char hash[SHA256_DIGEST_LENGTH];
513 SHA256_Init(&sha256);
514 sha256_update(&sha256, push->req->scheme);
515 sha256_update(&sha256, "://");
516 sha256_update(&sha256, push->req->authority);
517 sha256_update(&sha256, push->req->path);
518 SHA256_Final(hash, &sha256);
521 for (i = 0; i != sizeof(val); ++i)
522 val = val * 256 + hash[i];
523 *phash = val >> (64 - diary->mask_bits);
528 static unsigned int val_apr_hash(const char *str)
530 apr_ssize_t len = strlen(str);
531 return apr_hashfunc_default(str, &len);
534 static void calc_apr_hash(h2_push_diary *diary, apr_uint64_t *phash, h2_push *push)
537 #if APR_UINT64_MAX > UINT_MAX
538 val = ((apr_uint64_t)(val_apr_hash(push->req->scheme)) << 32);
539 val ^= ((apr_uint64_t)(val_apr_hash(push->req->authority)) << 16);
540 val ^= val_apr_hash(push->req->path);
542 val = val_apr_hash(push->req->scheme);
543 val ^= val_apr_hash(push->req->authority);
544 val ^= val_apr_hash(push->req->path);
549 static apr_int32_t ceil_power_of_2(apr_int32_t n)
551 if (n <= 2) return 2;
561 static h2_push_diary *diary_create(apr_pool_t *p, h2_push_digest_type dtype,
564 h2_push_diary *diary = NULL;
567 diary = apr_pcalloc(p, sizeof(*diary));
569 diary->NMax = ceil_power_of_2(N);
570 diary->N = diary->NMax;
571 /* the mask we use in value comparison depends on where we got
572 * the values from. If we calculate them ourselves, we can use
574 * If we set the diary via a compressed golomb set, we have less
575 * relevant bits and need to use a smaller mask. */
576 diary->mask_bits = 64;
577 /* grows by doubling, start with a power of 2 */
578 diary->entries = apr_array_make(p, 16, sizeof(h2_push_diary_entry));
582 case H2_PUSH_DIGEST_SHA256:
583 diary->dtype = H2_PUSH_DIGEST_SHA256;
584 diary->dcalc = calc_sha256_hash;
586 #endif /* ifdef H2_OPENSSL */
588 diary->dtype = H2_PUSH_DIGEST_APR_HASH;
589 diary->dcalc = calc_apr_hash;
597 h2_push_diary *h2_push_diary_create(apr_pool_t *p, int N)
599 return diary_create(p, H2_PUSH_DIGEST_SHA256, N);
602 static int h2_push_diary_find(h2_push_diary *diary, apr_uint64_t hash)
605 h2_push_diary_entry *e;
608 /* search from the end, where the last accessed digests are */
609 for (i = diary->entries->nelts-1; i >= 0; --i) {
610 e = &APR_ARRAY_IDX(diary->entries, i, h2_push_diary_entry);
611 if (e->hash == hash) {
619 static h2_push_diary_entry *move_to_last(h2_push_diary *diary, apr_size_t idx)
621 h2_push_diary_entry *entries = (h2_push_diary_entry*)diary->entries->elts;
622 h2_push_diary_entry e;
623 apr_size_t lastidx = diary->entries->nelts-1;
625 /* move entry[idx] to the end */
628 memmove(entries+idx, entries+idx+1, sizeof(e) * (lastidx - idx));
629 entries[lastidx] = e;
631 return &entries[lastidx];
634 static void h2_push_diary_append(h2_push_diary *diary, h2_push_diary_entry *e)
636 h2_push_diary_entry *ne;
638 if (diary->entries->nelts < diary->N) {
639 /* append a new diary entry at the end */
640 APR_ARRAY_PUSH(diary->entries, h2_push_diary_entry) = *e;
641 ne = &APR_ARRAY_IDX(diary->entries, diary->entries->nelts-1, h2_push_diary_entry);
644 /* replace content with new digest. keeps memory usage constant once diary is full */
645 ne = move_to_last(diary, 0);
648 /* Intentional no APLOGNO */
649 ap_log_perror(APLOG_MARK, GCSLOG_LEVEL, 0, diary->entries->pool,
650 "push_diary_append: %"APR_UINT64_T_HEX_FMT, ne->hash);
653 apr_array_header_t *h2_push_diary_update(h2_session *session, apr_array_header_t *pushes)
655 apr_array_header_t *npushes = pushes;
656 h2_push_diary_entry e;
659 if (session->push_diary && pushes) {
662 for (i = 0; i < pushes->nelts; ++i) {
665 push = APR_ARRAY_IDX(pushes, i, h2_push*);
666 session->push_diary->dcalc(session->push_diary, &e.hash, push);
667 idx = h2_push_diary_find(session->push_diary, e.hash);
669 /* Intentional no APLOGNO */
670 ap_log_cerror(APLOG_MARK, GCSLOG_LEVEL, 0, session->c,
671 "push_diary_update: already there PUSH %s", push->req->path);
672 move_to_last(session->push_diary, idx);
675 /* Intentional no APLOGNO */
676 ap_log_cerror(APLOG_MARK, GCSLOG_LEVEL, 0, session->c,
677 "push_diary_update: adding PUSH %s", push->req->path);
679 npushes = apr_array_make(pushes->pool, 5, sizeof(h2_push_diary_entry*));
681 APR_ARRAY_PUSH(npushes, h2_push*) = push;
682 h2_push_diary_append(session->push_diary, &e);
689 apr_array_header_t *h2_push_collect_update(h2_stream *stream,
690 const struct h2_request *req,
691 const struct h2_headers *res)
693 h2_session *session = stream->session;
694 const char *cache_digest = apr_table_get(req->headers, "Cache-Digest");
695 apr_array_header_t *pushes;
698 if (cache_digest && session->push_diary) {
699 status = h2_push_diary_digest64_set(session->push_diary, req->authority,
700 cache_digest, stream->pool);
701 if (status != APR_SUCCESS) {
702 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,
703 H2_SSSN_LOG(APLOGNO(03057), session,
704 "push diary set from Cache-Digest: %s"), cache_digest);
707 pushes = h2_push_collect(stream->pool, req, stream->push_policy, res);
708 return h2_push_diary_update(stream->session, pushes);
711 static apr_int32_t h2_log2inv(unsigned char log2)
713 return log2? (1 << log2) : 1;
718 h2_push_diary *diary;
723 apr_uint64_t fixed_mask;
732 static int cmp_puint64(const void *p1, const void *p2)
734 const apr_uint64_t *pu1 = p1, *pu2 = p2;
735 return (*pu1 > *pu2)? 1 : ((*pu1 == *pu2)? 0 : -1);
738 /* in golomb bit stream encoding, bit 0 is the 8th of the first char, or
740 * char(bit/8) & cbit_mask[(bit % 8)]
742 static unsigned char cbit_mask[] = {
753 static apr_status_t gset_encode_bit(gset_encoder *encoder, int bit)
755 if (++encoder->bit >= 8) {
756 if (++encoder->offset >= encoder->datalen) {
757 apr_size_t nlen = encoder->datalen*2;
758 unsigned char *ndata = apr_pcalloc(encoder->pool, nlen);
762 memcpy(ndata, encoder->data, encoder->datalen);
763 encoder->data = ndata;
764 encoder->datalen = nlen;
767 encoder->data[encoder->offset] = 0xffu;
770 encoder->data[encoder->offset] &= ~cbit_mask[encoder->bit];
775 static apr_status_t gset_encode_next(gset_encoder *encoder, apr_uint64_t pval)
777 apr_uint64_t delta, flex_bits;
778 apr_status_t status = APR_SUCCESS;
781 delta = pval - encoder->last;
782 encoder->last = pval;
783 flex_bits = (delta >> encoder->fixed_bits);
784 /* Intentional no APLOGNO */
785 ap_log_perror(APLOG_MARK, GCSLOG_LEVEL, 0, encoder->pool,
786 "h2_push_diary_enc: val=%"APR_UINT64_T_HEX_FMT", delta=%"
787 APR_UINT64_T_HEX_FMT" flex_bits=%"APR_UINT64_T_FMT", "
788 ", fixed_bits=%d, fixed_val=%"APR_UINT64_T_HEX_FMT,
789 pval, delta, flex_bits, encoder->fixed_bits, delta&encoder->fixed_mask);
790 for (; flex_bits != 0; --flex_bits) {
791 status = gset_encode_bit(encoder, 1);
792 if (status != APR_SUCCESS) {
796 status = gset_encode_bit(encoder, 0);
797 if (status != APR_SUCCESS) {
801 for (i = encoder->fixed_bits-1; i >= 0; --i) {
802 status = gset_encode_bit(encoder, (delta >> i) & 1);
803 if (status != APR_SUCCESS) {
811 * Get a cache digest as described in
812 * https://datatracker.ietf.org/doc/draft-kazuho-h2-cache-digest/
813 * from the contents of the push diary.
815 * @param diary the diary to calculdate the digest from
816 * @param p the pool to use
817 * @param pdata on successful return, the binary cache digest
818 * @param plen on successful return, the length of the binary data
820 apr_status_t h2_push_diary_digest_get(h2_push_diary *diary, apr_pool_t *pool,
821 int maxP, const char *authority,
822 const char **pdata, apr_size_t *plen)
825 unsigned char log2n, log2pmax;
826 gset_encoder encoder;
827 apr_uint64_t *hashes;
828 apr_size_t hash_count;
830 nelts = diary->entries->nelts;
832 if (nelts > APR_UINT32_MAX) {
833 /* should not happen */
836 N = ceil_power_of_2(nelts);
839 /* Now log2p is the max number of relevant bits, so that
840 * log2p + log2n == mask_bits. We can uise a lower log2p
841 * and have a shorter set encoding...
843 log2pmax = h2_log2(ceil_power_of_2(maxP));
845 memset(&encoder, 0, sizeof(encoder));
846 encoder.diary = diary;
847 encoder.log2p = H2MIN(diary->mask_bits - log2n, log2pmax);
848 encoder.mask_bits = log2n + encoder.log2p;
849 encoder.delta_bits = diary->mask_bits - encoder.mask_bits;
850 encoder.fixed_bits = encoder.log2p;
851 encoder.fixed_mask = 1;
852 encoder.fixed_mask = (encoder.fixed_mask << encoder.fixed_bits) - 1;
854 encoder.datalen = 512;
855 encoder.data = apr_pcalloc(encoder.pool, encoder.datalen);
857 encoder.data[0] = log2n;
858 encoder.data[1] = encoder.log2p;
863 /* Intentional no APLOGNO */
864 ap_log_perror(APLOG_MARK, GCSLOG_LEVEL, 0, pool,
865 "h2_push_diary_digest_get: %d entries, N=%d, log2n=%d, "
866 "mask_bits=%d, enc.mask_bits=%d, delta_bits=%d, enc.log2p=%d, authority=%s",
867 (int)nelts, (int)N, (int)log2n, diary->mask_bits,
868 (int)encoder.mask_bits, (int)encoder.delta_bits,
869 (int)encoder.log2p, authority);
871 if (!authority || !diary->authority
872 || !strcmp("*", authority) || !strcmp(diary->authority, authority)) {
873 hash_count = diary->entries->nelts;
874 hashes = apr_pcalloc(encoder.pool, hash_count);
875 for (i = 0; i < hash_count; ++i) {
876 hashes[i] = ((&APR_ARRAY_IDX(diary->entries, i, h2_push_diary_entry))->hash
877 >> encoder.delta_bits);
880 qsort(hashes, hash_count, sizeof(apr_uint64_t), cmp_puint64);
881 for (i = 0; i < hash_count; ++i) {
882 if (!i || (hashes[i] != hashes[i-1])) {
883 gset_encode_next(&encoder, hashes[i]);
886 /* Intentional no APLOGNO */
887 ap_log_perror(APLOG_MARK, GCSLOG_LEVEL, 0, pool,
888 "h2_push_diary_digest_get: golomb compressed hashes, %d bytes",
889 (int)encoder.offset + 1);
891 *pdata = (const char *)encoder.data;
892 *plen = encoder.offset + 1;
898 h2_push_diary *diary;
901 const unsigned char *data;
905 apr_uint64_t last_val;
908 static int gset_decode_next_bit(gset_decoder *decoder)
910 if (++decoder->bit >= 8) {
911 if (++decoder->offset >= decoder->datalen) {
916 return (decoder->data[decoder->offset] & cbit_mask[decoder->bit])? 1 : 0;
919 static apr_status_t gset_decode_next(gset_decoder *decoder, apr_uint64_t *phash)
921 apr_uint64_t flex = 0, fixed = 0, delta;
924 /* read 1 bits until we encounter 0, then read log2n(diary-P) bits.
925 * On a malformed bit-string, this will not fail, but produce results
926 * which are pbly too large. Luckily, the diary will modulo the hash.
929 int bit = gset_decode_next_bit(decoder);
939 for (i = 0; i < decoder->log2p; ++i) {
940 int bit = gset_decode_next_bit(decoder);
944 fixed = (fixed << 1) | bit;
947 delta = (flex << decoder->log2p) | fixed;
948 *phash = delta + decoder->last_val;
949 decoder->last_val = *phash;
951 /* Intentional no APLOGNO */
952 ap_log_perror(APLOG_MARK, GCSLOG_LEVEL, 0, decoder->pool,
953 "h2_push_diary_digest_dec: val=%"APR_UINT64_T_HEX_FMT", delta=%"
954 APR_UINT64_T_HEX_FMT", flex=%d, fixed=%"APR_UINT64_T_HEX_FMT,
955 *phash, delta, (int)flex, fixed);
961 * Initialize the push diary by a cache digest as described in
962 * https://datatracker.ietf.org/doc/draft-kazuho-h2-cache-digest/
964 * @param diary the diary to set the digest into
965 * @param data the binary cache digest
966 * @param len the length of the cache digest
967 * @return APR_EINVAL if digest was not successfully parsed
969 apr_status_t h2_push_diary_digest_set(h2_push_diary *diary, const char *authority,
970 const char *data, apr_size_t len)
972 gset_decoder decoder;
973 unsigned char log2n, log2p;
975 apr_pool_t *pool = diary->entries->pool;
976 h2_push_diary_entry e;
977 apr_status_t status = APR_SUCCESS;
980 /* at least this should be there */
985 diary->mask_bits = log2n + log2p;
986 if (diary->mask_bits > 64) {
991 /* whatever is in the digest, it replaces the diary entries */
992 apr_array_clear(diary->entries);
993 if (!authority || !strcmp("*", authority)) {
994 diary->authority = NULL;
996 else if (!diary->authority || strcmp(diary->authority, authority)) {
997 diary->authority = apr_pstrdup(diary->entries->pool, authority);
1000 N = h2_log2inv(log2n + log2p);
1002 decoder.diary = diary;
1003 decoder.pool = pool;
1004 decoder.log2p = log2p;
1005 decoder.data = (const unsigned char*)data;
1006 decoder.datalen = len;
1009 decoder.last_val = 0;
1012 /* Determine effective N we use for storage */
1014 /* a totally empty cache digest. someone tells us that she has no
1015 * entries in the cache at all. Use our own preferences for N+mask
1017 diary->N = diary->NMax;
1020 else if (N > diary->NMax) {
1021 /* Store not more than diary is configured to hold. We open us up
1022 * to DOS attacks otherwise. */
1023 diary->N = diary->NMax;
1026 /* Intentional no APLOGNO */
1027 ap_log_perror(APLOG_MARK, GCSLOG_LEVEL, 0, pool,
1028 "h2_push_diary_digest_set: N=%d, log2n=%d, "
1029 "diary->mask_bits=%d, dec.log2p=%d",
1030 (int)diary->N, (int)log2n, diary->mask_bits,
1031 (int)decoder.log2p);
1033 for (i = 0; i < diary->N; ++i) {
1034 if (gset_decode_next(&decoder, &e.hash) != APR_SUCCESS) {
1035 /* the data may have less than N values */
1038 h2_push_diary_append(diary, &e);
1041 /* Intentional no APLOGNO */
1042 ap_log_perror(APLOG_MARK, GCSLOG_LEVEL, 0, pool,
1043 "h2_push_diary_digest_set: diary now with %d entries, mask_bits=%d",
1044 (int)diary->entries->nelts, diary->mask_bits);
1048 apr_status_t h2_push_diary_digest64_set(h2_push_diary *diary, const char *authority,
1049 const char *data64url, apr_pool_t *pool)
1052 apr_size_t len = h2_util_base64url_decode(&data, data64url, pool);
1053 /* Intentional no APLOGNO */
1054 ap_log_perror(APLOG_MARK, GCSLOG_LEVEL, 0, pool,
1055 "h2_push_diary_digest64_set: digest=%s, dlen=%d",
1056 data64url, (int)len);
1057 return h2_push_diary_digest_set(diary, authority, data, len);