From: Hugo Landau Date: Mon, 23 May 2022 09:42:03 +0000 (+0100) Subject: QUIC wire format support X-Git-Tag: openssl-3.2.0-alpha1~2609 X-Git-Url: https://git.openssl.org/gitweb/?p=openssl.git;a=commitdiff_plain;h=416d0a638c1635a182e57fe80c7c065dd76818c0 QUIC wire format support Reviewed-by: Tim Hudson Reviewed-by: Tomas Mraz (Merged from https://github.com/openssl/openssl/pull/18382) --- diff --git a/crypto/build.info b/crypto/build.info index f568234081..27ef6ba92e 100644 --- a/crypto/build.info +++ b/crypto/build.info @@ -97,7 +97,8 @@ $UTIL_COMMON=\ cryptlib.c params.c params_from_text.c bsearch.c ex_data.c o_str.c \ threads_pthread.c threads_win.c threads_none.c initthread.c \ context.c sparse_array.c asn1_dsa.c packet.c param_build.c \ - param_build_set.c der_writer.c threads_lib.c params_dup.c + param_build_set.c der_writer.c threads_lib.c params_dup.c \ + quic_vlint.c SOURCE[../libcrypto]=$UTIL_COMMON \ mem.c mem_sec.c \ diff --git a/crypto/packet.c b/crypto/packet.c index 09f6a9cea9..5123426d75 100644 --- a/crypto/packet.c +++ b/crypto/packet.c @@ -225,6 +225,18 @@ static int put_value(unsigned char *data, size_t value, size_t len) return 1; } +static int put_quic_value(unsigned char *data, size_t value, size_t len) +{ + if (data == NULL) + return 1; + + /* Value too large for field. */ + if (ossl_quic_vlint_encode_len(value) > len) + return 0; + + ossl_quic_vlint_encode_n(data, value, len); + return 1; +} /* * Internal helper function used by WPACKET_close(), WPACKET_finish() and @@ -261,10 +273,15 @@ static int wpacket_intern_close(WPACKET *pkt, WPACKET_SUB *sub, int doclose) if (sub->lenbytes > 0) { unsigned char *buf = GETBUF(pkt); - if (buf != NULL - && !put_value(&buf[sub->packet_len], packlen, - sub->lenbytes)) - return 0; + if (buf != NULL) { + if ((sub->flags & WPACKET_FLAGS_QUIC_VLINT) == 0) { + if (!put_value(&buf[sub->packet_len], packlen, sub->lenbytes)) + return 0; + } else { + if (!put_quic_value(&buf[sub->packet_len], packlen, sub->lenbytes)) + return 0; + } + } } else if (pkt->endfirst && sub->parent != NULL && (packlen != 0 || (sub->flags @@ -510,3 +527,54 @@ void WPACKET_cleanup(WPACKET *pkt) } pkt->subs = NULL; } + +int WPACKET_start_quic_sub_packet_bound(WPACKET *pkt, size_t max_len) +{ + size_t enclen = ossl_quic_vlint_encode_len(max_len); + + if (enclen == 0) + return 0; + + if (WPACKET_start_sub_packet_len__(pkt, enclen) == 0) + return 0; + + pkt->subs->flags |= WPACKET_FLAGS_QUIC_VLINT; + return 1; +} + +int WPACKET_start_quic_sub_packet(WPACKET *pkt) +{ + /* + * Assume no (sub)packet will exceed 4GiB, thus the 8-byte encoding need not + * be used. + */ + return WPACKET_start_quic_sub_packet_bound(pkt, OSSL_QUIC_VLINT_4B_MIN); +} + +int WPACKET_quic_sub_allocate_bytes(WPACKET *pkt, size_t len, unsigned char **allocbytes) +{ + if (!WPACKET_start_quic_sub_packet_bound(pkt, len) + || !WPACKET_allocate_bytes(pkt, len, allocbytes) + || !WPACKET_close(pkt)) + return 0; + + return 1; +} + +/* + * Write a QUIC variable-length integer to the packet. + */ +int WPACKET_quic_write_vlint(WPACKET *pkt, uint64_t v) +{ + unsigned char *b = NULL; + size_t enclen = ossl_quic_vlint_encode_len(v); + + if (enclen == 0) + return 0; + + if (WPACKET_allocate_bytes(pkt, enclen, &b) == 0) + return 0; + + ossl_quic_vlint_encode(b, v); + return 1; +} diff --git a/crypto/quic_vlint.c b/crypto/quic_vlint.c new file mode 100644 index 0000000000..92f14c6d77 --- /dev/null +++ b/crypto/quic_vlint.c @@ -0,0 +1,77 @@ +#include "internal/quic_vlint.h" +#include "internal/e_os.h" + +void ossl_quic_vlint_encode_n(uint8_t *buf, uint64_t v, int n) +{ + if (n == 1) { + buf[0] = (uint8_t)v; + } else if (n == 2) { + buf[0] = (uint8_t)(0x40 | ((v >> 8) & 0x3F)); + buf[1] = (uint8_t)v; + } else if (n == 4) { + buf[0] = (uint8_t)(0x80 | ((v >> 24) & 0x3F)); + buf[1] = (uint8_t)(v >> 16); + buf[2] = (uint8_t)(v >> 8); + buf[3] = (uint8_t)v; + } else { + buf[0] = (uint8_t)(0xC0 | ((v >> 56) & 0x3F)); + buf[1] = (uint8_t)(v >> 48); + buf[2] = (uint8_t)(v >> 40); + buf[3] = (uint8_t)(v >> 32); + buf[4] = (uint8_t)(v >> 24); + buf[5] = (uint8_t)(v >> 16); + buf[6] = (uint8_t)(v >> 8); + buf[7] = (uint8_t)v; + } +} + +void ossl_quic_vlint_encode(uint8_t *buf, uint64_t v) +{ + ossl_quic_vlint_encode_n(buf, v, ossl_quic_vlint_encode_len(v)); +} + +uint64_t ossl_quic_vlint_decode_unchecked(const unsigned char *buf) +{ + uint8_t first_byte = buf[0]; + size_t sz = ossl_quic_vlint_decode_len(first_byte); + + if (sz == 1) + return first_byte & 0x3F; + + if (sz == 2) + return ((uint64_t)(first_byte & 0x3F) << 8) + | buf[1]; + + if (sz == 4) + return ((uint64_t)(first_byte & 0x3F) << 24) + | ((uint64_t)buf[1] << 16) + | ((uint64_t)buf[2] << 8) + | buf[3]; + + return ((uint64_t)(first_byte & 0x3F) << 56) + | ((uint64_t)buf[1] << 48) + | ((uint64_t)buf[2] << 40) + | ((uint64_t)buf[3] << 32) + | ((uint64_t)buf[4] << 24) + | ((uint64_t)buf[5] << 16) + | ((uint64_t)buf[6] << 8) + | buf[7]; +} + +int ossl_quic_vlint_decode(const unsigned char *buf, size_t buf_len, uint64_t *v) +{ + size_t dec_len; + uint64_t x; + + if (buf_len < 1) + return 0; + + dec_len = ossl_quic_vlint_decode_len(buf[0]); + if (buf_len < dec_len) + return 0; + + x = ossl_quic_vlint_decode_unchecked(buf); + + *v = x; + return dec_len; +} diff --git a/include/internal/packet.h b/include/internal/packet.h index 170997db60..ab211f0dc2 100644 --- a/include/internal/packet.h +++ b/include/internal/packet.h @@ -18,6 +18,7 @@ # include # include "internal/numbers.h" +# include "internal/quic_vlint.h" typedef struct { /* Pointer to where we are currently reading from */ @@ -228,6 +229,28 @@ __owur static ossl_inline int PACKET_peek_net_4(const PACKET *pkt, return 1; } +/* + * Decodes a QUIC variable-length integer in |pkt| and stores the result in + * |data|. + */ +__owur static ossl_inline int PACKET_get_quic_vlint(PACKET *pkt, + uint64_t *data) +{ + size_t enclen; + + if (PACKET_remaining(pkt) < 1) + return 0; + + enclen = ossl_quic_vlint_decode_len(*pkt->curr); + + if (PACKET_remaining(pkt) < enclen) + return 0; + + *data = ossl_quic_vlint_decode_unchecked(pkt->curr); + packet_forward(pkt, enclen); + return 1; +} + /* Equivalent of n2l */ /* Get 4 bytes in network order from |pkt| and store the value in |*data| */ __owur static ossl_inline int PACKET_get_net_4(PACKET *pkt, unsigned long *data) @@ -594,6 +617,33 @@ __owur static ossl_inline int PACKET_get_length_prefixed_3(PACKET *pkt, return 1; } +/* + * Reads a variable-length vector prefixed with a QUIC variable-length integer + * denoting the length, and stores the contents in |subpkt|. |pkt| can equal + * |subpkt|. Data is not copied: the |subpkt| packet will share its underlying + * buffer with the original |pkt|, so data wrapped by |pkt| must outlive the + * |subpkt|. Upon failure, the original |pkt| and |subpkt| are not modified. + */ +__owur static ossl_inline int PACKET_get_quic_length_prefixed(PACKET *pkt, + PACKET *subpkt) +{ + uint64_t length; + const unsigned char *data; + PACKET tmp = *pkt; + + if (!PACKET_get_quic_vlint(&tmp, &length) || + length > SIZE_MAX || + !PACKET_get_bytes(&tmp, &data, (size_t)length)) { + return 0; + } + + *pkt = tmp; + subpkt->curr = data; + subpkt->remaining = (size_t)length; + + return 1; +} + /* Writeable packets */ typedef struct wpacket_sub WPACKET_SUB; @@ -658,6 +708,8 @@ struct wpacket_st { */ #define WPACKET_FLAGS_ABANDON_ON_ZERO_LENGTH 2 +/* QUIC variable-length integer length prefix */ +#define WPACKET_FLAGS_QUIC_VLINT 4 /* * Initialise a WPACKET with the buffer in |buf|. The buffer must exist @@ -899,4 +951,33 @@ int WPACKET_is_null_buf(WPACKET *pkt); /* Release resources in a WPACKET if a failure has occurred. */ void WPACKET_cleanup(WPACKET *pkt); +/* + * Starts a QUIC sub-packet headed by a QUIC variable-length integer. A 4-byte + * representation is used. + */ +__owur int WPACKET_start_quic_sub_packet(WPACKET *pkt); + +/* + * Starts a QUIC sub-packet headed by a QUIC variable-length integer. max_len + * specifies the upper bound for the sub-packet size at the time the sub-packet + * is closed, which determines the encoding size for tthe variable-length + * integer header. max_len can be a precise figure or a worst-case bound + * if a precise figure is not available. + */ +__owur int WPACKET_start_quic_sub_packet_bound(WPACKET *pkt, size_t max_len); + +/* + * Allocates a QUIC sub-packet with exactly len bytes of payload, headed by a + * QUIC variable-length integer. The pointer to the payload buffer is output and + * must be filled by the caller. This function assures optimal selection of + * variable-length integer encoding length. + */ +__owur int WPACKET_quic_sub_allocate_bytes(WPACKET *pkt, size_t len, + unsigned char **bytes); + +/* + * Write a QUIC variable-length integer to the packet. + */ +__owur int WPACKET_quic_write_vlint(WPACKET *pkt, uint64_t v); + #endif /* OSSL_INTERNAL_PACKET_H */ diff --git a/include/internal/quic_vlint.h b/include/internal/quic_vlint.h new file mode 100644 index 0000000000..b21dd3fa4b --- /dev/null +++ b/include/internal/quic_vlint.h @@ -0,0 +1,123 @@ +/* +* Copyright 2022 The OpenSSL Project Authors. All Rights Reserved. +* +* Licensed under the Apache License 2.0 (the "License"). You may not use +* this file except in compliance with the License. You can obtain a copy +* in the file LICENSE in the source distribution or at +* https://www.openssl.org/source/license.html +*/ + +#ifndef OSSL_INTERNAL_QUIC_VLINT_H +# define OSSL_INTERNAL_QUIC_VLINT_H +# pragma once + +#include "internal/e_os.h" + +/* The smallest value requiring a 1, 2, 4, or 8-byte representation. */ +#define OSSL_QUIC_VLINT_1B_MIN 0 +#define OSSL_QUIC_VLINT_2B_MIN 64 +#define OSSL_QUIC_VLINT_4B_MIN 16384 +#define OSSL_QUIC_VLINT_8B_MIN 1073741824 + +/* The largest value representable in a given number of bytes. */ +#define OSSL_QUIC_VLINT_1B_MAX (OSSL_QUIC_VLINT_2B_MIN - 1) +#define OSSL_QUIC_VLINT_2B_MAX (OSSL_QUIC_VLINT_4B_MIN - 1) +#define OSSL_QUIC_VLINT_4B_MAX (OSSL_QUIC_VLINT_8B_MIN - 1) +#define OSSL_QUIC_VLINT_8B_MAX (((uint64_t)1 << 62) - 1) + +/* The largest value representable as a variable-length integer. */ +#define OSSL_QUIC_VLINT_MAX OSSL_QUIC_VLINT_8B_MAX + +/* + * Returns the number of bytes needed to encode v in the QUIC variable-length + * integer encoding. + * + * Returns 0 if v exceeds OSSL_QUIC_VLINT_MAX. + */ +static ossl_unused ossl_inline size_t ossl_quic_vlint_encode_len(uint64_t v) +{ + if (v < OSSL_QUIC_VLINT_2B_MIN) + return 1; + + if (v < OSSL_QUIC_VLINT_4B_MIN) + return 2; + + if (v < OSSL_QUIC_VLINT_8B_MIN) + return 4; + + if (v <= OSSL_QUIC_VLINT_MAX) + return 8; + + return 0; +} + +/* + * This function writes a QUIC varable-length encoded integer to buf. + * The smallest usable representation is used. + * + * It is the caller's responsibility to ensure that the buffer is big enough by + * calling ossl_quic_vlint_encode_len(v) before calling this function. + * + * Precondition: buf is at least ossl_quic_vlint_enc_len(v) bytes in size + * (unchecked) + * Precondition: v does not exceed OSSL_QUIC_VLINT_MAX + * (unchecked) + */ +void ossl_quic_vlint_encode(unsigned char *buf, uint64_t v); + +/* + * This function writes a QUIC variable-length encoded integer to buf. The + * specified number of bytes n are used for the encoding, which means that the + * encoded value may take up more space than necessary. + * + * It is the caller's responsibility to ensure that the buffer is of at least n + * bytes, and that v is representable by a n-byte QUIC variable-length integer. + * The representable ranges are: + * + * 1-byte encoding: [0, 2** 6-1] + * 2-byte encoding: [0, 2**14-1] + * 4-byte encoding: [0, 2**30-1] + * 8-byte encoding: [0, 2**62-1] + * + * Precondition: buf is at least n bytes in size (unchecked) + * Precondition: v does not exceed the representable range + * (ossl_quic_vlint_encode_len(v) <= n) (unchecked) + * Precondition: v does not exceed OSSL_QUIC_VLINT_MAX + * (unchecked) + */ +void ossl_quic_vlint_encode_n(unsigned char *buf, uint64_t v, int n); + +/* + * Given the first byte of an encoded QUIC variable-length integer, returns + * the number of bytes comprising the encoded integer, including the first + * byte. + */ +static ossl_unused ossl_inline size_t ossl_quic_vlint_decode_len(uint8_t first_byte) +{ + return 1U << ((first_byte & 0xC0) >> 6); +} + +/* + * Given a buffer containing an encoded QUIC variable-length integer, returns + * the decoded value. The buffer must be of at least + * ossl_quic_vlint_decode_len(buf[0]) bytes in size, and the caller is responsible + * for checking this. + * + * Precondition: buf is at least ossl_quic_vlint_decode_len(buf[0]) bytes in size + * (unchecked) + */ +uint64_t ossl_quic_vlint_decode_unchecked(const unsigned char *buf); + +/* + * Given a buffer buf of buf_len bytes in length, attempts to decode an encoded + * QUIC variable-length integer at the start of the buffer and writes the result + * to *v. If buf_len is inadequate, suggesting a truncated encoded integer, the + * function fails and 0 is returned. Otherwise, returns the number of bytes + * consumed. + * + * Precondition: buf is at least buf_len bytes in size + * Precondition: v (unchecked) + */ +int ossl_quic_vlint_decode(const unsigned char *buf, size_t buf_len, uint64_t *v); + +#endif diff --git a/ssl/build.info b/ssl/build.info index 0851357f81..fd13ede5e8 100644 --- a/ssl/build.info +++ b/ssl/build.info @@ -32,7 +32,7 @@ SOURCE[../libssl]=\ tls_depr.c $KTLSSRC # For shared builds we need to include the libcrypto packet.c and sources # needed in providers (s3_cbc.c and record/tls_pad.c) in libssl as well. -SHARED_SOURCE[../libssl]=record/tls_pad.c ../crypto/packet.c +SHARED_SOURCE[../libssl]=record/tls_pad.c ../crypto/packet.c ../crypto/quic_vlint.c IF[{- !$disabled{'deprecated-3.0'} -}] SHARED_SOURCE[../libssl]=s3_cbc.c SOURCE[../libssl]=ssl_rsa_legacy.c diff --git a/test/build.info b/test/build.info index 4552c7686e..a6dffe280e 100644 --- a/test/build.info +++ b/test/build.info @@ -270,7 +270,7 @@ IF[{- !$disabled{tests} -}] INCLUDE[bad_dtls_test]=../include ../apps/include DEPEND[bad_dtls_test]=../libcrypto ../libssl libtestutil.a - SOURCE[packettest]=packettest.c + SOURCE[packettest]=packettest.c ../crypto/quic_vlint.c INCLUDE[packettest]=../include ../apps/include DEPEND[packettest]=../libcrypto libtestutil.a @@ -816,7 +816,7 @@ IF[{- !$disabled{tests} -}] PROGRAMS{noinst}=tls13secretstest SOURCE[tls13secretstest]=tls13secretstest.c DEFINE[tls13secretstest]=OPENSSL_NO_KTLS - SOURCE[tls13secretstest]= ../ssl/tls13_enc.c ../crypto/packet.c + SOURCE[tls13secretstest]= ../ssl/tls13_enc.c ../crypto/packet.c ../crypto/quic_vlint.c INCLUDE[tls13secretstest]=.. ../include ../apps/include DEPEND[tls13secretstest]=../libcrypto ../libssl libtestutil.a ENDIF diff --git a/test/packettest.c b/test/packettest.c index e8aec47446..c62247f9be 100644 --- a/test/packettest.c +++ b/test/packettest.c @@ -465,6 +465,110 @@ static int test_PACKET_as_length_prefixed_2(void) return 1; } +static int test_PACKET_get_quic_vlint(void) +{ + struct quic_test_case { + unsigned char buf[16]; + size_t expected_read_count; + uint64_t value; + }; + + static const struct quic_test_case cases[] = { + { {0x00}, 1, 0 }, + { {0x01}, 1, 1 }, + { {0x3e}, 1, 62 }, + { {0x3f}, 1, 63 }, + { {0x40,0x00}, 2, 0 }, + { {0x40,0x01}, 2, 1 }, + { {0x40,0x02}, 2, 2 }, + { {0x40,0xff}, 2, 255 }, + { {0x41,0x00}, 2, 256 }, + { {0x7f,0xfe}, 2, 16382 }, + { {0x7f,0xff}, 2, 16383 }, + { {0x80,0x00,0x00,0x00}, 4, 0 }, + { {0x80,0x00,0x00,0x01}, 4, 1 }, + { {0x80,0x00,0x01,0x02}, 4, 258 }, + { {0x80,0x18,0x49,0x65}, 4, 1591653 }, + { {0xbe,0x18,0x49,0x65}, 4, 1041779045 }, + { {0xbf,0xff,0xff,0xff}, 4, 1073741823 }, + { {0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, 8, 0 }, + { {0xc0,0x00,0x00,0x00,0x00,0x00,0x01,0x02}, 8, 258 }, + { {0xfd,0x1f,0x59,0x8d,0xc9,0xf8,0x71,0x8a}, 8, 4404337426105397642 }, + }; + + PACKET pkt; + size_t i; + uint64_t v; + + for (i = 0; i < OSSL_NELEM(cases); ++i) { + memset(&pkt, 0, sizeof(pkt)); + v = 55; + + if (!TEST_true(PACKET_buf_init(&pkt, cases[i].buf, sizeof(cases[i].buf))) + || !TEST_true(PACKET_get_quic_vlint(&pkt, &v)) + || !TEST_uint64_t_eq(v, cases[i].value) + || !TEST_size_t_eq(PACKET_remaining(&pkt), + sizeof(cases[i].buf) - cases[i].expected_read_count) + ) + return 0; + } + + return 1; +} + +static int test_PACKET_get_quic_length_prefixed(void) +{ + struct quic_test_case { + unsigned char buf[16]; + size_t enclen, len; + int fail; + }; + + static const struct quic_test_case cases[] = { + /* success cases */ + { {0x00}, 1, 0, 0 }, + { {0x01}, 1, 1, 0 }, + { {0x02}, 1, 2, 0 }, + { {0x03}, 1, 3, 0 }, + { {0x04}, 1, 4, 0 }, + { {0x05}, 1, 5, 0 }, + + /* failure cases */ + { {0x10}, 1, 0, 1 }, + { {0x3f}, 1, 0, 1 }, + }; + + size_t i; + PACKET pkt, subpkt = {0}; + + for (i = 0; i < OSSL_NELEM(cases); ++i) { + memset(&pkt, 0, sizeof(pkt)); + + if (!TEST_true(PACKET_buf_init(&pkt, cases[i].buf, + cases[i].fail + ? sizeof(cases[i].buf) + : cases[i].enclen + cases[i].len))) + return 0; + + if (!TEST_int_eq(PACKET_get_quic_length_prefixed(&pkt, &subpkt), !cases[i].fail)) + return 0; + + if (cases[i].fail) { + if (!TEST_ptr_eq(pkt.curr, cases[i].buf)) + return 0; + continue; + } + + if (!TEST_ptr_eq(subpkt.curr, cases[i].buf + cases[i].enclen)) + return 0; + + if (!TEST_size_t_eq(subpkt.remaining, cases[i].len)) + return 0; + } + + return 1; +} + int setup_tests(void) { unsigned int i; @@ -495,5 +599,7 @@ int setup_tests(void) ADD_TEST(test_PACKET_get_length_prefixed_3); ADD_TEST(test_PACKET_as_length_prefixed_1); ADD_TEST(test_PACKET_as_length_prefixed_2); + ADD_TEST(test_PACKET_get_quic_vlint); + ADD_TEST(test_PACKET_get_quic_length_prefixed); return 1; } diff --git a/test/testutil.h b/test/testutil.h index 52fb17c3c6..397ab62f15 100644 --- a/test/testutil.h +++ b/test/testutil.h @@ -282,6 +282,8 @@ DECLARE_COMPARISONS(char, char) DECLARE_COMPARISONS(unsigned char, uchar) DECLARE_COMPARISONS(long, long) DECLARE_COMPARISONS(unsigned long, ulong) +DECLARE_COMPARISONS(int64_t, int64_t) +DECLARE_COMPARISONS(uint64_t, uint64_t) DECLARE_COMPARISONS(double, double) DECLARE_COMPARISONS(time_t, time_t) @@ -431,6 +433,13 @@ void test_perror(const char *s); # define TEST_ulong_gt(a, b) test_ulong_gt(__FILE__, __LINE__, #a, #b, a, b) # define TEST_ulong_ge(a, b) test_ulong_ge(__FILE__, __LINE__, #a, #b, a, b) +# define TEST_uint64_t_eq(a, b) test_uint64_t_eq(__FILE__, __LINE__, #a, #b, a, b) +# define TEST_uint64_t_ne(a, b) test_uint64_t_ne(__FILE__, __LINE__, #a, #b, a, b) +# define TEST_uint64_t_lt(a, b) test_uint64_t_lt(__FILE__, __LINE__, #a, #b, a, b) +# define TEST_uint64_t_le(a, b) test_uint64_t_le(__FILE__, __LINE__, #a, #b, a, b) +# define TEST_uint64_t_gt(a, b) test_uint64_t_gt(__FILE__, __LINE__, #a, #b, a, b) +# define TEST_uint64_t_ge(a, b) test_uint64_t_ge(__FILE__, __LINE__, #a, #b, a, b) + # define TEST_size_t_eq(a, b) test_size_t_eq(__FILE__, __LINE__, #a, #b, a, b) # define TEST_size_t_ne(a, b) test_size_t_ne(__FILE__, __LINE__, #a, #b, a, b) # define TEST_size_t_lt(a, b) test_size_t_lt(__FILE__, __LINE__, #a, #b, a, b) diff --git a/test/testutil/tests.c b/test/testutil/tests.c index b431657e05..fbc2a0958e 100644 --- a/test/testutil/tests.c +++ b/test/testutil/tests.c @@ -208,7 +208,7 @@ void test_openssl_errors(void) * The desc argument is a printf format string followed by its arguments and * this is included in the output if the condition being tested for is false. */ -#define DEFINE_COMPARISON(type, name, opname, op, fmt) \ +#define DEFINE_COMPARISON(type, name, opname, op, fmt, cast) \ int test_ ## name ## _ ## opname(const char *file, int line, \ const char *s1, const char *s2, \ const type t1, const type t2) \ @@ -217,29 +217,31 @@ void test_openssl_errors(void) return 1; \ test_fail_message(NULL, file, line, #type, s1, s2, #op, \ "[" fmt "] compared to [" fmt "]", \ - t1, t2); \ + (cast)t1, (cast)t2); \ return 0; \ } -#define DEFINE_COMPARISONS(type, name, fmt) \ - DEFINE_COMPARISON(type, name, eq, ==, fmt) \ - DEFINE_COMPARISON(type, name, ne, !=, fmt) \ - DEFINE_COMPARISON(type, name, lt, <, fmt) \ - DEFINE_COMPARISON(type, name, le, <=, fmt) \ - DEFINE_COMPARISON(type, name, gt, >, fmt) \ - DEFINE_COMPARISON(type, name, ge, >=, fmt) - -DEFINE_COMPARISONS(int, int, "%d") -DEFINE_COMPARISONS(unsigned int, uint, "%u") -DEFINE_COMPARISONS(char, char, "%c") -DEFINE_COMPARISONS(unsigned char, uchar, "%u") -DEFINE_COMPARISONS(long, long, "%ld") -DEFINE_COMPARISONS(unsigned long, ulong, "%lu") -DEFINE_COMPARISONS(size_t, size_t, "%zu") -DEFINE_COMPARISONS(double, double, "%g") - -DEFINE_COMPARISON(void *, ptr, eq, ==, "%p") -DEFINE_COMPARISON(void *, ptr, ne, !=, "%p") +#define DEFINE_COMPARISONS(type, name, fmt, cast) \ + DEFINE_COMPARISON(type, name, eq, ==, fmt, cast) \ + DEFINE_COMPARISON(type, name, ne, !=, fmt, cast) \ + DEFINE_COMPARISON(type, name, lt, <, fmt, cast) \ + DEFINE_COMPARISON(type, name, le, <=, fmt, cast) \ + DEFINE_COMPARISON(type, name, gt, >, fmt, cast) \ + DEFINE_COMPARISON(type, name, ge, >=, fmt, cast) + +DEFINE_COMPARISONS(int, int, "%d", int) +DEFINE_COMPARISONS(unsigned int, uint, "%u", unsigned int) +DEFINE_COMPARISONS(char, char, "%c", char) +DEFINE_COMPARISONS(unsigned char, uchar, "%u", unsigned char) +DEFINE_COMPARISONS(long, long, "%ld", long) +DEFINE_COMPARISONS(unsigned long, ulong, "%lu", unsigned long) +DEFINE_COMPARISONS(int64_t, int64_t, "%lld", long long) +DEFINE_COMPARISONS(uint64_t, uint64_t, "%llu", unsigned long long) +DEFINE_COMPARISONS(size_t, size_t, "%zu", size_t) +DEFINE_COMPARISONS(double, double, "%g", double) + +DEFINE_COMPARISON(void *, ptr, eq, ==, "%p", void *) +DEFINE_COMPARISON(void *, ptr, ne, !=, "%p", void *) int test_ptr_null(const char *file, int line, const char *s, const void *p) { diff --git a/test/wpackettest.c b/test/wpackettest.c index b03dfcd2e0..a90c9a1553 100644 --- a/test/wpackettest.c +++ b/test/wpackettest.c @@ -26,6 +26,30 @@ static const unsigned char simpleder[] = { 0xfc, 0x04, 0x00, 0x01, 0x02, 0x03, 0xff, 0xfe, 0xfd }; +/* QUIC sub-packet with 4-byte length prefix, containing a 1-byte vlint */ +static const unsigned char quic1[] = { 0x80, 0x00, 0x00, 0x01, 0x09 }; +/* QUIC sub-packet with 1-byte length prefix, containing a 1-byte vlint */ +static const unsigned char quic2[] = { 0x01, 0x09 }; +/* QUIC sub-packet with 2-byte length prefix, containing a 2-byte vlint */ +static const unsigned char quic3[] = { 0x40, 0x02, 0x40, 0x41 }; +/* QUIC sub-packet with 8-byte length prefix, containing a 4-byte vlint */ +static const unsigned char quic4[] = { + 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, + 0x80, 0x01, 0x3c, 0x6a +}; +/* QUIC sub-packet with 8-byte length prefix, containing a 8-byte vlint */ +static const unsigned char quic5[] = { + 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, + 0xef, 0x77, 0x21, 0x3f, 0x3f, 0x50, 0x5b, 0xa5 +}; +/* QUIC sub-packet, length known up-front */ +static const unsigned char quic6[] = { 0x03, 0x55, 0x66, 0x77 }; +/* Nested and sequential sub-packets with length prefixes */ +static const unsigned char quic7[] = { + 0x07, 0x80, 0x00, 0x00, 0x08, 0x65, 0x14, 0x40, 0x01, 0x05, + 0x40, 0x01, 0x11, 0x40, 0x01, 0x12, 0x40, 0x01, 0x13 +}; + static BUF_MEM *buf; static int cleanup(WPACKET *pkt) @@ -424,6 +448,179 @@ static int test_WPACKET_init_der(void) return 1; } +static int test_WPACKET_quic(void) +{ + WPACKET pkt; + size_t written, len; + unsigned char *bytes; + + /* QUIC sub-packet with 4-byte length prefix, containing a 1-byte vlint */ + if (!TEST_true(WPACKET_init(&pkt, buf)) + || !TEST_true(WPACKET_start_quic_sub_packet(&pkt)) + || !TEST_true(WPACKET_quic_write_vlint(&pkt, 0x09)) + /* Can't finish because we have a sub packet */ + || !TEST_false(WPACKET_finish(&pkt)) + || !TEST_true(WPACKET_close(&pkt)) + /* Sub packet is closed so can't close again */ + || !TEST_false(WPACKET_close(&pkt)) + /* Now a top level so finish should succeed */ + || !TEST_true(WPACKET_finish(&pkt)) + || !TEST_true(WPACKET_get_total_written(&pkt, &written)) + || !TEST_mem_eq(buf->data, written, quic1, sizeof(quic1))) + return cleanup(&pkt); + + /* QUIC sub-packet with 1-byte length prefix, containing a 1-byte vlint */ + if (!TEST_true(WPACKET_init(&pkt, buf)) + || !TEST_true(WPACKET_start_quic_sub_packet_bound(&pkt, OSSL_QUIC_VLINT_1B_MAX)) + || !TEST_true(WPACKET_quic_write_vlint(&pkt, 0x09)) + || !TEST_false(WPACKET_finish(&pkt)) + || !TEST_true(WPACKET_close(&pkt)) + || !TEST_false(WPACKET_close(&pkt)) + || !TEST_true(WPACKET_finish(&pkt)) + || !TEST_true(WPACKET_get_total_written(&pkt, &written)) + || !TEST_mem_eq(buf->data, written, quic2, sizeof(quic2))) + return cleanup(&pkt); + + /* QUIC sub-packet with 2-byte length prefix, containing a 2-byte vlint */ + if (!TEST_true(WPACKET_init(&pkt, buf)) + || !TEST_true(WPACKET_start_quic_sub_packet_bound(&pkt, OSSL_QUIC_VLINT_2B_MIN)) + || !TEST_true(WPACKET_quic_write_vlint(&pkt, 0x41)) + || !TEST_false(WPACKET_finish(&pkt)) + || !TEST_true(WPACKET_close(&pkt)) + || !TEST_false(WPACKET_close(&pkt)) + || !TEST_true(WPACKET_finish(&pkt)) + || !TEST_true(WPACKET_get_total_written(&pkt, &written)) + || !TEST_mem_eq(buf->data, written, quic3, sizeof(quic3))) + return cleanup(&pkt); + + /* QUIC sub-packet with 8-byte length prefix, containing a 4-byte vlint */ + if (!TEST_true(WPACKET_init(&pkt, buf)) + || !TEST_true(WPACKET_start_quic_sub_packet_bound(&pkt, OSSL_QUIC_VLINT_8B_MIN)) + || !TEST_true(WPACKET_quic_write_vlint(&pkt, 0x13c6a)) + || !TEST_false(WPACKET_finish(&pkt)) + || !TEST_true(WPACKET_close(&pkt)) + || !TEST_false(WPACKET_close(&pkt)) + || !TEST_true(WPACKET_finish(&pkt)) + || !TEST_true(WPACKET_get_total_written(&pkt, &written)) + || !TEST_mem_eq(buf->data, written, quic4, sizeof(quic4))) + return cleanup(&pkt); + + /* QUIC sub-packet with 8-byte length prefix, containing a 8-byte vlint */ + if (!TEST_true(WPACKET_init(&pkt, buf)) + || !TEST_true(WPACKET_start_quic_sub_packet_bound(&pkt, OSSL_QUIC_VLINT_8B_MIN)) + || !TEST_true(WPACKET_quic_write_vlint(&pkt, 0x2f77213f3f505ba5ULL)) + || !TEST_false(WPACKET_finish(&pkt)) + || !TEST_true(WPACKET_close(&pkt)) + || !TEST_false(WPACKET_close(&pkt)) + || !TEST_true(WPACKET_finish(&pkt)) + || !TEST_true(WPACKET_get_total_written(&pkt, &written)) + || !TEST_mem_eq(buf->data, written, quic5, sizeof(quic5))) + return cleanup(&pkt); + + /* QUIC sub-packet, length known up-front */ + if (!TEST_true(WPACKET_init(&pkt, buf)) + || !TEST_true(WPACKET_quic_sub_allocate_bytes(&pkt, 3, &bytes))) + return cleanup(&pkt); + + bytes[0] = 0x55; + bytes[1] = 0x66; + bytes[2] = 0x77; + + if (!TEST_true(WPACKET_finish(&pkt)) + || !TEST_true(WPACKET_get_total_written(&pkt, &written)) + || !TEST_mem_eq(buf->data, written, quic6, sizeof(quic6))) + return cleanup(&pkt); + + /* Nested and sequential sub-packets with length prefixes */ + if (!TEST_true(WPACKET_init(&pkt, buf)) + || !TEST_true(WPACKET_quic_write_vlint(&pkt, 0x07)) + || !TEST_true(WPACKET_get_length(&pkt, &len)) + || !TEST_size_t_eq(len, 1) + || !TEST_true(WPACKET_start_quic_sub_packet_bound(&pkt, OSSL_QUIC_VLINT_4B_MIN)) + || !TEST_true(WPACKET_quic_write_vlint(&pkt, 0x2514)) + || !TEST_true(WPACKET_get_length(&pkt, &len)) + || !TEST_size_t_eq(len, 2) + || !TEST_true(WPACKET_start_quic_sub_packet_bound(&pkt, OSSL_QUIC_VLINT_2B_MIN)) + || !TEST_true(WPACKET_quic_write_vlint(&pkt, 0x05)) + || !TEST_true(WPACKET_get_length(&pkt, &len)) + || !TEST_size_t_eq(len, 1) + || !TEST_true(WPACKET_close(&pkt)) + || !TEST_true(WPACKET_start_quic_sub_packet_bound(&pkt, OSSL_QUIC_VLINT_2B_MIN)) + || !TEST_true(WPACKET_quic_write_vlint(&pkt, 0x11)) + || !TEST_true(WPACKET_close(&pkt)) + || !TEST_true(WPACKET_get_length(&pkt, &len)) + || !TEST_size_t_eq(len, 8) + || !TEST_true(WPACKET_close(&pkt)) + || !TEST_true(WPACKET_start_quic_sub_packet_bound(&pkt, OSSL_QUIC_VLINT_2B_MIN)) + || !TEST_true(WPACKET_quic_write_vlint(&pkt, 0x12)) + || !TEST_true(WPACKET_close(&pkt)) + || !TEST_true(WPACKET_start_quic_sub_packet_bound(&pkt, OSSL_QUIC_VLINT_2B_MIN)) + || !TEST_true(WPACKET_quic_write_vlint(&pkt, 0x13)) + || !TEST_true(WPACKET_close(&pkt)) + || !TEST_true(WPACKET_finish(&pkt)) + || !TEST_true(WPACKET_get_total_written(&pkt, &written)) + || !TEST_mem_eq(buf->data, written, quic7, sizeof(quic7))) + return cleanup(&pkt); + + /* Trying to encode a value above OSSL_QUIC_VLINT_MAX should fail */ + if (!TEST_true(WPACKET_init(&pkt, buf)) + || !TEST_false(WPACKET_quic_write_vlint(&pkt, OSSL_QUIC_VLINT_MAX+1)) + || !TEST_true(WPACKET_quic_write_vlint(&pkt, OSSL_QUIC_VLINT_MAX))) + return cleanup(&pkt); + + WPACKET_cleanup(&pkt); + return 1; +} + +static int test_WPACKET_quic_vlint_random(void) +{ + size_t i, written; + uint64_t expected, actual = 0; + unsigned char rand_data[9]; + WPACKET pkt; + PACKET read_pkt = {0}; + + for (i = 0; i < 10000; ++i) { + if (!TEST_true(RAND_bytes(rand_data, sizeof(rand_data)))) + return cleanup(&pkt); + + expected = *(uint64_t*)rand_data; + + /* + * Ensure that all size classes get tested with equal probability. + */ + switch (rand_data[8] & 3) { + case 0: + expected &= OSSL_QUIC_VLINT_1B_MAX; + break; + case 1: + expected &= OSSL_QUIC_VLINT_2B_MAX; + break; + case 2: + expected &= OSSL_QUIC_VLINT_4B_MAX; + break; + case 3: + expected &= OSSL_QUIC_VLINT_8B_MAX; + break; + } + + if (!TEST_true(WPACKET_init(&pkt, buf)) + || !TEST_true(WPACKET_quic_write_vlint(&pkt, expected)) + || !TEST_true(WPACKET_get_total_written(&pkt, &written))) + return cleanup(&pkt); + + if (!TEST_true(PACKET_buf_init(&read_pkt, (unsigned char *)buf->data, written)) + || !TEST_true(PACKET_get_quic_vlint(&read_pkt, &actual)) + || !TEST_uint64_t_eq(expected, actual)) + return cleanup(&pkt); + + WPACKET_cleanup(&pkt); + } + + WPACKET_cleanup(&pkt); + return 1; +} + int setup_tests(void) { if (!TEST_ptr(buf = BUF_MEM_new())) @@ -436,6 +633,8 @@ int setup_tests(void) ADD_TEST(test_WPACKET_allocate_bytes); ADD_TEST(test_WPACKET_memcpy); ADD_TEST(test_WPACKET_init_der); + ADD_TEST(test_WPACKET_quic); + ADD_TEST(test_WPACKET_quic_vlint_random); return 1; }