#include "sni.hpp"


typedef struct {
    sockaddr_t client;
    sockaddr_t server;
} sni_cache_key_t;
typedef struct {
    char host[MAX_SNI_LEN+1];
} sni_cache_val_t;
CONF_DEF(config) {
    ConfigKey sni_cache;
};
typedef HashCache<sni_cache_key_t, sni_cache_val_t> SniCache;
CONF_INIT(config) {
    CONF_KEY_INST(sni_cache, true, SniCache, size_t);
}
HOOK(CACHE_CLEAN, HOOK_PRIO_MID) {
    if (config) {
        SniCache* cache = config->sni_cache.ctx_as<SniCache>();
        if (cache) {
            cache->invalidate();
        }
    }
}


//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//


static INLINE bool buf_match(const char*& b, size_t& bl, const char* s, size_t sl) {
    if (bl >= sl && memcmp(b, s, sl) == 0) {
        b += sl;
        bl -= sl;
        return true;
    }
    return false;
}


static INLINE bool buf_size(const char*& b, size_t& bl, size_t& s, size_t sl) {
    assert(sl <= sizeof(size_t));
    s = 0;
    if (sl > bl) return false;
    while (sl--) {
        s = s << 8;
        ((unsigned char*)&s)[0] = ((const unsigned char*)b)[0];
        ++b;
        --bl;
    }
    return true;
}


TEST(buf_size) {
    const char *bar = "\x01\x02\x03\x04", *p;
    size_t l, rv;
    p = bar; l = strlen(p);
    if (!buf_size(p, l, rv, 1) || rv != 0x01) return false;
    p = bar; l = strlen(p);
    if (!buf_size(p, l, rv, 2) || rv != 0x0102) return false;
    p = bar; l = strlen(p);
    if (!buf_size(p, l, rv, 3) || rv != 0x010203) return false;
    p = bar; l = strlen(p);
    if (!buf_size(p, l, rv, 4) || rv != 0x01020304) return false;
    return true;
}


static INLINE bool buf_inc(const char*& b, size_t& bl, size_t sl) {
    if (sl > bl) return false;
    b += sl;
    bl -= sl;
    return true;
}


static INLINE bool buf_arr(const char*& b, size_t& bl, size_t sl) {
    if (sl > bl) return false;
    size_t al;
    unless (buf_size(b, bl, al, sl)) {
        return false;
    }
    unless (buf_inc(b, bl, al)) {
        b -= sl;
        bl += sl;
        return false;
    }
    return true;
}


//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//


static char* sni(const char* b, size_t l) {
    // Handshake + Version
    if (!buf_match(b, l, "\x16\x03\x00", 3) && !buf_match(b, l, "\x16\x03\x01", 3) && !buf_match(b, l, "\x16\x03\x02", 3) && !buf_match(b, l, "\x16\x03\x03", 3)) return NULL;

    // Len
    if (!buf_inc(b, l, 2)) return NULL;

    // Client Hello
    if (!buf_match(b, l, "\x01", 1)) return NULL;

    // Len
    if (!buf_inc(b, l, 3)) return NULL;

    // Version
    if (!buf_match(b, l, "\x03\x00", 2) && !buf_match(b, l, "\x03\x01", 2) && !buf_match(b, l, "\x03\x02", 2) && !buf_match(b, l, "\x03\x03", 2)) return NULL;

    // Rand
    if (!buf_inc(b, l, 32)) return NULL;

    // SID
    if (!buf_arr(b, l, 1)) return NULL;

    // Cipher Suites
    if (!buf_arr(b, l, 2)) return NULL;

    // Compression Methods
    if (!buf_arr(b, l, 1)) return NULL;

    // Extensions Length
    size_t tmp;
    if (!buf_size(b, l, tmp, 2)) return NULL;
    l = MIN(l, tmp);

    // Extension Types (SNI: 0)
    while (l) {
        if (!buf_size(b, l, tmp, 2)) return NULL;
        if (tmp != 0) {
            if (!buf_arr(b, l, 2)) return NULL;
            continue;
        }

        // list len (2) + item len (2)
        if (!buf_inc(b, l, 4)) return NULL;

        // item type (1)
        if (!buf_size(b, l, tmp, 1)) return NULL;
        if (tmp != 0) return NULL;

        // SNI len (2)
        if (!buf_size(b, l, tmp, 2)) return NULL;
        if (tmp > l || tmp < 3 || tmp > MAX_SNI_LEN) return NULL;

        // SNI validation
        if (!validate_host(b, tmp)) {
            return NULL;
        }

        // great success
        return strndup(b, tmp);
    }

    return NULL;
}


TEST(client_helo_sni) {
    char* host = sni("\x16\x03\x01\x02\x00\x01\x00\x01\xfc\x03\x03\x06\x90\x6a\x86\xfe\x34\xf5\x1c\x7c\xd5\x24\x67\x60\xfc\x1e\x20\xfd\x57\x21\xfa\x9e\x21\x36\x5f\x97\x95\xd7\x94\x21\x76\xcf\x14\x20\xc2\x00\xb0\xd2\xa1\x76\x80\x77\x63\x0b\x93\x20\x61\x78\x66\xd6\xe8\xb6\xaa\xcf\x56\xb2\x7d\x40\xbb\x5c\x77\xda\x6b\x50\xea\xc3\x00\x2c\xc0\x2b\xc0\x2c\xc0\x2f\xc0\x30\x00\x9e\x00\x9f\xc0\x09\xc0\x0a\xc0\x13\xc0\x14\x00\x33\x00\x39\x00\x32\x00\x38\xc0\x07\xc0\x11\x00\x9c\x00\x9d\x00\x2f\x00\x35\x00\x05\x00\xff\x01\x00\x01\x87\x00\x00\x00\x28\x00\x26\x00\x00\x23\x72\x33\x2d\x2d\x2d\x73\x6e\x2d\x32\x35\x61\x75\x78\x61\x2d\x31\x71\x68\x65\x2e\x67\x6f\x6f\x67\x6c\x65\x76\x69\x64\x65\x6f\x2e\x63\x6f\x6d\x00\x0b\x00\x04\x03\x00\x01\x02\x00\x0a\x00\x34\x00\x32\x00\x0e\x00\x0d\x00\x19\x00\x0b\x00\x0c\x00\x18\x00\x09\x00\x0a\x00\x16\x00\x17\x00\x08\x00\x06\x00\x07\x00\x14\x00\x15\x00\x04\x00\x05\x00\x12\x00\x13\x00\x01\x00\x02\x00\x03\x00\x0f\x00\x10\x00\x11\x00\x0d\x00\x20\x00\x1e\x06\x01\x06\x02\x06\x03\x05\x01\x05\x02\x05\x03\x04\x01\x04\x02\x04\x03\x03\x01\x03\x02\x03\x03\x02\x01\x02\x02\x02\x03\x00\x15\x00\xf3", 274);
    if (!host || strcmp(host, "r3---sn-25auxa-1qhe.googlevideo.com") != 0) return false;
    free(host);
    return true;
}


static bool sni(const char* buf, size_t len, const sockaddr_t& client, const sockaddr_t& server, char*& host) {
    assert(!host);
    bool handshake_success = false;
    if (buf && len) {
        #if (PARSE_CLIENT_HELO)
            host = sni(buf, len);
            handshake_success = (host != NULL);
        #else
            handshake_success = ssl_sni(buf, len, host);
        #endif
    }

    // TODO: session tickets?
    // TODO: caching sni by src/dst ok?
    if (!config || !config->sni_cache.ctx_as<void*>()) {
        return handshake_success;
    }
    assert(!client.addr4.sin_port);
    assert(server.addr4.sin_port);
    bool hit;
    sni_cache_val_t* node = config->sni_cache.ctx_as<SniCache>()->get((sni_cache_key_t){client, server}, hit);
    if (host) {
        assert(strlen(host) < sizeof(node->host));
        strcpy(node->host, host);
    } else if (hit) {
        if (*node->host) {
            log(debug, "SNI cache hit");
            host = strdup(node->host); // TODO: slab_pool?
        }
    } else {
        node->host[0] = '\0';
    }

    return handshake_success;
}


bool sni(int fd, const sockaddr_t& client, const sockaddr_t& server, buf_t*& buf, char*& host) {
    buf = buf_pool.pop();
    char* b;
    size_t l;
    buf->get_space(b, l);
    ssize_t len = read(fd, b, l); // TODO: don't have to do this if cached?
    log(io, "SNI read(%d): %zd", fd, len);
    if (len > 0) {
        buf->add_data((size_t)len);
    } else {
        buf_pool.push(buf);
        buf = NULL;
    }
    return sni(b, MAX(len,0), client, server, host);
}


//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//


char* cn_cert(const X509* x) {
    const char* rv = strstr(x->name, "/CN=");
    if (!rv) return NULL;
    rv += 4;
    while (*rv == '.' || *rv == '*') rv++;
    size_t l = strchrnul(rv, '/') - rv;
    if (validate_host(rv, l)) {
        return strndup(rv, l);
    } else {
        return NULL;
    }
}


static char* cn_cert(const char* b, size_t l) {
    char* rv = NULL;
    X509* x = d2i_X509(NULL, (const unsigned char**)&b, l);
    if (x) {
        rv = cn_cert(x);
        X509_free(x);
    }
    return rv;
}


static char* cn_ssl(const char* b, size_t l) { // NOPE if too short/not definitive yet
    if (!buf_inc(b, l, 2)) return NULL; // skip length
    if (!buf_match(b, l,
        "\x04"     // server helo
        "\x00"     // no session id
        "\x01"     // X509
        "\x00\x02" // handshake version
        ,1+1+1+2)) return NULL;
    size_t len;
    if (!buf_size(b, l, len, 2)) return NULL; // cert len
    if (!buf_inc(b, l, 4)) return NULL; // some extension lenghts
    if (l < len) return (char*)NOPE; // pretty sure it will come
    return cn_cert(b, len);
}


static char* cn_tls(const char* b, size_t l) { // NOPE if too short/not definitive yet
    while (true) {
        bool is_hs = false;
        if (buf_match(b, l, "\x16", 1)) {
            is_hs = true; // handshake
        } else if (buf_match(b, l, "\x14", 1)) {
            return NULL; // cipher spec -> encrypted handshake
        } else if (!buf_inc(b, l, 1)) {
            return NULL; //XXX: cert handshake msg might still come in a separate read
        }

        size_t version;
        if (!buf_size(b, l, version, 2)) return NULL; // we require this to be there for the basic version check
        if (version != 0x300 && version != 0x301 && version != 0x302 && version != 0x303) return NULL;

        size_t hs_len;
        if (!buf_size(b, l, hs_len, 2)) return NULL;

        if (!is_hs) {
            if (!buf_inc(b, l, hs_len)) return NULL;
        } else {
            while (true) {
                if (buf_match(b, l, "\x0b", 1)) { // found certificate, return NOPE upon errors, as we're pretty sure it will come
                    if (!buf_inc(b, l, 6)) return (char*)NOPE; // not interested in cert list len
                    size_t len;
                    if (!buf_size(b, l, len, 3)) return (char*)NOPE;
                    if (l < len) return (char*)NOPE;
                    return cn_cert(b, len);
                } else if (buf_match(b, l, "\x16", 1)) { // most probably new handshake from start
                    b--;
                    l++;
                    break;
                } else if (buf_inc(b, l, 1)) { // ignore type
                    if (!buf_arr(b, l, 3)) return NULL; // assume 3 bytes length for non-cert type
                } else {
                    return NULL;
                }
            }
        }
    }
}


TEST(server_helo_cn) {
    char* x = cn_tls(
        "\x16\x03\x01\x00\x35\x02\x00\x00\x31\x03\x01\xdc\x2b\xb8\x20\x73\x6d\xfa\x10\xb5\x84\xa6\x5c\x6f\xfa\x16\x2c\xbd\xee\xfe\x5b\xad\x16\xf4\x95\xe3\x13\x0f\x3b\x0a\x16\xa1\xd4\x00\xc0\x14\x00\x00\x09\x00\x23\x00\x00\xff\x01\x00\x01\x00"
        "\x16\x03\x01\x10\xd4\x0b\x00\x10\xd0\x00\x10\xcd\x00\x05\x40\x30\x82\x05\x3c\x30\x82\x04\x24\xa0\x03\x02\x01\x02\x02\x10\x52\x43\x4d\x90\x5e\x3f\x4f\x0b\x80\x37\x45\x77\xa9\x8f\x70\xd8\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x30\x81\x90\x31\x0b\x30\x09\x06\x03\x55\x04\x06\x13\x02\x47\x42\x31\x1b\x30\x19\x06\x03\x55\x04\x08\x13\x12\x47\x72\x65\x61\x74\x65\x72\x20\x4d\x61\x6e\x63\x68\x65\x73\x74\x65\x72\x31\x10\x30\x0e\x06\x03\x55\x04\x07\x13\x07\x53\x61\x6c\x66\x6f\x72\x64\x31\x1a\x30\x18\x06\x03\x55\x04\x0a\x13\x11\x43\x4f\x4d\x4f\x44\x4f\x20\x43\x41\x20\x4c\x69\x6d\x69\x74\x65\x64\x31\x36\x30\x34\x06\x03\x55\x04\x03\x13\x2d\x43\x4f\x4d\x4f\x44\x4f\x20\x52\x53\x41\x20\x44\x6f\x6d\x61\x69\x6e\x20\x56\x61\x6c\x69\x64\x61\x74\x69\x6f\x6e\x20\x53\x65\x63\x75\x72\x65\x20\x53\x65\x72\x76\x65\x72\x20\x43\x41\x30\x1e\x17\x0d\x31\x35\x31\x30\x30\x31\x30\x30\x30\x30\x30\x30\x5a\x17\x0d\x31\x37\x31\x32\x32\x39\x32\x33\x35\x39\x35\x39\x5a\x30\x4c\x31\x21\x30\x1f\x06\x03\x55\x04\x0b\x13\x18\x44\x6f\x6d\x61\x69\x6e\x20\x43\x6f\x6e\x74\x72\x6f\x6c\x20\x56\x61\x6c\x69\x64\x61\x74\x65\x64\x31\x14\x30\x12\x06\x03\x55\x04\x0b\x13\x0b\x50\x6f\x73\x69\x74\x69\x76\x65\x53\x53\x4c\x31\x11\x30\x0f\x06\x03\x55\x04\x03\x13\x08\x68\x65\x69\x73\x65\x2e\x64\x65\x30\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x00\x30\x82\x01\x0a\x02\x82\x01\x01\x00\xb8\xe3\x3a\xd9\xdb\x88\xc6\xde\x9d\x7e\xbf\xcc\x5a\x53\xfc\x45\x01\x9a\x29\x19\xaf\xf3\xc1\x4b\xad\x20\x1b\xc3\xb3\xa1\x26\x61\x51\xf6\x6c\xec\x54\x9d\xf8\x8e\xd0\xc4\x9f\x07\x33\x18\x68\x12\x9f\x8a\xbd\xc8\xf7\xf7\xce\xd9\x0e\x87\x8c\xec\xeb\x22\x06\x51\x29\xd6\x84\xd5\x2c\x7b\x52\xed\x9f\xd3\x9d\x5f\x25\x38\xdf\x23\x2c\x46\x13\x1c\xa8\x35\xf2\xb2\xf3\x4b\xd8\x1b\x5b\x89\x31\x3c\xb9\x2d\xe7\xb7\x34\x06\xeb\x5a\xe4\xd3\xe5\xd8\xe5\xa2\xc1\x9f\xe2\xef\x15\x2a\xa8\xd5\x54\xce\x68\xbe\xd6\xe1\x3c\x2d\x19\xc8\x8b\xa0\x4e\x65\x41\x68\x46\x1a\x24\x7d\xb6\x9c\x01\x22\x79\x14\x31\x10\xac\xa8\x88\x88\xea\x62\x7a\x9f\xc7\x7d\x74\x93\x9c\x17\x58\x6e\x96\x6f\xbb\x3d\x50\x8c\xeb\x15\x95\xa1\x14\x2f\xfc\x8e\xfb\xf8\x0b\x5a\xa2\x53\xe0\x08\x17\x80\x16\xfd\xa1\x3f\xc0\xc9\x36\xd1\xd2\x8b\x46\xc7\xf5\x98\xbd\xf9\x99\xb6\x17\xd9\xb3\xf2\xec\xa5\x49\xf1\x31\xd3\x02\x34\x00\x0d\xff\xd3\x67\xc1\xf0\x21\x05\x74\xd6\x54\x38\x49\xb7\x7a\x3a\x11\x47\x40\x59\x6b\xc1\x03\x9e\x67\x32\x29\xcb\xd1\xcc\xa3\x9a\xb8\x93\xaa\x6c\x2b\x17\xa1\x02\x03\x01\x00\x01\xa3\x82\x01\xd3\x30\x82\x01\xcf\x30\x1f\x06\x03\x55\x1d\x23\x04\x18\x30\x16\x80\x14\x90\xaf\x6a\x3a\x94\x5a\x0b\xd8\x90\xea\x12\x56\x73\xdf\x43\xb4\x3a\x28\xda\xe7\x30\x1d\x06\x03\x55\x1d\x0e\x04\x16\x04\x14\xda\xfb\x9f\xef\x75\x37\xed\x4b\xed\x75\x50\x87\x07\xd5\x6a\x38\xca\xd8\x28\x20\x30\x0e\x06\x03\x55\x1d\x0f\x01\x01\xff\x04\x04\x03\x02\x05\xa0\x30\x0c\x06\x03\x55\x1d\x13\x01\x01\xff\x04\x02\x30\x00\x30\x1d\x06\x03\x55\x1d\x25\x04\x16\x30\x14\x06\x08\x2b\x06\x01\x05\x05\x07\x03\x01\x06\x08\x2b\x06\x01\x05\x05\x07\x03\x02\x30\x4f\x06\x03\x55\x1d\x20\x04\x48\x30\x46\x30\x3a\x06\x0b\x2b\x06\x01\x04\x01\xb2\x31\x01\x02\x02\x07\x30\x2b\x30\x29\x06\x08\x2b\x06\x01\x05\x05\x07\x02\x01\x16\x1d\x68\x74\x74\x70\x73\x3a\x2f\x2f\x73\x65\x63\x75\x72\x65\x2e\x63\x6f\x6d\x6f\x64\x6f\x2e\x63\x6f\x6d\x2f\x43\x50\x53\x30\x08\x06\x06\x67\x81\x0c\x01\x02\x01\x30\x54\x06\x03\x55\x1d\x1f\x04\x4d\x30\x4b\x30\x49\xa0\x47\xa0\x45\x86\x43\x68\x74\x74\x70\x3a\x2f\x2f\x63\x72\x6c\x2e\x63\x6f\x6d\x6f\x64\x6f\x63\x61\x2e\x63\x6f\x6d\x2f\x43\x4f\x4d\x4f\x44\x4f\x52\x53\x41\x44\x6f\x6d\x61\x69\x6e\x56\x61\x6c\x69\x64\x61\x74\x69\x6f\x6e\x53\x65\x63\x75\x72\x65\x53\x65\x72\x76\x65\x72\x43\x41\x2e\x63\x72\x6c\x30\x81\x85\x06\x08\x2b\x06\x01\x05\x05\x07\x01\x01\x04\x79\x30\x77\x30\x4f\x06\x08\x2b\x06\x01\x05\x05\x07\x30\x02\x86\x43\x68\x74\x74\x70\x3a\x2f\x2f\x63\x72\x74\x2e\x63\x6f\x6d\x6f\x64\x6f\x63\x61\x2e\x63\x6f\x6d\x2f\x43\x4f\x4d\x4f\x44\x4f\x52\x53\x41\x44\x6f\x6d\x61\x69\x6e\x56\x61\x6c\x69\x64\x61\x74\x69\x6f\x6e\x53\x65\x63\x75\x72\x65\x53\x65\x72\x76\x65\x72\x43\x41\x2e\x63\x72\x74\x30\x24\x06\x08\x2b\x06\x01\x05\x05\x07\x30\x01\x86\x18\x68\x74\x74\x70\x3a\x2f\x2f\x6f\x63\x73\x70\x2e\x63\x6f\x6d\x6f\x64\x6f\x63\x61\x2e\x63\x6f\x6d\x30\x21\x06\x03\x55\x1d\x11\x04\x1a\x30\x18\x82\x08\x68\x65\x69\x73\x65\x2e\x64\x65\x82\x0c\x77\x77\x77\x2e\x68\x65\x69\x73\x65\x2e\x64\x65\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0b\x05\x00\x03\x82\x01\x01\x00\x7f\xf6\xe8\xaa\x1b\x75\x96\xee\xd6\xd9\xe9\xac\x0c\x46\xee\xea\x93\x20\x54\xfd\xa0\xb1\x0d\x60\xc4\x92\xfa\x81\x2d\x61\xd7\xb5\x8e\x10\x0e\x4a\xe3\xe7\x6f\x24\xab\x37\xab\xc8\x17\x3c\xb0\xac\x3c\x45\x50\xc8\x9b\xac\x78\x70\x8c\xec\x59\x0d\x55\x83\xa1\xef\x2f\x46\x15\x96\x96\x05\xae\x5f\x25\x15\x81\xfc\xb7\x1c\xde\xda\x0d\x17\x3d\xe0\x07\x02\xa3\x8d\x19\xa2\xce\x9a\x54\x43\x6a\xc5\x43\xf8\x7c\x67\x5b\x65\xa3\x8a\xfd\x50\xcb\x73\x4b\x37\x76\xec\xab\x99\x8b\x03\x8f\x3b\xb6\x13\xa4\xb6\x01\x6c\xde\xb8\x2a\xf2\x2d\xd2\x89\xf4\x7d\x4f\xda\x0b\x9c\x40\x49\xf3\xc6\xe8\x4e\x5b\xe2\xa8\x25\x09\xd0\x50\x3f\xa1\x23\x1b\x58\xa7\x86\x40\x69\xd2\x8f\x3a\x22\x62\xbd\xda\x0b\x23\xb9\x2c\xa0\xfa\x65\x73\x01\x9c\x33\xf5\x56\x8f\x16\xd0\x25\x1f\x96\xc8\xeb\xc5\x66\xc9\x5c\xdd\x0b\xe3\x29\xa5\xc6\x35\x89\xcb\xfb\x4f\xef\x87\x6e\xe0\x57\x13\x01\x13\x93\x09\xe4\x9c\xe1\xd5\x3b\x7f\x1d\x53\xa3\xc7\x27\xab\xb9\x44\xf5\x3a\x4c\x54\x4d\xbc\x21\x8b\xd1\xb3\x76\x7e\xa3\x11\x1d\x86\x1c\x66\x44\xf1\xff\x68\xb1\x0c\x1a\x4c\x2b\xc2\x03\xa2\x00\x06\x0c\x30\x82\x06\x08\x30\x82\x03\xf0\xa0\x03\x02\x01\x02\x02\x10\x2b\x2e\x6e\xea\xd9\x75\x36\x6c\x14\x8a\x6e\xdb\xa3\x7c\x8c\x07\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0c\x05\x00\x30\x81\x85\x31\x0b\x30\x09\x06\x03\x55\x04\x06\x13\x02\x47\x42\x31\x1b\x30\x19\x06\x03\x55\x04\x08\x13\x12\x47\x72\x65\x61\x74",
        58+1440
    );
    if (!x || x == NOPE) {
        return false;
    } else if (strcmp(x, "heise.de") != 0) {
        free(x);
        return false;
    } else {
        free(x);
        return true;
    }
}


tristate_t cn_cert(const char* b, size_t l, const sockaddr_t& client, const sockaddr_t& server, char*& rv) {
    assert(!rv);
    rv = cn_tls(b, l) ?: cn_ssl(b, l);
    if (rv == NOPE) {
        rv = NULL;
        return TRI_NONE;
    }

    if (!config || !config->sni_cache.ctx_as<void*>()) {
        return rv? TRI_TRUE: TRI_FALSE;
    }
    assert(!client.addr4.sin_port);
    assert(server.addr4.sin_port);
    bool hit;
    sni_cache_val_t* node = config->sni_cache.ctx_as<SniCache>()->get((sni_cache_key_t){client, server}, hit);
    if (rv) {
        assert(strlen(rv) < sizeof(node->host));
        strcpy(node->host, rv);
        return TRI_TRUE;
    } else if (hit) {
        if (*node->host) {
            log(debug, "CN cache hit");
            rv = strdup(node->host);
            return TRI_TRUE;
        } else {
            return TRI_FALSE;
        }
    } else {
        node->host[0] = '\0';
        return TRI_FALSE;
    }
}