Blame modules/http2/h2_push.c

Packit 90a5c9
/* Licensed to the Apache Software Foundation (ASF) under one or more
Packit 90a5c9
 * contributor license agreements.  See the NOTICE file distributed with
Packit 90a5c9
 * this work for additional information regarding copyright ownership.
Packit 90a5c9
 * The ASF licenses this file to You under the Apache License, Version 2.0
Packit 90a5c9
 * (the "License"); you may not use this file except in compliance with
Packit 90a5c9
 * the License.  You may obtain a copy of the License at
Packit 90a5c9
 *
Packit 90a5c9
 *     http://www.apache.org/licenses/LICENSE-2.0
Packit 90a5c9
 *
Packit 90a5c9
 * Unless required by applicable law or agreed to in writing, software
Packit 90a5c9
 * distributed under the License is distributed on an "AS IS" BASIS,
Packit 90a5c9
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Packit 90a5c9
 * See the License for the specific language governing permissions and
Packit 90a5c9
 * limitations under the License.
Packit 90a5c9
 */
Packit 90a5c9
 
Packit 90a5c9
#include <assert.h>
Packit 90a5c9
#include <stdio.h>
Packit 90a5c9
Packit 90a5c9
#include <apr_lib.h>
Packit 90a5c9
#include <apr_strings.h>
Packit 90a5c9
#include <apr_hash.h>
Packit 90a5c9
#include <apr_time.h>
Packit 90a5c9
Packit 90a5c9
#ifdef H2_OPENSSL
Packit 90a5c9
#include <openssl/sha.h>
Packit 90a5c9
#endif
Packit 90a5c9
Packit 90a5c9
#include <httpd.h>
Packit 90a5c9
#include <http_core.h>
Packit 90a5c9
#include <http_log.h>
Packit 90a5c9
Packit 90a5c9
#include "h2_private.h"
Packit 90a5c9
#include "h2_h2.h"
Packit 90a5c9
#include "h2_util.h"
Packit 90a5c9
#include "h2_push.h"
Packit 90a5c9
#include "h2_request.h"
Packit 90a5c9
#include "h2_headers.h"
Packit 90a5c9
#include "h2_session.h"
Packit 90a5c9
#include "h2_stream.h"
Packit 90a5c9
Packit 90a5c9
/*******************************************************************************
Packit 90a5c9
 * link header handling 
Packit 90a5c9
 ******************************************************************************/
Packit 90a5c9
Packit 90a5c9
static const char *policy_str(h2_push_policy policy)
Packit 90a5c9
{
Packit 90a5c9
    switch (policy) {
Packit 90a5c9
        case H2_PUSH_NONE:
Packit 90a5c9
            return "none";
Packit 90a5c9
        case H2_PUSH_FAST_LOAD:
Packit 90a5c9
            return "fast-load";
Packit 90a5c9
        case H2_PUSH_HEAD:
Packit 90a5c9
            return "head";
Packit 90a5c9
        default:
Packit 90a5c9
            return "default";
Packit 90a5c9
    }
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
typedef struct {
Packit 90a5c9
    const h2_request *req;
Packit 90a5c9
    int push_policy;
Packit 90a5c9
    apr_pool_t *pool;
Packit 90a5c9
    apr_array_header_t *pushes;
Packit 90a5c9
    const char *s;
Packit 90a5c9
    size_t slen;
Packit 90a5c9
    size_t i;
Packit 90a5c9
    
Packit 90a5c9
    const char *link;
Packit 90a5c9
    apr_table_t *params;
Packit 90a5c9
    char b[4096];
Packit 90a5c9
} link_ctx;
Packit 90a5c9
Packit 90a5c9
static int attr_char(char c) 
Packit 90a5c9
{
Packit 90a5c9
    switch (c) {
Packit 90a5c9
        case '!':
Packit 90a5c9
        case '#':
Packit 90a5c9
        case '$':
Packit 90a5c9
        case '&':
Packit 90a5c9
        case '+':
Packit 90a5c9
        case '-':
Packit 90a5c9
        case '.':
Packit 90a5c9
        case '^':
Packit 90a5c9
        case '_':
Packit 90a5c9
        case '`':
Packit 90a5c9
        case '|':
Packit 90a5c9
        case '~':
Packit 90a5c9
            return 1;
Packit 90a5c9
        default:
Packit 90a5c9
            return apr_isalnum(c);
Packit 90a5c9
    }
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static int ptoken_char(char c) 
Packit 90a5c9
{
Packit 90a5c9
    switch (c) {
Packit 90a5c9
        case '!':
Packit 90a5c9
        case '#':
Packit 90a5c9
        case '$':
Packit 90a5c9
        case '&':
Packit 90a5c9
        case '\'':
Packit 90a5c9
        case '(':
Packit 90a5c9
        case ')':
Packit 90a5c9
        case '*':
Packit 90a5c9
        case '+':
Packit 90a5c9
        case '-':
Packit 90a5c9
        case '.':
Packit 90a5c9
        case '/':
Packit 90a5c9
        case ':':
Packit 90a5c9
        case '<':
Packit 90a5c9
        case '=':
Packit 90a5c9
        case '>':
Packit 90a5c9
        case '?':
Packit 90a5c9
        case '@':
Packit 90a5c9
        case '[':
Packit 90a5c9
        case ']':
Packit 90a5c9
        case '^':
Packit 90a5c9
        case '_':
Packit 90a5c9
        case '`':
Packit 90a5c9
        case '{':
Packit 90a5c9
        case '|':
Packit 90a5c9
        case '}':
Packit 90a5c9
        case '~':
Packit 90a5c9
            return 1;
Packit 90a5c9
        default:
Packit 90a5c9
            return apr_isalnum(c);
Packit 90a5c9
    }
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static int skip_ws(link_ctx *ctx)
Packit 90a5c9
{
Packit 90a5c9
    char c;
Packit 90a5c9
    while (ctx->i < ctx->slen 
Packit 90a5c9
           && (((c = ctx->s[ctx->i]) == ' ') || (c == '\t'))) {
Packit 90a5c9
        ++ctx->i;
Packit 90a5c9
    }
Packit 90a5c9
    return (ctx->i < ctx->slen);
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static int find_chr(link_ctx *ctx, char c, size_t *pidx)
Packit 90a5c9
{
Packit 90a5c9
    size_t j;
Packit 90a5c9
    for (j = ctx->i; j < ctx->slen; ++j) {
Packit 90a5c9
        if (ctx->s[j] == c) {
Packit 90a5c9
            *pidx = j;
Packit 90a5c9
            return 1;
Packit 90a5c9
        }
Packit 90a5c9
    } 
Packit 90a5c9
    return 0;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static int read_chr(link_ctx *ctx, char c)
Packit 90a5c9
{
Packit 90a5c9
    if (ctx->i < ctx->slen && ctx->s[ctx->i] == c) {
Packit 90a5c9
        ++ctx->i;
Packit 90a5c9
        return 1;
Packit 90a5c9
    }
Packit 90a5c9
    return 0;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static char *mk_str(link_ctx *ctx, size_t end) 
Packit 90a5c9
{
Packit 90a5c9
    if (ctx->i < end) {
Packit 90a5c9
        return apr_pstrndup(ctx->pool, ctx->s + ctx->i, end - ctx->i);
Packit 90a5c9
    }
Packit 90a5c9
    return (char*)"";
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static int read_qstring(link_ctx *ctx, const char **ps)
Packit 90a5c9
{
Packit 90a5c9
    if (skip_ws(ctx) && read_chr(ctx, '\"')) {
Packit 90a5c9
        size_t end;
Packit 90a5c9
        if (find_chr(ctx, '\"', &end)) {
Packit 90a5c9
            *ps = mk_str(ctx, end);
Packit 90a5c9
            ctx->i = end + 1;
Packit 90a5c9
            return 1;
Packit 90a5c9
        }
Packit 90a5c9
    }
Packit 90a5c9
    return 0;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static int read_ptoken(link_ctx *ctx, const char **ps)
Packit 90a5c9
{
Packit 90a5c9
    if (skip_ws(ctx)) {
Packit 90a5c9
        size_t i;
Packit 90a5c9
        for (i = ctx->i; i < ctx->slen && ptoken_char(ctx->s[i]); ++i) {
Packit 90a5c9
            /* nop */
Packit 90a5c9
        }
Packit 90a5c9
        if (i > ctx->i) {
Packit 90a5c9
            *ps = mk_str(ctx, i);
Packit 90a5c9
            ctx->i = i;
Packit 90a5c9
            return 1;
Packit 90a5c9
        }
Packit 90a5c9
    }
Packit 90a5c9
    return 0;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
Packit 90a5c9
static int read_link(link_ctx *ctx)
Packit 90a5c9
{
Packit 90a5c9
    if (skip_ws(ctx) && read_chr(ctx, '<')) {
Packit 90a5c9
        size_t end;
Packit 90a5c9
        if (find_chr(ctx, '>', &end)) {
Packit 90a5c9
            ctx->link = mk_str(ctx, end);
Packit 90a5c9
            ctx->i = end + 1;
Packit 90a5c9
            return 1;
Packit 90a5c9
        }
Packit 90a5c9
    }
Packit 90a5c9
    return 0;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static int read_pname(link_ctx *ctx, const char **pname)
Packit 90a5c9
{
Packit 90a5c9
    if (skip_ws(ctx)) {
Packit 90a5c9
        size_t i;
Packit 90a5c9
        for (i = ctx->i; i < ctx->slen && attr_char(ctx->s[i]); ++i) {
Packit 90a5c9
            /* nop */
Packit 90a5c9
        }
Packit 90a5c9
        if (i > ctx->i) {
Packit 90a5c9
            *pname = mk_str(ctx, i);
Packit 90a5c9
            ctx->i = i;
Packit 90a5c9
            return 1;
Packit 90a5c9
        }
Packit 90a5c9
    }
Packit 90a5c9
    return 0;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static int read_pvalue(link_ctx *ctx, const char **pvalue)
Packit 90a5c9
{
Packit 90a5c9
    if (skip_ws(ctx) && read_chr(ctx, '=')) {
Packit 90a5c9
        if (read_qstring(ctx, pvalue) || read_ptoken(ctx, pvalue)) {
Packit 90a5c9
            return 1;
Packit 90a5c9
        }
Packit 90a5c9
    }
Packit 90a5c9
    return 0;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static int read_param(link_ctx *ctx)
Packit 90a5c9
{
Packit 90a5c9
    if (skip_ws(ctx) && read_chr(ctx, ';')) {
Packit 90a5c9
        const char *name, *value = "";
Packit 90a5c9
        if (read_pname(ctx, &name)) {
Packit 90a5c9
            read_pvalue(ctx, &value); /* value is optional */
Packit 90a5c9
            apr_table_setn(ctx->params, name, value);
Packit 90a5c9
            return 1;
Packit 90a5c9
        }
Packit 90a5c9
    }
Packit 90a5c9
    return 0;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static int read_sep(link_ctx *ctx)
Packit 90a5c9
{
Packit 90a5c9
    if (skip_ws(ctx) && read_chr(ctx, ',')) {
Packit 90a5c9
        return 1;
Packit 90a5c9
    }
Packit 90a5c9
    return 0;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static void init_params(link_ctx *ctx) 
Packit 90a5c9
{
Packit 90a5c9
    if (!ctx->params) {
Packit 90a5c9
        ctx->params = apr_table_make(ctx->pool, 5);
Packit 90a5c9
    }
Packit 90a5c9
    else {
Packit 90a5c9
        apr_table_clear(ctx->params);
Packit 90a5c9
    }
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static int same_authority(const h2_request *req, const apr_uri_t *uri)
Packit 90a5c9
{
Packit 90a5c9
    if (uri->scheme != NULL && strcmp(uri->scheme, req->scheme)) {
Packit 90a5c9
        return 0;
Packit 90a5c9
    }
Packit 90a5c9
    if (uri->hostinfo != NULL && strcmp(uri->hostinfo, req->authority)) {
Packit 90a5c9
        return 0;
Packit 90a5c9
    }
Packit 90a5c9
    return 1;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static int set_push_header(void *ctx, const char *key, const char *value) 
Packit 90a5c9
{
Packit 90a5c9
    size_t klen = strlen(key);
Packit 90a5c9
    if (H2_HD_MATCH_LIT("User-Agent", key, klen)
Packit 90a5c9
        || H2_HD_MATCH_LIT("Accept", key, klen)
Packit 90a5c9
        || H2_HD_MATCH_LIT("Accept-Encoding", key, klen)
Packit 90a5c9
        || H2_HD_MATCH_LIT("Accept-Language", key, klen)
Packit 90a5c9
        || H2_HD_MATCH_LIT("Cache-Control", key, klen)) {
Packit 90a5c9
        apr_table_setn(ctx, key, value);
Packit 90a5c9
    }
Packit 90a5c9
    return 1;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static int has_param(link_ctx *ctx, const char *param)
Packit 90a5c9
{
Packit 90a5c9
    const char *p = apr_table_get(ctx->params, param);
Packit 90a5c9
    return !!p;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static int has_relation(link_ctx *ctx, const char *rel)
Packit 90a5c9
{
Packit 90a5c9
    const char *s, *val = apr_table_get(ctx->params, "rel");
Packit 90a5c9
    if (val) {
Packit 90a5c9
        if (!strcmp(rel, val)) {
Packit 90a5c9
            return 1;
Packit 90a5c9
        }
Packit 90a5c9
        s = ap_strstr_c(val, rel);
Packit 90a5c9
        if (s && (s == val || s[-1] == ' ')) {
Packit 90a5c9
            s += strlen(rel);
Packit 90a5c9
            if (!*s || *s == ' ') {
Packit 90a5c9
                return 1;
Packit 90a5c9
            }
Packit 90a5c9
        }
Packit 90a5c9
    }
Packit 90a5c9
    return 0;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static int add_push(link_ctx *ctx)
Packit 90a5c9
{
Packit 90a5c9
    /* so, we have read a Link header and need to decide
Packit 90a5c9
     * if we transform it into a push.
Packit 90a5c9
     */
Packit 90a5c9
    if (has_relation(ctx, "preload") && !has_param(ctx, "nopush")) {
Packit 90a5c9
        apr_uri_t uri;
Packit 90a5c9
        if (apr_uri_parse(ctx->pool, ctx->link, &uri) == APR_SUCCESS) {
Packit 90a5c9
            if (uri.path && same_authority(ctx->req, &uri)) {
Packit 90a5c9
                char *path;
Packit 90a5c9
                const char *method;
Packit 90a5c9
                apr_table_t *headers;
Packit 90a5c9
                h2_request *req;
Packit 90a5c9
                h2_push *push;
Packit 90a5c9
                
Packit 90a5c9
                /* We only want to generate pushes for resources in the
Packit 90a5c9
                 * same authority than the original request.
Packit 90a5c9
                 * icing: i think that is wise, otherwise we really need to
Packit 90a5c9
                 * check that the vhost/server is available and uses the same
Packit 90a5c9
                 * TLS (if any) parameters.
Packit 90a5c9
                 */
Packit 90a5c9
                path = apr_uri_unparse(ctx->pool, &uri, APR_URI_UNP_OMITSITEPART);
Packit 90a5c9
                push = apr_pcalloc(ctx->pool, sizeof(*push));
Packit 90a5c9
                switch (ctx->push_policy) {
Packit 90a5c9
                    case H2_PUSH_HEAD:
Packit 90a5c9
                        method = "HEAD";
Packit 90a5c9
                        break;
Packit 90a5c9
                    default:
Packit 90a5c9
                        method = "GET";
Packit 90a5c9
                        break;
Packit 90a5c9
                }
Packit 90a5c9
                headers = apr_table_make(ctx->pool, 5);
Packit 90a5c9
                apr_table_do(set_push_header, headers, ctx->req->headers, NULL);
Packit 90a5c9
                req = h2_req_create(0, ctx->pool, method, ctx->req->scheme,
Packit 90a5c9
                                    ctx->req->authority, path, headers,
Packit 90a5c9
                                    ctx->req->serialize);
Packit 90a5c9
                /* atm, we do not push on pushes */
Packit 90a5c9
                h2_request_end_headers(req, ctx->pool, 1, 0);
Packit 90a5c9
                push->req = req;
Packit 90a5c9
                if (has_param(ctx, "critical")) {
Packit 90a5c9
                    h2_priority *prio = apr_pcalloc(ctx->pool, sizeof(*prio));
Packit 90a5c9
                    prio->dependency = H2_DEPENDANT_BEFORE;
Packit 90a5c9
                    push->priority = prio;
Packit 90a5c9
                }
Packit 90a5c9
                if (!ctx->pushes) {
Packit 90a5c9
                    ctx->pushes = apr_array_make(ctx->pool, 5, sizeof(h2_push*));
Packit 90a5c9
                }
Packit 90a5c9
                APR_ARRAY_PUSH(ctx->pushes, h2_push*) = push;
Packit 90a5c9
            }
Packit 90a5c9
        }
Packit 90a5c9
    }
Packit 90a5c9
    return 0;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static void inspect_link(link_ctx *ctx, const char *s, size_t slen)
Packit 90a5c9
{
Packit 90a5c9
    /* RFC 5988 <https://tools.ietf.org/html/rfc5988#section-6.2.1>
Packit 90a5c9
      Link           = "Link" ":" #link-value
Packit 90a5c9
      link-value     = "<" URI-Reference ">" *( ";" link-param )
Packit 90a5c9
      link-param     = ( ( "rel" "=" relation-types )
Packit 90a5c9
                     | ( "anchor" "=" <"> URI-Reference <"> )
Packit 90a5c9
                     | ( "rev" "=" relation-types )
Packit 90a5c9
                     | ( "hreflang" "=" Language-Tag )
Packit 90a5c9
                     | ( "media" "=" ( MediaDesc | ( <"> MediaDesc <"> ) ) )
Packit 90a5c9
                     | ( "title" "=" quoted-string )
Packit 90a5c9
                     | ( "title*" "=" ext-value )
Packit 90a5c9
                     | ( "type" "=" ( media-type | quoted-mt ) )
Packit 90a5c9
                     | ( link-extension ) )
Packit 90a5c9
      link-extension = ( parmname [ "=" ( ptoken | quoted-string ) ] )
Packit 90a5c9
                     | ( ext-name-star "=" ext-value )
Packit 90a5c9
      ext-name-star  = parmname "*" ; reserved for RFC2231-profiled
Packit 90a5c9
                                    ; extensions.  Whitespace NOT
Packit 90a5c9
                                    ; allowed in between.
Packit 90a5c9
      ptoken         = 1*ptokenchar
Packit 90a5c9
      ptokenchar     = "!" | "#" | "$" | "%" | "&" | "'" | "("
Packit 90a5c9
                     | ")" | "*" | "+" | "-" | "." | "/" | DIGIT
Packit 90a5c9
                     | ":" | "<" | "=" | ">" | "?" | "@" | ALPHA
Packit 90a5c9
                     | "[" | "]" | "^" | "_" | "`" | "{" | "|"
Packit 90a5c9
                     | "}" | "~"
Packit 90a5c9
      media-type     = type-name "/" subtype-name
Packit 90a5c9
      quoted-mt      = <"> media-type <">
Packit 90a5c9
      relation-types = relation-type
Packit 90a5c9
                     | <"> relation-type *( 1*SP relation-type ) <">
Packit 90a5c9
      relation-type  = reg-rel-type | ext-rel-type
Packit 90a5c9
      reg-rel-type   = LOALPHA *( LOALPHA | DIGIT | "." | "-" )
Packit 90a5c9
      ext-rel-type   = URI
Packit 90a5c9
      
Packit 90a5c9
      and from <https://tools.ietf.org/html/rfc5987>
Packit 90a5c9
      parmname      = 1*attr-char
Packit 90a5c9
      attr-char     = ALPHA / DIGIT
Packit 90a5c9
                       / "!" / "#" / "$" / "&" / "+" / "-" / "."
Packit 90a5c9
                       / "^" / "_" / "`" / "|" / "~"
Packit 90a5c9
     */
Packit 90a5c9
Packit 90a5c9
     ctx->s = s;
Packit 90a5c9
     ctx->slen = slen;
Packit 90a5c9
     ctx->i = 0;
Packit 90a5c9
     
Packit 90a5c9
     while (read_link(ctx)) {
Packit 90a5c9
        init_params(ctx);
Packit 90a5c9
        while (read_param(ctx)) {
Packit 90a5c9
            /* nop */
Packit 90a5c9
        }
Packit 90a5c9
        add_push(ctx);
Packit 90a5c9
        if (!read_sep(ctx)) {
Packit 90a5c9
            break;
Packit 90a5c9
        }
Packit 90a5c9
     }
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static int head_iter(void *ctx, const char *key, const char *value) 
Packit 90a5c9
{
Packit 90a5c9
    if (!apr_strnatcasecmp("link", key)) {
Packit 90a5c9
        inspect_link(ctx, value, strlen(value));
Packit 90a5c9
    }
Packit 90a5c9
    return 1;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
apr_array_header_t *h2_push_collect(apr_pool_t *p, const h2_request *req,
Packit 90a5c9
                                    int push_policy, const h2_headers *res)
Packit 90a5c9
{
Packit 90a5c9
    if (req && push_policy != H2_PUSH_NONE) {
Packit 90a5c9
        /* Collect push candidates from the request/response pair.
Packit 90a5c9
         * 
Packit 90a5c9
         * One source for pushes are "rel=preload" link headers
Packit 90a5c9
         * in the response.
Packit 90a5c9
         * 
Packit 90a5c9
         * TODO: This may be extended in the future by hooks or callbacks
Packit 90a5c9
         * where other modules can provide push information directly.
Packit 90a5c9
         */
Packit 90a5c9
        if (res->headers) {
Packit 90a5c9
            link_ctx ctx;
Packit 90a5c9
            
Packit 90a5c9
            memset(&ctx, 0, sizeof(ctx));
Packit 90a5c9
            ctx.req = req;
Packit 90a5c9
            ctx.push_policy = push_policy;
Packit 90a5c9
            ctx.pool = p;
Packit 90a5c9
            
Packit 90a5c9
            apr_table_do(head_iter, &ctx, res->headers, NULL);
Packit 90a5c9
            if (ctx.pushes) {
Packit 90a5c9
                apr_table_setn(res->headers, "push-policy", 
Packit 90a5c9
                               policy_str(push_policy));
Packit 90a5c9
            }
Packit 90a5c9
            return ctx.pushes;
Packit 90a5c9
        }
Packit 90a5c9
    }
Packit 90a5c9
    return NULL;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
/*******************************************************************************
Packit 90a5c9
 * push diary 
Packit 90a5c9
 *
Packit 90a5c9
 * - The push diary keeps track of resources already PUSHed via HTTP/2 on this
Packit 90a5c9
 *   connection. It records a hash value from the absolute URL of the resource
Packit 90a5c9
 *   pushed.
Packit 90a5c9
 * - Lacking openssl, it uses 'apr_hashfunc_default' for the value
Packit 90a5c9
 * - with openssl, it uses SHA256 to calculate the hash value
Packit 90a5c9
 * - whatever the method to generate the hash, the diary keeps a maximum of 64
Packit 90a5c9
 *   bits per hash, limiting the memory consumption to about 
Packit 90a5c9
 *      H2PushDiarySize * 8 
Packit 90a5c9
 *   bytes. Entries are sorted by most recently used and oldest entries are
Packit 90a5c9
 *   forgotten first.
Packit 90a5c9
 * - Clients can initialize/replace the push diary by sending a 'Cache-Digest'
Packit 90a5c9
 *   header. Currently, this is the base64url encoded value of the cache digest
Packit 90a5c9
 *   as specified in https://datatracker.ietf.org/doc/draft-kazuho-h2-cache-digest/
Packit 90a5c9
 *   This draft can be expected to evolve and the definition of the header
Packit 90a5c9
 *   will be added there and refined.
Packit 90a5c9
 * - The cache digest header is a Golomb Coded Set of hash values, but it may
Packit 90a5c9
 *   limit the amount of bits per hash value even further. For a good description
Packit 90a5c9
 *   of GCS, read here:
Packit 90a5c9
 *      http://giovanni.bajo.it/post/47119962313/golomb-coded-sets-smaller-than-bloom-filters
Packit 90a5c9
 * - The means that the push diary might be initialized with hash values of much
Packit 90a5c9
 *   less than 64 bits, leading to more false positives, but smaller digest size.
Packit 90a5c9
 ******************************************************************************/
Packit 90a5c9
 
Packit 90a5c9
 
Packit 90a5c9
#define GCSLOG_LEVEL   APLOG_TRACE1
Packit 90a5c9
Packit 90a5c9
typedef struct h2_push_diary_entry {
Packit 90a5c9
    apr_uint64_t hash;
Packit 90a5c9
} h2_push_diary_entry;
Packit 90a5c9
Packit 90a5c9
Packit 90a5c9
#ifdef H2_OPENSSL
Packit 90a5c9
static void sha256_update(SHA256_CTX *ctx, const char *s)
Packit 90a5c9
{
Packit 90a5c9
    SHA256_Update(ctx, s, strlen(s));
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static void calc_sha256_hash(h2_push_diary *diary, apr_uint64_t *phash, h2_push *push) 
Packit 90a5c9
{
Packit 90a5c9
    SHA256_CTX sha256;
Packit 90a5c9
    apr_uint64_t val;
Packit 90a5c9
    unsigned char hash[SHA256_DIGEST_LENGTH];
Packit 90a5c9
    int i;
Packit 90a5c9
    
Packit 90a5c9
    SHA256_Init(&sha256);
Packit 90a5c9
    sha256_update(&sha256, push->req->scheme);
Packit 90a5c9
    sha256_update(&sha256, "://");
Packit 90a5c9
    sha256_update(&sha256, push->req->authority);
Packit 90a5c9
    sha256_update(&sha256, push->req->path);
Packit 90a5c9
    SHA256_Final(hash, &sha256);
Packit 90a5c9
Packit 90a5c9
    val = 0;
Packit 90a5c9
    for (i = 0; i != sizeof(val); ++i)
Packit 90a5c9
        val = val * 256 + hash[i];
Packit 90a5c9
    *phash = val >> (64 - diary->mask_bits);
Packit 90a5c9
}
Packit 90a5c9
#endif
Packit 90a5c9
Packit 90a5c9
Packit 90a5c9
static unsigned int val_apr_hash(const char *str) 
Packit 90a5c9
{
Packit 90a5c9
    apr_ssize_t len = strlen(str);
Packit 90a5c9
    return apr_hashfunc_default(str, &len;;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static void calc_apr_hash(h2_push_diary *diary, apr_uint64_t *phash, h2_push *push) 
Packit 90a5c9
{
Packit 90a5c9
    apr_uint64_t val;
Packit 90a5c9
#if APR_UINT64_MAX > UINT_MAX
Packit 90a5c9
    val = ((apr_uint64_t)(val_apr_hash(push->req->scheme)) << 32);
Packit 90a5c9
    val ^= ((apr_uint64_t)(val_apr_hash(push->req->authority)) << 16);
Packit 90a5c9
    val ^= val_apr_hash(push->req->path);
Packit 90a5c9
#else
Packit 90a5c9
    val = val_apr_hash(push->req->scheme);
Packit 90a5c9
    val ^= val_apr_hash(push->req->authority);
Packit 90a5c9
    val ^= val_apr_hash(push->req->path);
Packit 90a5c9
#endif
Packit 90a5c9
    *phash = val;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static apr_int32_t ceil_power_of_2(apr_int32_t n)
Packit 90a5c9
{
Packit 90a5c9
    if (n <= 2) return 2;
Packit 90a5c9
    --n;
Packit 90a5c9
    n |= n >> 1;
Packit 90a5c9
    n |= n >> 2;
Packit 90a5c9
    n |= n >> 4;
Packit 90a5c9
    n |= n >> 8;
Packit 90a5c9
    n |= n >> 16;
Packit 90a5c9
    return ++n;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static h2_push_diary *diary_create(apr_pool_t *p, h2_push_digest_type dtype, 
Packit 90a5c9
                                   int N)
Packit 90a5c9
{
Packit 90a5c9
    h2_push_diary *diary = NULL;
Packit 90a5c9
    
Packit 90a5c9
    if (N > 0) {
Packit 90a5c9
        diary = apr_pcalloc(p, sizeof(*diary));
Packit 90a5c9
        
Packit 90a5c9
        diary->NMax        = ceil_power_of_2(N);
Packit 90a5c9
        diary->N           = diary->NMax;
Packit 90a5c9
        /* the mask we use in value comparison depends on where we got
Packit 90a5c9
         * the values from. If we calculate them ourselves, we can use
Packit 90a5c9
         * the full 64 bits.
Packit 90a5c9
         * If we set the diary via a compressed golomb set, we have less
Packit 90a5c9
         * relevant bits and need to use a smaller mask. */
Packit 90a5c9
        diary->mask_bits   = 64;
Packit 90a5c9
        /* grows by doubling, start with a power of 2 */
Packit 90a5c9
        diary->entries     = apr_array_make(p, 16, sizeof(h2_push_diary_entry));
Packit 90a5c9
        
Packit 90a5c9
        switch (dtype) {
Packit 90a5c9
#ifdef H2_OPENSSL
Packit 90a5c9
            case H2_PUSH_DIGEST_SHA256:
Packit 90a5c9
                diary->dtype       = H2_PUSH_DIGEST_SHA256;
Packit 90a5c9
                diary->dcalc       = calc_sha256_hash;
Packit 90a5c9
                break;
Packit 90a5c9
#endif /* ifdef H2_OPENSSL */
Packit 90a5c9
            default:
Packit 90a5c9
                diary->dtype       = H2_PUSH_DIGEST_APR_HASH;
Packit 90a5c9
                diary->dcalc       = calc_apr_hash;
Packit 90a5c9
                break;
Packit 90a5c9
        }
Packit 90a5c9
    }
Packit 90a5c9
    
Packit 90a5c9
    return diary;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
h2_push_diary *h2_push_diary_create(apr_pool_t *p, int N)
Packit 90a5c9
{
Packit 90a5c9
    return diary_create(p, H2_PUSH_DIGEST_SHA256, N);
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static int h2_push_diary_find(h2_push_diary *diary, apr_uint64_t hash)
Packit 90a5c9
{
Packit 90a5c9
    if (diary) {
Packit 90a5c9
        h2_push_diary_entry *e;
Packit 90a5c9
        int i;
Packit 90a5c9
Packit 90a5c9
        /* search from the end, where the last accessed digests are */
Packit 90a5c9
        for (i = diary->entries->nelts-1; i >= 0; --i) {
Packit 90a5c9
            e = &APR_ARRAY_IDX(diary->entries, i, h2_push_diary_entry);
Packit 90a5c9
            if (e->hash == hash) {
Packit 90a5c9
                return i;
Packit 90a5c9
            }
Packit 90a5c9
        }
Packit 90a5c9
    }
Packit 90a5c9
    return -1;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static h2_push_diary_entry *move_to_last(h2_push_diary *diary, apr_size_t idx)
Packit 90a5c9
{
Packit 90a5c9
    h2_push_diary_entry *entries = (h2_push_diary_entry*)diary->entries->elts;
Packit 90a5c9
    h2_push_diary_entry e;
Packit 90a5c9
    apr_size_t lastidx = diary->entries->nelts-1;
Packit 90a5c9
    
Packit 90a5c9
    /* move entry[idx] to the end */
Packit 90a5c9
    if (idx < lastidx) {
Packit 90a5c9
        e =  entries[idx];
Packit 90a5c9
        memmove(entries+idx, entries+idx+1, sizeof(e) * (lastidx - idx));
Packit 90a5c9
        entries[lastidx] = e;
Packit 90a5c9
    }
Packit 90a5c9
    return &entries[lastidx];
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static void h2_push_diary_append(h2_push_diary *diary, h2_push_diary_entry *e)
Packit 90a5c9
{
Packit 90a5c9
    h2_push_diary_entry *ne;
Packit 90a5c9
    
Packit 90a5c9
    if (diary->entries->nelts < diary->N) {
Packit 90a5c9
        /* append a new diary entry at the end */
Packit 90a5c9
        APR_ARRAY_PUSH(diary->entries, h2_push_diary_entry) = *e;
Packit 90a5c9
        ne = &APR_ARRAY_IDX(diary->entries, diary->entries->nelts-1, h2_push_diary_entry);
Packit 90a5c9
    }
Packit 90a5c9
    else {
Packit 90a5c9
        /* replace content with new digest. keeps memory usage constant once diary is full */
Packit 90a5c9
        ne = move_to_last(diary, 0);
Packit 90a5c9
        *ne = *e;
Packit 90a5c9
    }
Packit 90a5c9
    /* Intentional no APLOGNO */
Packit 90a5c9
    ap_log_perror(APLOG_MARK, GCSLOG_LEVEL, 0, diary->entries->pool,
Packit 90a5c9
                  "push_diary_append: %"APR_UINT64_T_HEX_FMT, ne->hash);
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
apr_array_header_t *h2_push_diary_update(h2_session *session, apr_array_header_t *pushes)
Packit 90a5c9
{
Packit 90a5c9
    apr_array_header_t *npushes = pushes;
Packit 90a5c9
    h2_push_diary_entry e;
Packit 90a5c9
    int i, idx;
Packit 90a5c9
    
Packit 90a5c9
    if (session->push_diary && pushes) {
Packit 90a5c9
        npushes = NULL;
Packit 90a5c9
        
Packit 90a5c9
        for (i = 0; i < pushes->nelts; ++i) {
Packit 90a5c9
            h2_push *push;
Packit 90a5c9
            
Packit 90a5c9
            push = APR_ARRAY_IDX(pushes, i, h2_push*);
Packit 90a5c9
            session->push_diary->dcalc(session->push_diary, &e.hash, push);
Packit 90a5c9
            idx = h2_push_diary_find(session->push_diary, e.hash);
Packit 90a5c9
            if (idx >= 0) {
Packit 90a5c9
                /* Intentional no APLOGNO */
Packit 90a5c9
                ap_log_cerror(APLOG_MARK, GCSLOG_LEVEL, 0, session->c,
Packit 90a5c9
                              "push_diary_update: already there PUSH %s", push->req->path);
Packit 90a5c9
                move_to_last(session->push_diary, idx);
Packit 90a5c9
            }
Packit 90a5c9
            else {
Packit 90a5c9
                /* Intentional no APLOGNO */
Packit 90a5c9
                ap_log_cerror(APLOG_MARK, GCSLOG_LEVEL, 0, session->c,
Packit 90a5c9
                              "push_diary_update: adding PUSH %s", push->req->path);
Packit 90a5c9
                if (!npushes) {
Packit 90a5c9
                    npushes = apr_array_make(pushes->pool, 5, sizeof(h2_push_diary_entry*));
Packit 90a5c9
                }
Packit 90a5c9
                APR_ARRAY_PUSH(npushes, h2_push*) = push;
Packit 90a5c9
                h2_push_diary_append(session->push_diary, &e);
Packit 90a5c9
            }
Packit 90a5c9
        }
Packit 90a5c9
    }
Packit 90a5c9
    return npushes;
Packit 90a5c9
}
Packit 90a5c9
    
Packit 90a5c9
apr_array_header_t *h2_push_collect_update(h2_stream *stream, 
Packit 90a5c9
                                           const struct h2_request *req, 
Packit 90a5c9
                                           const struct h2_headers *res)
Packit 90a5c9
{
Packit 90a5c9
    h2_session *session = stream->session;
Packit 90a5c9
    const char *cache_digest = apr_table_get(req->headers, "Cache-Digest");
Packit 90a5c9
    apr_array_header_t *pushes;
Packit 90a5c9
    apr_status_t status;
Packit 90a5c9
    
Packit 90a5c9
    if (cache_digest && session->push_diary) {
Packit 90a5c9
        status = h2_push_diary_digest64_set(session->push_diary, req->authority, 
Packit 90a5c9
                                            cache_digest, stream->pool);
Packit 90a5c9
        if (status != APR_SUCCESS) {
Packit 90a5c9
            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,
Packit 90a5c9
                          H2_SSSN_LOG(APLOGNO(03057), session,
Packit 90a5c9
                          "push diary set from Cache-Digest: %s"), cache_digest);
Packit 90a5c9
        }
Packit 90a5c9
    }
Packit 90a5c9
    pushes = h2_push_collect(stream->pool, req, stream->push_policy, res);
Packit 90a5c9
    return h2_push_diary_update(stream->session, pushes);
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static apr_int32_t h2_log2inv(unsigned char log2)
Packit 90a5c9
{
Packit 90a5c9
    return log2? (1 << log2) : 1;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
Packit 90a5c9
typedef struct {
Packit 90a5c9
    h2_push_diary *diary;
Packit 90a5c9
    unsigned char log2p;
Packit 90a5c9
    int mask_bits;
Packit 90a5c9
    int delta_bits;
Packit 90a5c9
    int fixed_bits;
Packit 90a5c9
    apr_uint64_t fixed_mask;
Packit 90a5c9
    apr_pool_t *pool;
Packit 90a5c9
    unsigned char *data;
Packit 90a5c9
    apr_size_t datalen;
Packit 90a5c9
    apr_size_t offset;
Packit 90a5c9
    unsigned int bit;
Packit 90a5c9
    apr_uint64_t last;
Packit 90a5c9
} gset_encoder;
Packit 90a5c9
Packit 90a5c9
static int cmp_puint64(const void *p1, const void *p2)
Packit 90a5c9
{
Packit 90a5c9
    const apr_uint64_t *pu1 = p1, *pu2 = p2;
Packit 90a5c9
    return (*pu1 > *pu2)? 1 : ((*pu1 == *pu2)? 0 : -1);
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
/* in golomb bit stream encoding, bit 0 is the 8th of the first char, or
Packit 90a5c9
 * more generally: 
Packit 90a5c9
 *      char(bit/8) & cbit_mask[(bit % 8)]
Packit 90a5c9
 */
Packit 90a5c9
static unsigned char cbit_mask[] = {
Packit 90a5c9
    0x80u,
Packit 90a5c9
    0x40u,
Packit 90a5c9
    0x20u,
Packit 90a5c9
    0x10u,
Packit 90a5c9
    0x08u,
Packit 90a5c9
    0x04u,
Packit 90a5c9
    0x02u,
Packit 90a5c9
    0x01u,
Packit 90a5c9
};
Packit 90a5c9
Packit 90a5c9
static apr_status_t gset_encode_bit(gset_encoder *encoder, int bit)
Packit 90a5c9
{
Packit 90a5c9
    if (++encoder->bit >= 8) {
Packit 90a5c9
        if (++encoder->offset >= encoder->datalen) {
Packit 90a5c9
            apr_size_t nlen = encoder->datalen*2;
Packit 90a5c9
            unsigned char *ndata = apr_pcalloc(encoder->pool, nlen);
Packit 90a5c9
            if (!ndata) {
Packit 90a5c9
                return APR_ENOMEM;
Packit 90a5c9
            }
Packit 90a5c9
            memcpy(ndata, encoder->data, encoder->datalen);
Packit 90a5c9
            encoder->data = ndata;
Packit 90a5c9
            encoder->datalen = nlen;
Packit 90a5c9
        }
Packit 90a5c9
        encoder->bit = 0;
Packit 90a5c9
        encoder->data[encoder->offset] = 0xffu;
Packit 90a5c9
    }
Packit 90a5c9
    if (!bit) {
Packit 90a5c9
        encoder->data[encoder->offset] &= ~cbit_mask[encoder->bit];
Packit 90a5c9
    }
Packit 90a5c9
    return APR_SUCCESS;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static apr_status_t gset_encode_next(gset_encoder *encoder, apr_uint64_t pval)
Packit 90a5c9
{
Packit 90a5c9
    apr_uint64_t delta, flex_bits;
Packit 90a5c9
    apr_status_t status = APR_SUCCESS;
Packit 90a5c9
    int i;
Packit 90a5c9
    
Packit 90a5c9
    delta = pval - encoder->last;
Packit 90a5c9
    encoder->last = pval;
Packit 90a5c9
    flex_bits = (delta >> encoder->fixed_bits);
Packit 90a5c9
    /* Intentional no APLOGNO */
Packit 90a5c9
    ap_log_perror(APLOG_MARK, GCSLOG_LEVEL, 0, encoder->pool,
Packit 90a5c9
                  "h2_push_diary_enc: val=%"APR_UINT64_T_HEX_FMT", delta=%"
Packit 90a5c9
                  APR_UINT64_T_HEX_FMT" flex_bits=%"APR_UINT64_T_FMT", "
Packit 90a5c9
                  ", fixed_bits=%d, fixed_val=%"APR_UINT64_T_HEX_FMT, 
Packit 90a5c9
                  pval, delta, flex_bits, encoder->fixed_bits, delta&encoder->fixed_mask);
Packit 90a5c9
    for (; flex_bits != 0; --flex_bits) {
Packit 90a5c9
        status = gset_encode_bit(encoder, 1);
Packit 90a5c9
        if (status != APR_SUCCESS) {
Packit 90a5c9
            return status;
Packit 90a5c9
        }
Packit 90a5c9
    }
Packit 90a5c9
    status = gset_encode_bit(encoder, 0);
Packit 90a5c9
    if (status != APR_SUCCESS) {
Packit 90a5c9
        return status;
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    for (i = encoder->fixed_bits-1; i >= 0; --i) {
Packit 90a5c9
        status = gset_encode_bit(encoder, (delta >> i) & 1);
Packit 90a5c9
        if (status != APR_SUCCESS) {
Packit 90a5c9
            return status;
Packit 90a5c9
        }
Packit 90a5c9
    }
Packit 90a5c9
    return APR_SUCCESS;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
/**
Packit 90a5c9
 * Get a cache digest as described in 
Packit 90a5c9
 * https://datatracker.ietf.org/doc/draft-kazuho-h2-cache-digest/
Packit 90a5c9
 * from the contents of the push diary.
Packit 90a5c9
 * 
Packit 90a5c9
 * @param diary the diary to calculdate the digest from
Packit 90a5c9
 * @param p the pool to use
Packit 90a5c9
 * @param pdata on successful return, the binary cache digest
Packit 90a5c9
 * @param plen on successful return, the length of the binary data
Packit 90a5c9
 */
Packit 90a5c9
apr_status_t h2_push_diary_digest_get(h2_push_diary *diary, apr_pool_t *pool, 
Packit 90a5c9
                                      int maxP, const char *authority, 
Packit 90a5c9
                                      const char **pdata, apr_size_t *plen)
Packit 90a5c9
{
Packit 90a5c9
    int nelts, N, i;
Packit 90a5c9
    unsigned char log2n, log2pmax;
Packit 90a5c9
    gset_encoder encoder;
Packit 90a5c9
    apr_uint64_t *hashes;
Packit 90a5c9
    apr_size_t hash_count;
Packit 90a5c9
    
Packit 90a5c9
    nelts = diary->entries->nelts;
Packit 90a5c9
    
Packit 90a5c9
    if (nelts > APR_UINT32_MAX) {
Packit 90a5c9
        /* should not happen */
Packit 90a5c9
        return APR_ENOTIMPL;
Packit 90a5c9
    }
Packit 90a5c9
    N = ceil_power_of_2(nelts);
Packit 90a5c9
    log2n = h2_log2(N);
Packit 90a5c9
    
Packit 90a5c9
    /* Now log2p is the max number of relevant bits, so that
Packit 90a5c9
     * log2p + log2n == mask_bits. We can uise a lower log2p
Packit 90a5c9
     * and have a shorter set encoding...
Packit 90a5c9
     */
Packit 90a5c9
    log2pmax = h2_log2(ceil_power_of_2(maxP));
Packit 90a5c9
    
Packit 90a5c9
    memset(&encoder, 0, sizeof(encoder));
Packit 90a5c9
    encoder.diary = diary;
Packit 90a5c9
    encoder.log2p = H2MIN(diary->mask_bits - log2n, log2pmax);
Packit 90a5c9
    encoder.mask_bits = log2n + encoder.log2p;
Packit 90a5c9
    encoder.delta_bits = diary->mask_bits - encoder.mask_bits;
Packit 90a5c9
    encoder.fixed_bits = encoder.log2p;
Packit 90a5c9
    encoder.fixed_mask = 1;
Packit 90a5c9
    encoder.fixed_mask = (encoder.fixed_mask << encoder.fixed_bits) - 1;
Packit 90a5c9
    encoder.pool = pool;
Packit 90a5c9
    encoder.datalen = 512;
Packit 90a5c9
    encoder.data = apr_pcalloc(encoder.pool, encoder.datalen);
Packit 90a5c9
    
Packit 90a5c9
    encoder.data[0] = log2n;
Packit 90a5c9
    encoder.data[1] = encoder.log2p;
Packit 90a5c9
    encoder.offset = 1;
Packit 90a5c9
    encoder.bit = 8;
Packit 90a5c9
    encoder.last = 0;
Packit 90a5c9
    
Packit 90a5c9
    /* Intentional no APLOGNO */
Packit 90a5c9
    ap_log_perror(APLOG_MARK, GCSLOG_LEVEL, 0, pool,
Packit 90a5c9
                  "h2_push_diary_digest_get: %d entries, N=%d, log2n=%d, "
Packit 90a5c9
                  "mask_bits=%d, enc.mask_bits=%d, delta_bits=%d, enc.log2p=%d, authority=%s", 
Packit 90a5c9
                  (int)nelts, (int)N, (int)log2n, diary->mask_bits, 
Packit 90a5c9
                  (int)encoder.mask_bits, (int)encoder.delta_bits, 
Packit 90a5c9
                  (int)encoder.log2p, authority);
Packit 90a5c9
                  
Packit 90a5c9
    if (!authority || !diary->authority 
Packit 90a5c9
        || !strcmp("*", authority) || !strcmp(diary->authority, authority)) {
Packit 90a5c9
        hash_count = diary->entries->nelts;
Packit 90a5c9
        hashes = apr_pcalloc(encoder.pool, hash_count);
Packit 90a5c9
        for (i = 0; i < hash_count; ++i) {
Packit 90a5c9
            hashes[i] = ((&APR_ARRAY_IDX(diary->entries, i, h2_push_diary_entry))->hash 
Packit 90a5c9
                         >> encoder.delta_bits);
Packit 90a5c9
        }
Packit 90a5c9
        
Packit 90a5c9
        qsort(hashes, hash_count, sizeof(apr_uint64_t), cmp_puint64);
Packit 90a5c9
        for (i = 0; i < hash_count; ++i) {
Packit 90a5c9
            if (!i || (hashes[i] != hashes[i-1])) {
Packit 90a5c9
                gset_encode_next(&encoder, hashes[i]);
Packit 90a5c9
            }
Packit 90a5c9
        }
Packit 90a5c9
        /* Intentional no APLOGNO */
Packit 90a5c9
        ap_log_perror(APLOG_MARK, GCSLOG_LEVEL, 0, pool,
Packit 90a5c9
                      "h2_push_diary_digest_get: golomb compressed hashes, %d bytes",
Packit 90a5c9
                      (int)encoder.offset + 1);
Packit 90a5c9
    }
Packit 90a5c9
    *pdata = (const char *)encoder.data;
Packit 90a5c9
    *plen = encoder.offset + 1;
Packit 90a5c9
    
Packit 90a5c9
    return APR_SUCCESS;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
typedef struct {
Packit 90a5c9
    h2_push_diary *diary;
Packit 90a5c9
    apr_pool_t *pool;
Packit 90a5c9
    unsigned char log2p;
Packit 90a5c9
    const unsigned char *data;
Packit 90a5c9
    apr_size_t datalen;
Packit 90a5c9
    apr_size_t offset;
Packit 90a5c9
    unsigned int bit;
Packit 90a5c9
    apr_uint64_t last_val;
Packit 90a5c9
} gset_decoder;
Packit 90a5c9
Packit 90a5c9
static int gset_decode_next_bit(gset_decoder *decoder)
Packit 90a5c9
{
Packit 90a5c9
    if (++decoder->bit >= 8) {
Packit 90a5c9
        if (++decoder->offset >= decoder->datalen) {
Packit 90a5c9
            return -1;
Packit 90a5c9
        }
Packit 90a5c9
        decoder->bit = 0;
Packit 90a5c9
    }
Packit 90a5c9
    return (decoder->data[decoder->offset] & cbit_mask[decoder->bit])? 1 : 0;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
static apr_status_t gset_decode_next(gset_decoder *decoder, apr_uint64_t *phash)
Packit 90a5c9
{
Packit 90a5c9
    apr_uint64_t flex = 0, fixed = 0, delta;
Packit 90a5c9
    int i;
Packit 90a5c9
    
Packit 90a5c9
    /* read 1 bits until we encounter 0, then read log2n(diary-P) bits.
Packit 90a5c9
     * On a malformed bit-string, this will not fail, but produce results
Packit 90a5c9
     * which are pbly too large. Luckily, the diary will modulo the hash.
Packit 90a5c9
     */
Packit 90a5c9
    while (1) {
Packit 90a5c9
        int bit = gset_decode_next_bit(decoder);
Packit 90a5c9
        if (bit == -1) {
Packit 90a5c9
            return APR_EINVAL;
Packit 90a5c9
        }
Packit 90a5c9
        if (!bit) {
Packit 90a5c9
            break;
Packit 90a5c9
        }
Packit 90a5c9
        ++flex;
Packit 90a5c9
    }
Packit 90a5c9
    
Packit 90a5c9
    for (i = 0; i < decoder->log2p; ++i) {
Packit 90a5c9
        int bit = gset_decode_next_bit(decoder);
Packit 90a5c9
        if (bit == -1) {
Packit 90a5c9
            return APR_EINVAL;
Packit 90a5c9
        }
Packit 90a5c9
        fixed = (fixed << 1) | bit;
Packit 90a5c9
    }
Packit 90a5c9
    
Packit 90a5c9
    delta = (flex << decoder->log2p) | fixed;
Packit 90a5c9
    *phash = delta + decoder->last_val;
Packit 90a5c9
    decoder->last_val = *phash;
Packit 90a5c9
    
Packit 90a5c9
    /* Intentional no APLOGNO */
Packit 90a5c9
    ap_log_perror(APLOG_MARK, GCSLOG_LEVEL, 0, decoder->pool,
Packit 90a5c9
                  "h2_push_diary_digest_dec: val=%"APR_UINT64_T_HEX_FMT", delta=%"
Packit 90a5c9
                  APR_UINT64_T_HEX_FMT", flex=%d, fixed=%"APR_UINT64_T_HEX_FMT, 
Packit 90a5c9
                  *phash, delta, (int)flex, fixed);
Packit 90a5c9
                  
Packit 90a5c9
    return APR_SUCCESS;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
/**
Packit 90a5c9
 * Initialize the push diary by a cache digest as described in 
Packit 90a5c9
 * https://datatracker.ietf.org/doc/draft-kazuho-h2-cache-digest/
Packit 90a5c9
 * .
Packit 90a5c9
 * @param diary the diary to set the digest into
Packit 90a5c9
 * @param data the binary cache digest
Packit 90a5c9
 * @param len the length of the cache digest
Packit 90a5c9
 * @return APR_EINVAL if digest was not successfully parsed
Packit 90a5c9
 */
Packit 90a5c9
apr_status_t h2_push_diary_digest_set(h2_push_diary *diary, const char *authority, 
Packit 90a5c9
                                      const char *data, apr_size_t len)
Packit 90a5c9
{
Packit 90a5c9
    gset_decoder decoder;
Packit 90a5c9
    unsigned char log2n, log2p;
Packit 90a5c9
    int N, i;
Packit 90a5c9
    apr_pool_t *pool = diary->entries->pool;
Packit 90a5c9
    h2_push_diary_entry e;
Packit 90a5c9
    apr_status_t status = APR_SUCCESS;
Packit 90a5c9
    
Packit 90a5c9
    if (len < 2) {
Packit 90a5c9
        /* at least this should be there */
Packit 90a5c9
        return APR_EINVAL;
Packit 90a5c9
    }
Packit 90a5c9
    log2n = data[0];
Packit 90a5c9
    log2p = data[1];
Packit 90a5c9
    diary->mask_bits = log2n + log2p;
Packit 90a5c9
    if (diary->mask_bits > 64) {
Packit 90a5c9
        /* cannot handle */
Packit 90a5c9
        return APR_ENOTIMPL;
Packit 90a5c9
    }
Packit 90a5c9
    
Packit 90a5c9
    /* whatever is in the digest, it replaces the diary entries */
Packit 90a5c9
    apr_array_clear(diary->entries);
Packit 90a5c9
    if (!authority || !strcmp("*", authority)) {
Packit 90a5c9
        diary->authority = NULL;
Packit 90a5c9
    }
Packit 90a5c9
    else if (!diary->authority || strcmp(diary->authority, authority)) {
Packit 90a5c9
        diary->authority = apr_pstrdup(diary->entries->pool, authority);
Packit 90a5c9
    }
Packit 90a5c9
Packit 90a5c9
    N = h2_log2inv(log2n + log2p);
Packit 90a5c9
Packit 90a5c9
    decoder.diary    = diary;
Packit 90a5c9
    decoder.pool     = pool;
Packit 90a5c9
    decoder.log2p    = log2p;
Packit 90a5c9
    decoder.data     = (const unsigned char*)data;
Packit 90a5c9
    decoder.datalen  = len;
Packit 90a5c9
    decoder.offset   = 1;
Packit 90a5c9
    decoder.bit      = 8;
Packit 90a5c9
    decoder.last_val = 0;
Packit 90a5c9
    
Packit 90a5c9
    diary->N = N;
Packit 90a5c9
    /* Determine effective N we use for storage */
Packit 90a5c9
    if (!N) {
Packit 90a5c9
        /* a totally empty cache digest. someone tells us that she has no
Packit 90a5c9
         * entries in the cache at all. Use our own preferences for N+mask 
Packit 90a5c9
         */
Packit 90a5c9
        diary->N = diary->NMax;
Packit 90a5c9
        return APR_SUCCESS;
Packit 90a5c9
    }
Packit 90a5c9
    else if (N > diary->NMax) {
Packit 90a5c9
        /* Store not more than diary is configured to hold. We open us up
Packit 90a5c9
         * to DOS attacks otherwise. */
Packit 90a5c9
        diary->N = diary->NMax;
Packit 90a5c9
    }
Packit 90a5c9
    
Packit 90a5c9
    /* Intentional no APLOGNO */
Packit 90a5c9
    ap_log_perror(APLOG_MARK, GCSLOG_LEVEL, 0, pool,
Packit 90a5c9
                  "h2_push_diary_digest_set: N=%d, log2n=%d, "
Packit 90a5c9
                  "diary->mask_bits=%d, dec.log2p=%d", 
Packit 90a5c9
                  (int)diary->N, (int)log2n, diary->mask_bits, 
Packit 90a5c9
                  (int)decoder.log2p);
Packit 90a5c9
                  
Packit 90a5c9
    for (i = 0; i < diary->N; ++i) {
Packit 90a5c9
        if (gset_decode_next(&decoder, &e.hash) != APR_SUCCESS) {
Packit 90a5c9
            /* the data may have less than N values */
Packit 90a5c9
            break;
Packit 90a5c9
        }
Packit 90a5c9
        h2_push_diary_append(diary, &e);
Packit 90a5c9
    }
Packit 90a5c9
    
Packit 90a5c9
    /* Intentional no APLOGNO */
Packit 90a5c9
    ap_log_perror(APLOG_MARK, GCSLOG_LEVEL, 0, pool,
Packit 90a5c9
                  "h2_push_diary_digest_set: diary now with %d entries, mask_bits=%d", 
Packit 90a5c9
                  (int)diary->entries->nelts, diary->mask_bits);
Packit 90a5c9
    return status;
Packit 90a5c9
}
Packit 90a5c9
Packit 90a5c9
apr_status_t h2_push_diary_digest64_set(h2_push_diary *diary, const char *authority, 
Packit 90a5c9
                                        const char *data64url, apr_pool_t *pool)
Packit 90a5c9
{
Packit 90a5c9
    const char *data;
Packit 90a5c9
    apr_size_t len = h2_util_base64url_decode(&data, data64url, pool);
Packit 90a5c9
    /* Intentional no APLOGNO */
Packit 90a5c9
    ap_log_perror(APLOG_MARK, GCSLOG_LEVEL, 0, pool,
Packit 90a5c9
                  "h2_push_diary_digest64_set: digest=%s, dlen=%d", 
Packit 90a5c9
                  data64url, (int)len);
Packit 90a5c9
    return h2_push_diary_digest_set(diary, authority, data, len);
Packit 90a5c9
}
Packit 90a5c9