view src/event/ngx_event_quic_transport.c @ 7690:ae35ccba7aa6 quic

Extracted transport part of the code into separate file. All code dealing with serializing/deserializing is moved int srv/event/ngx_event_quic_transport.c/h file. All macros for dealing with data are internal to source file. The header file exposes frame types and error codes. The exported functions are currently packet header parsers and writers and frames parser/writer. The ngx_quic_header_t structure is updated with 'log' member. This avoids passing extra argument to parsing functions that need to report errors.
author Vladimir Homutov <vl@nginx.com>
date Wed, 18 Mar 2020 12:58:27 +0300
parents
children 78540e2160d0
line wrap: on
line source


/*
 * Copyright (C) Nginx, Inc.
 */


#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_event.h>


#if (NGX_HAVE_NONALIGNED)

#define ngx_quic_parse_uint16(p)  ntohs(*(uint16_t *) (p))
#define ngx_quic_parse_uint32(p)  ntohl(*(uint32_t *) (p))

#define ngx_quic_write_uint16  ngx_quic_write_uint16_aligned
#define ngx_quic_write_uint32  ngx_quic_write_uint32_aligned

#else

#define ngx_quic_parse_uint16(p)  ((p)[0] << 8 | (p)[1])
#define ngx_quic_parse_uint32(p)                                              \
    ((uint32_t) (p)[0] << 24 | (p)[1] << 16 | (p)[2] << 8 | (p)[3])

#define ngx_quic_write_uint16(p, s)                                           \
    ((p)[0] = (u_char) ((s) >> 8),                                            \
     (p)[1] = (u_char)  (s),                                                  \
     (p) + sizeof(uint16_t))

#define ngx_quic_write_uint32(p, s)                                           \
    ((p)[0] = (u_char) ((s) >> 24),                                           \
     (p)[1] = (u_char) ((s) >> 16),                                           \
     (p)[2] = (u_char) ((s) >> 8),                                            \
     (p)[3] = (u_char)  (s),                                                  \
     (p) + sizeof(uint32_t))

#endif


#define ngx_quic_write_uint16_aligned(p, s)                                   \
    (*(uint16_t *) (p) = htons((uint16_t) (s)), (p) + sizeof(uint16_t))

#define ngx_quic_write_uint32_aligned(p, s)                                   \
    (*(uint32_t *) (p) = htonl((uint32_t) (s)), (p) + sizeof(uint32_t))

#define ngx_quic_varint_len(value)                                            \
     ((value) <= 63 ? 1                                                       \
     : ((uint32_t) value) <= 16383 ? 2                                        \
     : ((uint64_t) value) <= 1073741823 ?  4                                  \
     : 8)


static uint64_t ngx_quic_parse_int(u_char **pos);
static void ngx_quic_build_int(u_char **pos, uint64_t value);

static size_t ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack);
static size_t ngx_quic_create_crypto(u_char *p,
    ngx_quic_crypto_frame_t *crypto);
static size_t ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf);


/* literal errors indexed by corresponding value */
static char *ngx_quic_errors[] = {
    "NO_ERROR",
    "INTERNAL_ERROR",
    "SERVER_BUSY",
    "FLOW_CONTROL_ERROR",
    "STREAM_LIMIT_ERROR",
    "STREAM_STATE_ERROR",
    "FINAL_SIZE_ERROR",
    "FRAME_ENCODING_ERROR",
    "TRANSPORT_PARAMETER_ERROR",
    "CONNECTION_ID_LIMIT_ERROR",
    "PROTOCOL_VIOLATION",
    "INVALID_TOKEN",
    "",
    "CRYPTO_BUFFER_EXCEEDED",
    "CRYPTO_ERROR",
};


static uint64_t
ngx_quic_parse_int(u_char **pos)
{
    u_char      *p;
    uint64_t     value;
    ngx_uint_t   len;

    p = *pos;
    len = 1 << ((*p & 0xc0) >> 6);
    value = *p++ & 0x3f;

    while (--len) {
        value = (value << 8) + *p++;
    }

    *pos = p;
    return value;
}


static void
ngx_quic_build_int(u_char **pos, uint64_t value)
{
    u_char      *p;
    ngx_uint_t   len;//, len2;

    p = *pos;
    len = 0;

    while (value >> ((1 << len) * 8 - 2)) {
        len++;
    }

    *p = len << 6;

//    len2 =
    len = (1 << len);
    len--;
    *p |= value >> (len * 8);
    p++;

    while (len) {
        *p++ = value >> ((len-- - 1) * 8);
    }

    *pos = p;
//    return len2;
}


u_char *
ngx_quic_error_text(uint64_t error_code)
{
    return (u_char *) ngx_quic_errors[error_code];
}


ngx_int_t
ngx_quic_parse_long_header(ngx_quic_header_t *pkt)
{
    u_char  *p;

    p = pkt->data;

    ngx_quic_hexdump0(pkt->log, "long input", pkt->data, pkt->len);

    if (!(p[0] & NGX_QUIC_PKT_LONG)) {
        ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "not a long packet");
        return NGX_ERROR;
    }

    pkt->flags = *p++;

    pkt->version = ngx_quic_parse_uint32(p);
    p += sizeof(uint32_t);

    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
                   "quic flags:%xi version:%xD", pkt->flags, pkt->version);

    if (pkt->version != quic_version) {
        ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "unsupported quic version");
        return NGX_ERROR;
    }

    pkt->dcid.len = *p++;
    pkt->dcid.data = p;
    p += pkt->dcid.len;

    pkt->scid.len = *p++;
    pkt->scid.data = p;
    p += pkt->scid.len;

    pkt->raw->pos = p;

    return NGX_OK;
}


size_t
ngx_quic_create_long_header(ngx_quic_header_t *pkt, ngx_str_t *out,
    size_t pkt_len, u_char **pnp)
{
    u_char    *p, *start;

    p = start = out->data;

    *p++ = pkt->flags;

    p = ngx_quic_write_uint32(p, quic_version);

    *p++ = pkt->scid.len;
    p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len);

    *p++ = pkt->dcid.len;
    p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len);

    if (pkt->level == ssl_encryption_initial) {
        ngx_quic_build_int(&p, pkt->token.len);
    }

    ngx_quic_build_int(&p, pkt_len + 1); // length (inc. pnl)

    *pnp = p;

    *p++ = (uint64_t) (*pkt->number);

    return p - start;
}


ngx_int_t
ngx_quic_parse_short_header(ngx_quic_header_t *pkt, ngx_str_t *dcid)
{
    u_char  *p;

    p = pkt->data;

    ngx_quic_hexdump0(pkt->log, "short input", pkt->data, pkt->len);

    if ((p[0] & NGX_QUIC_PKT_LONG)) {
        ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "not a short packet");
        return NGX_ERROR;
    }

    pkt->flags = *p++;

    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
                   "quic flags:%xi", pkt->flags);

    if (ngx_memcmp(p, dcid->data, dcid->len) != 0) {
        ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "unexpected quic dcid");
        return NGX_ERROR;
    }

    pkt->dcid.len = dcid->len;
    pkt->dcid.data = p;
    p += pkt->dcid.len;

    pkt->raw->pos = p;

    return NGX_OK;
}


ngx_int_t
ngx_quic_parse_initial_header(ngx_quic_header_t *pkt)
{
    u_char     *p;
    ngx_int_t   plen;

    p = pkt->raw->pos;

    pkt->token.len = ngx_quic_parse_int(&p);
    pkt->token.data = p;

    p += pkt->token.len;

    plen = ngx_quic_parse_int(&p);

    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
                   "quic packet length: %d", plen);

    if (plen > pkt->data + pkt->len - p) {
        ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "truncated initial packet");
        return NGX_ERROR;
    }

    pkt->raw->pos = p;
    pkt->len = plen;

    ngx_quic_hexdump0(pkt->log, "DCID", pkt->dcid.data, pkt->dcid.len);
    ngx_quic_hexdump0(pkt->log, "SCID", pkt->scid.data, pkt->scid.len);
    ngx_quic_hexdump0(pkt->log, "token", pkt->token.data, pkt->token.len);

    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
                   "quic packet length: %d", plen);

    return NGX_OK;
}


ngx_int_t
ngx_quic_parse_handshake_header(ngx_quic_header_t *pkt)
{
    u_char     *p;
    ngx_int_t   plen;

    p = pkt->raw->pos;

    plen = ngx_quic_parse_int(&p);

    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
                   "quic packet length: %d", plen);

    if (plen > pkt->data + pkt->len - p) {
        ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "truncated handshake packet");
        return NGX_ERROR;
    }

    pkt->raw->pos = p;
    pkt->len = plen;

    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0,
                   "quic packet length: %d", plen);

    return NGX_OK;
}


#define ngx_quic_stream_bit_off(val)  (((val) & 0x04) ? 1 : 0)
#define ngx_quic_stream_bit_len(val)  (((val) & 0x02) ? 1 : 0)
#define ngx_quic_stream_bit_fin(val)  (((val) & 0x01) ? 1 : 0)

ssize_t
ngx_quic_parse_frame(u_char *start, u_char *end, ngx_quic_frame_t *frame)
{
    u_char *p;

    size_t npad;

    p = start;

    frame->type = *p++;  // TODO: check overflow (p < end)

    switch (frame->type) {

    case NGX_QUIC_FT_CRYPTO:
        frame->u.crypto.offset = *p++;
        frame->u.crypto.len = ngx_quic_parse_int(&p);
        frame->u.crypto.data = p;
        p += frame->u.crypto.len;

        break;

    case NGX_QUIC_FT_PADDING:
        npad = 0;
        while (p < end && *p == NGX_QUIC_FT_PADDING) { // XXX
            p++; npad++;
        }

        break;

    case NGX_QUIC_FT_ACK:
    case NGX_QUIC_FT_ACK_ECN:

        frame->u.ack.largest = ngx_quic_parse_int(&p);
        frame->u.ack.delay = ngx_quic_parse_int(&p);
        frame->u.ack.range_count =ngx_quic_parse_int(&p);
        frame->u.ack.first_range =ngx_quic_parse_int(&p);

        if (frame->u.ack.range_count) {
            frame->u.ack.ranges[0] = ngx_quic_parse_int(&p);
        }

        if (frame->type ==NGX_QUIC_FT_ACK_ECN) {
            return NGX_ERROR;
        }

        break;

    case NGX_QUIC_FT_PING:
        break;

    case NGX_QUIC_FT_NEW_CONNECTION_ID:

        frame->u.ncid.seqnum = ngx_quic_parse_int(&p);
        frame->u.ncid.retire = ngx_quic_parse_int(&p);
        frame->u.ncid.len = *p++;
        ngx_memcpy(frame->u.ncid.cid, p, frame->u.ncid.len);
        p += frame->u.ncid.len;

        ngx_memcpy(frame->u.ncid.srt, p, 16);
        p += 16;

        break;

    case NGX_QUIC_FT_CONNECTION_CLOSE:

        frame->u.close.error_code = ngx_quic_parse_int(&p);
        frame->u.close.frame_type = ngx_quic_parse_int(&p); // not in 0x1d CC
        frame->u.close.reason.len = ngx_quic_parse_int(&p);
        frame->u.close.reason.data = p;
        p += frame->u.close.reason.len;

        if (frame->u.close.error_code > NGX_QUIC_ERR_LAST) {
            frame->u.close.error_code = NGX_QUIC_ERR_LAST;
        }
        break;

    case NGX_QUIC_FT_STREAM0:
    case NGX_QUIC_FT_STREAM1:
    case NGX_QUIC_FT_STREAM2:
    case NGX_QUIC_FT_STREAM3:
    case NGX_QUIC_FT_STREAM4:
    case NGX_QUIC_FT_STREAM5:
    case NGX_QUIC_FT_STREAM6:
    case NGX_QUIC_FT_STREAM7:

        frame->u.stream.type = frame->type;

        frame->u.stream.off = ngx_quic_stream_bit_off(frame->type);
        frame->u.stream.len = ngx_quic_stream_bit_len(frame->type);
        frame->u.stream.fin = ngx_quic_stream_bit_fin(frame->type);

        frame->u.stream.stream_id = ngx_quic_parse_int(&p);
        if (frame->type & 0x04) {
            frame->u.stream.offset = ngx_quic_parse_int(&p);
        } else {
            frame->u.stream.offset = 0;
        }

        if (frame->type & 0x02) {
            frame->u.stream.length = ngx_quic_parse_int(&p);
        } else {
            frame->u.stream.length = end - p; /* up to packet end */
        }

        frame->u.stream.data = p;

        p += frame->u.stream.length;

        break;

    default:
        return NGX_ERROR;
    }

    return p - start;
}


ssize_t
ngx_quic_create_frame(u_char *p, u_char *end, ngx_quic_frame_t *f)
{
    // TODO: handle end arg

    switch (f->type) {
    case NGX_QUIC_FT_ACK:
        return ngx_quic_create_ack(p, &f->u.ack);

    case NGX_QUIC_FT_CRYPTO:
        return ngx_quic_create_crypto(p, &f->u.crypto);

    case NGX_QUIC_FT_STREAM0:
    case NGX_QUIC_FT_STREAM1:
    case NGX_QUIC_FT_STREAM2:
    case NGX_QUIC_FT_STREAM3:
    case NGX_QUIC_FT_STREAM4:
    case NGX_QUIC_FT_STREAM5:
    case NGX_QUIC_FT_STREAM6:
    case NGX_QUIC_FT_STREAM7:
        return ngx_quic_create_stream(p, &f->u.stream);

    default:
        /* BUG: unsupported frame type generated */
        return NGX_ERROR;
    }
}


size_t
ngx_quic_frame_len(ngx_quic_frame_t *frame)
{
     switch (frame->type) {
        case NGX_QUIC_FT_ACK:
            return ngx_quic_create_ack(NULL, &frame->u.ack);
        case NGX_QUIC_FT_CRYPTO:
            return ngx_quic_create_crypto(NULL, &frame->u.crypto);

        case NGX_QUIC_FT_STREAM0:
        case NGX_QUIC_FT_STREAM1:
        case NGX_QUIC_FT_STREAM2:
        case NGX_QUIC_FT_STREAM3:
        case NGX_QUIC_FT_STREAM4:
        case NGX_QUIC_FT_STREAM5:
        case NGX_QUIC_FT_STREAM6:
        case NGX_QUIC_FT_STREAM7:
            return ngx_quic_create_stream(NULL, &frame->u.stream);
        default:
            /* BUG: unsupported frame type generated */
            return 0;
     }
}


static size_t
ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack)
{
    size_t  len;

    /* minimal ACK packet */

    if (p == NULL) {
        len = ngx_quic_varint_len(NGX_QUIC_FT_ACK);
        len += ngx_quic_varint_len(ack->pn);
        len += ngx_quic_varint_len(0);
        len += ngx_quic_varint_len(0);
        len += ngx_quic_varint_len(ack->pn);

        return len;
    }

    ngx_quic_build_int(&p, NGX_QUIC_FT_ACK);
    ngx_quic_build_int(&p, ack->pn);
    ngx_quic_build_int(&p, 0);
    ngx_quic_build_int(&p, 0);
    ngx_quic_build_int(&p, ack->pn);

    return 5;
}


static size_t
ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto)
{
    size_t   len;
    u_char  *start;

    if (p == NULL) {
        len = ngx_quic_varint_len(NGX_QUIC_FT_CRYPTO);
        len += ngx_quic_varint_len(crypto->offset);
        len += ngx_quic_varint_len(crypto->len);
        len += crypto->len;

        return len;
    }

    start = p;

    ngx_quic_build_int(&p, NGX_QUIC_FT_CRYPTO);
    ngx_quic_build_int(&p, crypto->offset);
    ngx_quic_build_int(&p, crypto->len);
    p = ngx_cpymem(p, crypto->data, crypto->len);

    return p - start;
}


static size_t
ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf)
{
    size_t   len;
    u_char  *start;

    if (!sf->len) {
#if 0
        ngx_log_error(NGX_LOG_INFO, log, 0,
                      "attempt to generate a stream frame without length");
#endif
        // XXX: handle error in caller
        return NGX_ERROR;
    }

    if (p == NULL) {
        len = ngx_quic_varint_len(sf->type);

        if (sf->off) {
            len += ngx_quic_varint_len(sf->offset);
        }

        len += ngx_quic_varint_len(sf->stream_id);

        /* length is always present in generated frames */
        len += ngx_quic_varint_len(sf->length);

        len += sf->length;

        return len;
    }

    start = p;

    ngx_quic_build_int(&p, sf->type);
    ngx_quic_build_int(&p, sf->stream_id);

    if (sf->off) {
        ngx_quic_build_int(&p, sf->offset);
    }

    /* length is always present in generated frames */
    ngx_quic_build_int(&p, sf->length);

    p = ngx_cpymem(p, sf->data, sf->length);

    return p - start;
}