QUIC Demuxer and Record Layer (RX Side)
authorHugo Landau <hlandau@openssl.org>
Fri, 22 Jul 2022 12:08:38 +0000 (13:08 +0100)
committerTomas Mraz <tomas@openssl.org>
Fri, 2 Sep 2022 08:03:55 +0000 (10:03 +0200)
Reviewed-by: Paul Dale <pauli@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/18949)

20 files changed:
crypto/bio/bio_local.h
include/internal/bio_addr.h [new file with mode: 0644]
include/internal/quic_demux.h [new file with mode: 0644]
include/internal/quic_record.h [new file with mode: 0644]
include/internal/quic_record_util.h [new file with mode: 0644]
include/internal/quic_types.h
include/internal/quic_wire.h
include/internal/quic_wire_pkt.h [new file with mode: 0644]
ssl/quic/build.info
ssl/quic/quic_demux.c [new file with mode: 0644]
ssl/quic/quic_record.c [new file with mode: 0644]
ssl/quic/quic_record_util.c [new file with mode: 0644]
ssl/quic/quic_wire.c
ssl/quic/quic_wire_pkt.c [new file with mode: 0644]
ssl/ssl_local.h
ssl/tls13_enc.c
test/build.info
test/quic_record_test.c [new file with mode: 0644]
test/quic_wire_test.c
test/recipes/70-test_quic_record.t [new file with mode: 0644]

index bcb383f0eaa9b5e512ed7b832ff894530dc1789c..56ad6d7d8766ab66bda7c8a33b0f2939d9e479df 100644 (file)
@@ -9,6 +9,7 @@
 
 #include "internal/e_os.h"
 #include "internal/sockets.h"
+#include "internal/bio_addr.h"
 
 /* BEGIN BIO_ADDRINFO/BIO_ADDR stuff. */
 
@@ -63,17 +64,6 @@ struct bio_addrinfo_st {
     struct bio_addrinfo_st *bai_next;
 };
 # endif
-
-union bio_addr_st {
-    struct sockaddr sa;
-# if OPENSSL_USE_IPV6
-    struct sockaddr_in6 s_in6;
-# endif
-    struct sockaddr_in s_in;
-# ifndef OPENSSL_NO_UNIX_SOCK
-    struct sockaddr_un s_un;
-# endif
-};
 #endif
 
 /* END BIO_ADDRINFO/BIO_ADDR stuff. */
diff --git a/include/internal/bio_addr.h b/include/internal/bio_addr.h
new file mode 100644 (file)
index 0000000..a6449b7
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * 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_BIO_ADDR_H
+# define OSSL_BIO_ADDR_H
+
+# include "internal/e_os.h"
+# include "internal/sockets.h"
+
+# ifndef OPENSSL_NO_SOCK
+union bio_addr_st {
+    struct sockaddr sa;
+#  if OPENSSL_USE_IPV6
+    struct sockaddr_in6 s_in6;
+#  endif
+    struct sockaddr_in s_in;
+#  ifndef OPENSSL_NO_UNIX_SOCK
+    struct sockaddr_un s_un;
+#  endif
+};
+# endif
+
+#endif
diff --git a/include/internal/quic_demux.h b/include/internal/quic_demux.h
new file mode 100644 (file)
index 0000000..7d4b0df
--- /dev/null
@@ -0,0 +1,251 @@
+/*
+ * 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_QUIC_DEMUX_H
+# define OSSL_QUIC_DEMUX_H
+
+# include <openssl/ssl.h>
+# include "internal/quic_types.h"
+# include "internal/bio_addr.h"
+
+/*
+ * QUIC Demuxer
+ * ============
+ *
+ * The QUIC connection demuxer is the entity responsible for receiving datagrams
+ * from the network via a datagram BIO. It parses packet headers to determine
+ * each packet's destination connection ID (DCID) and hands off processing of
+ * the packet to the correct QUIC Record Layer (QRL)'s RX side.
+ *
+ * A QRL is instantiated per QUIC connection and contains the cryptographic
+ * resources needed to decrypt QUIC packets for that connection. Received
+ * datagrams are passed from the demuxer to the QRL via a callback registered
+ * for a specific DCID by the QRL; thus the demuxer has no specific knowledge of
+ * the QRL and is not coupled to it.
+ *
+ * A connection may have multiple connection IDs associated with it; a QRL
+ * handles this simply by registering multiple connection IDs with the demuxer
+ * via multiple register calls.
+ *
+ * URX Queue
+ * ---------
+ *
+ * Since the demuxer must handle the initial reception of datagrams from the OS,
+ * RX queue management for new, unprocessed datagrams is also handled by the
+ * demuxer.
+ *
+ * The demuxer maintains a queue of Unprocessed RX Entries (URXEs), which store
+ * unprocessed (i.e., encrypted, unvalidated) data received from the network.
+ * The URXE queue is designed to allow multiple datagrams to be received in a
+ * single call to BIO_recvmmsg, where supported.
+ *
+ * One URXE is used per received datagram. Each datagram may contain multiple
+ * packets, however, this is not the demuxer's concern. QUIC prohibits different
+ * packets in the same datagram from containing different DCIDs; the demuxer
+ * only considers the DCID of the first packet in a datagram when deciding how
+ * to route a received datagram, and it is the responsibility of the QRL to
+ * enforce this rule. Packets other than the first packet in a datagram are not
+ * examined by the demuxer, and the demuxer does not perform validation of
+ * packet headers other than to the minimum extent necessary to extract the
+ * DCID; further parsing and validation of packet headers is the responsibility
+ * of the QRL.
+ *
+ * Rather than defining an opaque interface, the URXE structure internals
+ * are exposed. Since the demuxer is only exposed to other parts of the QUIC
+ * implementation internals, this poses no problem, and has a number of
+ * advantages:
+ *
+ *   - Fields in the URXE can be allocated to support requirements in other
+ *     components, like the QRL, which would otherwise have to allocate extra
+ *     memory corresponding to each URXE.
+ *
+ *   - Other components, like the QRL, can keep the URXE in queues of its own
+ *     when it is not being managed by the demuxer.
+ *
+ * URX Queue Structure
+ * -------------------
+ *
+ * The URXE queue is maintained as a simple doubly-linked list. URXE entries are
+ * moved between different lists in their lifecycle (for example, from a free
+ * list to a pending list and vice versa). The buffer into which datagrams are
+ * received immediately follows this URXE header structure and is part of the
+ * same allocation.
+ */
+
+typedef struct quic_urxe_st QUIC_URXE;
+
+/* Maximum number of packets we allow to exist in one datagram. */
+#define QUIC_MAX_PKT_PER_URXE       (sizeof(uint64_t) * 8)
+
+struct quic_urxe_st {
+    QUIC_URXE *prev, *next;
+
+    /*
+     * The URXE data starts after this structure so we don't need a pointer.
+     * data_len stores the current length (i.e., the length of the received
+     * datagram) and alloc_len stores the allocation length. The URXE will be
+     * reallocated if we need a larger allocation than is available, though this
+     * should not be common as we will have a good idea of worst-case MTUs up
+     * front.
+     */
+    size_t          data_len, alloc_len;
+
+    /*
+     * Bitfields per packet. processed indicates the packet has been processed
+     * and must not be processed again, hpr_removed indicates header protection
+     * has already been removed. Used by QRL only; not used by the demuxer.
+     */
+    uint64_t        processed, hpr_removed;
+
+    /*
+     * Address of peer we received the datagram from, and the local interface
+     * address we received it on. If local address support is not enabled, local
+     * is zeroed.
+     */
+    BIO_ADDR        peer, local;
+};
+
+/* Accessors for URXE buffer. */
+static ossl_unused ossl_inline unsigned char *
+ossl_quic_urxe_data(const QUIC_URXE *e)
+{
+    return (unsigned char *)&e[1];
+}
+
+static ossl_unused ossl_inline unsigned char *
+ossl_quic_urxe_data_end(const QUIC_URXE *e)
+{
+    return ossl_quic_urxe_data(e) + e->data_len;
+}
+
+/* List structure tracking a queue of URXEs. */
+typedef struct quic_urxe_list_st {
+    QUIC_URXE *head, *tail;
+} QUIC_URXE_LIST;
+
+/*
+ * List management helpers. These are used by the demuxer but can also be used
+ * by users of the demuxer to manage URXEs.
+ */
+void ossl_quic_urxe_remove(QUIC_URXE_LIST *l, QUIC_URXE *e);
+void ossl_quic_urxe_insert_head(QUIC_URXE_LIST *l, QUIC_URXE *e);
+void ossl_quic_urxe_insert_tail(QUIC_URXE_LIST *l, QUIC_URXE *e);
+
+/* Opaque type representing a demuxer. */
+typedef struct quic_demux_st QUIC_DEMUX;
+
+/*
+ * Called when a datagram is received for a given connection ID.
+ *
+ * e is a URXE containing the datagram payload. It is permissible for the callee
+ * to mutate this buffer; once the demuxer calls this callback, it will never
+ * read the buffer again.
+ *
+ * The callee must arrange for ossl_quic_demux_release_urxe to be called on the URXE
+ * at some point in the future (this need not be before the callback returns).
+ *
+ * At the time the callback is made, the URXE will not be in any queue,
+ * therefore the callee can use the prev and next fields as it wishes.
+ */
+typedef void (ossl_quic_demux_cb_fn)(QUIC_URXE *e, void *arg);
+
+/*
+ * Creates a new demuxer. The given BIO is used to receive datagrams from the
+ * network using BIO_recvmmsg. short_conn_id_len is the length of destination
+ * connection IDs used in RX'd packets; it must have the same value for all
+ * connections used on a socket. default_urxe_alloc_len is the buffer size to
+ * receive datagrams into; it should be a value large enough to contain any
+ * received datagram according to local MTUs, etc.
+ */
+QUIC_DEMUX *ossl_quic_demux_new(BIO *net_bio,
+                                size_t short_conn_id_len,
+                                size_t default_urxe_alloc_len);
+
+/*
+ * Destroy a demuxer. All URXEs must have been released back to the demuxer
+ * before calling this. No-op if demux is NULL.
+ */
+void ossl_quic_demux_free(QUIC_DEMUX *demux);
+
+/*
+ * Register a datagram handler callback for a connection ID.
+ *
+ * ossl_quic_demux_pump will call the specified function if it receives a datagram
+ * the first packet of which has the specified destination connection ID.
+ *
+ * It is assumed all packets in a datagram have the same destination connection
+ * ID (as QUIC mandates this), but it is the user's responsibility to check for
+ * this and reject subsequent packets in a datagram that violate this rule.
+ *
+ * dst_conn_id is a destination connection ID; it is copied and need not remain
+ * valid after this function returns.
+ *
+ * cb_arg is passed to cb when it is called. For information on the callback,
+ * see its typedef above.
+ *
+ * Only one handler can be set for a given connection ID. If a handler is
+ * already set for the given connection ID, returns 0.
+ *
+ * Returns 1 on success or 0 on failure.
+ */
+int ossl_quic_demux_register(QUIC_DEMUX *demux,
+                             const QUIC_CONN_ID *dst_conn_id,
+                             ossl_quic_demux_cb_fn *cb,
+                             void *cb_arg);
+
+/*
+ * Unregisters any datagram handler callback set for the given connection ID.
+ * Fails if no handler is registered for the given connection ID.
+ *
+ * Returns 1 on success or 0 on failure.
+ */
+int ossl_quic_demux_unregister(QUIC_DEMUX *demux,
+                               const QUIC_CONN_ID *dst_conn_id);
+
+/*
+ * Unregisters any datagram handler callback from all connection IDs it is used
+ * for. cb and cb_arg must both match the values passed to
+ * ossl_quic_demux_register.
+ */
+void ossl_quic_demux_unregister_by_cb(QUIC_DEMUX *demux,
+                                      ossl_quic_demux_cb_fn *cb,
+                                      void *cb_arg);
+
+/*
+ * Releases a URXE back to the demuxer. No reference must be made to the URXE or
+ * its buffer after calling this function. The URXE must not be in any queue;
+ * that is, its prev and next pointers must be NULL.
+ */
+void ossl_quic_demux_release_urxe(QUIC_DEMUX *demux,
+                                  QUIC_URXE *e);
+
+/*
+ * Process any unprocessed RX'd datagrams, by calling registered callbacks by
+ * connection ID, reading more datagrams from the BIO if necessary.
+ *
+ * Returns 1 on success or 0 on failure.
+ */
+int ossl_quic_demux_pump(QUIC_DEMUX *demux);
+
+/*
+ * Artificially inject a packet into the demuxer for testing purposes. The
+ * buffer must not exceed the URXE size being used by the demuxer.
+ *
+ * If peer or local are NULL, their respective fields are zeroed in the injected
+ * URXE.
+ *
+ * Returns 1 on success or 0 on failure.
+ */
+int ossl_quic_demux_inject(QUIC_DEMUX *demux,
+                           const unsigned char *buf,
+                           size_t buf_len,
+                           const BIO_ADDR *peer,
+                           const BIO_ADDR *local);
+
+#endif
diff --git a/include/internal/quic_record.h b/include/internal/quic_record.h
new file mode 100644 (file)
index 0000000..06284c2
--- /dev/null
@@ -0,0 +1,311 @@
+/*
+ * 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_QUIC_RECORD_H
+# define OSSL_QUIC_RECORD_H
+
+# include <openssl/ssl.h>
+# include "internal/quic_wire_pkt.h"
+# include "internal/quic_types.h"
+# include "internal/quic_record_util.h"
+# include "internal/quic_demux.h"
+
+/*
+ * QUIC Record Layer
+ * =================
+ */
+typedef struct ossl_qrl_st OSSL_QRL;
+
+typedef struct ossl_qrl_args_st {
+    OSSL_LIB_CTX   *libctx;
+    const char     *propq;
+
+    /* Demux to receive datagrams from. */
+    QUIC_DEMUX     *rx_demux;
+
+    /* Length of connection IDs used in short-header packets in bytes. */
+    size_t          short_conn_id_len;
+
+    /* Initial reference PN used for RX. */
+    QUIC_PN         rx_init_largest_pn[QUIC_PN_SPACE_NUM];
+} OSSL_QRL_ARGS;
+
+/* Instantiates a new QRL. */
+OSSL_QRL *ossl_qrl_new(const OSSL_QRL_ARGS *args);
+
+/*
+ * Frees the QRL. All packets obtained using ossl_qrl_read_pkt must already
+ * have been released by calling ossl_qrl_release_pkt.
+ *
+ * You do not need to call ossl_qrl_remove_dst_conn_id first; this function will
+ * unregister the QRL from the demuxer for all registered destination connection
+ * IDs (DCIDs) automatically.
+ */
+void ossl_qrl_free(OSSL_QRL *qrl);
+
+/*
+ * DCID Management
+ * ===============
+ */
+
+/*
+ * Adds a given DCID to the QRL. The QRL will register the DCID with the demuxer
+ * so that incoming packets with that DCID are passed to the given QRL. Multiple
+ * DCIDs may be associated with a QRL at any one time. You will need to add at
+ * least one DCID after instantiating the QRL. A zero-length DCID is a valid
+ * input to this function. This function fails if the DCID is already
+ * registered.
+ *
+ * Returns 1 on success or 0 on error.
+ */
+int ossl_qrl_add_dst_conn_id(OSSL_QRL *qrl,
+                             const QUIC_CONN_ID *dst_conn_id);
+
+/*
+ * Remove a DCID previously registered with ossl_qrl_add_dst_conn_id. The DCID
+ * is unregistered from the demuxer. Fails if the DCID is not registered with
+ * the demuxer.
+ *
+ * Returns 1 on success or 0 on error.
+ */
+int ossl_qrl_remove_dst_conn_id(OSSL_QRL *qrl,
+                                const QUIC_CONN_ID *dst_conn_id);
+
+/*
+ * Secret Management
+ * =================
+ *
+ * A QRL has several encryption levels (Initial, Handshake, 0-RTT, 1-RTT) and
+ * two directions (RX, TX). At any given time, key material is managed for each
+ * (EL, RX/TX) combination.
+ *
+ * Broadly, for a given (EL, RX/TX), the following state machine is applicable:
+ *
+ *   WAITING_FOR_KEYS --[Provide]--> HAVE_KEYS --[Discard]--> | DISCARDED |
+ *         \-------------------------------------[Discard]--> |           |
+ *
+ * To transition the RX side of an EL from WAITING_FOR_KEYS to HAVE_KEYS, call
+ * ossl_qrl_provide_rx_secret (or for the INITIAL EL,
+ * ossl_qrl_provide_rx_secret_initial).
+ *
+ * Once keys have been provisioned for an EL, you call
+ * ossl_qrl_discard_enc_level to transition the EL to the DISCARDED state. You
+ * can also call this function to transition directly to the DISCARDED state
+ * even before any keys have been provisioned for that EL.
+ *
+ * The DISCARDED state is terminal for a given EL; you cannot provide a secret
+ * again for that EL after reaching it.
+ *
+ * Incoming packets cannot be processed and decrypted if they target an EL
+ * not in the HAVE_KEYS state. However, there is a distinction between
+ * the WAITING_FOR_KEYS and DISCARDED states:
+ *
+ *   - In the WAITING_FOR_KEYS state, the QRL assumes keys for the given
+ *     EL will eventually arrive. Therefore, if it receives any packet
+ *     for an EL in this state, it buffers it and tries to process it
+ *     again once the EL reaches HAVE_KEYS.
+ *
+ *   - In the DISCARDED state, the QRL assumes no keys for the given
+ *     EL will ever arrive again. If it receives any packet for an EL
+ *     in this state, it is simply discarded.
+ *
+ * If the user wishes to instantiate a new QRL to replace an old one for
+ * whatever reason, for example to take over for an already established QUIC
+ * connection, it is important that all ELs no longer being used (i.e., INITIAL,
+ * 0-RTT, 1-RTT) are transitioned to the DISCARDED state. Otherwise, the QRL
+ * will assume that keys for these ELs will arrive in future, and will buffer
+ * any received packets for those ELs perpetually. This can be done by calling
+ * ossl_qrl_discard_enc_level for all non-1-RTT ELs immediately after
+ * instantiating the QRL.
+ *
+ * The INITIAL EL is not setup automatically when the QRL is instantiated. This
+ * allows the caller to instead discard it immediately after instantiation of
+ * the QRL if it is not needed, for example if the QRL is being instantiated to
+ * take over handling of an existing connection which has already passed the
+ * INITIAL phase. This avoids the unnecessary derivation of INITIAL keys where
+ * they are not needed. In the ordinary case, ossl_qrl_provide_rx_secret_initial
+ * should be called immediately after instantiation.
+ */
+
+/*
+ * A QUIC client sends its first INITIAL packet with a random DCID, which is
+ * used to compute the secret used for INITIAL packet encryption. This function
+ * must be called to provide the DCID used for INITIAL packet secret computation
+ * before the QRL can process any INITIAL response packets.
+ *
+ * It is possible to use the QRL without ever calling this, for example if there
+ * is no desire to handle INITIAL packets (e.g. if the QRL is instantiated to
+ * succeed a previous QRL and handle a connection which is already established.)
+ * However, in this case you should make sure you call
+ * ossl_qrl_discard_enc_level (see above).
+ *
+ * Returns 1 on success or 0 on error.
+ */
+int ossl_qrl_provide_rx_secret_initial(OSSL_QRL *qrl,
+                                       const QUIC_CONN_ID *dst_conn_id);
+
+/*
+ * Provides a secret to the QRL, which arises due to an encryption level change.
+ * enc_level is a QUIC_ENC_LEVEL_* value. This function cannot be used to
+ * initialise the INITIAL encryption level; see
+ * ossl_qrl_provide_rx_secret_initial instead.
+ *
+ * You should seek to call this function for a given EL before packets of that
+ * EL arrive and are processed by the QRL. However, if packets have already
+ * arrived for a given EL, the QRL will defer processing of them and perform
+ * processing of them when this function is eventually called for the EL in
+ * question.
+ *
+ * suite_id is a QRL_SUITE_* value which determines the AEAD function used for
+ * the QRL.
+ *
+ * The secret passed is used directly to derive the "quic key", "quic iv" and
+ * "quic hp" values.
+ *
+ * secret_len is the length of the secret buffer in bytes. The buffer must be
+ * sized correctly to the chosen suite, else the function fails.
+ *
+ * This function can only be called once for a given EL. Subsequent calls fail,
+ * as do calls made after a corresponding call to ossl_qrl_discard_enc_level for
+ * that EL. The secret for a EL cannot be changed after it is set because QUIC
+ * has no facility for introducing additional key material after an EL is setup.
+ * QUIC key updates are managed automatically by the QRL and do not require user
+ * intervention.
+ *
+ * Returns 1 on success or 0 on failure.
+ */
+int ossl_qrl_provide_rx_secret(OSSL_QRL              *qrl,
+                               uint32_t               enc_level,
+                               uint32_t               suite_id,
+                               const unsigned char   *secret,
+                               size_t                 secret_len);
+
+/*
+ * Informs the QRL that it can now discard key material for a given EL. The QRL
+ * will no longer be able to process incoming packets received at that
+ * encryption level. This function is idempotent and succeeds if the EL has
+ * already been discarded.
+ *
+ * Returns 1 on success and 0 on failure.
+ */
+int ossl_qrl_discard_enc_level(OSSL_QRL *qrl, uint32_t enc_level);
+
+/*
+ * Packet Reception
+ * ================
+ */
+
+/* Information about a received packet. */
+typedef struct ossl_qrl_rx_pkt_st {
+    /* Opaque handle to be passed to ossl_qrl_release_pkt. */
+    void               *handle;
+
+    /*
+     * Points to a logical representation of the decoded QUIC packet header. The
+     * data and len fields point to the decrypted QUIC payload (i.e., to a
+     * sequence of zero or more (potentially malformed) frames to be decoded).
+     */
+    QUIC_PKT_HDR       *hdr;
+
+    /*
+     * Address the packet was received from. If this is not available for this
+     * packet, this field is NULL (but this can only occur for manually injected
+     * packets).
+     */
+    const BIO_ADDR     *peer;
+
+    /*
+     * Local address the packet was sent to. If this is not available for this
+     * packet, this field is NULL.
+     */
+    const BIO_ADDR     *local;
+
+    /*
+     * This is the length of the datagram which contained this packet. Note that
+     * the datagram may have contained other packets than this. The intended use
+     * for this is so that the user can enforce minimum datagram sizes (e.g. for
+     * datagrams containing INITIAL packets), as required by RFC 9000.
+     */
+    size_t              datagram_len;
+} OSSL_QRL_RX_PKT;
+
+/*
+ * Tries to read a new decrypted packet from the QRL.
+ *
+ * On success, all fields of *pkt are filled and 1 is returned.
+ * Else, returns 0.
+ *
+ * The resources referenced by pkt->hdr, pkt->data and pkt->peer will remain
+ * allocated at least until the user frees them by calling ossl_qrl_release_pkt,
+ * which must be called once you are done with the packet.
+ */
+int ossl_qrl_read_pkt(OSSL_QRL *qrl, OSSL_QRL_RX_PKT *pkt);
+
+/*
+ * Release the resources pointed to by an OSSL_QRL_RX_PKT returned by
+ * ossl_qrl_read_pkt. Pass the opaque value pkt->handle returned in the
+ * structure.
+ */
+void ossl_qrl_release_pkt(OSSL_QRL *qrl, void *handle);
+
+/*
+ * Returns 1 if there are any already processed (i.e. decrypted) packets waiting
+ * to be read from the QRL.
+ */
+int ossl_qrl_processed_read_pending(OSSL_QRL *qrl);
+
+/*
+ * Returns 1 if there arre any unprocessed (i.e. not yet decrypted) packets
+ * waiting to be processed by the QRL. These may or may not result in
+ * successfully decrypted packets once processed. This indicates whether
+ * unprocessed data is buffered by the QRL, not whether any data is available in
+ * a kernel socket buffer.
+ */
+int ossl_qrl_unprocessed_read_pending(OSSL_QRL *qrl);
+
+/*
+ * Returns the number of UDP payload bytes received from the network so far
+ * since the last time this counter was cleared. If clear is 1, clears the
+ * counter and returns the old value.
+ *
+ * The intended use of this is to allow callers to determine how much credit to
+ * add to their anti-amplification budgets. This is reported separately instead
+ * of in the OSSL_QRL_RX_PKT structure so that a caller can apply
+ * anti-amplification credit as soon as a datagram is received, before it has
+ * necessarily read all processed packets contained within that datagram from
+ * the QRL.
+ */
+uint64_t ossl_qrl_get_bytes_received(OSSL_QRL *qrl, int clear);
+
+/*
+ * Sets a callback which is called when a packet is received and being
+ * validated before being queued in the read queue. This is called before packet
+ * body decryption. pn_space is a QUIC_PN_SPACE_* value denoting which PN space
+ * the PN belongs to.
+ *
+ * If this callback returns 1, processing continues normally.
+ * If this callback returns 0, the packet is discarded.
+ *
+ * Other packets in the same datagram will still be processed where possible.
+ *
+ * The intended use for this function is to allow early validation of whether
+ * a PN is a potential duplicate before spending CPU time decrypting the
+ * packet payload.
+ *
+ * The callback is optional and can be unset by passing NULL for cb.
+ * cb_arg is an opaque value passed to cb.
+ */
+typedef int (ossl_qrl_early_rx_validation_cb)(QUIC_PN pn, int pn_space,
+                                              void *arg);
+
+int ossl_qrl_set_early_rx_validation_cb(OSSL_QRL *qrl,
+                                        ossl_qrl_early_rx_validation_cb *cb,
+                                        void *cb_arg);
+
+#endif
diff --git a/include/internal/quic_record_util.h b/include/internal/quic_record_util.h
new file mode 100644 (file)
index 0000000..cc10350
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * 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_QUIC_RECORD_UTIL_H
+# define OSSL_QUIC_RECORD_UTIL_H
+
+# include <openssl/ssl.h>
+
+/*
+ * QUIC Key Derivation Utilities
+ * =============================
+ */
+
+/* HKDF-Extract(salt, IKM) (RFC 5869) */
+int ossl_quic_hkdf_extract(OSSL_LIB_CTX *libctx,
+                           const char *propq,
+                           const EVP_MD *md,
+                           const unsigned char *salt, size_t salt_len,
+                           const unsigned char *ikm, size_t ikm_len,
+                           unsigned char *out, size_t out_len);
+
+/*
+ * QUIC Record Layer Ciphersuite Info
+ * ==================================
+ */
+
+/* Available QUIC Record Layer (QRL) ciphersuites. */
+# define QRL_SUITE_AES128GCM            1 /* SHA256 */
+# define QRL_SUITE_AES256GCM            2 /* SHA384 */
+# define QRL_SUITE_CHACHA20POLY1305     3 /* SHA256 */
+
+/* Returns cipher name in bytes or NULL if suite ID is invalid. */
+const char *ossl_qrl_get_suite_cipher_name(uint32_t suite_id);
+
+/* Returns hash function name in bytes or NULL if suite ID is invalid. */
+const char *ossl_qrl_get_suite_md_name(uint32_t suite_id);
+
+/* Returns secret length in bytes or 0 if suite ID is invalid. */
+uint32_t ossl_qrl_get_suite_secret_len(uint32_t suite_id);
+
+/* Returns key length in bytes or 0 if suite ID is invalid. */
+uint32_t ossl_qrl_get_suite_cipher_key_len(uint32_t suite_id);
+
+/* Returns IV length in bytes or 0 if suite ID is invalid. */
+uint32_t ossl_qrl_get_suite_cipher_iv_len(uint32_t suite_id);
+
+/* Returns AEAD auth tag length in bytes or 0 if suite ID is invalid. */
+uint32_t ossl_qrl_get_suite_cipher_tag_len(uint32_t suite_id);
+
+/* Returns a QUIC_HDR_PROT_CIPHER_* value or 0 if suite ID is invalid. */
+uint32_t ossl_qrl_get_suite_hdr_prot_cipher_id(uint32_t suite_id);
+
+/* Returns header protection key length in bytes or 0 if suite ID is invalid. */
+uint32_t ossl_qrl_get_suite_hdr_prot_key_len(uint32_t suite_id);
+
+#endif
index bd37019d21599a13af6bc16dac645f7ae2cfb0ca..b8b60c5cafa8130d48d516666c3b8b001c6e5ab2 100644 (file)
 # define OSSL_QUIC_TYPES_H
 
 # include <openssl/ssl.h>
+# include <assert.h>
+# include <string.h>
+
+/* QUIC encryption levels. */
+#define QUIC_ENC_LEVEL_INITIAL          0
+#define QUIC_ENC_LEVEL_HANDSHAKE        1
+#define QUIC_ENC_LEVEL_0RTT             2
+#define QUIC_ENC_LEVEL_1RTT             3
+#define QUIC_ENC_LEVEL_NUM              4
+
+/* QUIC packet number spaces. */
+#define QUIC_PN_SPACE_INITIAL           0
+#define QUIC_PN_SPACE_HANDSHAKE         1
+#define QUIC_PN_SPACE_APP               2
+#define QUIC_PN_SPACE_NUM               3
+
+static ossl_unused ossl_inline uint32_t
+ossl_quic_enc_level_to_pn_space(uint32_t enc_level)
+{
+    switch (enc_level) {
+        case QUIC_ENC_LEVEL_INITIAL:
+            return QUIC_PN_SPACE_INITIAL;
+        case QUIC_ENC_LEVEL_HANDSHAKE:
+            return QUIC_PN_SPACE_HANDSHAKE;
+        case QUIC_ENC_LEVEL_0RTT:
+        case QUIC_ENC_LEVEL_1RTT:
+            return QUIC_PN_SPACE_APP;
+        default:
+            assert(0);
+            return QUIC_PN_SPACE_APP;
+    }
+}
 
 /* QUIC packet number spaces. */
 #define QUIC_PN_SPACE_INITIAL       0
@@ -32,4 +64,19 @@ static ossl_unused ossl_inline QUIC_PN ossl_quic_pn_min(QUIC_PN a, QUIC_PN b)
     return a < b ? a : b;
 }
 
+/* QUIC connection ID representation. */
+#define QUIC_MAX_CONN_ID_LEN   20
+
+typedef struct quic_conn_id_st {
+    unsigned char id_len, id[QUIC_MAX_CONN_ID_LEN];
+} QUIC_CONN_ID;
+
+static ossl_unused ossl_inline int ossl_quic_conn_id_eq(const QUIC_CONN_ID *a,
+                                                        const QUIC_CONN_ID *b)
+{
+    if (a->id_len != b->id_len || a->id_len > QUIC_MAX_CONN_ID_LEN)
+        return 0;
+    return memcmp(a->id, b->id, a->id_len) == 0;
+}
+
 #endif
index 1ead6fcf99def9421e084dbfde3706c75b6576cd..704684b0b4c36a65abf3963687cc30100d08e379 100644 (file)
@@ -166,16 +166,10 @@ typedef struct ossl_quic_frame_stop_sending_st {
 } OSSL_QUIC_FRAME_STOP_SENDING;
 
 /* QUIC Frame: NEW_CONNECTION_ID */
-#define OSSL_QUIC_MAX_CONN_ID_LEN       20
-typedef struct ossl_quic_conn_id_st {
-    unsigned char   id_len; /* length of id in bytes */
-    unsigned char   id[OSSL_QUIC_MAX_CONN_ID_LEN];
-} OSSL_QUIC_CONN_ID;
-
 typedef struct ossl_quic_frame_new_conn_id_st {
     uint64_t              seq_num;
     uint64_t              retire_prior_to;
-    OSSL_QUIC_CONN_ID     conn_id;
+    QUIC_CONN_ID          conn_id;
     unsigned char         stateless_reset_token[16];
 } OSSL_QUIC_FRAME_NEW_CONN_ID;
 
diff --git a/include/internal/quic_wire_pkt.h b/include/internal/quic_wire_pkt.h
new file mode 100644 (file)
index 0000000..614593b
--- /dev/null
@@ -0,0 +1,435 @@
+/*
+ * 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_QUIC_WIRE_PKT_H
+# define OSSL_QUIC_WIRE_PKT_H
+
+# include <openssl/ssl.h>
+# include "internal/packet.h"
+# include "internal/quic_types.h"
+
+# define QUIC_VERSION_NONE   ((uint32_t)0)   /* Used for version negotiation */
+# define QUIC_VERSION_1      ((uint32_t)1)   /* QUIC v1 */
+
+/* QUIC logical packet type. These do not match wire values. */
+# define QUIC_PKT_TYPE_INITIAL        1
+# define QUIC_PKT_TYPE_0RTT           2
+# define QUIC_PKT_TYPE_HANDSHAKE      3
+# define QUIC_PKT_TYPE_RETRY          4
+# define QUIC_PKT_TYPE_1RTT           5
+# define QUIC_PKT_TYPE_VERSION_NEG    6
+
+/*
+ * Smallest possible QUIC packet size as per RFC (aside from version negotiation
+ * packets).
+ */
+#define QUIC_MIN_VALID_PKT_LEN_CRYPTO      21
+#define QUIC_MIN_VALID_PKT_LEN_VERSION_NEG  7
+#define QUIC_MIN_VALID_PKT_LEN              QUIC_MIN_VALID_PKT_LEN_VERSION_NEG
+
+typedef struct quic_pkt_hdr_ptrs_st QUIC_PKT_HDR_PTRS;
+
+/*
+ * QUIC Packet Header Protection
+ * =============================
+ *
+ * Functions to apply and remove QUIC packet header protection. A header
+ * protector is initialised using ossl_quic_hdr_protector_init and must be
+ * destroyed using ossl_quic_hdr_protector_destroy when no longer needed.
+ */
+typedef struct quic_hdr_protector_st {
+    OSSL_LIB_CTX       *libctx;
+    const char         *propq;
+    EVP_CIPHER_CTX     *cipher_ctx;
+    EVP_CIPHER         *cipher;
+    uint32_t            cipher_id;
+} QUIC_HDR_PROTECTOR;
+
+# define QUIC_HDR_PROT_CIPHER_AES_128    1
+# define QUIC_HDR_PROT_CIPHER_AES_256    2
+# define QUIC_HDR_PROT_CIPHER_CHACHA     3
+
+/*
+ * Initialises a header protector.
+ *
+ *   cipher_id:
+ *      The header protection cipher method to use. One of
+ *      QUIC_HDR_PROT_CIPHER_*. Must be chosen based on negotiated TLS cipher
+ *      suite.
+ *
+ *   quic_hp_key:
+ *      This must be the "quic hp" key derived from a traffic secret.
+ *
+ *      The length of the quic_hp_key must correspond to that expected for the
+ *      given cipher ID.
+ *
+ * The header protector performs amortisable initialisation in this function,
+ * therefore a header protector should be used for as long as possible.
+ *
+ * Returns 1 on success and 0 on failure.
+ */
+int ossl_quic_hdr_protector_init(QUIC_HDR_PROTECTOR *hpr,
+                                 OSSL_LIB_CTX *libctx,
+                                 const char *propq,
+                                 uint32_t cipher_id,
+                                 const unsigned char *quic_hp_key,
+                                 size_t quic_hp_key_len);
+
+/*
+ * Destroys a header protector. This is also safe to call on a zero-initialized
+ * OSSL_QUIC_HDR_PROTECTOR structure which has not been initialized, or which
+ * has already been destroyed.
+ */
+void ossl_quic_hdr_protector_destroy(QUIC_HDR_PROTECTOR *hpr);
+
+/*
+ * Removes header protection from a packet. The packet payload must currently be
+ * encrypted (i.e., you must remove header protection before decrypting packets
+ * received). The function examines the header buffer to determine which bytes
+ * of the header need to be decrypted.
+ *
+ * If this function fails, no data is modified.
+ *
+ * This is implemented as a call to ossl_quic_hdr_protector_decrypt_fields().
+ *
+ * Returns 1 on success and 0 on failure.
+ */
+int ossl_quic_hdr_protector_decrypt(QUIC_HDR_PROTECTOR *hpr,
+                                    QUIC_PKT_HDR_PTRS *ptrs);
+
+/*
+ * Applies header protection to a packet. The packet payload must already have
+ * been encrypted (i.e., you must apply header protection after encrypting
+ * a packet). The function examines the header buffer to determine which bytes
+ * of the header need to be encrypted.
+ *
+ * This is implemented as a call to ossl_quic_hdr_protector_encrypt_fields().
+ *
+ * Returns 1 on success and 0 on failure.
+ */
+int ossl_quic_hdr_protector_encrypt(QUIC_HDR_PROTECTOR *hpr,
+                                    QUIC_PKT_HDR_PTRS *ptrs);
+
+/*
+ * Removes header protection from a packet. The packet payload must currently
+ * be encrypted. This is a low-level function which assumes you have already
+ * determined which parts of the packet header need to be decrypted.
+ *
+ * sample:
+ *   The range of bytes in the packet to be used to generate the header
+ *   protection mask. It is permissible to set sample_len to the size of the
+ *   remainder of the packet; this function will only use as many bytes as
+ *   needed. If not enough sample bytes are provided, this function fails.
+ *
+ * first_byte:
+ *   The first byte of the QUIC packet header to be decrypted.
+ *
+ * pn:
+ *   Pointer to the start of the PN field. The caller is responsible
+ *   for ensuring at least four bytes follow this pointer.
+ *
+ * Returns 1 on success and 0 on failure.
+ */
+int ossl_quic_hdr_protector_decrypt_fields(QUIC_HDR_PROTECTOR *hpr,
+                                           const unsigned char *sample,
+                                           size_t sample_len,
+                                           unsigned char *first_byte,
+                                           unsigned char *pn_bytes);
+
+/*
+ * Works analogously to ossl_hdr_protector_decrypt_fields, but applies header
+ * protection instead of removing it.
+ */
+int ossl_quic_hdr_protector_encrypt_fields(QUIC_HDR_PROTECTOR *hpr,
+                                           const unsigned char *sample,
+                                           size_t sample_len,
+                                           unsigned char *first_byte,
+                                           unsigned char *pn_bytes);
+
+/*
+ * QUIC Packet Header
+ * ==================
+ *
+ * This structure provides a logical representation of a QUIC packet header.
+ *
+ * QUIC packet formats fall into the following categories:
+ *
+ *   Long Packets, which is subdivided into five possible packet types:
+ *     Version Negotiation (a special case);
+ *     Initial;
+ *     0-RTT;
+ *     Handshake; and
+ *     Retry
+ *
+ *   Short Packets, which comprises only a single packet type (1-RTT).
+ *
+ * The packet formats vary and common fields are found in some packets but
+ * not others. The below table indicates which fields are present in which
+ * kinds of packet. * indicates header protection is applied.
+ *
+ *   SLLLLL         Legend: 1=1-RTT, i=Initial, 0=0-RTT, h=Handshake
+ *   1i0hrv                 r=Retry, v=Version Negotiation
+ *   ------
+ *   1i0hrv         Header Form (0=Short, 1=Long)
+ *   1i0hr          Fixed Bit (always 1)
+ *   1              Spin Bit
+ *   1       *      Reserved Bits
+ *   1       *      Key Phase
+ *   1i0h    *      Packet Number Length
+ *    i0hr?         Long Packet Type
+ *    i0h           Type-Specific Bits
+ *    i0hr          Version (note: always 0 for Version Negotiation packets)
+ *   1i0hrv         Destination Connection ID
+ *    i0hrv         Source Connection ID
+ *   1i0h    *      Packet Number
+ *    i             Token
+ *    i0h           Length
+ *       r          Retry Token
+ *       r          Retry Integrity Tag
+ *
+ * For each field below, the conditions under which the field is valid are
+ * specified. If a field is not currently valid, it is initialized to a zero or
+ * NULL value.
+ */
+typedef struct quic_pkt_hdr_st {
+    /* [ALL] A QUIC_PKT_TYPE_* value. Always valid. */
+    unsigned int    type        :8;
+
+    /* [S] Value of the spin bit. Valid if (type == 1RTT). */
+    unsigned int    spin_bit    :1;
+
+    /*
+     * [S] Value of the Key Phase bit in the short packet.
+     * Valid if (type == 1RTT && !partial).
+     */
+    unsigned int    key_phase   :1;
+
+    /*
+     * [1i0h] Length of packet number in bytes. This is the decoded value.
+     * Valid if ((type == 1RTT || (version && type != RETRY)) && !partial).
+     */
+    unsigned int    pn_len      :4;
+
+    /*
+     * [ALL] Set to 1 if this is a partial decode because the packet header
+     * has not yet been deprotected. pn_len, pn and key_phase are not valid if
+     * this is set.
+     */
+    unsigned int    partial     :1;
+
+    /*
+     * [ALL] Whether the fixed bit was set. Note that only Version Negotiation
+     * packets are allowed to have this unset, so this will always be 1 for all
+     * other packet types (decode will fail if it is not set). Ignored when
+     * encoding unless encoding a Version Negotiation packet.
+     */
+    unsigned int    fixed       :1;
+
+    /* [L] Version field. Valid if (type != 1RTT). */
+    uint32_t        version;
+
+    /* [ALL] Number of bytes in the connection ID (max 20). Always valid. */
+    QUIC_CONN_ID    dst_conn_id;
+
+    /*
+     * [L] Number of bytes in the connection ID (max 20).
+     * Valid if (type != 1RTT).
+     */
+    QUIC_CONN_ID    src_conn_id;
+
+    /*
+     * [1i0h] Relatively-encoded packet number in raw, encoded form. The correct
+     * decoding of this value is context-dependent. The number of bytes valid in
+     * this buffer is determined by pn_len above. If the decode was partial,
+     * this field is not valid.
+     *
+     * Valid if ((type == 1RTT || (version && type != RETRY)) && !partial).
+     */
+    unsigned char           pn[4];
+
+    /*
+     * [i] Token field in Initial packet. Points to memory inside the decoded
+     * PACKET, and therefore is valid for as long as the PACKET's buffer is
+     * valid. token_len is the length of the token in bytes.
+     *
+     * Valid if (type == INITIAL).
+     */
+    const unsigned char    *token;
+    size_t                  token_len;
+
+    /*
+     * [i0h] Payload length in bytes.
+     *
+     * Valid if (type != 1RTT && type != RETRY && version).
+     */
+    size_t                  len;
+
+    /*
+     * Pointer to start of payload data in the packet. Points to memory inside
+     * the decoded PACKET, and therefore is valid for as long as the PACKET'S
+     * buffer is valid. The length of the buffer in bytes is in len above.
+     *
+     * For Version Negotiation packets, points to the array of supported
+     * versions.
+     *
+     * For Retry packets, points to the Retry packet payload, which comprises
+     * the Retry Token followed by a 16-byte Retry Integrity Tag.
+     *
+     * Regardless of whether a packet is a Version Negotiation packet (where the
+     * payload contains a list of supported versions), a Retry packet (where the
+     * payload contains a Retry Token and Retry Integrity Tag), or any other
+     * packet type (where the payload contains frames), the payload is not
+     * validated and the user must parse the payload bearing this in mind.
+     *
+     * If the decode was partial (partial is set), this points to the start of
+     * the packet number field, rather than the protected payload, as the length
+     * of the packet number field is unknown. The len field reflects this in
+     * this case (i.e., the len field is the number of payload bytes plus the
+     * number of bytes comprising the PN).
+     */
+    const unsigned char    *data;
+} QUIC_PKT_HDR;
+
+/*
+ * Extra information which can be output by the packet header decode functions
+ * for the assistance of the header protector. This avoids the header protector
+ * needing to partially re-decode the packet header.
+ */
+struct quic_pkt_hdr_ptrs_st {
+    unsigned char    *raw_start;        /* start of packet */
+    unsigned char    *raw_sample;       /* start of sampling range */
+    size_t            raw_sample_len;   /* maximum length of sampling range */
+
+    /*
+     * Start of PN field. Guaranteed to be NULL unless at least four bytes are
+     * available via this pointer.
+     */
+    unsigned char    *raw_pn;
+};
+
+/*
+ * If partial is 1, reads the unprotected parts of a protected packet header
+ * from a PACKET, performing a partial decode.
+ *
+ * If partial is 0, the input is assumed to have already had header protection
+ * removed, and all header fields are decoded.
+ *
+ * On success, the logical decode of the packet header is written to *hdr.
+ * hdr->partial is set or cleared according to whether a partial decode was
+ * performed. *ptrs is filled with pointers to various parts of the packet
+ * buffer.
+ *
+ * In order to decode short packets, the connection ID length being used must be
+ * known contextually, and should be passed as short_conn_id_len. If
+ * short_conn_id_len is set to an invalid value (a value greater than
+ * QUIC_MAX_CONN_ID_LEN), this function fails when trying to decode a short
+ * packet, but succeeds for long packets.
+ *
+ * Returns 1 on success and 0 on failure.
+ */
+int ossl_quic_wire_decode_pkt_hdr(PACKET *pkt,
+                                  size_t short_conn_id_len,
+                                  int partial,
+                                  QUIC_PKT_HDR *hdr,
+                                  QUIC_PKT_HDR_PTRS *ptrs);
+
+/*
+ * Encodes a packet header. The packet is written to pkt.
+ *
+ * The length of the (encrypted) packet payload should be written to hdr->len
+ * and will be placed in the serialized packet header. The payload data itself
+ * is not copied; the caller should write hdr->len bytes of encrypted payload to
+ * the WPACKET immediately after the call to this function. However,
+ * WPACKET_reserve_bytes is called for the payload size.
+ *
+ * This function does not apply header protection. You must apply header
+ * protection yourself after calling this function. *ptrs is filled with
+ * pointers which can be passed to a header protector, but this must be
+ * performed after the encrypted payload is written.
+ *
+ * The pointers in *ptrs are direct pointers into the WPACKET buffer. If more
+ * data is written to the WPACKET buffer, WPACKET buffer reallocations may
+ * occur, causing these pointers to become invalid. Therefore, you must not call
+ * any write WPACKET function between this call and the call to
+ * ossl_quic_hdr_protector_encrypt. This function calls WPACKET_reserve_bytes
+ * for the payload length, so you may assume hdr->len bytes are already free to
+ * write at the WPACKET cursor location once this function returns successfully.
+ * It is recommended that you call this function, write the encrypted payload,
+ * call ossl_quic_hdr_protector_encrypt, and then call
+ * WPACKET_allocate_bytes(hdr->len).
+ *
+ * Version Negotiation and Retry packets do not use header protection; for these
+ * header types, the fields in *ptrs are all written as zero. Version
+ * Negotiation, Retry and 1-RTT packets do not contain a Length field, but
+ * hdr->len bytes of data are still reserved in the WPACKET.
+ *
+ * If serializing a short packet and short_conn_id_len does not match the DCID
+ * specified in hdr, the function fails.
+ *
+ * Returns 1 on success and 0 on failure.
+ */
+int ossl_quic_wire_encode_pkt_hdr(WPACKET *pkt,
+                                  size_t short_conn_id_len,
+                                  const QUIC_PKT_HDR *hdr,
+                                  QUIC_PKT_HDR_PTRS *ptrs);
+
+/*
+ * Retrieves only the DCID from a packet header. This is intended for demuxer
+ * use. It avoids the need to parse the rest of the packet header twice.
+ *
+ * Information on packet length is not decoded, as this only needs to be used on
+ * the first packet in a datagram, therefore this takes a buffer and not a
+ * PACKET.
+ *
+ * Returns 1 on success and 0 on failure.
+ */
+int ossl_quic_wire_get_pkt_hdr_dst_conn_id(const unsigned char *buf,
+                                           size_t buf_len,
+                                           size_t short_conn_id_len,
+                                           QUIC_CONN_ID *dst_conn_id);
+
+/*
+ * Packet Number Encoding
+ * ======================
+ */
+
+/*
+ * Decode an encoded packet header QUIC PN.
+ *
+ * enc_pn is the raw encoded PN to decode. enc_pn_len is its length in bytes as
+ * indicated by packet headers. largest_pn is the largest PN successfully
+ * processed in the relevant PN space.
+ *
+ * The resulting PN is written to *res_pn.
+ *
+ * Returns 1 on success or 0 on failure.
+ */
+int ossl_quic_wire_decode_pkt_hdr_pn(const unsigned char *enc_pn,
+                                     size_t enc_pn_len,
+                                     QUIC_PN largest_pn,
+                                     QUIC_PN *res_pn);
+
+/*
+ * Determine how many bytes should be used to encode a PN. Returns the number of
+ * bytes (which will be in range [1, 4]).
+ */
+int ossl_quic_wire_determine_pn_len(QUIC_PN pn, QUIC_PN largest_acked);
+
+/*
+ * Encode a PN for a packet header using the specified number of bytes, which
+ * should have been determined by calling ossl_quic_wire_determine_pn_len. The
+ * PN encoding process is done in two parts to allow the caller to override PN
+ * encoding length if it wishes.
+ *
+ * Returns 1 on success and 0 on failure.
+ */
+int ossl_quic_wire_encode_pkt_hdr_pn(QUIC_PN pn,
+                                     unsigned char *enc_pn,
+                                     size_t enc_pn_len);
+#endif
index 9e411011e778833a37b341457fe9e6936861cce1..482338be95907aff6048ed95bc6bf54eb9416219 100644 (file)
@@ -1,3 +1,3 @@
 $LIBSSL=../../libssl
 
-SOURCE[$LIBSSL]=quic_method.c quic_impl.c quic_wire.c quic_ackm.c quic_statm.c cc_dummy.c
+SOURCE[$LIBSSL]=quic_method.c quic_impl.c quic_wire.c quic_ackm.c quic_statm.c cc_dummy.c quic_demux.c quic_record.c quic_record_util.c quic_wire_pkt.c
diff --git a/ssl/quic/quic_demux.c b/ssl/quic/quic_demux.c
new file mode 100644 (file)
index 0000000..3eb4f6d
--- /dev/null
@@ -0,0 +1,523 @@
+#include "internal/quic_demux.h"
+#include "internal/quic_wire_pkt.h"
+#include "internal/common.h"
+#include <openssl/lhash.h>
+
+#define OSSL_QUIC_DEMUX_MAX_MSGS_PER_CALL    32
+
+void ossl_quic_urxe_remove(QUIC_URXE_LIST *l, QUIC_URXE *e)
+{
+    /* Must be in list currently. */
+    OPENSSL_assert((e->prev != NULL || l->head == e)
+                   && (e->next != NULL || l->tail == e));
+
+    if (e->prev != NULL)
+        e->prev->next = e->next;
+    if (e->next != NULL)
+        e->next->prev = e->prev;
+
+    if (e == l->head)
+        l->head = e->next;
+    if (e == l->tail)
+        l->tail = e->prev;
+
+    e->next = e->prev = NULL;
+}
+
+void ossl_quic_urxe_insert_head(QUIC_URXE_LIST *l, QUIC_URXE *e)
+{
+    /* Must not be in list. */
+    OPENSSL_assert(e->prev == NULL && e->next == NULL);
+
+    if (l->head == NULL) {
+        l->head = l->tail = e;
+        e->next = e->prev = NULL;
+        return;
+    }
+
+    l->head->prev = e;
+    e->next = l->head;
+    e->prev = NULL;
+    l->head = e;
+}
+
+void ossl_quic_urxe_insert_tail(QUIC_URXE_LIST *l, QUIC_URXE *e)
+{
+    /* Must not be in list. */
+    OPENSSL_assert(e->prev == NULL && e->next == NULL);
+
+    if (l->tail == NULL) {
+        l->head = l->tail = e;
+        e->next = e->prev = NULL;
+        return;
+    }
+
+    l->tail->next = e;
+    e->prev = l->tail;
+    e->next = NULL;
+    l->tail = e;
+}
+
+/* Structure used to track a given connection ID. */
+typedef struct quic_demux_conn_st QUIC_DEMUX_CONN;
+
+struct quic_demux_conn_st {
+    QUIC_DEMUX_CONN            *next; /* used when unregistering only */
+    QUIC_CONN_ID                dst_conn_id;
+    ossl_quic_demux_cb_fn      *cb;
+    void                       *cb_arg;
+};
+
+DEFINE_LHASH_OF_EX(QUIC_DEMUX_CONN);
+
+static unsigned long demux_conn_hash(const QUIC_DEMUX_CONN *conn)
+{
+    size_t i;
+    unsigned long v = 0;
+
+    assert(conn->dst_conn_id.id_len <= QUIC_MAX_CONN_ID_LEN);
+
+    for (i = 0; i < conn->dst_conn_id.id_len; ++i)
+        v ^= ((unsigned long)conn->dst_conn_id.id[i])
+             << ((i * 8) % (sizeof(unsigned long) * 8));
+
+    return v;
+}
+
+static int demux_conn_cmp(const QUIC_DEMUX_CONN *a, const QUIC_DEMUX_CONN *b)
+{
+    return !ossl_quic_conn_id_eq(&a->dst_conn_id, &b->dst_conn_id);
+}
+
+struct quic_demux_st {
+    /* The underlying transport BIO with datagram semantics. */
+    BIO                        *net_bio;
+
+    /*
+     * QUIC short packets do not contain the length of the connection ID field,
+     * therefore it must be known contextually. The demuxer requires connection
+     * IDs of the same length to be used for all incoming packets.
+     */
+    size_t                      short_conn_id_len;
+
+    /* Default URXE buffer size in bytes. */
+    size_t                      default_urxe_alloc_len;
+
+    /* Hashtable mapping connection IDs to QUIC_DEMUX_CONN structures. */
+    LHASH_OF(QUIC_DEMUX_CONN)  *conns_by_id;
+
+    /*
+     * List of URXEs which are not currently in use (i.e., not filled with
+     * unconsumed data). These are moved to the pending list as they are filled.
+     */
+    QUIC_URXE_LIST              urx_free;
+    size_t                      num_urx_free;
+
+    /*
+     * List of URXEs which are filled with received encrypted data. These are
+     * removed from this list as we invoke the callbacks for each of them. They
+     * are then not on any list managed by us; we forget about them until our
+     * user calls ossl_quic_demux_release_urxe to return the URXE to us, at
+     * which point we add it to the free list.
+     */
+    QUIC_URXE_LIST              urx_pending;
+
+    /* Whether to use local address support. */
+    char                        use_local_addr;
+};
+
+QUIC_DEMUX *ossl_quic_demux_new(BIO *net_bio,
+                                size_t short_conn_id_len,
+                                size_t default_urxe_alloc_len)
+{
+    QUIC_DEMUX *demux;
+
+    demux = OPENSSL_zalloc(sizeof(QUIC_DEMUX));
+    if (demux == NULL)
+        return NULL;
+
+    demux->net_bio                  = net_bio;
+    demux->short_conn_id_len        = short_conn_id_len;
+    demux->default_urxe_alloc_len   = default_urxe_alloc_len;
+
+    demux->conns_by_id
+        = lh_QUIC_DEMUX_CONN_new(demux_conn_hash, demux_conn_cmp);
+    if (demux->conns_by_id == NULL) {
+        OPENSSL_free(demux);
+        return NULL;
+    }
+
+    if (net_bio != NULL
+        && BIO_dgram_get_local_addr_cap(net_bio)
+        && BIO_dgram_set_local_addr_enable(net_bio, 1))
+        demux->use_local_addr = 1;
+
+    return demux;
+}
+
+static void demux_free_conn_it(QUIC_DEMUX_CONN *conn, void *arg)
+{
+    OPENSSL_free(conn);
+}
+
+static void demux_free_urxl(QUIC_URXE_LIST *l)
+{
+    QUIC_URXE *e, *enext;
+
+    for (e = l->head; e != NULL; e = enext) {
+        enext = e->next;
+        OPENSSL_free(e);
+    }
+
+    l->head = l->tail = NULL;
+}
+
+void ossl_quic_demux_free(QUIC_DEMUX *demux)
+{
+    if (demux == NULL)
+        return;
+
+    /* Free all connection structures. */
+    lh_QUIC_DEMUX_CONN_doall_arg(demux->conns_by_id, demux_free_conn_it, NULL);
+    lh_QUIC_DEMUX_CONN_free(demux->conns_by_id);
+
+    /* Free all URXEs we are holding. */
+    demux_free_urxl(&demux->urx_free);
+    demux_free_urxl(&demux->urx_pending);
+
+    OPENSSL_free(demux);
+}
+
+static QUIC_DEMUX_CONN *demux_get_by_conn_id(QUIC_DEMUX *demux,
+                                             const QUIC_CONN_ID *dst_conn_id)
+{
+    QUIC_DEMUX_CONN key;
+
+    if (dst_conn_id->id_len > QUIC_MAX_CONN_ID_LEN)
+        return 0;
+
+    key.dst_conn_id = *dst_conn_id;
+    return lh_QUIC_DEMUX_CONN_retrieve(demux->conns_by_id, &key);
+}
+
+int ossl_quic_demux_register(QUIC_DEMUX *demux,
+                             const QUIC_CONN_ID *dst_conn_id,
+                             ossl_quic_demux_cb_fn *cb, void *cb_arg)
+{
+    QUIC_DEMUX_CONN *conn;
+
+    if (dst_conn_id == NULL
+        || dst_conn_id->id_len > QUIC_MAX_CONN_ID_LEN
+        || cb == NULL)
+        return 0;
+
+    /* Ensure not already registered. */
+    if (demux_get_by_conn_id(demux, dst_conn_id) != NULL)
+        /* Handler already registered with this connection ID. */
+        return 0;
+
+    conn = OPENSSL_zalloc(sizeof(QUIC_DEMUX_CONN));
+    if (conn == NULL)
+        return 0;
+
+    conn->dst_conn_id   = *dst_conn_id;
+    conn->cb            = cb;
+    conn->cb_arg        = cb_arg;
+
+    lh_QUIC_DEMUX_CONN_insert(demux->conns_by_id, conn);
+    return 1;
+}
+
+static void demux_unregister(QUIC_DEMUX *demux,
+                             QUIC_DEMUX_CONN *conn)
+{
+    lh_QUIC_DEMUX_CONN_delete(demux->conns_by_id, conn);
+    OPENSSL_free(conn);
+}
+
+int ossl_quic_demux_unregister(QUIC_DEMUX *demux,
+                               const QUIC_CONN_ID *dst_conn_id)
+{
+    QUIC_DEMUX_CONN *conn;
+
+    if (dst_conn_id == NULL
+        || dst_conn_id->id_len > QUIC_MAX_CONN_ID_LEN)
+        return 0;
+
+    conn = demux_get_by_conn_id(demux, dst_conn_id);
+    if (conn == NULL)
+        return 0;
+
+    demux_unregister(demux, conn);
+    return 1;
+}
+
+struct unreg_arg {
+    ossl_quic_demux_cb_fn *cb;
+    void *cb_arg;
+    QUIC_DEMUX_CONN *head;
+};
+
+static void demux_unregister_by_cb(QUIC_DEMUX_CONN *conn, void *arg_)
+{
+    struct unreg_arg *arg = arg_;
+
+    if (conn->cb == arg->cb && conn->cb_arg == arg->cb_arg) {
+        conn->next = arg->head;
+        arg->head = conn;
+    }
+}
+
+void ossl_quic_demux_unregister_by_cb(QUIC_DEMUX *demux,
+                                      ossl_quic_demux_cb_fn *cb,
+                                      void *cb_arg)
+{
+    QUIC_DEMUX_CONN *conn, *cnext;
+    struct unreg_arg arg = {0};
+    arg.cb      = cb;
+    arg.cb_arg  = cb_arg;
+
+    lh_QUIC_DEMUX_CONN_doall_arg(demux->conns_by_id,
+                                 demux_unregister_by_cb, &arg);
+
+    for (conn = arg.head; conn != NULL; conn = cnext) {
+        cnext = conn->next;
+        demux_unregister(demux, conn);
+    }
+}
+
+static QUIC_URXE *demux_alloc_urxe(size_t alloc_len)
+{
+    QUIC_URXE *e;
+
+    if (alloc_len >= SIZE_MAX - sizeof(QUIC_URXE))
+        return NULL;
+
+    e = OPENSSL_malloc(sizeof(QUIC_URXE) + alloc_len);
+    if (e == NULL)
+        return NULL;
+
+    e->prev = e->next   = NULL;
+    e->alloc_len        = alloc_len;
+    e->data_len         = 0;
+    return e;
+}
+
+static int demux_ensure_free_urxe(QUIC_DEMUX *demux, size_t min_num_free)
+{
+    QUIC_URXE *e;
+
+    while (demux->num_urx_free < min_num_free) {
+        e = demux_alloc_urxe(demux->default_urxe_alloc_len);
+        if (e == NULL)
+            return 0;
+
+        ossl_quic_urxe_insert_tail(&demux->urx_free, e);
+        ++demux->num_urx_free;
+    }
+
+    return 1;
+}
+
+/*
+ * Receive datagrams from network, placing them into URXEs.
+ *
+ * Returns 1 on success or 0 on failure.
+ *
+ * Precondition: at least one URXE is free
+ * Precondition: there are no pending URXEs
+ */
+static int demux_recv(QUIC_DEMUX *demux)
+{
+    BIO_MSG msg[OSSL_QUIC_DEMUX_MAX_MSGS_PER_CALL];
+    ossl_ssize_t rd, i;
+    QUIC_URXE *urxe = demux->urx_free.head, *unext;
+
+    /* This should never be called when we have any pending URXE. */
+    assert(demux->urx_pending.head == NULL);
+
+    if (demux->net_bio == NULL)
+        return 0;
+
+    /*
+     * Opportunistically receive as many messages as possible in a single
+     * syscall, determined by how many free URXEs are available.
+     */
+    for (i = 0; i < (ossl_ssize_t)OSSL_NELEM(msg); ++i, urxe = urxe->next) {
+        if (urxe == NULL) {
+            /* We need at least one URXE to receive into. */
+            if (!ossl_assert(i > 0))
+                return 0;
+
+            break;
+        }
+
+        /* Ensure we zero any fields added to BIO_MSG at a later date. */
+        memset(&msg[i], 0, sizeof(BIO_MSG));
+        msg[i].data     = ossl_quic_urxe_data(urxe);
+        msg[i].data_len = urxe->alloc_len;
+        msg[i].peer     = &urxe->peer;
+        if (demux->use_local_addr)
+            msg[i].local = &urxe->local;
+        else
+            BIO_ADDR_clear(&urxe->local);
+    }
+
+    rd = BIO_recvmmsg(demux->net_bio, msg, sizeof(BIO_MSG), i, 0);
+    if (rd <= 0)
+        return 0;
+
+    urxe = demux->urx_free.head;
+    for (i = 0; i < rd; ++i, urxe = unext) {
+        unext = urxe->next;
+        /* Set URXE with actual length of received datagram. */
+        urxe->data_len      = msg[i].data_len;
+        /* Move from free list to pending list. */
+        ossl_quic_urxe_remove(&demux->urx_free, urxe);
+        --demux->num_urx_free;
+        ossl_quic_urxe_insert_tail(&demux->urx_pending, urxe);
+    }
+
+    return 1;
+}
+
+/* Extract destination connection ID from the first packet in a datagram. */
+static int demux_identify_conn_id(QUIC_DEMUX *demux,
+                                  QUIC_URXE *e,
+                                  QUIC_CONN_ID *dst_conn_id)
+{
+    return ossl_quic_wire_get_pkt_hdr_dst_conn_id(ossl_quic_urxe_data(e),
+                                                  e->data_len,
+                                                  demux->short_conn_id_len,
+                                                  dst_conn_id);
+}
+
+/* Identify the connection structure corresponding to a given URXE. */
+static QUIC_DEMUX_CONN *demux_identify_conn(QUIC_DEMUX *demux, QUIC_URXE *e)
+{
+    QUIC_CONN_ID dst_conn_id;
+
+    if (!demux_identify_conn_id(demux, e, &dst_conn_id))
+        /*
+         * Datagram is so badly malformed we can't get the DCID from the first
+         * packet in it, so just give up.
+         */
+        return NULL;
+
+    return demux_get_by_conn_id(demux, &dst_conn_id);
+}
+
+/* Process a single pending URXE. */
+static int demux_process_pending_urxe(QUIC_DEMUX *demux, QUIC_URXE *e)
+{
+    QUIC_DEMUX_CONN *conn;
+
+    /* The next URXE we process should be at the head of the pending list. */
+    OPENSSL_assert(e == demux->urx_pending.head);
+
+    conn = demux_identify_conn(demux, e);
+    if (conn == NULL) {
+        /*
+         * We could not identify a connection. We will never be able to process
+         * this datagram, so get rid of it.
+         */
+        ossl_quic_urxe_remove(&demux->urx_pending, e);
+        ossl_quic_urxe_insert_tail(&demux->urx_free, e);
+        ++demux->num_urx_free;
+        return 1; /* keep processing pending URXEs */
+    }
+
+    /*
+     * Remove from list and invoke callback. The URXE now belongs to the
+     * callback. (QUIC_DEMUX_CONN never has non-NULL cb.)
+     */
+    ossl_quic_urxe_remove(&demux->urx_pending, e);
+    conn->cb(e, conn->cb_arg);
+    return 1;
+}
+
+/* Process pending URXEs to generate callbacks. */
+static int demux_process_pending_urxl(QUIC_DEMUX *demux)
+{
+    QUIC_URXE *e;
+
+    while ((e = demux->urx_pending.head) != NULL)
+        if (!demux_process_pending_urxe(demux, e))
+            return 0;
+
+    return 1;
+}
+
+/*
+ * Drain the pending URXE list, processing any pending URXEs by making their
+ * callbacks. If no URXEs are pending, a network read is attempted first.
+ */
+int ossl_quic_demux_pump(QUIC_DEMUX *demux)
+{
+    int ret;
+
+    if (demux->urx_pending.head == NULL) {
+        ret = demux_ensure_free_urxe(demux, OSSL_QUIC_DEMUX_MAX_MSGS_PER_CALL);
+        if (ret != 1)
+            return 0;
+
+        ret = demux_recv(demux);
+        if (ret != 1)
+            return 0;
+
+        /*
+         * If demux_recv returned successfully, we should always have something.
+         */
+        assert(demux->urx_pending.head != NULL);
+    }
+
+    return demux_process_pending_urxl(demux);
+}
+
+/* Artificially inject a packet into the demuxer for testing purposes. */
+int ossl_quic_demux_inject(QUIC_DEMUX *demux,
+                           const unsigned char *buf,
+                           size_t buf_len,
+                           const BIO_ADDR *peer,
+                           const BIO_ADDR *local)
+{
+    int ret;
+    QUIC_URXE *urxe;
+
+    ret = demux_ensure_free_urxe(demux, 1);
+    if (ret != 1)
+        return 0;
+
+    urxe = demux->urx_free.head;
+    if (buf_len > urxe->alloc_len)
+        return 0;
+
+    memcpy(ossl_quic_urxe_data(urxe), buf, buf_len);
+    urxe->data_len = buf_len;
+
+    if (peer != NULL)
+        urxe->peer = *peer;
+    else
+        BIO_ADDR_clear(&urxe->local);
+
+    if (local != NULL)
+        urxe->local = *local;
+    else
+        BIO_ADDR_clear(&urxe->local);
+
+    /* Move from free list to pending list. */
+    ossl_quic_urxe_remove(&demux->urx_free, urxe);
+    --demux->num_urx_free;
+    ossl_quic_urxe_insert_tail(&demux->urx_pending, urxe);
+
+    return demux_process_pending_urxl(demux);
+}
+
+/* Called by our user to return a URXE to the free list. */
+void ossl_quic_demux_release_urxe(QUIC_DEMUX *demux,
+                                  QUIC_URXE *e)
+{
+    OPENSSL_assert(e->prev == NULL && e->next == NULL);
+    ossl_quic_urxe_insert_tail(&demux->urx_free, e);
+    ++demux->num_urx_free;
+}
diff --git a/ssl/quic/quic_record.c b/ssl/quic/quic_record.c
new file mode 100644 (file)
index 0000000..95044d2
--- /dev/null
@@ -0,0 +1,1315 @@
+/*
+ * 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
+ */
+
+#include "internal/quic_record.h"
+#include "internal/common.h"
+#include "../ssl_local.h"
+
+/*
+ * Mark a packet in a bitfield.
+ *
+ * pkt_idx: index of packet within datagram.
+ */
+static ossl_inline void pkt_mark(uint64_t *bitf, size_t pkt_idx)
+{
+    assert(pkt_idx < QUIC_MAX_PKT_PER_URXE);
+    *bitf |= ((uint64_t)1) << pkt_idx;
+}
+
+/* Returns 1 if a packet is in the bitfield. */
+static ossl_inline int pkt_is_marked(const uint64_t *bitf, size_t pkt_idx)
+{
+    assert(pkt_idx < QUIC_MAX_PKT_PER_URXE);
+    return (*bitf & (((uint64_t)1) << pkt_idx)) != 0;
+}
+
+/*
+ * RXE
+ * ===
+ *
+ * RX Entries (RXEs) store processed (i.e., decrypted) data received from the
+ * network. One RXE is used per received QUIC packet.
+ */
+typedef struct rxe_st RXE;
+
+struct rxe_st {
+    RXE                *prev, *next;
+    size_t              data_len, alloc_len;
+
+    /* Extra fields for per-packet information. */
+    QUIC_PKT_HDR        hdr; /* data/len are decrypted payload */
+
+    /* Decoded packet number. */
+    QUIC_PN             pn;
+
+    /* Addresses copied from URXE. */
+    BIO_ADDR            peer, local;
+
+    /* Total length of the datagram which contained this packet. */
+    size_t              datagram_len;
+};
+
+typedef struct ossl_qrl_rxe_list_st {
+    RXE *head, *tail;
+} RXE_LIST;
+
+static ossl_inline unsigned char *rxe_data(const RXE *e)
+{
+    return (unsigned char *)(e + 1);
+}
+
+static void rxe_remove(RXE_LIST *l, RXE *e)
+{
+    if (e->prev != NULL)
+        e->prev->next = e->next;
+    if (e->next != NULL)
+        e->next->prev = e->prev;
+
+    if (e == l->head)
+        l->head = e->next;
+    if (e == l->tail)
+        l->tail = e->prev;
+
+    e->next = e->prev = NULL;
+}
+
+static void rxe_insert_tail(RXE_LIST *l, RXE *e)
+{
+    if (l->tail == NULL) {
+        l->head = l->tail = e;
+        e->next = e->prev = NULL;
+        return;
+    }
+
+    l->tail->next = e;
+    e->prev = l->tail;
+    e->next = NULL;
+    l->tail = e;
+}
+
+/*
+ * QRL
+ * ===
+ */
+
+/* (Encryption level, direction)-specific state. */
+typedef struct ossl_qrl_enc_level_st {
+    /* Hash function used for key derivation. */
+    EVP_MD                     *md;
+    /* Context used for packet body ciphering. */
+    EVP_CIPHER_CTX             *cctx;
+    /* IV used to construct nonces used for AEAD packet body ciphering. */
+    unsigned char               iv[EVP_MAX_IV_LENGTH];
+    /* Have we permanently discarded this encryption level? */
+    unsigned char               discarded;
+    /* QRL_SUITE_* value. */
+    uint32_t                    suite_id;
+    /* Length of authentication tag. */
+    uint32_t                    tag_len;
+    /*
+     * Cryptographic context used to apply and remove header protection from
+     * packet headers.
+     */
+    QUIC_HDR_PROTECTOR          hpr;
+} OSSL_QRL_ENC_LEVEL;
+
+struct ossl_qrl_st {
+    OSSL_LIB_CTX               *libctx;
+    const char                 *propq;
+
+    /* Demux to receive datagrams from. */
+    QUIC_DEMUX                 *rx_demux;
+
+    /* Length of connection IDs used in short-header packets in bytes. */
+    size_t                      short_conn_id_len;
+
+    /*
+     * List of URXEs which are filled with received encrypted data.
+     * These are returned to the DEMUX's free list as they are processed.
+     */
+    QUIC_URXE_LIST              urx_pending;
+
+    /*
+     * List of URXEs which we could not decrypt immediately and which are being
+     * kept in case they can be decrypted later.
+     */
+    QUIC_URXE_LIST              urx_deferred;
+
+    /*
+     * List of RXEs which are not currently in use. These are moved
+     * to the pending list as they are filled.
+     */
+    RXE_LIST                    rx_free;
+
+    /*
+     * List of RXEs which are filled with decrypted packets ready to be passed
+     * to the user. A RXE is removed from all lists inside the QRL when passed
+     * to the user, then returned to the free list when the user returns it.
+     */
+    RXE_LIST                    rx_pending;
+
+    /* Largest PN we have received and processed in a given PN space. */
+    QUIC_PN                     rx_largest_pn[QUIC_PN_SPACE_NUM];
+
+    /* Per encryption-level state. */
+    OSSL_QRL_ENC_LEVEL          rx_el[QUIC_ENC_LEVEL_NUM];
+    OSSL_QRL_ENC_LEVEL          tx_el[QUIC_ENC_LEVEL_NUM];
+
+    /* Bytes we have received since this counter was last cleared. */
+    uint64_t                    bytes_received;
+
+    /* Validation callback. */
+    ossl_qrl_early_rx_validation_cb    *rx_validation_cb;
+    void                               *rx_validation_cb_arg;
+};
+
+static void qrl_on_rx(QUIC_URXE *urxe, void *arg);
+
+/* Constants used for key derivation in QUIC v1. */
+static const unsigned char quic_client_in_label[] = {
+    0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x69, 0x6e /* "client in" */
+};
+static const unsigned char quic_server_in_label[] = {
+    0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x69, 0x6e /* "server in" */
+};
+static const unsigned char quic_v1_iv_label[] = {
+    0x71, 0x75, 0x69, 0x63, 0x20, 0x69, 0x76 /* "quic iv" */
+};
+static const unsigned char quic_v1_key_label[] = {
+    0x71, 0x75, 0x69, 0x63, 0x20, 0x6b, 0x65, 0x79 /* "quic key" */
+};
+static const unsigned char quic_v1_hp_label[] = {
+    0x71, 0x75, 0x69, 0x63, 0x20, 0x68, 0x70 /* "quic hp" */
+};
+/* Salt used to derive Initial packet protection keys (RFC 9001 Section 5.2). */
+static const unsigned char quic_v1_initial_salt[] = {
+    0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17,
+    0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a
+};
+
+static ossl_inline OSSL_QRL_ENC_LEVEL *qrl_get_el(OSSL_QRL *qrl,
+                                                  uint32_t enc_level,
+                                                  int is_tx)
+{
+    if (!ossl_assert(enc_level < QUIC_ENC_LEVEL_NUM))
+        return NULL;
+    return is_tx ? &qrl->tx_el[enc_level] : &qrl->rx_el[enc_level];
+}
+
+/*
+ * Returns 1 if we have key material for a given encryption level, 0 if we do
+ * not yet have material and -1 if the EL is discarded.
+ */
+static int qrl_have_el(OSSL_QRL *qrl, uint32_t enc_level, int is_tx)
+{
+    OSSL_QRL_ENC_LEVEL *el = qrl_get_el(qrl, enc_level, is_tx);
+
+    if (el->cctx != NULL)
+        return 1;
+    if (el->discarded)
+        return -1;
+    return 0;
+}
+
+/* Drops keying material for a given encryption level. */
+static void qrl_el_discard(OSSL_QRL *qrl, uint32_t enc_level,
+                           int is_tx, int final)
+{
+    OSSL_QRL_ENC_LEVEL *el = qrl_get_el(qrl, enc_level, is_tx);
+
+    if (el->discarded)
+        return;
+
+    if (el->cctx != NULL) {
+        ossl_quic_hdr_protector_destroy(&el->hpr);
+
+        EVP_CIPHER_CTX_free(el->cctx);
+        el->cctx    = NULL;
+
+        EVP_MD_free(el->md);
+        el->md      = NULL;
+    }
+
+    /* Zeroise IV. */
+    OPENSSL_cleanse(el->iv, sizeof(el->iv));
+
+    if (final)
+        el->discarded = 1;
+}
+
+/*
+ * Sets up cryptographic state for a given encryption level and direction by
+ * deriving "quic iv", "quic key" and "quic hp" values from a given secret.
+ *
+ * md is a hash function used for key derivation. If it is NULL, this function
+ * fetches the necessary hash function itself. If it is non-NULL, this function
+ * can reuse the caller's reference to a suitable EVP_MD; the EVP_MD provided
+ * must match the suite.
+ *
+ * On success where md is non-NULL, takes ownership of the caller's reference to
+ * md.
+ */
+static int qrl_el_set_secret(OSSL_QRL *qrl, uint32_t enc_level,
+                             uint32_t suite_id, EVP_MD *md,
+                             int is_tx,
+                             const unsigned char *secret,
+                             size_t secret_len)
+{
+    OSSL_QRL_ENC_LEVEL *el = qrl_get_el(qrl, enc_level, is_tx);
+    unsigned char key[EVP_MAX_KEY_LENGTH], hpr_key[EVP_MAX_KEY_LENGTH];
+    size_t key_len = 0, hpr_key_len = 0, iv_len = 0;
+    const char *cipher_name = NULL, *md_name = NULL;
+    EVP_CIPHER *cipher = NULL;
+    EVP_CIPHER_CTX *cctx = NULL;
+    int own_md = 0, have_hpr = 0;
+
+    if (el->discarded)
+        /* Should not be trying to reinitialise an EL which was discarded. */
+        return 0;
+
+    cipher_name = ossl_qrl_get_suite_cipher_name(suite_id);
+    iv_len      = ossl_qrl_get_suite_cipher_iv_len(suite_id);
+    key_len     = ossl_qrl_get_suite_cipher_key_len(suite_id);
+    hpr_key_len = ossl_qrl_get_suite_hdr_prot_key_len(suite_id);
+    if (cipher_name == NULL)
+        return 0;
+
+    if (secret_len != ossl_qrl_get_suite_secret_len(suite_id))
+        return 0;
+
+    if (md == NULL) {
+        md_name = ossl_qrl_get_suite_md_name(suite_id);
+
+        if ((md = EVP_MD_fetch(qrl->libctx,
+                                   md_name, qrl->propq)) == NULL)
+            return 0;
+
+        own_md = 1;
+    }
+
+    /* Derive "quic iv" key. */
+    if (!tls13_hkdf_expand_ex(qrl->libctx, qrl->propq,
+                              md,
+                              secret,
+                              quic_v1_iv_label,
+                              sizeof(quic_v1_iv_label),
+                              NULL, 0,
+                              el->iv, iv_len, 0))
+        goto err;
+
+    /* Derive "quic key" key. */
+    if (!tls13_hkdf_expand_ex(qrl->libctx, qrl->propq,
+                              md,
+                              secret,
+                              quic_v1_key_label,
+                              sizeof(quic_v1_key_label),
+                              NULL, 0,
+                              key, key_len, 0))
+        goto err;
+
+    /* Derive "quic hp" key. */
+    if (!tls13_hkdf_expand_ex(qrl->libctx, qrl->propq,
+                              md,
+                              secret,
+                              quic_v1_hp_label,
+                              sizeof(quic_v1_hp_label),
+                              NULL, 0,
+                              hpr_key, hpr_key_len, 0))
+        goto err;
+
+    /* Free any old context which is using old keying material. */
+    if (el->cctx != NULL) {
+        ossl_quic_hdr_protector_destroy(&el->hpr);
+        EVP_CIPHER_CTX_free(el->cctx);
+        el->cctx = NULL;
+    }
+
+    /* Setup header protection context. */
+    if (!ossl_quic_hdr_protector_init(&el->hpr,
+                                      qrl->libctx,
+                                      qrl->propq,
+                                      ossl_qrl_get_suite_hdr_prot_cipher_id(suite_id),
+                                      hpr_key,
+                                      hpr_key_len))
+        goto err;
+
+    have_hpr = 1;
+
+    /* Create and initialise cipher context. */
+    if ((cipher = EVP_CIPHER_fetch(qrl->libctx, cipher_name,
+                                   qrl->propq)) == NULL)
+        goto err;
+
+    if (!ossl_assert(iv_len  == (size_t)EVP_CIPHER_get_iv_length(cipher))
+        || !ossl_assert(key_len == (size_t)EVP_CIPHER_get_key_length(cipher)))
+        goto err;
+
+    if ((cctx = EVP_CIPHER_CTX_new()) == NULL)
+        goto err;
+
+    /* IV will be changed on RX so we don't need to use a real value here. */
+    if (!EVP_CipherInit_ex(cctx, cipher, NULL, key, el->iv, 0))
+        goto err;
+
+    el->suite_id    = suite_id;
+    el->cctx        = cctx;
+    el->md          = md;
+    el->tag_len     = ossl_qrl_get_suite_cipher_tag_len(suite_id);
+
+    /* Zeroize intermediate keys. */
+    OPENSSL_cleanse(key, sizeof(key));
+    OPENSSL_cleanse(hpr_key, sizeof(hpr_key));
+    EVP_CIPHER_free(cipher);
+    return 1;
+
+err:
+    if (have_hpr)
+        ossl_quic_hdr_protector_destroy(&el->hpr);
+    EVP_CIPHER_CTX_free(cctx);
+    EVP_CIPHER_free(cipher);
+    if (own_md)
+        EVP_MD_free(md);
+    return 0;
+}
+
+OSSL_QRL *ossl_qrl_new(const OSSL_QRL_ARGS *args)
+{
+    OSSL_QRL *qrl;
+    size_t i;
+
+    if (args->rx_demux == NULL)
+        return 0;
+
+    qrl = OPENSSL_zalloc(sizeof(OSSL_QRL));
+    if (qrl == NULL)
+        return 0;
+
+    for (i = 0; i < OSSL_NELEM(qrl->rx_largest_pn); ++i)
+        qrl->rx_largest_pn[i] = args->rx_init_largest_pn[i];
+
+    qrl->libctx             = args->libctx;
+    qrl->propq              = args->propq;
+    qrl->rx_demux           = args->rx_demux;
+    qrl->short_conn_id_len  = args->short_conn_id_len;
+    return qrl;
+}
+
+static void qrl_cleanup_rxl(RXE_LIST *l)
+{
+    RXE *e, *enext;
+    for (e = l->head; e != NULL; e = enext) {
+        enext = e->next;
+        OPENSSL_free(e);
+    }
+    l->head = l->tail = NULL;
+}
+
+static void qrl_cleanup_urxl(OSSL_QRL *qrl, QUIC_URXE_LIST *l)
+{
+    QUIC_URXE *e, *enext;
+    for (e = l->head; e != NULL; e = enext) {
+        enext = e->next;
+        ossl_quic_demux_release_urxe(qrl->rx_demux, e);
+    }
+    l->head = l->tail = NULL;
+}
+
+void ossl_qrl_free(OSSL_QRL *qrl)
+{
+    uint32_t i;
+
+    /* Unregister from the RX DEMUX. */
+    ossl_quic_demux_unregister_by_cb(qrl->rx_demux, qrl_on_rx, qrl);
+
+    /* Free RXE queue data. */
+    qrl_cleanup_rxl(&qrl->rx_free);
+    qrl_cleanup_rxl(&qrl->rx_pending);
+    qrl_cleanup_urxl(qrl, &qrl->urx_pending);
+    qrl_cleanup_urxl(qrl, &qrl->urx_deferred);
+
+    /* Drop keying material and crypto resources. */
+    for (i = 0; i < QUIC_ENC_LEVEL_NUM; ++i) {
+        qrl_el_discard(qrl, i, 0, 1);
+        qrl_el_discard(qrl, i, 1, 1);
+    }
+
+    OPENSSL_free(qrl);
+}
+
+static void qrl_on_rx(QUIC_URXE *urxe, void *arg)
+{
+    OSSL_QRL *qrl = arg;
+
+    /* Initialize our own fields inside the URXE and add to the pending list. */
+    urxe->processed     = 0;
+    urxe->hpr_removed   = 0;
+    ossl_quic_urxe_insert_tail(&qrl->urx_pending, urxe);
+}
+
+int ossl_qrl_add_dst_conn_id(OSSL_QRL *qrl,
+                             const QUIC_CONN_ID *dst_conn_id)
+{
+    return ossl_quic_demux_register(qrl->rx_demux,
+                                    dst_conn_id,
+                                    qrl_on_rx,
+                                    qrl);
+}
+
+int ossl_qrl_remove_dst_conn_id(OSSL_QRL *qrl,
+                                const QUIC_CONN_ID *dst_conn_id)
+{
+    return ossl_quic_demux_unregister(qrl->rx_demux, dst_conn_id);
+}
+
+static void qrl_requeue_deferred(OSSL_QRL *qrl)
+{
+    QUIC_URXE *e;
+
+    while ((e = qrl->urx_deferred.head) != NULL) {
+        ossl_quic_urxe_remove(&qrl->urx_deferred, e);
+        ossl_quic_urxe_insert_head(&qrl->urx_pending, e);
+    }
+}
+
+int ossl_qrl_provide_rx_secret(OSSL_QRL *qrl, uint32_t enc_level,
+                               uint32_t suite_id,
+                               const unsigned char *secret, size_t secret_len)
+{
+    if (enc_level == QUIC_ENC_LEVEL_INITIAL || enc_level >= QUIC_ENC_LEVEL_NUM)
+        return 0;
+
+    if (!qrl_el_set_secret(qrl, enc_level, suite_id, NULL,
+                           /*is_tx=*/0, secret, secret_len))
+        return 0;
+
+    /*
+     * Any packets we previously could not decrypt, we may now be able to
+     * decrypt, so move any datagrams containing deferred packets from the
+     * deferred to the pending queue.
+     */
+    qrl_requeue_deferred(qrl);
+    return 1;
+}
+
+/* Initialise key material for the INITIAL encryption level. */
+int ossl_qrl_provide_rx_secret_initial(OSSL_QRL *qrl,
+                                       const QUIC_CONN_ID *dst_conn_id)
+{
+    unsigned char initial_secret[32];
+    unsigned char client_initial_secret[32], server_initial_secret[32];
+    EVP_MD *sha256;
+    int have_rx = 0;
+
+    /* Initial encryption always uses SHA-256. */
+    if ((sha256 = EVP_MD_fetch(qrl->libctx,
+                               "SHA256", qrl->propq)) == NULL)
+        return 0;
+
+    /* Derive initial secret from destination connection ID. */
+    if (!ossl_quic_hkdf_extract(qrl->libctx, qrl->propq,
+                                sha256,
+                                quic_v1_initial_salt,
+                                sizeof(quic_v1_initial_salt),
+                                dst_conn_id->id,
+                                dst_conn_id->id_len,
+                                initial_secret,
+                                sizeof(initial_secret)))
+        goto err;
+
+    /* Derive "client in" secret. */
+    if (!tls13_hkdf_expand_ex(qrl->libctx, qrl->propq,
+                              sha256,
+                              initial_secret,
+                              quic_client_in_label,
+                              sizeof(quic_client_in_label),
+                              NULL, 0,
+                              client_initial_secret,
+                              sizeof(client_initial_secret), 0))
+        goto err;
+
+    /* Derive "server in" secret. */
+    if (!tls13_hkdf_expand_ex(qrl->libctx, qrl->propq,
+                              sha256,
+                              initial_secret,
+                              quic_server_in_label,
+                              sizeof(quic_server_in_label),
+                              NULL, 0,
+                              server_initial_secret,
+                              sizeof(server_initial_secret), 0))
+        goto err;
+
+    /* Setup RX cipher. Initial encryption always uses AES-128-GCM. */
+    if (!qrl_el_set_secret(qrl, QUIC_ENC_LEVEL_INITIAL,
+                           QRL_SUITE_AES128GCM,
+                           sha256,
+                           /*is_tx=*/0,
+                           server_initial_secret,
+                           sizeof(server_initial_secret)))
+        goto err;
+
+    have_rx = 1;
+
+    /*
+     * qrl_el_set_secret takes ownership of our ref to SHA256, so get a new ref
+     * for the following call for the TX side.
+     */
+    if (!EVP_MD_up_ref(sha256)) {
+        sha256 = NULL;
+        goto err;
+    }
+
+    /* Setup TX cipher. */
+    if (!qrl_el_set_secret(qrl, QUIC_ENC_LEVEL_INITIAL,
+                            QRL_SUITE_AES128GCM,
+                            sha256,
+                            /*is_tx=*/1,
+                            client_initial_secret,
+                            sizeof(client_initial_secret)))
+        goto err;
+
+    /*
+     * Any packets we previously could not decrypt, we may now be able to
+     * decrypt, so move any datagrams containing deferred packets from the
+     * deferred to the pending queue.
+     */
+    qrl_requeue_deferred(qrl);
+    return 1;
+
+err:
+    if (have_rx)
+        qrl_el_discard(qrl, QUIC_ENC_LEVEL_INITIAL, /*is_tx=*/0, 0);
+
+    EVP_MD_free(sha256);
+    return 0;
+}
+
+int ossl_qrl_discard_enc_level(OSSL_QRL *qrl, uint32_t enc_level)
+{
+    if (enc_level >= QUIC_ENC_LEVEL_NUM)
+        return 0;
+
+    qrl_el_discard(qrl, enc_level, 0, 1);
+    return 1;
+}
+
+/* Returns 1 if there are one or more pending RXEs. */
+int ossl_qrl_processed_read_pending(OSSL_QRL *qrl)
+{
+    return qrl->rx_pending.head != NULL;
+}
+
+/* Returns 1 if there are yet-unprocessed packets. */
+int ossl_qrl_unprocessed_read_pending(OSSL_QRL *qrl)
+{
+    return qrl->urx_pending.head != NULL || qrl->urx_deferred.head != NULL;
+}
+
+/* Pop the next pending RXE. Returns NULL if no RXE is pending. */
+static RXE *qrl_pop_pending_rxe(OSSL_QRL *qrl)
+{
+    RXE *rxe = qrl->rx_pending.head;
+
+    if (rxe == NULL)
+        return NULL;
+
+    rxe_remove(&qrl->rx_pending, rxe);
+    return rxe;
+}
+
+/* Allocate a new RXE. */
+static RXE *qrl_alloc_rxe(size_t alloc_len)
+{
+    RXE *rxe;
+
+    if (alloc_len >= SIZE_MAX - sizeof(RXE))
+        return NULL;
+
+    rxe = OPENSSL_malloc(sizeof(RXE) + alloc_len);
+    if (rxe == NULL)
+        return NULL;
+
+    rxe->prev = rxe->next = NULL;
+    rxe->alloc_len = alloc_len;
+    rxe->data_len  = 0;
+    return rxe;
+}
+
+/*
+ * Ensures there is at least one RXE in the RX free list, allocating a new entry
+ * if necessary. The returned RXE is in the RX free list; it is not popped.
+ *
+ * alloc_len is a hint which may be used to determine the RXE size if allocation
+ * is necessary. Returns NULL on allocation failure.
+ */
+static RXE *qrl_ensure_free_rxe(OSSL_QRL *qrl, size_t alloc_len)
+{
+    RXE *rxe;
+
+    if (qrl->rx_free.head != NULL)
+        return qrl->rx_free.head;
+
+    rxe = qrl_alloc_rxe(alloc_len);
+    if (rxe == NULL)
+        return NULL;
+
+    rxe_insert_tail(&qrl->rx_free, rxe);
+    return rxe;
+}
+
+/*
+ * Resize the data buffer attached to an RXE to be n bytes in size. The address
+ * of the RXE might change; the new address is returned, or NULL on failure, in
+ * which case the original RXE remains valid.
+ */
+static RXE *qrl_resize_rxe(RXE_LIST *rxl, RXE *rxe, size_t n)
+{
+    RXE *rxe2;
+
+    /* Should never happen. */
+    if (rxe == NULL)
+        return NULL;
+
+    if (n >= SIZE_MAX - sizeof(RXE))
+        return NULL;
+
+    /*
+     * NOTE: We do not clear old memory, although it does contain decrypted
+     * data.
+     */
+    rxe2 = OPENSSL_realloc(rxe, sizeof(RXE) + n);
+    if (rxe2 == NULL)
+        /* original RXE is still in tact unchanged */
+        return NULL;
+
+    if (rxe != rxe2) {
+        if (rxl->head == rxe)
+            rxl->head = rxe2;
+        if (rxl->tail == rxe)
+            rxl->tail = rxe2;
+        if (rxe->prev != NULL)
+            rxe->prev->next = rxe2;
+        if (rxe->next != NULL)
+            rxe->next->prev = rxe2;
+    }
+
+    rxe2->alloc_len = n;
+    return rxe2;
+}
+
+/*
+ * Ensure the data buffer attached to an RXE is at least n bytes in size.
+ * Returns NULL on failure.
+ */
+static RXE *qrl_reserve_rxe(RXE_LIST *rxl,
+                            RXE *rxe, size_t n)
+{
+    if (rxe->alloc_len >= n)
+        return rxe;
+
+    return qrl_resize_rxe(rxl, rxe, n);
+}
+
+/* Return a RXE handed out to the user back to our freelist. */
+static void qrl_recycle_rxe(OSSL_QRL *qrl, RXE *rxe)
+{
+    /* RXE should not be in any list */
+    assert(rxe->prev == NULL && rxe->next == NULL);
+    rxe_insert_tail(&qrl->rx_free, rxe);
+}
+
+/*
+ * Given a pointer to a pointer pointing to a buffer and the size of that
+ * buffer, copy the buffer into *prxe, expanding the RXE if necessary (its
+ * pointer may change due to realloc). *pi is the offset in bytes to copy the
+ * buffer to, and on success is updated to be the offset pointing after the
+ * copied buffer. *pptr is updated to point to the new location of the buffer.
+ */
+static int qrl_relocate_buffer(OSSL_QRL *qrl, RXE **prxe, size_t *pi,
+                               const unsigned char **pptr, size_t buf_len)
+{
+    RXE *rxe;
+    unsigned char *dst;
+
+    if (!buf_len)
+        return 1;
+
+    if ((rxe = qrl_reserve_rxe(&qrl->rx_free, *prxe, *pi + buf_len)) == NULL)
+        return 0;
+
+    *prxe = rxe;
+    dst = (unsigned char *)rxe_data(rxe) + *pi;
+
+    memcpy(dst, *pptr, buf_len);
+    *pi += buf_len;
+    *pptr = dst;
+    return 1;
+}
+
+static uint32_t qrl_determine_enc_level(const QUIC_PKT_HDR *hdr)
+{
+    switch (hdr->type) {
+        case QUIC_PKT_TYPE_INITIAL:
+            return QUIC_ENC_LEVEL_INITIAL;
+        case QUIC_PKT_TYPE_HANDSHAKE:
+            return QUIC_ENC_LEVEL_HANDSHAKE;
+        case QUIC_PKT_TYPE_0RTT:
+            return QUIC_ENC_LEVEL_0RTT;
+        case QUIC_PKT_TYPE_1RTT:
+            return QUIC_ENC_LEVEL_1RTT;
+
+        default:
+            assert(0);
+        case QUIC_PKT_TYPE_RETRY:
+        case QUIC_PKT_TYPE_VERSION_NEG:
+            return QUIC_ENC_LEVEL_INITIAL; /* not used */
+    }
+}
+
+static uint32_t rxe_determine_pn_space(RXE *rxe)
+{
+    uint32_t enc_level;
+
+    enc_level = qrl_determine_enc_level(&rxe->hdr);
+    return ossl_quic_enc_level_to_pn_space(enc_level);
+}
+
+static int qrl_validate_hdr_early(OSSL_QRL *qrl, RXE *rxe,
+                                  RXE *first_rxe)
+{
+    /* Ensure version is what we want. */
+    if (rxe->hdr.version != QUIC_VERSION_1
+        && rxe->hdr.version != QUIC_VERSION_NONE)
+        return 0;
+
+    /* Clients should never receive 0-RTT packets. */
+    if (rxe->hdr.type == QUIC_PKT_TYPE_0RTT)
+        return 0;
+
+    /* Version negotiation and retry packets must be the first packet. */
+    if (first_rxe != NULL && (rxe->hdr.type == QUIC_PKT_TYPE_VERSION_NEG
+                              || rxe->hdr.type == QUIC_PKT_TYPE_RETRY))
+        return 0;
+
+    /*
+     * If this is not the first packet in a datagram, the destination connection
+     * ID must match the one in that packet.
+     */
+    if (first_rxe != NULL &&
+        !ossl_quic_conn_id_eq(&first_rxe->hdr.dst_conn_id,
+                              &rxe->hdr.dst_conn_id))
+        return 0;
+
+    return 1;
+}
+
+/* Validate header and decode PN. */
+static int qrl_validate_hdr(OSSL_QRL *qrl, RXE *rxe)
+{
+    int pn_space = rxe_determine_pn_space(rxe);
+
+    if (!ossl_quic_wire_decode_pkt_hdr_pn(rxe->hdr.pn, rxe->hdr.pn_len,
+                                          qrl->rx_largest_pn[pn_space],
+                                          &rxe->pn))
+        return 0;
+
+    /*
+     * Allow our user to decide whether to discard the packet before we try and
+     * decrypt it.
+     */
+    if (qrl->rx_validation_cb != NULL
+        && !qrl->rx_validation_cb(rxe->pn, pn_space, qrl->rx_validation_cb_arg))
+        return 0;
+
+    return 1;
+}
+
+/*
+ * Tries to decrypt a packet payload.
+ *
+ * Returns 1 on success or 0 on failure (which is permanent). The payload is
+ * decrypted from src and written to dst. The buffer dst must be of at least
+ * src_len bytes in length. The actual length of the output in bytes is written
+ * to *dec_len on success, which will always be equal to or less than (usually
+ * less than) src_len.
+ */
+static int qrl_decrypt_pkt_body(OSSL_QRL *qrl, unsigned char *dst,
+                                const unsigned char *src,
+                                size_t src_len, size_t *dec_len,
+                                const unsigned char *aad, size_t aad_len,
+                                QUIC_PN pn, uint32_t enc_level)
+{
+    int l = 0, l2 = 0;
+    unsigned char nonce[EVP_MAX_IV_LENGTH];
+    size_t nonce_len, i;
+    OSSL_QRL_ENC_LEVEL *el = &qrl->rx_el[enc_level];
+
+    if (src_len > INT_MAX || aad_len > INT_MAX || el->tag_len >= src_len)
+        return 0;
+
+    /* We should not have been called if we do not have key material. */
+    if (!ossl_assert(qrl_have_el(qrl, enc_level, /*is_tx=*/0) == 1))
+        return 0;
+
+    /* Construct nonce (nonce=IV ^ PN). */
+    nonce_len = EVP_CIPHER_CTX_get_iv_length(el->cctx);
+    if (!ossl_assert(nonce_len >= sizeof(QUIC_PN)))
+        return 0;
+
+    memcpy(nonce, el->iv, nonce_len);
+    for (i = 0; i < sizeof(QUIC_PN); ++i)
+        nonce[nonce_len - i - 1] ^= (unsigned char)(pn >> (i * 8));
+
+    /* type and key will already have been setup; feed the IV. */
+    if (EVP_CipherInit_ex(el->cctx, NULL,
+                          NULL, NULL, nonce, /*enc=*/0) != 1)
+        return 0;
+
+    /* Feed the AEAD tag we got so the cipher can validate it. */
+    if (EVP_CIPHER_CTX_ctrl(el->cctx, EVP_CTRL_AEAD_SET_TAG,
+                            el->tag_len,
+                            (unsigned char *)src + src_len - el->tag_len) != 1)
+        return 0;
+
+    /* Feed AAD data. */
+    if (EVP_CipherUpdate(el->cctx, NULL, &l, aad, aad_len) != 1)
+        return 0;
+
+    /* Feed encrypted packet body. */
+    if (EVP_CipherUpdate(el->cctx, dst, &l, src, src_len - el->tag_len) != 1)
+        return 0;
+
+    /* Ensure authentication succeeded. */
+    if (EVP_CipherFinal_ex(el->cctx, NULL, &l2) != 1)
+        return 0;
+
+    *dec_len = l;
+    return 1;
+}
+
+static ossl_inline void ignore_res(int x)
+{
+    /* No-op. */
+}
+
+/* Process a single packet in a datagram. */
+static int qrl_process_pkt(OSSL_QRL *qrl, QUIC_URXE *urxe,
+                           PACKET *pkt, size_t pkt_idx,
+                           RXE **first_rxe,
+                           size_t datagram_len)
+{
+    RXE *rxe;
+    const unsigned char *eop = NULL;
+    size_t i, aad_len = 0, dec_len = 0;
+    PACKET orig_pkt = *pkt;
+    const unsigned char *sop = PACKET_data(pkt);
+    unsigned char *dst;
+    char need_second_decode = 0, already_processed = 0;
+    QUIC_PKT_HDR_PTRS ptrs;
+    uint32_t pn_space, enc_level;
+
+    /*
+     * Get a free RXE. If we need to allocate a new one, use the packet length
+     * as a good ballpark figure.
+     */
+    rxe = qrl_ensure_free_rxe(qrl, PACKET_remaining(pkt));
+    if (rxe == NULL)
+        return 0;
+
+    /* Have we already processed this packet? */
+    if (pkt_is_marked(&urxe->processed, pkt_idx))
+        already_processed = 1;
+
+    /*
+     * Decode the header into the RXE structure. We first decrypt and read the
+     * unprotected part of the packet header (unless we already removed header
+     * protection, in which case we decode all of it).
+     */
+    need_second_decode = !pkt_is_marked(&urxe->hpr_removed, pkt_idx);
+    if (!ossl_quic_wire_decode_pkt_hdr(pkt,
+                                      qrl->short_conn_id_len,
+                                      need_second_decode, &rxe->hdr, &ptrs))
+        goto malformed;
+
+    /*
+     * Our successful decode above included an intelligible length and the
+     * PACKET is now pointing to the end of the QUIC packet.
+     */
+    eop = PACKET_data(pkt);
+
+    /*
+     * Make a note of the first RXE so we can later ensure the destination
+     * connection IDs of all packets in a datagram mater.
+     */
+    if (pkt_idx == 0)
+        *first_rxe = rxe;
+
+    /*
+     * Early header validation. Since we now know the packet length, we can also
+     * now skip over it if we already processed it.
+     */
+    if (already_processed
+        || !qrl_validate_hdr_early(qrl, rxe, pkt_idx == 0 ? NULL : *first_rxe))
+        goto malformed;
+
+    if (rxe->hdr.type == QUIC_PKT_TYPE_VERSION_NEG
+        || rxe->hdr.type == QUIC_PKT_TYPE_RETRY) {
+        /*
+         * Version negotiation and retry packets are a special case. They do not
+         * contain a payload which needs decrypting and have no header
+         * protection.
+         */
+
+        /* Just copy the payload from the URXE to the RXE. */
+        if ((rxe = qrl_reserve_rxe(&qrl->rx_free, rxe, rxe->hdr.len)) == NULL)
+            /*
+             * Allocation failure. EOP will be pointing to the end of the
+             * datagram so processing of this datagram will end here.
+             */
+            goto malformed;
+
+        /* We are now committed to returning the packet. */
+        memcpy(rxe_data(rxe), rxe->hdr.data, rxe->hdr.len);
+        pkt_mark(&urxe->processed, pkt_idx);
+
+        rxe->hdr.data = rxe_data(rxe);
+
+        /* Move RXE to pending. */
+        rxe_remove(&qrl->rx_free, rxe);
+        rxe_insert_tail(&qrl->rx_pending, rxe);
+        return 0; /* success, did not defer */
+    }
+
+    /* Determine encryption level of packet. */
+    enc_level = qrl_determine_enc_level(&rxe->hdr);
+
+    /* If we do not have keying material for this encryption level yet, defer. */
+    switch (qrl_have_el(qrl, enc_level, /*is_tx=*/0)) {
+        case 1:
+            /* We have keys. */
+            break;
+        case 0:
+            /* No keys yet. */
+            goto cannot_decrypt;
+        default:
+            /* We already discarded keys for this EL, we will never process this.*/
+            goto malformed;
+    }
+
+    /*
+     * We will copy any token included in the packet to the start of our RXE
+     * data buffer (so that we don't reference the URXE buffer any more and can
+     * recycle it). Track our position in the RXE buffer by index instead of
+     * pointer as the pointer may change as reallocs occur.
+     */
+    i = 0;
+
+    /*
+     * rxe->hdr.data is now pointing at the (encrypted) packet payload. rxe->hdr
+     * also has fields pointing into the PACKET buffer which will be going away
+     * soon (the URXE will be reused for another incoming packet).
+     *
+     * Firstly, relocate some of these fields into the RXE as needed.
+     *
+     * Relocate token buffer and fix pointer.
+     */
+    if (rxe->hdr.type == QUIC_PKT_TYPE_INITIAL
+        && !qrl_relocate_buffer(qrl, &rxe, &i, &rxe->hdr.token,
+                                rxe->hdr.token_len))
+        goto malformed;
+
+    /* Now remove header protection. */
+    *pkt = orig_pkt;
+
+    if (need_second_decode) {
+        if (!ossl_quic_hdr_protector_decrypt(&qrl->rx_el[enc_level].hpr, &ptrs))
+            goto malformed;
+
+        /*
+         * We have removed header protection, so don't attempt to do it again if
+         * the packet gets deferred and processed again.
+         */
+        pkt_mark(&urxe->hpr_removed, pkt_idx);
+
+        /* Decode the now unprotected header. */
+        if (ossl_quic_wire_decode_pkt_hdr(pkt, qrl->short_conn_id_len,
+                                          0, &rxe->hdr, NULL) != 1)
+            goto malformed;
+    }
+
+    /* Validate header and decode PN. */
+    if (!qrl_validate_hdr(qrl, rxe))
+        goto malformed;
+
+    /*
+     * We automatically discard INITIAL keys when successfully decrypting a
+     * HANDSHAKE packet.
+     */
+    if (enc_level == QUIC_ENC_LEVEL_HANDSHAKE)
+        qrl_el_discard(qrl, QUIC_ENC_LEVEL_INITIAL, 0, 1);
+
+    /*
+     * The AAD data is the entire (unprotected) packet header including the PN.
+     * The packet header has been unprotected in place, so we can just reuse the
+     * PACKET buffer. The header ends where the payload begins.
+     */
+    aad_len = rxe->hdr.data - sop;
+
+    /* Ensure the RXE buffer size is adequate for our payload. */
+    if ((rxe = qrl_reserve_rxe(&qrl->rx_free, rxe, rxe->hdr.len + i)) == NULL) {
+        /*
+         * Allocation failure, treat as malformed and do not bother processing
+         * any further packets in the datagram as they are likely to also
+         * encounter allocation failures.
+         */
+        eop = NULL;
+        goto malformed;
+    }
+
+    /*
+     * We decrypt the packet body to immediately after the token at the start of
+     * the RXE buffer (where present).
+     *
+     * Do the decryption from the PACKET (which points into URXE memory) to our
+     * RXE payload (single-copy decryption), then fixup the pointers in the
+     * header to point to our new buffer.
+     *
+     * If decryption fails this is considered a permanent error; we defer
+     * packets we don't yet have decryption keys for above, so if this fails,
+     * something has gone wrong with the handshake process or a packet has been
+     * corrupted.
+     */
+    dst = (unsigned char *)rxe_data(rxe) + i;
+    if (!qrl_decrypt_pkt_body(qrl, dst, rxe->hdr.data, rxe->hdr.len,
+                              &dec_len, sop, aad_len, rxe->pn, enc_level))
+        goto malformed;
+
+    /*
+     * We have now successfully decrypted the packet payload. If there are
+     * additional packets in the datagram, it is possible we will fail to
+     * decrypt them and need to defer them until we have some key material we
+     * don't currently possess. If this happens, the URXE will be moved to the
+     * deferred queue. Since a URXE corresponds to one datagram, which may
+     * contain multiple packets, we must ensure any packets we have already
+     * processed in the URXE are not processed again (this is an RFC
+     * requirement). We do this by marking the nth packet in the datagram as
+     * processed.
+     *
+     * We are now committed to returning this decrypted packet to the user,
+     * meaning we now consider the packet processed and must mark it
+     * accordingly.
+     */
+    pkt_mark(&urxe->processed, pkt_idx);
+
+    /*
+     * Update header to point to the decrypted buffer, which may be shorter
+     * due to AEAD tags, block padding, etc.
+     */
+    rxe->hdr.data       = dst;
+    rxe->hdr.len        = dec_len;
+    rxe->data_len       = dec_len;
+    rxe->datagram_len   = datagram_len;
+
+    /* We processed the PN successfully, so update largest processed PN. */
+    pn_space = rxe_determine_pn_space(rxe);
+    if (rxe->pn > qrl->rx_largest_pn[pn_space])
+        qrl->rx_largest_pn[pn_space] = rxe->pn;
+
+    /* Copy across network addresses from URXE to RXE. */
+    rxe->peer   = urxe->peer;
+    rxe->local  = urxe->local;
+
+    /* Move RXE to pending. */
+    rxe_remove(&qrl->rx_free, rxe);
+    rxe_insert_tail(&qrl->rx_pending, rxe);
+    return 0; /* success, did not defer; not distinguished from failure */
+
+cannot_decrypt:
+    /*
+     * We cannot process this packet right now (but might be able to later). We
+     * MUST attempt to process any other packets in the datagram, so defer it
+     * and skip over it.
+     */
+    assert(eop != NULL && eop >= PACKET_data(pkt));
+    /*
+     * We don't care if this fails as it will just result in the packet being at
+     * the end of the datagram buffer.
+     */
+    ignore_res(PACKET_forward(pkt, eop - PACKET_data(pkt)));
+    return 1; /* deferred */
+
+malformed:
+    if (eop != NULL) {
+        /*
+         * This packet cannot be processed and will never be processable. We
+         * were at least able to decode its header and determine its length, so
+         * we can skip over it and try to process any subsequent packets in the
+         * datagram.
+         *
+         * Mark as processed as an optimization.
+         */
+        assert(eop >= PACKET_data(pkt));
+        pkt_mark(&urxe->processed, pkt_idx);
+        /* We don't care if this fails (see above) */
+        ignore_res(PACKET_forward(pkt, eop - PACKET_data(pkt)));
+    } else {
+        /*
+         * This packet cannot be processed and will never be processable.
+         * Because even its header is not intelligible, we cannot examine any
+         * further packets in the datagram because its length cannot be
+         * discerned.
+         *
+         * Advance over the entire remainder of the datagram, and mark it as
+         * processed gap as an optimization.
+         */
+        pkt_mark(&urxe->processed, pkt_idx);
+        /* We don't care if this fails (see above) */
+        ignore_res(PACKET_forward(pkt, PACKET_remaining(pkt)));
+    }
+    return 0; /* failure, did not defer; not distinguished from success */
+}
+
+/* Process a datagram which was received. */
+static int qrl_process_datagram(OSSL_QRL *qrl, QUIC_URXE *e,
+                                const unsigned char *data,
+                                size_t data_len)
+{
+    int have_deferred = 0;
+    PACKET pkt;
+    size_t pkt_idx = 0;
+    RXE *first_rxe = NULL;
+
+    qrl->bytes_received += data_len;
+
+    if (!PACKET_buf_init(&pkt, data, data_len))
+        return 0;
+
+    for (; PACKET_remaining(&pkt) > 0; ++pkt_idx) {
+        /*
+         * A packet smallest than the minimum possible QUIC packet size is not
+         * considered valid. We also ignore more than a certain number of
+         * packets within the same datagram.
+         */
+        if (PACKET_remaining(&pkt) < QUIC_MIN_VALID_PKT_LEN
+            || pkt_idx >= QUIC_MAX_PKT_PER_URXE)
+            break;
+
+        /*
+         * We note whether packet processing resulted in a deferral since
+         * this means we need to move the URXE to the deferred list rather
+         * than the free list after we're finished dealing with it for now.
+         *
+         * However, we don't otherwise care here whether processing succeeded or
+         * failed, as the RFC says even if a packet in a datagram is malformed,
+         * we should still try to process any packets following it.
+         *
+         * In the case where the packet is so malformed we can't determine its
+         * lenngth, qrl_process_pkt will take care of advancing to the end of
+         * the packet, so we will exit the loop automatically in this case.
+         */
+        if (qrl_process_pkt(qrl, e, &pkt, pkt_idx, &first_rxe, data_len))
+            have_deferred = 1;
+    }
+
+    /* Only report whether there were any deferrals. */
+    return have_deferred;
+}
+
+/* Process a single pending URXE. */
+static int qrl_process_one_urxl(OSSL_QRL *qrl, QUIC_URXE *e)
+{
+    int was_deferred;
+
+    /* The next URXE we process should be at the head of the pending list. */
+    if (!ossl_assert(e == qrl->urx_pending.head))
+        return 0;
+
+    /*
+     * Attempt to process the datagram. The return value indicates only if
+     * processing of the datagram was deferred. If we failed to process the
+     * datagram, we do not attempt to process it again and silently eat the
+     * error.
+     */
+    was_deferred = qrl_process_datagram(qrl, e, ossl_quic_urxe_data(e),
+                                        e->data_len);
+
+    /*
+     * Remove the URXE from the pending list and return it to
+     * either the free or deferred list.
+     */
+    ossl_quic_urxe_remove(&qrl->urx_pending, e);
+    if (was_deferred > 0)
+        ossl_quic_urxe_insert_tail(&qrl->urx_deferred, e);
+    else
+        ossl_quic_demux_release_urxe(qrl->rx_demux, e);
+
+    return 1;
+}
+
+/* Process any pending URXEs to generate pending RXEs. */
+static int qrl_process_urxl(OSSL_QRL *qrl)
+{
+    QUIC_URXE *e;
+
+    while ((e = qrl->urx_pending.head) != NULL)
+        if (!qrl_process_one_urxl(qrl, e))
+            return 0;
+
+    return 1;
+}
+
+int ossl_qrl_read_pkt(OSSL_QRL *qrl, OSSL_QRL_RX_PKT *pkt)
+{
+    RXE *rxe;
+
+    if (!ossl_qrl_processed_read_pending(qrl)) {
+        if (!qrl_process_urxl(qrl))
+            return 0;
+
+        if (!ossl_qrl_processed_read_pending(qrl))
+            return 0;
+    }
+
+    rxe = qrl_pop_pending_rxe(qrl);
+    if (!ossl_assert(rxe != NULL))
+        return 0;
+
+    pkt->handle     = rxe;
+    pkt->hdr        = &rxe->hdr;
+    pkt->peer
+        = BIO_ADDR_family(&rxe->peer) != AF_UNSPEC ? &rxe->peer : NULL;
+    pkt->local
+        = BIO_ADDR_family(&rxe->local) != AF_UNSPEC ? &rxe->local : NULL;
+    return 1;
+}
+
+void ossl_qrl_release_pkt(OSSL_QRL *qrl, void *handle)
+{
+    RXE *rxe = handle;
+
+    qrl_recycle_rxe(qrl, rxe);
+}
+
+uint64_t ossl_qrl_get_bytes_received(OSSL_QRL *qrl, int clear)
+{
+    uint64_t v = qrl->bytes_received;
+
+    if (clear)
+        qrl->bytes_received = 0;
+
+    return v;
+}
+
+int ossl_qrl_set_early_rx_validation_cb(OSSL_QRL *qrl,
+                                        ossl_qrl_early_rx_validation_cb *cb,
+                                        void *cb_arg)
+{
+    qrl->rx_validation_cb       = cb;
+    qrl->rx_validation_cb_arg   = cb_arg;
+    return 1;
+}
diff --git a/ssl/quic/quic_record_util.c b/ssl/quic/quic_record_util.c
new file mode 100644 (file)
index 0000000..6d0eeb5
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * 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
+ */
+
+#include "internal/quic_record_util.h"
+#include "internal/quic_wire_pkt.h"
+#include <openssl/kdf.h>
+#include <openssl/core_names.h>
+
+/*
+ * QUIC Key Derivation Utilities
+ * =============================
+ */
+int ossl_quic_hkdf_extract(OSSL_LIB_CTX *libctx,
+                           const char *propq,
+                           const EVP_MD *md,
+                           const unsigned char *salt, size_t salt_len,
+                           const unsigned char *ikm, size_t ikm_len,
+                           unsigned char *out, size_t out_len)
+{
+    int ret = 0;
+    EVP_KDF *kdf = NULL;
+    EVP_KDF_CTX *kctx = NULL;
+    OSSL_PARAM params[7], *p = params;
+    int mode = EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY;
+    const char *md_name;
+
+    if ((md_name = EVP_MD_get0_name(md)) == NULL
+        || (kdf = EVP_KDF_fetch(libctx, OSSL_KDF_NAME_HKDF, propq)) == NULL
+        || (kctx = EVP_KDF_CTX_new(kdf)) == NULL)
+        goto err;
+
+    *p++ = OSSL_PARAM_construct_int(OSSL_KDF_PARAM_MODE, &mode);
+    *p++ = OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST,
+                                            (char *)md_name, 0);
+    *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SALT,
+                                             (unsigned char *)salt, salt_len);
+    *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_KEY,
+                                             (unsigned char *)ikm, ikm_len);
+    *p++ = OSSL_PARAM_construct_end();
+
+    ret = EVP_KDF_derive(kctx, out, out_len, params);
+
+err:
+    EVP_KDF_CTX_free(kctx);
+    EVP_KDF_free(kdf);
+    return ret;
+}
+
+/*
+ * QUIC Record Layer Ciphersuite Info
+ * ==================================
+ */
+
+struct suite_info {
+    const char *cipher_name, *md_name;
+    uint32_t secret_len, cipher_key_len, cipher_iv_len, cipher_tag_len;
+    uint32_t hdr_prot_key_len, hdr_prot_cipher_id;
+};
+
+static const struct suite_info suite_aes128gcm = {
+    "AES-128-GCM", "SHA256", 32, 16, 12, 16, 16,
+    QUIC_HDR_PROT_CIPHER_AES_128
+};
+
+static const struct suite_info suite_aes256gcm = {
+    "AES-256-GCM", "SHA384", 48, 32, 12, 16, 32,
+    QUIC_HDR_PROT_CIPHER_AES_256
+};
+
+static const struct suite_info suite_chacha20poly1305 = {
+    "ChaCha20-Poly1305", "SHA256", 32, 32, 12, 16, 32,
+    QUIC_HDR_PROT_CIPHER_CHACHA
+};
+
+static const struct suite_info *get_suite(uint32_t suite_id)
+{
+    switch (suite_id) {
+        case QRL_SUITE_AES128GCM:
+            return &suite_aes128gcm;
+        case QRL_SUITE_AES256GCM:
+            return &suite_aes256gcm;
+        case QRL_SUITE_CHACHA20POLY1305:
+            return &suite_chacha20poly1305;
+        default:
+            return NULL;
+    }
+}
+
+const char *ossl_qrl_get_suite_cipher_name(uint32_t suite_id)
+{
+    const struct suite_info *c = get_suite(suite_id);
+    return c != NULL ? c->cipher_name : NULL;
+}
+
+const char *ossl_qrl_get_suite_md_name(uint32_t suite_id)
+{
+    const struct suite_info *c = get_suite(suite_id);
+    return c != NULL ? c->md_name : NULL;
+}
+
+uint32_t ossl_qrl_get_suite_secret_len(uint32_t suite_id)
+{
+    const struct suite_info *c = get_suite(suite_id);
+    return c != NULL ? c->secret_len : 0;
+}
+
+uint32_t ossl_qrl_get_suite_cipher_key_len(uint32_t suite_id)
+{
+    const struct suite_info *c = get_suite(suite_id);
+    return c != NULL ? c->cipher_key_len : 0;
+}
+
+uint32_t ossl_qrl_get_suite_cipher_iv_len(uint32_t suite_id)
+{
+    const struct suite_info *c = get_suite(suite_id);
+    return c != NULL ? c->cipher_iv_len : 0;
+}
+
+uint32_t ossl_qrl_get_suite_cipher_tag_len(uint32_t suite_id)
+{
+    const struct suite_info *c = get_suite(suite_id);
+    return c != NULL ? c->cipher_tag_len : 0;
+}
+
+uint32_t ossl_qrl_get_suite_hdr_prot_cipher_id(uint32_t suite_id)
+{
+    const struct suite_info *c = get_suite(suite_id);
+    return c != NULL ? c->hdr_prot_cipher_id : 0;
+}
+
+uint32_t ossl_qrl_get_suite_hdr_prot_key_len(uint32_t suite_id)
+{
+    const struct suite_info *c = get_suite(suite_id);
+    return c != NULL ? c->hdr_prot_key_len : 0;
+}
index a3a9b252faa0928852b73a7056ccffeae9653e10..4d19ad6013efbb7d91d03e4ef8c5ee9d885bcbff 100644 (file)
@@ -263,7 +263,7 @@ int ossl_quic_wire_encode_frame_streams_blocked(WPACKET *pkt,
 int ossl_quic_wire_encode_frame_new_conn_id(WPACKET *pkt,
                                             const OSSL_QUIC_FRAME_NEW_CONN_ID *f)
 {
-    if (f->conn_id.id_len > OSSL_QUIC_MAX_CONN_ID_LEN)
+    if (f->conn_id.id_len > QUIC_MAX_CONN_ID_LEN)
         return 0;
 
     if (!encode_frame_hdr(pkt, OSSL_QUIC_FRAME_TYPE_NEW_CONN_ID)
@@ -680,7 +680,7 @@ int ossl_quic_wire_decode_frame_new_conn_id(PACKET *pkt,
             || !PACKET_get_quic_vlint(pkt, &f->seq_num)
             || !PACKET_get_quic_vlint(pkt, &f->retire_prior_to)
             || !PACKET_get_1(pkt, &len)
-            || len > OSSL_QUIC_MAX_CONN_ID_LEN)
+            || len > QUIC_MAX_CONN_ID_LEN)
         return 0;
 
     f->conn_id.id_len = (unsigned char)len;
@@ -688,8 +688,8 @@ int ossl_quic_wire_decode_frame_new_conn_id(PACKET *pkt,
         return 0;
 
     /* Clear unused bytes to allow consistent memcmp. */
-    if (len < OSSL_QUIC_MAX_CONN_ID_LEN)
-        memset(f->conn_id.id + len, 0, OSSL_QUIC_MAX_CONN_ID_LEN - len);
+    if (len < QUIC_MAX_CONN_ID_LEN)
+        memset(f->conn_id.id + len, 0, QUIC_MAX_CONN_ID_LEN - len);
 
     if (!PACKET_copy_bytes(pkt, f->stateless_reset_token,
                            sizeof(f->stateless_reset_token)))
diff --git a/ssl/quic/quic_wire_pkt.c b/ssl/quic/quic_wire_pkt.c
new file mode 100644 (file)
index 0000000..5d90d70
--- /dev/null
@@ -0,0 +1,678 @@
+/*
+ * 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
+ */
+
+#include "internal/quic_wire_pkt.h"
+
+int ossl_quic_hdr_protector_init(QUIC_HDR_PROTECTOR *hpr,
+                                 OSSL_LIB_CTX *libctx,
+                                 const char *propq,
+                                 uint32_t cipher_id,
+                                 const unsigned char *quic_hp_key,
+                                 size_t quic_hp_key_len)
+{
+    const char *cipher_name = NULL;
+
+    switch (cipher_id) {
+        case QUIC_HDR_PROT_CIPHER_AES_128:
+            cipher_name = "AES-128-ECB";
+            break;
+        case QUIC_HDR_PROT_CIPHER_AES_256:
+            cipher_name = "AES-256-ECB";
+            break;
+        case QUIC_HDR_PROT_CIPHER_CHACHA:
+            cipher_name = "ChaCha20";
+            break;
+        default:
+            return 0;
+    }
+
+    hpr->cipher_ctx = EVP_CIPHER_CTX_new();
+    if (hpr->cipher_ctx == NULL)
+        return 0;
+
+    hpr->cipher = EVP_CIPHER_fetch(libctx, cipher_name, propq);
+    if (hpr->cipher == NULL
+        || quic_hp_key_len != (size_t)EVP_CIPHER_get_key_length(hpr->cipher))
+        goto err;
+
+    if (!EVP_CipherInit_ex(hpr->cipher_ctx, hpr->cipher, NULL,
+                           quic_hp_key, NULL, 1))
+        goto err;
+
+    hpr->libctx     = libctx;
+    hpr->propq      = propq;
+    hpr->cipher_id  = cipher_id;
+    return 1;
+
+err:
+    ossl_quic_hdr_protector_destroy(hpr);
+    return 0;
+}
+
+void ossl_quic_hdr_protector_destroy(QUIC_HDR_PROTECTOR *hpr)
+{
+    EVP_CIPHER_CTX_free(hpr->cipher_ctx);
+    hpr->cipher_ctx = NULL;
+
+    EVP_CIPHER_free(hpr->cipher);
+    hpr->cipher = NULL;
+}
+
+static int hdr_generate_mask(QUIC_HDR_PROTECTOR *hpr,
+                             const unsigned char *sample, size_t sample_len,
+                             unsigned char *mask)
+{
+    int l = 0;
+    unsigned char dst[16];
+    static const unsigned char zeroes[5] = {0};
+    size_t i;
+
+    if (hpr->cipher_id == QUIC_HDR_PROT_CIPHER_AES_128
+        || hpr->cipher_id == QUIC_HDR_PROT_CIPHER_AES_256) {
+        if (sample_len < 16)
+            return 0;
+
+        if (!EVP_CipherInit_ex(hpr->cipher_ctx, NULL, NULL, NULL, NULL, 1)
+            || !EVP_CipherUpdate(hpr->cipher_ctx, dst, &l, sample, 16))
+            return 0;
+
+        for (i = 0; i < 5; ++i)
+            mask[i] = dst[i];
+    } else if (hpr->cipher_id == QUIC_HDR_PROT_CIPHER_CHACHA) {
+        if (sample_len < 16)
+            return 0;
+
+        if (!EVP_CipherInit_ex(hpr->cipher_ctx, NULL, NULL, NULL, sample, 1)
+            || !EVP_CipherUpdate(hpr->cipher_ctx, mask, &l,
+                                 zeroes, sizeof(zeroes)))
+            return 0;
+    } else {
+        assert(0);
+        return 0;
+    }
+
+    return 1;
+}
+
+int ossl_quic_hdr_protector_decrypt(QUIC_HDR_PROTECTOR *hpr,
+                                    QUIC_PKT_HDR_PTRS *ptrs)
+{
+    return ossl_quic_hdr_protector_decrypt_fields(hpr,
+                                                  ptrs->raw_sample,
+                                                  ptrs->raw_sample_len,
+                                                  ptrs->raw_start,
+                                                  ptrs->raw_pn);
+}
+
+int ossl_quic_hdr_protector_decrypt_fields(QUIC_HDR_PROTECTOR *hpr,
+                                           const unsigned char *sample,
+                                           size_t sample_len,
+                                           unsigned char *first_byte,
+                                           unsigned char *pn_bytes)
+{
+    unsigned char mask[5], pn_len, i;
+
+    if (!hdr_generate_mask(hpr, sample, sample_len, mask))
+        return 0;
+
+    *first_byte ^= mask[0] & ((*first_byte & 0x80) != 0 ? 0xf : 0x1f);
+    pn_len = (*first_byte & 0x3) + 1;
+
+    for (i = 0; i < pn_len; ++i)
+        pn_bytes[i] ^= mask[i + 1];
+
+    return 1;
+}
+
+int ossl_quic_hdr_protector_encrypt(QUIC_HDR_PROTECTOR *hpr,
+                                    QUIC_PKT_HDR_PTRS *ptrs)
+{
+    return ossl_quic_hdr_protector_encrypt_fields(hpr,
+                                                  ptrs->raw_sample,
+                                                  ptrs->raw_sample_len,
+                                                  ptrs->raw_start,
+                                                  ptrs->raw_pn);
+}
+
+int ossl_quic_hdr_protector_encrypt_fields(QUIC_HDR_PROTECTOR *hpr,
+                                           const unsigned char *sample,
+                                           size_t sample_len,
+                                           unsigned char *first_byte,
+                                           unsigned char *pn_bytes)
+{
+    unsigned char mask[5], pn_len, i;
+
+    if (!hdr_generate_mask(hpr, sample, sample_len, mask))
+        return 0;
+
+    pn_len = (*first_byte & 0x3) + 1;
+    for (i = 0; i < pn_len; ++i)
+        pn_bytes[i] ^= mask[i + 1];
+
+    *first_byte ^= mask[0] & ((*first_byte & 0x80) != 0 ? 0xf : 0x1f);
+    return 1;
+}
+
+int ossl_quic_wire_decode_pkt_hdr(PACKET *pkt,
+                                  size_t short_conn_id_len,
+                                  int partial,
+                                  QUIC_PKT_HDR *hdr,
+                                  QUIC_PKT_HDR_PTRS *ptrs)
+{
+    unsigned int b0;
+    unsigned char *pn = NULL;
+    size_t l = PACKET_remaining(pkt);
+
+    if (ptrs != NULL) {
+        ptrs->raw_start         = (unsigned char *)PACKET_data(pkt);
+        ptrs->raw_sample        = NULL;
+        ptrs->raw_sample_len    = 0;
+        ptrs->raw_pn            = NULL;
+    }
+
+    if (l < QUIC_MIN_VALID_PKT_LEN
+        || !PACKET_get_1(pkt, &b0))
+        return 0;
+
+    hdr->partial = partial;
+
+    if ((b0 & 0x80) == 0) {
+        /* Short header. */
+        if (short_conn_id_len > QUIC_MAX_CONN_ID_LEN)
+            return 0;
+
+        if ((b0 & 0x40) == 0 /* fixed bit not set? */
+            || l < QUIC_MIN_VALID_PKT_LEN_CRYPTO)
+            return 0;
+
+        hdr->type       = QUIC_PKT_TYPE_1RTT;
+        hdr->fixed      = 1;
+        hdr->spin_bit   = (b0 & 0x20) != 0;
+        if (partial) {
+            hdr->key_phase  = 0; /* protected, zero for now */
+            hdr->pn_len     = 0; /* protected, zero for now */
+        } else {
+            hdr->key_phase  = (b0 & 0x4) != 0;
+            hdr->pn_len     = (b0 & 0x3) + 1;
+        }
+
+        /* Copy destination connection ID field to header structure. */
+        if (!PACKET_copy_bytes(pkt, hdr->dst_conn_id.id, short_conn_id_len))
+            return 0;
+
+        hdr->dst_conn_id.id_len = short_conn_id_len;
+
+        /*
+         * Skip over the PN. If this is a partial decode, the PN length field
+         * currently has header protection applied. Thus we do not know the
+         * length of the PN but we are allowed to assume it is 4 bytes long at
+         * this stage.
+         */
+        memset(hdr->pn, 0, sizeof(hdr->pn));
+        pn = (unsigned char *)PACKET_data(pkt);
+        if (partial) {
+            if (!PACKET_forward(pkt, sizeof(hdr->pn)))
+                return 0;
+        } else {
+            if (!PACKET_copy_bytes(pkt, hdr->pn, hdr->pn_len))
+                return 0;
+        }
+
+        /* Fields not used in short-header packets. */
+        hdr->version            = 0;
+        hdr->src_conn_id.id_len = 0;
+        hdr->token              = NULL;
+        hdr->token_len          = 0;
+
+        /*
+         * Short-header packets always come last in a datagram, the length
+         * is the remainder of the buffer.
+         */
+        hdr->len                = PACKET_remaining(pkt);
+        hdr->data               = PACKET_data(pkt);
+
+        /*
+         * Skip over payload so we are pointing at the start of the next packet,
+         * if any.
+         */
+        if (!PACKET_forward(pkt, hdr->len))
+            return 0;
+    } else {
+        /* Long header. */
+        unsigned long version;
+        unsigned int dst_conn_id_len, src_conn_id_len, raw_type;
+
+        if (!PACKET_get_net_4(pkt, &version))
+            return 0;
+
+        /*
+         * All QUIC packets must have the fixed bit set, except exceptionally
+         * for Version Negotiation packets.
+         */
+        if (version != 0 && (b0 & 0x40) == 0)
+            return 0;
+
+        if (!PACKET_get_1(pkt, &dst_conn_id_len)
+            || dst_conn_id_len > QUIC_MAX_CONN_ID_LEN
+            || !PACKET_copy_bytes(pkt, hdr->dst_conn_id.id, dst_conn_id_len)
+            || !PACKET_get_1(pkt, &src_conn_id_len)
+            || src_conn_id_len > QUIC_MAX_CONN_ID_LEN
+            || !PACKET_copy_bytes(pkt, hdr->src_conn_id.id, src_conn_id_len))
+            return 0;
+
+        hdr->version            = (uint32_t)version;
+        hdr->dst_conn_id.id_len = (unsigned char)dst_conn_id_len;
+        hdr->src_conn_id.id_len = (unsigned char)src_conn_id_len;
+
+        if (version == 0) {
+            /*
+             * Version negotiation packet. Version negotiation packets are
+             * identified by a version field of 0 and the type bits in the first
+             * byte are ignored (they may take any value, and we ignore them).
+             */
+            hdr->type       = QUIC_PKT_TYPE_VERSION_NEG;
+            hdr->fixed      = (b0 & 0x40) != 0;
+
+            hdr->data       = PACKET_data(pkt);
+            hdr->len        = PACKET_remaining(pkt);
+
+            /* Version negotiation packets are always fully decoded. */
+            hdr->partial    = 0;
+
+            /* Fields not used in version negotiation packets. */
+            hdr->pn_len             = 0;
+            hdr->spin_bit           = 0;
+            hdr->key_phase          = 0;
+            hdr->token              = NULL;
+            hdr->token_len          = 0;
+            memset(hdr->pn, 0, sizeof(hdr->pn));
+
+            if (!PACKET_forward(pkt, hdr->len))
+                return 0;
+        } else if (version != QUIC_VERSION_1) {
+            /* Unknown version, do not decode. */
+            return 0;
+        } else {
+            if (l < QUIC_MIN_VALID_PKT_LEN_CRYPTO)
+                return 0;
+
+            /* Get long packet type and decode to QUIC_PKT_TYPE_*. */
+            raw_type = ((b0 >> 4) & 0x3);
+
+            switch (raw_type) {
+                case 0: hdr->type = QUIC_PKT_TYPE_INITIAL; break;
+                case 1: hdr->type = QUIC_PKT_TYPE_0RTT; break;
+                case 2: hdr->type = QUIC_PKT_TYPE_HANDSHAKE; break;
+                case 3: hdr->type = QUIC_PKT_TYPE_RETRY; break;
+            }
+
+            hdr->pn_len     = 0;
+            hdr->fixed      = 1;
+
+            /* Fields not used in long-header packets. */
+            hdr->spin_bit   = 0;
+            hdr->key_phase  = 0;
+
+            if (hdr->type == QUIC_PKT_TYPE_INITIAL) {
+                /* Initial packet. */
+                uint64_t token_len;
+
+                if (!PACKET_get_quic_vlint(pkt, &token_len)
+                    || token_len > SIZE_MAX
+                    || !PACKET_get_bytes(pkt, &hdr->token, token_len))
+                    return 0;
+
+                hdr->token_len  = (size_t)token_len;
+                if (token_len == 0)
+                    hdr->token = NULL;
+            } else {
+                hdr->token      = NULL;
+                hdr->token_len  = 0;
+            }
+
+            if (hdr->type == QUIC_PKT_TYPE_RETRY) {
+                /* Retry packet. */
+                hdr->data       = PACKET_data(pkt);
+                hdr->len        = PACKET_remaining(pkt);
+
+                /* Retry packets are always fully decoded. */
+                hdr->partial    = 0;
+
+                /* Fields not used in Retry packets. */
+                memset(hdr->pn, 0, sizeof(hdr->pn));
+
+                if (!PACKET_forward(pkt, hdr->len))
+                    return 0;
+            } else {
+                /* Initial, 0-RTT or Handshake packet. */
+                uint64_t len;
+
+                hdr->pn_len = partial ? 0 : (b0 & 3) + 1;
+
+                if (!PACKET_get_quic_vlint(pkt, &len)
+                    || len < sizeof(hdr->pn)
+                    || len > PACKET_remaining(pkt))
+                    return 0;
+
+                /*
+                 * Skip over the PN. If this is a partial decode, the PN length
+                 * field currently has header protection applied. Thus we do not
+                 * know the length of the PN but we are allowed to assume it is
+                 * 4 bytes long at this stage.
+                 */
+                pn = (unsigned char *)PACKET_data(pkt);
+                memset(hdr->pn, 0, sizeof(hdr->pn));
+                if (partial) {
+                    if (!PACKET_forward(pkt, sizeof(hdr->pn)))
+                        return 0;
+
+                    hdr->len = (size_t)(len - sizeof(hdr->pn));
+                } else {
+                    if (!PACKET_copy_bytes(pkt, hdr->pn, hdr->pn_len))
+                        return 0;
+
+                    hdr->len = (size_t)(len - hdr->pn_len);
+                }
+
+                hdr->data = PACKET_data(pkt);
+
+                /* Skip over packet body. */
+                if (!PACKET_forward(pkt, hdr->len))
+                    return 0;
+            }
+        }
+    }
+
+    if (ptrs != NULL) {
+        ptrs->raw_pn = pn;
+        if (pn != NULL) {
+            ptrs->raw_sample        = pn + 4;
+            ptrs->raw_sample_len    = PACKET_end(pkt) - ptrs->raw_sample;
+        }
+    }
+
+    return 1;
+}
+
+int ossl_quic_wire_encode_pkt_hdr(WPACKET *pkt,
+                                  size_t short_conn_id_len,
+                                  const QUIC_PKT_HDR *hdr,
+                                  QUIC_PKT_HDR_PTRS *ptrs)
+{
+    unsigned char b0;
+    size_t off_start, off_sample, off_sample_end, off_pn;
+
+    if (!WPACKET_get_total_written(pkt, &off_start))
+        return 0;
+
+    if (ptrs != NULL) {
+        ptrs->raw_start         = NULL;
+        ptrs->raw_sample        = NULL;
+        ptrs->raw_sample_len    = 0;
+        ptrs->raw_pn            = 0;
+    }
+
+    /* Cannot serialize a partial header, or one whose DCID length is wrong. */
+    if (hdr->partial
+        || (hdr->type == QUIC_PKT_TYPE_1RTT
+            && hdr->dst_conn_id.id_len != short_conn_id_len))
+        return 0;
+
+    if (hdr->type == QUIC_PKT_TYPE_1RTT) {
+        /* Short header. */
+
+        /*
+         * Cannot serialize a header whose DCID length is wrong, or with an
+         * invalid PN length.
+         */
+        if (hdr->dst_conn_id.id_len != short_conn_id_len
+            || short_conn_id_len > QUIC_MAX_CONN_ID_LEN
+            || hdr->pn_len < 1 || hdr->pn_len > 4)
+            return 0;
+
+        b0 = (hdr->spin_bit << 5)
+             | (hdr->key_phase << 2)
+             | (hdr->pn_len - 1)
+             | 0x40; /* fixed bit */
+
+        if (!WPACKET_put_bytes_u8(pkt, b0)
+            || !WPACKET_memcpy(pkt, hdr->dst_conn_id.id, short_conn_id_len)
+            || !WPACKET_get_total_written(pkt, &off_pn)
+            || !WPACKET_memcpy(pkt, hdr->pn, hdr->pn_len))
+            return 0;
+    } else {
+        /* Long header. */
+        unsigned int raw_type;
+
+        if (hdr->dst_conn_id.id_len > QUIC_MAX_CONN_ID_LEN
+            || hdr->src_conn_id.id_len > QUIC_MAX_CONN_ID_LEN)
+            return 0;
+
+        if (hdr->type != QUIC_PKT_TYPE_VERSION_NEG
+            && hdr->type != QUIC_PKT_TYPE_RETRY
+            && (hdr->pn_len < 1 || hdr->pn_len > 4))
+            return 0;
+
+        switch (hdr->type) {
+            case QUIC_PKT_TYPE_VERSION_NEG:
+                if (hdr->version != 0)
+                    return 0;
+
+                /* Version negotiation packets use zero for the type bits */
+                raw_type = 0;
+                break;
+
+            case QUIC_PKT_TYPE_INITIAL:     raw_type = 0; break;
+            case QUIC_PKT_TYPE_0RTT:        raw_type = 1; break;
+            case QUIC_PKT_TYPE_HANDSHAKE:   raw_type = 2; break;
+            case QUIC_PKT_TYPE_RETRY:       raw_type = 3; break;
+            default:
+                return 0;
+        }
+
+        b0 = (raw_type << 4) | 0x80; /* long */
+        if (hdr->type != QUIC_PKT_TYPE_VERSION_NEG || hdr->fixed)
+            b0 |= 0x40; /* fixed */
+        if (hdr->type != QUIC_PKT_TYPE_RETRY
+            && hdr->type != QUIC_PKT_TYPE_VERSION_NEG)
+            b0 |= hdr->pn_len - 1;
+
+        if (!WPACKET_put_bytes_u8(pkt, b0)
+            || !WPACKET_put_bytes_u32(pkt, hdr->version)
+            || !WPACKET_put_bytes_u8(pkt, hdr->dst_conn_id.id_len)
+            || !WPACKET_memcpy(pkt, hdr->dst_conn_id.id,
+                               hdr->dst_conn_id.id_len)
+            || !WPACKET_put_bytes_u8(pkt, hdr->src_conn_id.id_len)
+            || !WPACKET_memcpy(pkt, hdr->src_conn_id.id,
+                               hdr->src_conn_id.id_len))
+            return 0;
+
+        if (hdr->type == QUIC_PKT_TYPE_VERSION_NEG
+            || hdr->type == QUIC_PKT_TYPE_RETRY) {
+            if (!WPACKET_reserve_bytes(pkt, hdr->len, NULL))
+                return 0;
+
+            return 1;
+        }
+
+        if (hdr->type == QUIC_PKT_TYPE_INITIAL) {
+            if (!WPACKET_quic_write_vlint(pkt, hdr->token_len)
+                || !WPACKET_memcpy(pkt, hdr->token, hdr->token_len))
+                return 0;
+        }
+
+        if (!WPACKET_quic_write_vlint(pkt, hdr->len + hdr->pn_len)
+            || !WPACKET_get_total_written(pkt, &off_pn)
+            || !WPACKET_memcpy(pkt, hdr->pn, hdr->pn_len))
+            return 0;
+    }
+
+    if (!WPACKET_reserve_bytes(pkt, hdr->len, NULL))
+        return 0;
+
+    off_sample = off_pn + 4;
+    if (!WPACKET_get_total_written(pkt, &off_sample_end))
+        return 0;
+
+    if (ptrs != NULL) {
+        ptrs->raw_start         = (unsigned char *)pkt->buf->data + off_start;
+        ptrs->raw_sample        = (unsigned char *)pkt->buf->data + off_sample;
+        ptrs->raw_sample_len    = off_sample_end - off_sample;
+        ptrs->raw_pn            = (unsigned char *)pkt->buf->data + off_pn;
+    }
+
+    return 1;
+}
+
+int ossl_quic_wire_get_pkt_hdr_dst_conn_id(const unsigned char *buf,
+                                           size_t buf_len,
+                                           size_t short_conn_id_len,
+                                           QUIC_CONN_ID *dst_conn_id)
+{
+    unsigned char b0;
+    size_t blen;
+
+    if (buf_len < QUIC_MIN_VALID_PKT_LEN
+        || short_conn_id_len > QUIC_MAX_CONN_ID_LEN)
+        return 0;
+
+    b0 = buf[0];
+    if ((b0 & 0x80) != 0) {
+        /*
+         * Long header. We need 6 bytes (initial byte, 4 version bytes, DCID
+         * length byte to begin with). This is covered by the buf_len test
+         * above.
+         */
+
+        /*
+         * If the version field is non-zero (meaning that this is not a Version
+         * Negotiation packet), the fixed bit must be set.
+         */
+        if ((buf[1] || buf[2] || buf[3] || buf[4]) && (b0 & 0x40) == 0)
+            return 0;
+
+        blen = (size_t)buf[5]; /* DCID Length */
+        if (blen > QUIC_MAX_CONN_ID_LEN
+            || buf_len < QUIC_MIN_VALID_PKT_LEN + blen)
+            return 0;
+
+        dst_conn_id->id_len = (unsigned char)blen;
+        memcpy(dst_conn_id->id, buf + 6, blen);
+        return 1;
+    } else {
+        /* Short header. */
+        if ((b0 & 0x40) == 0)
+            /* Fixed bit not set, not a valid QUIC packet header. */
+            return 0;
+
+        if (buf_len < QUIC_MIN_VALID_PKT_LEN_CRYPTO + short_conn_id_len)
+            return 0;
+
+        dst_conn_id->id_len = short_conn_id_len;
+        memcpy(dst_conn_id->id, buf + 1, short_conn_id_len);
+        return 1;
+    }
+}
+
+int ossl_quic_wire_decode_pkt_hdr_pn(const unsigned char *enc_pn,
+                                     size_t enc_pn_len,
+                                     QUIC_PN largest_pn,
+                                     QUIC_PN *res_pn)
+{
+    int64_t expected_pn, truncated_pn, candidate_pn, pn_win, pn_hwin, pn_mask;
+
+    switch (enc_pn_len) {
+        case 1:
+            truncated_pn = enc_pn[0];
+            break;
+        case 2:
+            truncated_pn = ((QUIC_PN)enc_pn[0] << 8)
+                         |  (QUIC_PN)enc_pn[1];
+            break;
+        case 3:
+            truncated_pn = ((QUIC_PN)enc_pn[0] << 16)
+                         | ((QUIC_PN)enc_pn[1] << 8)
+                         |  (QUIC_PN)enc_pn[2];
+            break;
+        case 4:
+            truncated_pn = ((QUIC_PN)enc_pn[0] << 24)
+                         | ((QUIC_PN)enc_pn[1] << 16)
+                         | ((QUIC_PN)enc_pn[2] << 8)
+                         |  (QUIC_PN)enc_pn[3];
+            break;
+        default:
+            return 0;
+    }
+
+    /* Implemented as per RFC 9000 Section A.3. */
+    expected_pn     = largest_pn + 1;
+    pn_win          = ((int64_t)1) << (enc_pn_len * 8);
+    pn_hwin         = pn_win / 2;
+    pn_mask         = pn_win - 1;
+    candidate_pn    = (expected_pn & ~pn_mask) | truncated_pn;
+    if (candidate_pn <= expected_pn - pn_hwin
+        && candidate_pn < (((int64_t)1) << 62) - pn_win)
+        *res_pn = candidate_pn + pn_win;
+    else if (candidate_pn > expected_pn + pn_hwin
+             && candidate_pn >= pn_win)
+        *res_pn = candidate_pn - pn_win;
+    else
+        *res_pn = candidate_pn;
+    return 1;
+}
+
+/* From RFC 9000 Section A.2. Simplified implementation. */
+int ossl_quic_wire_determine_pn_len(QUIC_PN pn,
+                                    QUIC_PN largest_acked)
+{
+    uint64_t num_unacked
+        = (largest_acked == QUIC_PN_INVALID) ? pn + 1 : pn - largest_acked;
+
+    /*
+     * num_unacked \in [    0, 2** 7] -> 1 byte
+     * num_unacked \in (2** 7, 2**15] -> 2 bytes
+     * num_unacked \in (2**15, 2**23] -> 3 bytes
+     * num_unacked \in (2**23,      ] -> 4 bytes
+     */
+
+    if (num_unacked <= (1U<<7))  return 1;
+    if (num_unacked <= (1U<<15)) return 2;
+    if (num_unacked <= (1U<<23)) return 3;
+    return 4;
+}
+
+int ossl_quic_wire_encode_pkt_hdr_pn(QUIC_PN pn,
+                                     unsigned char *enc_pn,
+                                     size_t enc_pn_len)
+{
+    switch (enc_pn_len) {
+        case 1:
+            enc_pn[0] = (unsigned char)pn;
+            break;
+        case 2:
+            enc_pn[1] = (unsigned char)pn;
+            enc_pn[0] = (unsigned char)(pn >> 8);
+            break;
+        case 3:
+            enc_pn[2] = (unsigned char)pn;
+            enc_pn[1] = (unsigned char)(pn >> 8);
+            enc_pn[0] = (unsigned char)(pn >> 16);
+            break;
+        case 4:
+            enc_pn[3] = (unsigned char)pn;
+            enc_pn[2] = (unsigned char)(pn >> 8);
+            enc_pn[1] = (unsigned char)(pn >> 16);
+            enc_pn[0] = (unsigned char)(pn >> 24);
+            break;
+        default:
+            return 0;
+    }
+
+    return 1;
+}
index 38b2c7e970fe0de0b30b4765b8129e9800799e78..fec587ee8a929aef585918fc94cff5dd1c8f38bc 100644 (file)
@@ -2688,11 +2688,19 @@ __owur size_t tls13_final_finish_mac(SSL_CONNECTION *s, const char *str, size_t
                                      unsigned char *p);
 __owur int tls13_change_cipher_state(SSL_CONNECTION *s, int which);
 __owur int tls13_update_key(SSL_CONNECTION *s, int send);
-__owur int tls13_hkdf_expand(SSL_CONNECTION *s, const EVP_MD *md,
+__owur int tls13_hkdf_expand(SSL_CONNECTION *s,
+                             const EVP_MD *md,
                              const unsigned char *secret,
                              const unsigned char *label, size_t labellen,
                              const unsigned char *data, size_t datalen,
                              unsigned char *out, size_t outlen, int fatal);
+__owur int tls13_hkdf_expand_ex(OSSL_LIB_CTX *libctx, const char *propq,
+                                const EVP_MD *md,
+                                const unsigned char *secret,
+                                const unsigned char *label, size_t labellen,
+                                const unsigned char *data, size_t datalen,
+                                unsigned char *out, size_t outlen,
+                                int raise_error);
 __owur int tls13_derive_key(SSL_CONNECTION *s, const EVP_MD *md,
                             const unsigned char *secret, unsigned char *key,
                             size_t keylen);
index 0af8ad2918ca6bd1102e9f6affc9f1b9017d9347..0d0c0a14e5411f09ee0f86bc694ca76a4da86b32 100644 (file)
@@ -30,16 +30,16 @@ static const unsigned char label_prefix[] = "tls13 ";
  * secret |outlen| bytes long and store it in the location pointed to be |out|.
  * The |data| value may be zero length. Any errors will be treated as fatal if
  * |fatal| is set. Returns 1 on success  0 on failure.
+ * If |raise_error| is set, ERR_raise is called on failure.
  */
-int tls13_hkdf_expand(SSL_CONNECTION *s, const EVP_MD *md,
-                      const unsigned char *secret,
-                      const unsigned char *label, size_t labellen,
-                      const unsigned char *data, size_t datalen,
-                      unsigned char *out, size_t outlen, int fatal)
+int tls13_hkdf_expand_ex(OSSL_LIB_CTX *libctx, const char *propq,
+                         const EVP_MD *md,
+                         const unsigned char *secret,
+                         const unsigned char *label, size_t labellen,
+                         const unsigned char *data, size_t datalen,
+                         unsigned char *out, size_t outlen, int raise_error)
 {
-    SSL_CTX *sctx = SSL_CONNECTION_GET_CTX(s);
-    EVP_KDF *kdf = EVP_KDF_fetch(sctx->libctx, OSSL_KDF_NAME_TLS1_3_KDF,
-                                 sctx->propq);
+    EVP_KDF *kdf = EVP_KDF_fetch(libctx, OSSL_KDF_NAME_TLS1_3_KDF, propq);
     EVP_KDF_CTX *kctx;
     OSSL_PARAM params[7], *p = params;
     int mode = EVP_PKEY_HKDEF_MODE_EXPAND_ONLY;
@@ -53,24 +53,20 @@ int tls13_hkdf_expand(SSL_CONNECTION *s, const EVP_MD *md,
         return 0;
 
     if (labellen > TLS13_MAX_LABEL_LEN) {
-        if (fatal) {
-            SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
-        } else {
+        if (raise_error)
             /*
              * Probably we have been called from SSL_export_keying_material(),
              * or SSL_export_keying_material_early().
              */
             ERR_raise(ERR_LIB_SSL, SSL_R_TLS_ILLEGAL_EXPORTER_LABEL);
-        }
+
         EVP_KDF_CTX_free(kctx);
         return 0;
     }
 
     if ((ret = EVP_MD_get_size(md)) <= 0) {
         EVP_KDF_CTX_free(kctx);
-        if (fatal)
-            SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
-        else
+        if (raise_error)
             ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
         return 0;
     }
@@ -96,15 +92,31 @@ int tls13_hkdf_expand(SSL_CONNECTION *s, const EVP_MD *md,
     EVP_KDF_CTX_free(kctx);
 
     if (ret != 0) {
-        if (fatal)
-            SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
-        else
+        if (raise_error)
             ERR_raise(ERR_LIB_SSL, ERR_R_INTERNAL_ERROR);
     }
 
     return ret == 0;
 }
 
+int tls13_hkdf_expand(SSL_CONNECTION *s, const EVP_MD *md,
+                      const unsigned char *secret,
+                      const unsigned char *label, size_t labellen,
+                      const unsigned char *data, size_t datalen,
+                      unsigned char *out, size_t outlen, int fatal)
+{
+    int ret;
+    SSL_CTX *sctx = SSL_CONNECTION_GET_CTX(s);
+
+    ret = tls13_hkdf_expand_ex(sctx->libctx, sctx->propq, md,
+                               secret, label, labellen, data, datalen,
+                               out, outlen, !fatal);
+    if (ret == 0 && fatal)
+        SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
+
+    return ret;
+}
+
 /*
  * Given a |secret| generate a |key| of length |keylen| bytes. Returns 1 on
  * success  0 on failure.
index ccd969ab01b3cf0b7807242f9d02d4291c1147e8..44dff9d475d963de7a35ca925b943719bc1bf1b1 100644 (file)
@@ -282,6 +282,10 @@ IF[{- !$disabled{tests} -}]
   INCLUDE[quic_wire_test]=../include ../apps/include
   DEPEND[quic_wire_test]=../libcrypto.a ../libssl.a libtestutil.a
 
+  SOURCE[quic_record_test]=quic_record_test.c
+  INCLUDE[quic_record_test]=../include ../apps/include
+  DEPEND[quic_record_test]=../libcrypto.a ../libssl.a libtestutil.a
+
   SOURCE[asynctest]=asynctest.c
   INCLUDE[asynctest]=../include ../apps/include
   DEPEND[asynctest]=../libcrypto
@@ -968,7 +972,7 @@ ENDIF
   ENDIF
 
   IF[{- !$disabled{'quic'} -}]
-    PROGRAMS{noinst}=quicapitest quic_wire_test quic_ackm_test
+    PROGRAMS{noinst}=quicapitest quic_wire_test quic_ackm_test quic_record_test
   ENDIF
 
   SOURCE[quicapitest]=quicapitest.c helpers/ssltestlib.c
diff --git a/test/quic_record_test.c b/test/quic_record_test.c
new file mode 100644 (file)
index 0000000..29c0dcf
--- /dev/null
@@ -0,0 +1,2378 @@
+/*
+ * 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
+ */
+
+#include "internal/quic_record.h"
+#include "testutil.h"
+
+static const QUIC_CONN_ID empty_conn_id = {0, {0}};
+
+struct test_case {
+    const unsigned char    *dgram;
+    size_t                  dgram_len;
+};
+
+#define TEST_OP_END                     0 /* end of script */
+#define TEST_OP_SET_SCID_LEN            1 /* change SCID length */
+#define TEST_OP_SET_INIT_LARGEST_PN     2 /* set initial largest PN */
+#define TEST_OP_ADD_RX_DCID             3 /* register an RX DCID */
+#define TEST_OP_INJECT                  4 /* inject a datagram into demux */
+#define TEST_OP_PROVIDE_SECRET          5 /* provide RX secret */
+#define TEST_OP_PROVIDE_SECRET_INITIAL  6 /* provide RX secret for initial */
+#define TEST_OP_DISCARD_EL              7 /* discard an encryption level */
+#define TEST_OP_CHECK_PKT               8 /* read packet, compare to expected */
+#define TEST_OP_CHECK_NO_PKT            9 /* check no packet is available to read */
+
+struct test_op {
+    unsigned char op;
+    const unsigned char *buf;
+    size_t buf_len;
+    const QUIC_PKT_HDR *hdr;
+    uint32_t enc_level, suite_id;
+    QUIC_PN largest_pn;
+    const QUIC_CONN_ID *dcid;
+    int (*new_qrl)(QUIC_DEMUX **demux, OSSL_QRL **qrl);
+};
+
+#define OP_END \
+    { TEST_OP_END }
+#define OP_SET_SCID_LEN(scid_len) \
+    { TEST_OP_SET_SCID_LEN, NULL, 0, NULL, (scid_len), 0, 0, NULL, NULL },
+#define OP_SET_INIT_LARGEST_PN(largest_pn) \
+    { TEST_OP_SET_INIT_LARGEST_PN, NULL, 0, NULL, 0, 0, (largest_pn), NULL, NULL },
+#define OP_ADD_RX_DCID(dcid) \
+    { TEST_OP_ADD_RX_DCID, NULL, 0, NULL, 0, 0, 0, &(dcid), NULL },
+#define OP_INJECT(dgram) \
+    { TEST_OP_INJECT, (dgram), sizeof(dgram), NULL, 0, 0, 0, NULL },
+#define OP_PROVIDE_SECRET(el, suite, key)                           \
+    {                                                               \
+        TEST_OP_PROVIDE_SECRET, (key), sizeof(key),                 \
+        NULL, (el), (suite), 0, NULL, NULL                          \
+    },
+#define OP_PROVIDE_SECRET_INITIAL(dcid) \
+    { TEST_OP_PROVIDE_SECRET_INITIAL, NULL, 0, NULL, 0, 0, 0, &(dcid), NULL },
+#define OP_DISCARD_EL(el) \
+    { TEST_OP_DISCARD_EL, NULL, 0, NULL, (el), 0, 0, NULL, NULL },
+#define OP_CHECK_PKT(expect_hdr, expect_body)                       \
+    {                                                               \
+        TEST_OP_CHECK_PKT, (expect_body), sizeof(expect_body),      \
+        &(expect_hdr), 0, 0, 0, NULL, NULL                          \
+    },
+#define OP_CHECK_NO_PKT() \
+    { TEST_OP_CHECK_NO_PKT, NULL, 0, NULL, 0, 0, 0, NULL, NULL },
+
+#define OP_INJECT_N(n)                                          \
+    OP_INJECT(script_##n##_in)
+#define OP_CHECK_PKT_N(n)                                       \
+    OP_CHECK_PKT(script_##n##_expect_hdr, script_##n##_body)
+
+#define OP_INJECT_CHECK(n)                                      \
+    OP_INJECT_N(n)                                              \
+    OP_CHECK_PKT_N(n)
+
+/* 1. RFC 9001 - A.3 Server Initial */
+static const unsigned char script_1_in[] = {
+    0xcf, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a,
+    0x42, 0x62, 0xb5, 0x00, 0x40, 0x75, 0xc0, 0xd9, 0x5a, 0x48, 0x2c, 0xd0,
+    0x99, 0x1c, 0xd2, 0x5b, 0x0a, 0xac, 0x40, 0x6a, 0x58, 0x16, 0xb6, 0x39,
+    0x41, 0x00, 0xf3, 0x7a, 0x1c, 0x69, 0x79, 0x75, 0x54, 0x78, 0x0b, 0xb3,
+    0x8c, 0xc5, 0xa9, 0x9f, 0x5e, 0xde, 0x4c, 0xf7, 0x3c, 0x3e, 0xc2, 0x49,
+    0x3a, 0x18, 0x39, 0xb3, 0xdb, 0xcb, 0xa3, 0xf6, 0xea, 0x46, 0xc5, 0xb7,
+    0x68, 0x4d, 0xf3, 0x54, 0x8e, 0x7d, 0xde, 0xb9, 0xc3, 0xbf, 0x9c, 0x73,
+    0xcc, 0x3f, 0x3b, 0xde, 0xd7, 0x4b, 0x56, 0x2b, 0xfb, 0x19, 0xfb, 0x84,
+    0x02, 0x2f, 0x8e, 0xf4, 0xcd, 0xd9, 0x37, 0x95, 0xd7, 0x7d, 0x06, 0xed,
+    0xbb, 0x7a, 0xaf, 0x2f, 0x58, 0x89, 0x18, 0x50, 0xab, 0xbd, 0xca, 0x3d,
+    0x20, 0x39, 0x8c, 0x27, 0x64, 0x56, 0xcb, 0xc4, 0x21, 0x58, 0x40, 0x7d,
+    0xd0, 0x74, 0xee
+};
+
+static const unsigned char script_1_body[] = {
+    0x02, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x40, 0x5a, 0x02, 0x00, 0x00,
+    0x56, 0x03, 0x03, 0xee, 0xfc, 0xe7, 0xf7, 0xb3, 0x7b, 0xa1, 0xd1, 0x63,
+    0x2e, 0x96, 0x67, 0x78, 0x25, 0xdd, 0xf7, 0x39, 0x88, 0xcf, 0xc7, 0x98,
+    0x25, 0xdf, 0x56, 0x6d, 0xc5, 0x43, 0x0b, 0x9a, 0x04, 0x5a, 0x12, 0x00,
+    0x13, 0x01, 0x00, 0x00, 0x2e, 0x00, 0x33, 0x00, 0x24, 0x00, 0x1d, 0x00,
+    0x20, 0x9d, 0x3c, 0x94, 0x0d, 0x89, 0x69, 0x0b, 0x84, 0xd0, 0x8a, 0x60,
+    0x99, 0x3c, 0x14, 0x4e, 0xca, 0x68, 0x4d, 0x10,  0x81, 0x28, 0x7c, 0x83,
+    0x4d, 0x53, 0x11, 0xbc, 0xf3, 0x2b, 0xb9, 0xda, 0x1a, 0x00, 0x2b, 0x00,
+    0x02, 0x03, 0x04
+};
+
+static const QUIC_CONN_ID script_1_dcid = {
+    8, { 0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08 }
+};
+
+static const QUIC_PKT_HDR script_1_expect_hdr = {
+    QUIC_PKT_TYPE_INITIAL,
+    0, 0, 2, 0, 1, 1, { 0, {0} },
+    { 8, {0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5 } },
+    { 0, 1, 0, 0 },
+    NULL, 0,
+    99, NULL
+};
+
+static const struct test_op script_1[] = {
+    OP_SET_SCID_LEN(2)
+    OP_SET_INIT_LARGEST_PN(0)
+    OP_ADD_RX_DCID(empty_conn_id)
+    OP_PROVIDE_SECRET_INITIAL(script_1_dcid)
+    OP_INJECT_CHECK(1)
+    OP_CHECK_NO_PKT()
+    OP_END
+};
+
+/* 2. RFC 9001 - A.5 ChaCha20-Poly1305 Short Header Packet */
+static const unsigned char script_2_in[] = {
+    0x4c, 0xfe, 0x41, 0x89, 0x65, 0x5e, 0x5c, 0xd5, 0x5c, 0x41, 0xf6, 0x90,
+    0x80, 0x57, 0x5d, 0x79, 0x99, 0xc2, 0x5a, 0x5b, 0xfb
+};
+
+static const unsigned char script_2_secret[] = {
+    0x9a, 0xc3, 0x12, 0xa7, 0xf8, 0x77, 0x46, 0x8e, 0xbe, 0x69, 0x42, 0x27,
+    0x48, 0xad, 0x00, 0xa1, 0x54, 0x43, 0xf1, 0x82, 0x03, 0xa0, 0x7d, 0x60,
+    0x60, 0xf6, 0x88, 0xf3, 0x0f, 0x21, 0x63, 0x2b
+};
+
+static const unsigned char script_2_body[] = {
+    0x01
+};
+
+static const QUIC_PKT_HDR script_2_expect_hdr = {
+    QUIC_PKT_TYPE_1RTT,
+    0, 0, 3, 0, 1, 0, {0, {0}}, {0, {0}},
+    {0x00, 0xbf, 0xf4, 0x00},
+    NULL, 0,
+    1, NULL
+};
+
+static const struct test_op script_2[] = {
+    OP_SET_INIT_LARGEST_PN(654360560)
+    OP_ADD_RX_DCID(empty_conn_id)
+    OP_PROVIDE_SECRET(QUIC_ENC_LEVEL_1RTT, QRL_SUITE_CHACHA20POLY1305,
+                      script_2_secret)
+    OP_INJECT_CHECK(2)
+    OP_CHECK_NO_PKT()
+    OP_END
+};
+
+/* 3. Real World - Version Negotiation Response */
+static const unsigned char script_3_in[] = {
+    0xc7,                               /* Long; Random Bits */
+    0x00, 0x00, 0x00, 0x00,             /* Version 0 (Version Negotiation) */
+    0x00,                               /* DCID */
+    0x0c, 0x35, 0x3c, 0x1b, 0x97, 0xca, /* SCID */
+    0xf8, 0x99, 0x11, 0x39, 0xad, 0x79,
+    0x1f,
+    0x00, 0x00, 0x00, 0x01,             /* Supported Version: 1 */
+    0xaa, 0x9a, 0x3a, 0x9a              /* Supported Version: Random (GREASE) */
+};
+
+static const QUIC_PKT_HDR script_3_expect_hdr = {
+    QUIC_PKT_TYPE_VERSION_NEG,
+    0,          /* Spin Bit */
+    0,          /* Key Phase */
+    0,          /* PN Length */
+    0,          /* Partial */
+    1,          /* Fixed */
+    0,          /* Version */
+    {0, {0}},                                   /* DCID */
+    {12, {0x35, 0x3c, 0x1b, 0x97, 0xca, 0xf8,   /* SCID */
+          0x99, 0x11, 0x39, 0xad, 0x79, 0x1f}},
+    {0},        /* PN */
+    NULL, 0,    /* Token/Token Len */
+    8, NULL
+};
+
+static const unsigned char script_3_body[] = {
+    0x00, 0x00, 0x00, 0x01,
+    0xaa, 0x9a, 0x3a, 0x9a
+};
+
+static const struct test_op script_3[] = {
+    OP_ADD_RX_DCID(empty_conn_id)
+    OP_INJECT_CHECK(3)
+    OP_CHECK_NO_PKT()
+    OP_END
+};
+
+/* 4. Real World - Retry (S2C) */
+static const unsigned char script_4_in[] = {
+    0xf0,                           /* Long; Retry */
+    0x00, 0x00, 0x00, 0x01,         /* Version 1 */
+    0x00,                           /* DCID */
+    0x04, 0xad, 0x15, 0x3f, 0xae,   /* SCID */
+    /* Retry Token, including 16-byte Retry Integrity Tag */
+    0xf6, 0x8b, 0x6e, 0xa3, 0xdc, 0x40, 0x38, 0xc6, 0xa5, 0x99, 0x1c, 0xa9,
+    0x77, 0xe6, 0x1d, 0x4f, 0x09, 0x36, 0x12, 0x26, 0x00, 0x56, 0x0b, 0x29,
+    0x7d, 0x5e, 0xda, 0x39, 0xc6, 0x61, 0x57, 0x69, 0x15, 0xff, 0x93, 0x39,
+    0x95, 0xf0, 0x57, 0xf1, 0xe5, 0x36, 0x08, 0xad, 0xd2, 0x75, 0xa9, 0x68,
+    0x29, 0xed, 0xaa, 0x03, 0x0e, 0x5f, 0xac, 0xbd, 0x26, 0x07, 0x95, 0x4e,
+    0x48, 0x61, 0x26, 0xc5, 0xe2, 0x6c, 0x60, 0xbf, 0xa8, 0x6f, 0x51, 0xbb,
+    0x1d, 0xf7, 0x98, 0x95, 0x3b, 0x2c, 0x50, 0x79, 0xcc, 0xde, 0x27, 0x84,
+    0x44, 0x9b, 0xb2, 0x4a, 0x94, 0x4d, 0x4d, 0x3d, 0xbc, 0x00, 0x9d, 0x69,
+    0xad, 0x45, 0x89, 0x04, 0x48, 0xca, 0x04, 0xf6, 0x3a, 0x62, 0xc1, 0x38,
+    0x9d, 0x82, 0xb3, 0x45, 0x62, 0x4c,
+};
+
+static const QUIC_PKT_HDR script_4_expect_hdr = {
+    QUIC_PKT_TYPE_RETRY,
+    0,          /* Spin Bit */
+    0,          /* Key Phase */
+    0,          /* PN Length */
+    0,          /* Partial */
+    1,          /* Fixed */
+    1,          /* Version */
+    {0, {0}},                           /* DCID */
+    {4, {0xad, 0x15, 0x3f, 0xae}},      /* SCID */
+    {0},        /* PN */
+    NULL, 0,    /* Token/Token Len */
+    114, NULL
+};
+
+static const unsigned char script_4_body[] = {
+    0xf6, 0x8b, 0x6e, 0xa3, 0xdc, 0x40, 0x38, 0xc6, 0xa5, 0x99, 0x1c, 0xa9,
+    0x77, 0xe6, 0x1d, 0x4f, 0x09, 0x36, 0x12, 0x26, 0x00, 0x56, 0x0b, 0x29,
+    0x7d, 0x5e, 0xda, 0x39, 0xc6, 0x61, 0x57, 0x69, 0x15, 0xff, 0x93, 0x39,
+    0x95, 0xf0, 0x57, 0xf1, 0xe5, 0x36, 0x08, 0xad, 0xd2, 0x75, 0xa9, 0x68,
+    0x29, 0xed, 0xaa, 0x03, 0x0e, 0x5f, 0xac, 0xbd, 0x26, 0x07, 0x95, 0x4e,
+    0x48, 0x61, 0x26, 0xc5, 0xe2, 0x6c, 0x60, 0xbf, 0xa8, 0x6f, 0x51, 0xbb,
+    0x1d, 0xf7, 0x98, 0x95, 0x3b, 0x2c, 0x50, 0x79, 0xcc, 0xde, 0x27, 0x84,
+    0x44, 0x9b, 0xb2, 0x4a, 0x94, 0x4d, 0x4d, 0x3d, 0xbc, 0x00, 0x9d, 0x69,
+    0xad, 0x45, 0x89, 0x04, 0x48, 0xca, 0x04, 0xf6, 0x3a, 0x62, 0xc1, 0x38,
+    0x9d, 0x82, 0xb3, 0x45, 0x62, 0x4c
+};
+
+static const struct test_op script_4[] = {
+    OP_ADD_RX_DCID(empty_conn_id)
+    OP_INJECT_CHECK(4)
+    OP_CHECK_NO_PKT()
+    OP_END
+};
+
+/*
+ * 5. Real World - S2C Multiple Packets
+ *      - Initial, Handshake, 1-RTT (AES-128-GCM/SHA256)
+ */
+static const QUIC_CONN_ID script_5_c2s_init_dcid = {
+    4, {0xad, 0x15, 0x3f, 0xae}
+};
+
+static const unsigned char script_5_handshake_secret[32] = {
+    0x5e, 0xc6, 0x4a, 0x4d, 0x0d, 0x40, 0x43, 0x3b, 0xd5, 0xbd, 0xe0, 0x19,
+    0x71, 0x47, 0x56, 0xf3, 0x59, 0x3a, 0xa6, 0xc9, 0x3e, 0xdc, 0x81, 0x1e,
+    0xc7, 0x72, 0x9d, 0x83, 0xd8, 0x8f, 0x88, 0x77
+};
+
+static const unsigned char script_5_1rtt_secret[32] = {
+    0x53, 0xf2, 0x1b, 0x94, 0xa7, 0x65, 0xf7, 0x76, 0xfb, 0x06, 0x27, 0xaa,
+    0xd2, 0x3f, 0xe0, 0x9a, 0xbb, 0xcf, 0x99, 0x6f, 0x13, 0x2c, 0x6a, 0x37,
+    0x95, 0xf3, 0xda, 0x21, 0xcb, 0xcb, 0xa5, 0x26,
+};
+
+static const unsigned char script_5_in[] = {
+    /* First Packet: Initial */
+    0xc4,                           /* Long, Initial, PN Length=2 bytes */
+    0x00, 0x00, 0x00, 0x01,         /* Version */
+    0x00,                           /* DCID */
+    0x04, 0x83, 0xd0, 0x0a, 0x27,   /* SCID */
+    0x00,                           /* Token Length */
+    0x41, 0xd2,                     /* Length (466) */
+    0xe3, 0xab,                     /* PN (0) */
+    0x22, 0x35, 0x34, 0x12, 0xcf, 0x20, 0x2b, 0x16, 0xaf, 0x08, 0xd4, 0xe0,
+    0x94, 0x8b, 0x1e, 0x62, 0xdf, 0x31, 0x61, 0xcc, 0xf9, 0xfa, 0x66, 0x4f,
+    0x18, 0x61, 0x07, 0xcb, 0x13, 0xd3, 0xf9, 0xbf, 0xe2, 0x8e, 0x25, 0x8d,
+    0xd1, 0xdf, 0x58, 0x9c, 0x05, 0x20, 0xf9, 0xf2, 0x01, 0x20, 0xe9, 0x39,
+    0xc3, 0x80, 0x77, 0xec, 0xa4, 0x57, 0xcf, 0x57, 0x8c, 0xdd, 0x68, 0x82,
+    0x91, 0xfe, 0x71, 0xa0, 0xfa, 0x56, 0x4c, 0xf2, 0xe7, 0x2b, 0xd0, 0xc0,
+    0xda, 0x81, 0xe2, 0x39, 0xb5, 0xf0, 0x0f, 0xd9, 0x07, 0xd5, 0x67, 0x09,
+    0x02, 0xf0, 0xff, 0x74, 0xb0, 0xa0, 0xd9, 0x3a, 0x7e, 0xb6, 0x57, 0x82,
+    0x47, 0x18, 0x66, 0xed, 0xe2, 0x18, 0x4d, 0xc2, 0x5c, 0x9f, 0x05, 0x09,
+    0x18, 0x24, 0x0e, 0x3f, 0x3d, 0xf9, 0x15, 0x8b, 0x08, 0xfd, 0x25, 0xe9,
+    0xc9, 0xb7, 0x8c, 0x18, 0x7b, 0xf3, 0x37, 0x58, 0xf0, 0xf0, 0xac, 0x33,
+    0x55, 0x3f, 0x39, 0xbc, 0x62, 0x03, 0x8a, 0xc0, 0xd6, 0xcc, 0x49, 0x47,
+    0xeb, 0x85, 0xb6, 0x72, 0xd7, 0xf8, 0xdc, 0x01, 0x32, 0xec, 0x1b, 0x4e,
+    0x38, 0x6e, 0x2c, 0xc5, 0x80, 0xf2, 0x43, 0x4a, 0xf5, 0xe5, 0xa2, 0xf8,
+    0x76, 0xa7, 0xa8, 0x57, 0x32, 0x67, 0x72, 0xeb, 0x82, 0xac, 0x3e, 0xc0,
+    0x15, 0x67, 0xac, 0x32, 0x19, 0x18, 0x0a, 0xef, 0x20, 0xa1, 0xe8, 0xaf,
+    0xac, 0x33, 0x87, 0x4c, 0x55, 0x05, 0x9b, 0x78, 0xf0, 0x3a, 0xce, 0x02,
+    0x28, 0x06, 0x84, 0x61, 0x97, 0xac, 0x87, 0x8f, 0x25, 0xe7, 0x1b, 0xa3,
+    0x02, 0x08, 0x4c, 0x2e, 0xef, 0xbd, 0x4f, 0x82, 0xe7, 0x37, 0x6c, 0x27,
+    0x6f, 0x85, 0xb4, 0xbc, 0x79, 0x38, 0x45, 0x80, 0x8a, 0xda, 0x2f, 0x11,
+    0x11, 0xac, 0x9c, 0xf3, 0x93, 0xc1, 0x49, 0x1b, 0x94, 0x12, 0x77, 0x07,
+    0xdc, 0xbf, 0xc2, 0xfd, 0x8b, 0xf6, 0xf1, 0x66, 0x1c, 0x7f, 0x07, 0xbf,
+    0x1f, 0xae, 0x27, 0x6c, 0x66, 0xe9, 0xa3, 0x64, 0x7a, 0x96, 0x78, 0x45,
+    0xfe, 0x4b, 0x8c, 0x6f, 0x7f, 0x03, 0x47, 0x3c, 0xd7, 0xf7, 0x63, 0x92,
+    0x58, 0x5b, 0x63, 0x83, 0x03, 0x05, 0xc3, 0x5d, 0x36, 0x62, 0x63, 0x5e,
+    0xcf, 0xfe, 0x0a, 0x29, 0xfa, 0xeb, 0xc8, 0xaf, 0xce, 0x31, 0x07, 0x6a,
+    0x09, 0x41, 0xc0, 0x2d, 0x98, 0x70, 0x05, 0x3b, 0x41, 0xfc, 0x7d, 0x61,
+    0xe0, 0x41, 0x7d, 0x13, 0x41, 0x51, 0x52, 0xb4, 0x78, 0xd5, 0x46, 0x51,
+    0x3b, 0xf1, 0xcd, 0xcc, 0x2e, 0x49, 0x30, 0x8b, 0x2a, 0xd2, 0xe6, 0x69,
+    0xb5, 0x6b, 0x7a, 0xf4, 0xbb, 0xd1, 0xf8, 0x4a, 0xe8, 0x53, 0x10, 0x46,
+    0x85, 0x8d, 0x66, 0x8e, 0x2b, 0xe8, 0x5d, 0xab, 0x7e, 0xfe, 0x5a, 0x79,
+    0xcf, 0xc5, 0x0c, 0x30, 0x9e, 0x98, 0x02, 0xb3, 0xa6, 0xd5, 0xfa, 0x25,
+    0xa8, 0xc8, 0xc1, 0xd9, 0x51, 0x60, 0x57, 0x5d, 0xfe, 0x75, 0x97, 0x05,
+    0xda, 0xbb, 0xc6, 0x6a, 0xbe, 0x5c, 0xa5, 0x65, 0x0a, 0x12, 0x33, 0x1c,
+    0xdf, 0xee, 0x08, 0xa9, 0x13, 0x13, 0x28, 0xce, 0x61, 0x59, 0xd1, 0x4e,
+    0xc7, 0x74, 0xfd, 0x64, 0xde, 0x08, 0xce, 0xda, 0x3f, 0xec, 0xad, 0xc9,
+    0xe1, 0xf9, 0x1f, 0x74, 0xf6, 0x86, 0x37, 0x6a, 0xa0, 0xc8, 0x0b, 0x1b,
+    0x94, 0x98, 0x86, 0x81, 0x3b, 0xfc, 0x47, 0x6c, 0xc9, 0x3e, 0x3c, 0x30,
+    0xc5, 0x9e, 0xb2, 0x32, 0x47, 0xf5, 0x0c, 0x6f,
+
+    /* Second Packet: Handshake */
+    0xe6,                           /* Long, Handshake, PN Length=2 bytes */
+    0x00, 0x00, 0x00, 0x01,         /* Version */
+    0x00,                           /* DCID */
+    0x04, 0x83, 0xd0, 0x0a, 0x27,   /* SCID */
+    0x42, 0x9c,                     /* Length (668) */
+    0x9c, 0x55,                     /* PN (0) */
+    0x55, 0xd4, 0x50, 0x02, 0x1a, 0x57, 0x84, 0x22, 0xcd, 0x01, 0xe5, 0x42,
+    0x1b, 0x1e, 0x06, 0xf1, 0x86, 0xe2, 0x90, 0xf8, 0x9c, 0x3d, 0xa2, 0x7c,
+    0xde, 0x2b, 0xc9, 0x2e, 0xcd, 0xa8, 0x4f, 0x5a, 0x20, 0xca, 0x96, 0xb6,
+    0x11, 0x4b, 0xc8, 0x71, 0x32, 0xb5, 0xc7, 0x1a, 0x69, 0x7f, 0x1e, 0x37,
+    0x49, 0xfb, 0x08, 0xce, 0x83, 0x5f, 0x02, 0x6d, 0x8a, 0x8f, 0xe7, 0x5d,
+    0xe1, 0x34, 0x31, 0x22, 0x53, 0x53, 0x32, 0xcb, 0x04, 0x21, 0xce, 0xbc,
+    0xa5, 0x1b, 0xdd, 0x4d, 0xd5, 0x1c, 0xd6, 0x5d, 0x88, 0x29, 0x5a, 0x19,
+    0x71, 0x6a, 0xc2, 0xfa, 0xb7, 0xb4, 0x7d, 0xd1, 0x72, 0x93, 0x8f, 0x7c,
+    0xb5, 0x36, 0x1b, 0xea, 0xf3, 0xf1, 0xd7, 0x6e, 0xd3, 0x91, 0x96, 0x62,
+    0x4d, 0xc6, 0xec, 0xb7, 0xb0, 0xb7, 0x9b, 0x95, 0x8b, 0x14, 0x8d, 0x1a,
+    0x0d, 0xb6, 0x3e, 0xec, 0xfe, 0x3b, 0x51, 0xea, 0x1a, 0x05, 0x14, 0x12,
+    0x93, 0x0e, 0x7e, 0xe6, 0xa2, 0xc5, 0x22, 0x87, 0x65, 0xf8, 0x5d, 0x3c,
+    0x55, 0x18, 0xcb, 0xe9, 0xef, 0x23, 0x43, 0xfe, 0xe8, 0x0d, 0xb2, 0x0f,
+    0xc5, 0xf4, 0xb3, 0xde, 0x0c, 0xea, 0xa4, 0x48, 0x8e, 0xbf, 0x1f, 0xc7,
+    0x99, 0x53, 0x8c, 0xc1, 0x3d, 0xba, 0xf4, 0x8e, 0x8e, 0x02, 0x52, 0xf6,
+    0x1f, 0xcf, 0x1d, 0xaa, 0xb3, 0xcb, 0x08, 0xc2, 0xe1, 0x70, 0x68, 0x74,
+    0x78, 0xa9, 0x30, 0x67, 0xba, 0x2b, 0xea, 0x35, 0x63, 0x47, 0xff, 0x29,
+    0x73, 0x29, 0xc6, 0xe8, 0x08, 0xa9, 0x1e, 0x8f, 0x28, 0x41, 0xa4, 0x24,
+    0x54, 0x26, 0x5f, 0x42, 0x77, 0xb1, 0x2b, 0x3d, 0x65, 0x67, 0x60, 0xa7,
+    0x23, 0x0d, 0xa7, 0xf4, 0xd6, 0xe9, 0x4e, 0x58, 0x43, 0x9f, 0x3c, 0x9e,
+    0x77, 0x61, 0xe5, 0x04, 0x4f, 0x73, 0xc9, 0x10, 0x79, 0xd0, 0xda, 0x3b,
+    0xc6, 0x19, 0x93, 0x9f, 0x48, 0x3b, 0x76, 0x38, 0xa1, 0x72, 0x49, 0x7d,
+    0x86, 0x7f, 0xe8, 0x1b, 0xa9, 0x5b, 0xc0, 0x47, 0xa0, 0x9c, 0x3f, 0x65,
+    0x60, 0x76, 0x59, 0xaf, 0x20, 0x2d, 0x40, 0xa6, 0x80, 0x49, 0x5a, 0x8f,
+    0x09, 0xf8, 0xf6, 0x97, 0xc1, 0xbd, 0xe1, 0x9f, 0x9b, 0xa2, 0x4c, 0x7b,
+    0x88, 0xac, 0xbe, 0x4b, 0x11, 0x28, 0xd7, 0x67, 0xe6, 0xad, 0xaf, 0xd0,
+    0xad, 0x01, 0x29, 0xa4, 0x4a, 0xc4, 0xb8, 0x2e, 0x42, 0x79, 0x24, 0x9e,
+    0xd5, 0x34, 0xae, 0x45, 0xf1, 0x0b, 0x38, 0x4a, 0x76, 0xfb, 0x50, 0xa2,
+    0x99, 0xc9, 0x5b, 0x6d, 0xc0, 0xb7, 0x55, 0xd8, 0x8d, 0x49, 0xdd, 0x1b,
+    0xb8, 0xec, 0x10, 0x57, 0x9e, 0x33, 0xb4, 0x10, 0x16, 0x19, 0xac, 0x69,
+    0xa2, 0x19, 0x1b, 0xd0, 0x77, 0x45, 0xeb, 0x49, 0x5c, 0xc5, 0x7c, 0xbe,
+    0x4b, 0x4a, 0x22, 0x5c, 0x3d, 0x0e, 0x6e, 0xe5, 0x4b, 0x36, 0x06, 0x63,
+    0x03, 0x97, 0xab, 0xed, 0xdc, 0xea, 0x64, 0xc2, 0x70, 0xb6, 0x7e, 0x35,
+    0xfb, 0x13, 0x66, 0x37, 0xa3, 0x3f, 0x28, 0x16, 0x6c, 0xe7, 0xd4, 0xe6,
+    0xca, 0x26, 0x0f, 0x19, 0xdd, 0x02, 0xae, 0xc1, 0xcf, 0x18, 0x7d, 0x56,
+    0xe6, 0x52, 0xf3, 0x37, 0xb5, 0x86, 0x9d, 0x1d, 0x55, 0xb3, 0x95, 0x19,
+    0x19, 0xa5, 0x44, 0x95, 0x81, 0xed, 0x02, 0x18, 0xf1, 0x85, 0x57, 0x78,
+    0x28, 0xc4, 0x9a, 0xba, 0xe8, 0x5e, 0x22, 0x8d, 0xc1, 0x7b, 0x2a, 0x8a,
+    0xc8, 0xb9, 0xdd, 0x82, 0xb2, 0x7b, 0x9f, 0x3d, 0xf5, 0x27, 0x2a, 0x48,
+    0x53, 0xc7, 0xa0, 0x70, 0x0e, 0x9d, 0x61, 0xaa, 0xe2, 0xad, 0x28, 0xf2,
+    0xb4, 0xfc, 0x56, 0x6b, 0x89, 0xe7, 0xf9, 0x51, 0xc9, 0xe9, 0xd3, 0x8a,
+    0x8c, 0x7e, 0x86, 0xdd, 0xba, 0x2f, 0x39, 0xbf, 0x26, 0x62, 0x23, 0xd6,
+    0x98, 0x6d, 0x3e, 0x72, 0xd7, 0x1b, 0xe1, 0x62, 0x94, 0x35, 0xe2, 0x18,
+    0x19, 0x46, 0xb8, 0x2c, 0xb5, 0x8f, 0x8f, 0xb0, 0x5b, 0x76, 0x7b, 0x7e,
+    0xb8, 0xc6, 0xb7, 0xe9, 0x4e, 0x9d, 0x30, 0x68, 0x03, 0x1e, 0x19, 0x73,
+    0xc5, 0x3e, 0x24, 0xe2, 0x95, 0x60, 0x1b, 0x27, 0x93, 0x7c, 0x17, 0xc2,
+    0xc6, 0xa3, 0xbd, 0xbd, 0x70, 0xc6, 0x60, 0x59, 0xc8, 0x5c, 0xd7, 0x9a,
+    0xc4, 0x29, 0xac, 0x0f, 0xaa, 0x0d, 0xa9, 0x92, 0xa3, 0x95, 0xd7, 0x0f,
+    0x6f, 0x74, 0x99, 0x9b, 0xc1, 0xd3, 0x68, 0x6d, 0xac, 0x82, 0x2d, 0x32,
+    0x41, 0x9e, 0x0c, 0xf7, 0x31, 0x59, 0x4c, 0x93, 0x1c, 0x3b, 0x71, 0x69,
+    0xcf, 0xc5, 0xca, 0x2b, 0xdf, 0xe7, 0xaa, 0xfd, 0x1d, 0x71, 0x01, 0x7e,
+    0x1c, 0x70, 0x62, 0x20, 0x61, 0xf8, 0x35, 0xc1, 0x71, 0xe7, 0x02, 0x0d,
+    0x88, 0x44, 0xd9, 0x00, 0xc5, 0xcc, 0x63, 0xe4, 0xf0, 0x86, 0xa7, 0xd0,
+    0xfe, 0xcc, 0xb7, 0x1d, 0xfc, 0x21, 0x61, 0x54, 0x15, 0xea, 0x81, 0x5e,
+    0xc0, 0x31, 0xfa, 0xbf, 0x7d, 0xb9, 0x3b, 0xa2, 0x1e, 0x42, 0x73, 0x05,
+    0x3c, 0xdb, 0x21, 0x59, 0x4f, 0x63,
+
+    /* Third Packet: 1-RTT */
+    0x5f,               /* Short, 1-RTT, Spin=0, KP=0, PN Length=2 bytes */
+    0x68, 0x47,         /* PN (0) */
+    0xa3, 0x3c, 0xa5, 0x27, 0x5e, 0xf9, 0x8d, 0xec, 0xea, 0x6c, 0x09, 0x18,
+    0x40, 0x80, 0xee, 0x9f, 0x6f, 0x73, 0x5c, 0x49, 0xe3, 0xec, 0xb7, 0x58,
+    0x05, 0x66, 0x8f, 0xa3, 0x52, 0x37, 0xa1, 0x22, 0x1f, 0xc6, 0x92, 0xd6,
+    0x59, 0x04, 0x99, 0xcb, 0x44, 0xef, 0x66, 0x05, 0x2d, 0xd0, 0x85, 0x24,
+    0xbb, 0xe3, 0xa1, 0xd1, 0xbe, 0xf7, 0x54, 0xad, 0x65, 0xf4, 0xd4, 0x59,
+    0x54, 0x87, 0x4e, 0x22, 0x4f, 0x06, 0x07, 0xa7, 0x8a, 0x14, 0x89, 0xd1,
+    0x3f, 0xd3, 0xe4, 0x6f, 0x71, 0x8f, 0x9a, 0xd2, 0x3b, 0x61, 0x0a, 0xba,
+    0x9a, 0x31, 0x56, 0xc7,
+};
+
+static const QUIC_PKT_HDR script_5a_expect_hdr = {
+    QUIC_PKT_TYPE_INITIAL,
+    0,          /* Spin Bit */
+    0,          /* Key Phase */
+    2,          /* PN Length */
+    0,          /* Partial */
+    1,          /* Fixed */
+    1,          /* Version */
+    {0, {0}},                           /* DCID */
+    {4, {0x83, 0xd0, 0x0a, 0x27}},      /* SCID */
+    {0},        /* PN */
+    NULL, 0,    /* Token/Token Len */
+    448, NULL
+};
+
+static const unsigned char script_5a_body[] = {
+    0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x40, 0x5a, 0x02, 0x00,
+    0x00, 0x56, 0x03, 0x03, 0xe2, 0xd2, 0x0a, 0x3b, 0xa2, 0xc4, 0xd2, 0x29,
+    0xc8, 0xe8, 0xba, 0x23, 0x31, 0x88, 0x2c, 0x71, 0xeb, 0xba, 0x42, 0x5f,
+    0x94, 0xe9, 0x0a, 0x90, 0x35, 0x31, 0x1e, 0xca, 0xed, 0xf8, 0x8a, 0x8d,
+    0x00, 0x13, 0x01, 0x00, 0x00, 0x2e, 0x00, 0x2b, 0x00, 0x02, 0x03, 0x04,
+    0x00, 0x33, 0x00, 0x24, 0x00, 0x1d, 0x00, 0x20, 0x96, 0x0b, 0x4b, 0x30,
+    0x66, 0x3a, 0x75, 0x01, 0x4a, 0xdc, 0x2a, 0x75, 0x1f, 0xce, 0x7a, 0x30,
+    0x9d, 0x00, 0xca, 0x20, 0xb4, 0xe0, 0x6b, 0x81, 0x23, 0x18, 0x0b, 0x20,
+    0x1f, 0x54, 0x86, 0x1d,
+};
+
+static const QUIC_PKT_HDR script_5b_expect_hdr = {
+    QUIC_PKT_TYPE_HANDSHAKE,
+    0,          /* Spin Bit */
+    0,          /* Key Phase */
+    2,          /* PN Length */
+    0,          /* Partial */
+    1,          /* Fixed */
+    1,          /* Version */
+    {0, {0}},                           /* DCID */
+    {4, {0x83, 0xd0, 0x0a, 0x27}},      /* SCID */
+    {0},        /* PN */
+    NULL, 0,    /* Token/Token Len */
+    650, NULL
+};
+
+static const unsigned char script_5b_body[] = {
+    0x06, 0x00, 0x42, 0x86, 0x08, 0x00, 0x00, 0x7d, 0x00, 0x7b, 0x00, 0x10,
+    0x00, 0x08, 0x00, 0x06, 0x05, 0x64, 0x75, 0x6d, 0x6d, 0x79, 0x00, 0x39,
+    0x00, 0x6b, 0x4b, 0x20, 0x0b, 0x1b, 0xe1, 0x1f, 0xd0, 0x78, 0xc0, 0x69,
+    0x72, 0x9c, 0xe2, 0xf7, 0x05, 0x04, 0x80, 0x08, 0x00, 0x00, 0x06, 0x04,
+    0x80, 0x08, 0x00, 0x00, 0x07, 0x04, 0x80, 0x08, 0x00, 0x00, 0x04, 0x04,
+    0x80, 0x0c, 0x00, 0x00, 0x08, 0x02, 0x40, 0x64, 0x09, 0x02, 0x40, 0x64,
+    0x01, 0x04, 0x80, 0x00, 0x75, 0x30, 0x03, 0x02, 0x45, 0xac, 0x0b, 0x01,
+    0x1a, 0x0c, 0x00, 0x02, 0x10, 0x41, 0x94, 0x41, 0x8d, 0x0d, 0xfb, 0x60,
+    0x7b, 0xdc, 0xcc, 0xa2, 0x9c, 0x3e, 0xa5, 0xdf, 0x8d, 0x00, 0x08, 0x2d,
+    0x71, 0x8a, 0x38, 0xdf, 0xdd, 0xe0, 0x03, 0x0e, 0x01, 0x04, 0x0f, 0x04,
+    0x83, 0xd0, 0x0a, 0x27, 0x10, 0x04, 0xad, 0x15, 0x3f, 0xae, 0x20, 0x01,
+    0x00, 0x0b, 0x00, 0x01, 0x8f, 0x00, 0x00, 0x01, 0x8b, 0x00, 0x01, 0x86,
+    0x30, 0x82, 0x01, 0x82, 0x30, 0x82, 0x01, 0x29, 0xa0, 0x03, 0x02, 0x01,
+    0x02, 0x02, 0x14, 0x0a, 0x73, 0x0f, 0x86, 0x18, 0xf2, 0xc3, 0x30, 0x01,
+    0xd2, 0xc0, 0xc1, 0x62, 0x52, 0x13, 0xf1, 0x9c, 0x13, 0x39, 0xb5, 0x30,
+    0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x30,
+    0x17, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x0c,
+    0x6d, 0x61, 0x70, 0x61, 0x6b, 0x74, 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x6c,
+    0x30, 0x1e, 0x17, 0x0d, 0x32, 0x32, 0x30, 0x38, 0x30, 0x32, 0x31, 0x32,
+    0x30, 0x30, 0x31, 0x38, 0x5a, 0x17, 0x0d, 0x32, 0x32, 0x30, 0x39, 0x30,
+    0x31, 0x31, 0x32, 0x30, 0x30, 0x31, 0x38, 0x5a, 0x30, 0x17, 0x31, 0x15,
+    0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x0c, 0x6d, 0x61, 0x70,
+    0x61, 0x6b, 0x74, 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x30, 0x59, 0x30,
+    0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08,
+    0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04,
+    0x67, 0xf4, 0xd3, 0x8f, 0x15, 0x6d, 0xee, 0x85, 0xcc, 0x2a, 0x77, 0xfc,
+    0x0b, 0x8f, 0x9f, 0xcf, 0xa9, 0x95, 0x5d, 0x5b, 0xcd, 0xb7, 0x8b, 0xba,
+    0x31, 0x0a, 0x73, 0x62, 0xc5, 0xd0, 0x0e, 0x07, 0x90, 0xae, 0x38, 0x43,
+    0x79, 0xce, 0x5e, 0x33, 0xad, 0x31, 0xbf, 0x9f, 0x2a, 0x56, 0x83, 0xa5,
+    0x24, 0x16, 0xab, 0x0c, 0xf1, 0x64, 0xbe, 0xe4, 0x93, 0xb5, 0x89, 0xd6,
+    0x05, 0xe4, 0xf7, 0x7b, 0xa3, 0x53, 0x30, 0x51, 0x30, 0x1d, 0x06, 0x03,
+    0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x02, 0x64, 0x0f, 0x55, 0x69,
+    0x14, 0x91, 0x19, 0xed, 0xf9, 0x1a, 0xe9, 0x1d, 0xa5, 0x5a, 0xd0, 0x48,
+    0x96, 0x9f, 0x60, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18,
+    0x30, 0x16, 0x80, 0x14, 0x02, 0x64, 0x0f, 0x55, 0x69, 0x14, 0x91, 0x19,
+    0xed, 0xf9, 0x1a, 0xe9, 0x1d, 0xa5, 0x5a, 0xd0, 0x48, 0x96, 0x9f, 0x60,
+    0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05,
+    0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48,
+    0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x47, 0x00, 0x30, 0x44, 0x02, 0x20,
+    0x0a, 0x82, 0x92, 0x6e, 0xd3, 0xc6, 0x66, 0xd9, 0xd3, 0x75, 0xff, 0x71,
+    0x3b, 0x61, 0x46, 0x21, 0x00, 0xe6, 0x21, 0x5d, 0x9c, 0x86, 0xe9, 0x65,
+    0x40, 0x4f, 0xeb, 0x70, 0x4f, 0x2c, 0xad, 0x00, 0x02, 0x20, 0x08, 0xc2,
+    0x07, 0x5d, 0x16, 0xfc, 0x54, 0x34, 0x2b, 0xb4, 0x18, 0x67, 0x44, 0x81,
+    0xc9, 0xa9, 0x67, 0x2e, 0xce, 0xa1, 0x02, 0x9f, 0x3b, 0xe5, 0x61, 0x16,
+    0x0b, 0x50, 0xf6, 0xa1, 0x50, 0x94, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x4a,
+    0x04, 0x03, 0x00, 0x46, 0x30, 0x44, 0x02, 0x20, 0x7d, 0x57, 0x17, 0x14,
+    0x46, 0x09, 0x95, 0x70, 0x09, 0x45, 0xe8, 0x9e, 0x5c, 0x87, 0x55, 0xd9,
+    0x08, 0xc6, 0x5e, 0x47, 0x73, 0x5e, 0xb1, 0xc9, 0xef, 0xcb, 0xe5, 0x7f,
+    0xcc, 0xb0, 0x28, 0xbc, 0x02, 0x20, 0x5d, 0xe4, 0x2b, 0x83, 0xd9, 0x78,
+    0x75, 0x45, 0xf3, 0x22, 0x2b, 0x38, 0xeb, 0x68, 0xe5, 0x71, 0x5d, 0xcb,
+    0xc3, 0x68, 0xb3, 0x0e, 0x7d, 0x5e, 0x1d, 0xc2, 0x1b, 0x8a, 0x62, 0x80,
+    0x48, 0x3e, 0x14, 0x00, 0x00, 0x20, 0x37, 0xcd, 0x55, 0xca, 0x3f, 0x4b,
+    0xf0, 0x95, 0xf8, 0xe4, 0xfe, 0x59, 0xab, 0xbc, 0xc1, 0x8f, 0x0c, 0x3f,
+    0x41, 0x59, 0xf6, 0x96, 0xdb, 0x75, 0xae, 0xe7, 0x86, 0x1a, 0x92, 0xa7,
+    0x53, 0x0a,
+};
+
+static const QUIC_PKT_HDR script_5c_expect_hdr = {
+    QUIC_PKT_TYPE_1RTT,
+    0,          /* Spin Bit */
+    0,          /* Key Phase */
+    2,          /* PN Length */
+    0,          /* Partial */
+    1,          /* Fixed */
+    0,          /* Version */
+    {0, {0}},                           /* DCID */
+    {0, {0}},                           /* SCID */
+    {0},        /* PN */
+    NULL, 0,    /* Token/Token Len */
+    72, NULL
+};
+
+static const unsigned char script_5c_body[] = {
+    0x18, 0x03, 0x00, 0x04, 0x92, 0xec, 0xaa, 0xd6, 0x47, 0xd8, 0x8b, 0x56,
+    0x3b, 0x5f, 0x67, 0xe6, 0xb9, 0xb9, 0xca, 0x72, 0xca, 0xf2, 0x49, 0x7d,
+    0x18, 0x02, 0x00, 0x04, 0xa9, 0x6e, 0x9b, 0x84, 0x26, 0x43, 0x00, 0xc7,
+    0x55, 0x71, 0x67, 0x2e, 0x52, 0xdd, 0x47, 0xfd, 0x06, 0x51, 0x33, 0x08,
+    0x18, 0x01, 0x00, 0x04, 0x36, 0xd5, 0x1f, 0x06, 0x4e, 0xbf, 0xb4, 0xc9,
+    0xef, 0x97, 0x1e, 0x9a, 0x3c, 0xab, 0x1e, 0xfc, 0xb7, 0x90, 0xc3, 0x1a,
+};
+
+static const struct test_op script_5[] = {
+    OP_ADD_RX_DCID(empty_conn_id)
+    OP_PROVIDE_SECRET_INITIAL(script_5_c2s_init_dcid)
+    OP_INJECT_N(5)
+    OP_CHECK_PKT_N(5a)
+    OP_CHECK_NO_PKT() /* not got secret for next packet yet */
+    OP_PROVIDE_SECRET(QUIC_ENC_LEVEL_HANDSHAKE,
+                      QRL_SUITE_AES128GCM, script_5_handshake_secret)
+    OP_CHECK_PKT_N(5b)
+    OP_CHECK_NO_PKT() /* not got secret for next packet yet */
+    OP_PROVIDE_SECRET(QUIC_ENC_LEVEL_1RTT,
+                      QRL_SUITE_AES128GCM, script_5_1rtt_secret)
+    OP_CHECK_PKT_N(5c)
+    OP_CHECK_NO_PKT()
+
+    /* Try injecting the packet again */
+    OP_INJECT_N(5)
+    /*
+     * Initial packet is not output due to receiving a Handshake packet causing
+     * auto-discard of Initial keys
+     */
+    OP_CHECK_PKT_N(5b)
+    OP_CHECK_PKT_N(5c)
+    OP_CHECK_NO_PKT()
+    /* Try again with discarded keys */
+    OP_DISCARD_EL(QUIC_ENC_LEVEL_HANDSHAKE)
+    OP_INJECT_N(5)
+    OP_CHECK_PKT_N(5c)
+    OP_CHECK_NO_PKT()
+    /* Try again */
+    OP_INJECT_N(5)
+    OP_CHECK_PKT_N(5c)
+    OP_CHECK_NO_PKT()
+    /* Try again with discarded 1-RTT keys */
+    OP_DISCARD_EL(QUIC_ENC_LEVEL_1RTT)
+    OP_INJECT_N(5)
+    OP_CHECK_NO_PKT()
+
+    /* Recreate QRL, test reading packets received before key */
+    OP_SET_SCID_LEN(0)
+    OP_ADD_RX_DCID(empty_conn_id)
+    OP_INJECT_N(5)
+    OP_CHECK_NO_PKT()
+    OP_PROVIDE_SECRET_INITIAL(script_5_c2s_init_dcid)
+    OP_CHECK_PKT_N(5a)
+    OP_CHECK_NO_PKT()
+    OP_PROVIDE_SECRET(QUIC_ENC_LEVEL_HANDSHAKE,
+                      QRL_SUITE_AES128GCM, script_5_handshake_secret)
+    OP_CHECK_PKT_N(5b)
+    OP_CHECK_NO_PKT()
+    OP_PROVIDE_SECRET(QUIC_ENC_LEVEL_1RTT,
+                      QRL_SUITE_AES128GCM, script_5_1rtt_secret)
+    OP_CHECK_PKT_N(5c)
+    OP_CHECK_NO_PKT()
+
+    OP_DISCARD_EL(QUIC_ENC_LEVEL_HANDSHAKE)
+    OP_DISCARD_EL(QUIC_ENC_LEVEL_1RTT)
+    OP_INJECT_N(5)
+    OP_CHECK_NO_PKT()
+
+    OP_END
+};
+
+/*
+ * 6. Real World - S2C Multiple Packets
+ *      - Initial, Handshake, 1-RTT (AES-256-GCM/SHA384)
+ */
+static const QUIC_CONN_ID script_6_c2s_init_dcid = {
+    4, {0xac, 0x88, 0x95, 0xbd}
+};
+
+static const unsigned char script_6_handshake_secret[48] = {
+    0xd1, 0x41, 0xb0, 0xf6, 0x0d, 0x8b, 0xbd, 0xe8, 0x5b, 0xa8, 0xff, 0xd7,
+    0x18, 0x9a, 0x23, 0x7b, 0x13, 0x5c, 0x1e, 0x90, 0x1d, 0x08, 0x95, 0xcc,
+    0xc5, 0x8e, 0x73, 0x4e, 0x02, 0x6f, 0x3c, 0xb6, 0x26, 0x77, 0x8d, 0x53,
+    0xc5, 0x62, 0x9f, 0xb5, 0xf0, 0x88, 0xfb, 0xe5, 0x14, 0x71, 0xab, 0xe6,
+};
+
+static const unsigned char script_6_1rtt_secret[48] = {
+    0x2d, 0x6b, 0x9d, 0xd4, 0x39, 0xa0, 0xe7, 0xff, 0x17, 0xe2, 0xcb, 0x5c,
+    0x0d, 0x4a, 0xf6, 0x3f, 0xf4, 0xfe, 0xfc, 0xe5, 0x22, 0xfa, 0xf5, 0x5b,
+    0xc0, 0xb2, 0x18, 0xbb, 0x92, 0x4d, 0x35, 0xea, 0x67, 0xa6, 0xe7, 0xc1,
+    0x90, 0x10, 0xc9, 0x14, 0x46, 0xf5, 0x95, 0x57, 0x8b, 0x90, 0x88, 0x5d,
+};
+
+static const unsigned char script_6_in[] = {
+    /* First Packet: Initial */
+    0xc5,                           /* Long, Initial, PN Length=2 bytes */
+    0x00, 0x00, 0x00, 0x01,         /* Version */
+    0x00,                           /* DCID */
+    0x04, 0x36, 0xf4, 0x75, 0x2d,   /* SCID */
+    0x00,                           /* Token Length */
+    0x41, 0xbe,                     /* Length (446) */
+    0xa9, 0xe2,                     /* PN (0) */
+    0x83, 0x39, 0x95, 0x8f, 0x8f, 0x8c, 0xa9, 0xaf, 0x10, 0x29, 0x3d, 0xfc,
+    0x56, 0x4a, 0x1c, 0x4b, 0xc9, 0x48, 0xb1, 0xaf, 0x36, 0xd5, 0xac, 0x95,
+    0xbf, 0xfd, 0x2c, 0x4d, 0x70, 0x2e, 0x5b, 0x7c, 0x22, 0x5f, 0x5f, 0xee,
+    0x10, 0x8f, 0xfb, 0x0b, 0x5f, 0x9d, 0x7e, 0x68, 0x2f, 0x94, 0x0b, 0xdb,
+    0xed, 0xef, 0xfa, 0x4e, 0xc6, 0xd5, 0xe7, 0xef, 0xe0, 0x78, 0x3c, 0xdc,
+    0xe9, 0xd8, 0xe8, 0x56, 0x71, 0xd7, 0xe7, 0x6c, 0x7f, 0x5d, 0xaa, 0x7a,
+    0x52, 0x1d, 0x95, 0x7a, 0x80, 0x70, 0x38, 0xc0, 0x8b, 0xa1, 0x2f, 0x09,
+    0x16, 0xd2, 0xec, 0xa3, 0x23, 0x72, 0x45, 0x3c, 0xbd, 0x8c, 0xda, 0xbb,
+    0x37, 0x5a, 0x8d, 0xb2, 0x00, 0x7e, 0x67, 0x0c, 0xa0, 0x32, 0xdd, 0x80,
+    0x07, 0x71, 0xb0, 0x95, 0x21, 0xbc, 0x1e, 0xbd, 0x63, 0x0a, 0x10, 0xe7,
+    0x4b, 0x6e, 0x2e, 0x85, 0x3a, 0x65, 0xf7, 0x06, 0x6e, 0x7e, 0x8f, 0x65,
+    0x8c, 0xb1, 0x93, 0xe9, 0x0d, 0xe8, 0x46, 0xe7, 0xcf, 0xa7, 0xd2, 0x8b,
+    0x15, 0x23, 0xec, 0xc3, 0xec, 0x44, 0xda, 0x62, 0x15, 0x35, 0x34, 0x2f,
+    0x62, 0x77, 0xc8, 0x1f, 0x83, 0x22, 0x00, 0xe5, 0xc0, 0x89, 0xb8, 0x97,
+    0xd2, 0x37, 0x02, 0xea, 0xa2, 0x35, 0xbf, 0x19, 0xf0, 0xba, 0x1d, 0xb7,
+    0xaa, 0x36, 0xbb, 0x11, 0x60, 0xc3, 0x45, 0x1f, 0xe5, 0x18, 0xde, 0x4c,
+    0x01, 0x23, 0x2d, 0x17, 0x78, 0xdd, 0x4c, 0x8a, 0x1e, 0x1b, 0xd4, 0xda,
+    0x56, 0x43, 0x13, 0xa4, 0x4f, 0xfd, 0xd5, 0x92, 0x6a, 0x05, 0x5f, 0x14,
+    0x63, 0x85, 0x7d, 0xf1, 0x31, 0xb8, 0x27, 0x0b, 0xa6, 0xb5, 0x50, 0xca,
+    0x8b, 0x0e, 0xa1, 0x0d, 0xf9, 0xc4, 0xea, 0x6a, 0x6e, 0x4b, 0x6d, 0xdf,
+    0x49, 0xe8, 0x32, 0xf6, 0x85, 0xc4, 0x29, 0x26, 0x32, 0xfb, 0x5e, 0xa8,
+    0x55, 0x6b, 0x67, 0xe9, 0xaa, 0x35, 0x33, 0x90, 0xd8, 0x2a, 0x71, 0x0b,
+    0x6a, 0x48, 0xc4, 0xa3, 0x8b, 0xe0, 0xe7, 0x00, 0x3d, 0xee, 0x30, 0x70,
+    0x84, 0xbd, 0xa3, 0x3c, 0x9e, 0xa3, 0x5c, 0x69, 0xab, 0x55, 0x7b, 0xe2,
+    0xe5, 0x86, 0x13, 0xcb, 0x93, 0x3f, 0xcb, 0x3e, 0x6d, 0xc9, 0xc2, 0x10,
+    0x2b, 0x00, 0x9b, 0x3f, 0x14, 0x4e, 0x04, 0x27, 0xc0, 0xae, 0x1d, 0x48,
+    0x89, 0x3a, 0xf4, 0xac, 0xe0, 0x05, 0x07, 0xc9, 0x74, 0x6e, 0x21, 0x01,
+    0xe9, 0x26, 0xfd, 0xb4, 0xb2, 0x2a, 0xda, 0x72, 0xda, 0xbf, 0x63, 0x9d,
+    0x37, 0xaf, 0x90, 0x05, 0xd6, 0x89, 0xc7, 0xa6, 0x81, 0x4e, 0x2a, 0x30,
+    0xe3, 0x05, 0x88, 0x9f, 0xd0, 0xba, 0x8d, 0xc4, 0x21, 0x52, 0x5a, 0x7a,
+    0xe1, 0xad, 0xd3, 0x88, 0xc2, 0x18, 0xad, 0x4c, 0xb1, 0x66, 0x73, 0x1b,
+    0xf2, 0xd1, 0xb9, 0x43, 0xaa, 0xc4, 0x66, 0xcd, 0x42, 0xfa, 0x80, 0xec,
+    0xa1, 0x7c, 0x45, 0x02, 0x53, 0x45, 0xd5, 0x07, 0xd4, 0x70, 0x12, 0x1b,
+    0x08, 0x05, 0x6e, 0x99, 0x0a, 0xd3, 0x5b, 0x99, 0x6b, 0x65, 0xc4, 0xc0,
+    0x04, 0x1b, 0x75, 0xf2, 0x86, 0x99, 0x09, 0x4a, 0x50, 0x70, 0x00, 0x7a,
+    0x93, 0xaa, 0xe6, 0xf4, 0x03, 0x29, 0x06, 0xa4, 0x30, 0x6d, 0x52, 0xbd,
+    0x60, 0xd1, 0x7e, 0xd6, 0x07, 0xc0, 0x41, 0x01, 0x12, 0x3e, 0x16, 0x94,
+
+    /* Second Packet: Handshake */
+    0xea,                           /* Long, Handshake, PN Length=2 bytes */
+    0x00, 0x00, 0x00, 0x01,         /* Version */
+    0x00,                           /* DCID */
+    0x04, 0x36, 0xf4, 0x75, 0x2d,   /* SCID */
+    0x42, 0xb0,                     /* Length (688) */
+    0x3a, 0xc5,                     /* PN (0) */
+    0x3b, 0x8e, 0x4c, 0x01, 0x72, 0x6b, 0xfa, 0xbb, 0xad, 0xf9, 0x9e, 0x21,
+    0xb1, 0xd0, 0x01, 0xf1, 0xd4, 0x67, 0x8d, 0x2c, 0xee, 0x04, 0x60, 0x4a,
+    0xe2, 0xe4, 0xc6, 0x89, 0x01, 0xae, 0x3c, 0x1f, 0xf7, 0xe6, 0xf7, 0xac,
+    0x26, 0xcf, 0x3c, 0x6d, 0x1d, 0xfd, 0x11, 0x02, 0x51, 0x73, 0xb5, 0xe1,
+    0xb2, 0x44, 0x42, 0x32, 0x0f, 0xf5, 0x3d, 0x55, 0x2d, 0x1f, 0x02, 0x29,
+    0x51, 0x35, 0xdb, 0xc7, 0x7a, 0x34, 0x4b, 0xec, 0x60, 0x49, 0xa2, 0x90,
+    0x11, 0xef, 0x5a, 0xa9, 0x1c, 0xf7, 0xd9, 0x21, 0x68, 0x1c, 0x2b, 0xc6,
+    0x57, 0xde, 0xb1, 0x0b, 0x31, 0xed, 0xef, 0x16, 0xba, 0x08, 0xb9, 0xe2,
+    0xd9, 0xd0, 0xd8, 0x1f, 0xc4, 0x32, 0xe8, 0x45, 0x2a, 0x86, 0xe4, 0xd3,
+    0xaf, 0x72, 0x4f, 0x30, 0x01, 0x71, 0x15, 0x9b, 0xa9, 0x55, 0x35, 0xf7,
+    0x39, 0x7e, 0x6a, 0x59, 0x18, 0x4f, 0xe6, 0xdf, 0xb5, 0x0d, 0xc2, 0xe7,
+    0xb2, 0xa1, 0xa6, 0xa3, 0x9c, 0xf0, 0x0d, 0x59, 0x05, 0x49, 0x95, 0xfa,
+    0xcc, 0x72, 0xd7, 0xc0, 0x84, 0x2e, 0xc4, 0x1c, 0xd4, 0xa0, 0xe3, 0x6c,
+    0x5a, 0x8c, 0x94, 0x4d, 0x37, 0x1a, 0x1c, 0x68, 0x93, 0x5f, 0xe5, 0x99,
+    0x27, 0xc6, 0x06, 0xaa, 0x1f, 0x29, 0x17, 0xc5, 0x8c, 0x3d, 0x53, 0xa7,
+    0x05, 0x3a, 0x44, 0x53, 0x86, 0xed, 0x56, 0x99, 0x4c, 0xe2, 0x7b, 0x3a,
+    0x1e, 0x5d, 0x6d, 0xac, 0x78, 0x1e, 0xfa, 0x55, 0x58, 0x6e, 0x72, 0xee,
+    0xf9, 0x33, 0x64, 0x7f, 0x93, 0x3c, 0xfe, 0x18, 0x97, 0x6b, 0x02, 0x74,
+    0x90, 0x0d, 0xba, 0x89, 0xc0, 0x22, 0x0a, 0x0a, 0x37, 0x4c, 0x28, 0x74,
+    0xa7, 0x3a, 0x44, 0x74, 0x42, 0xff, 0xf1, 0xd2, 0x8d, 0x0c, 0xc1, 0xed,
+    0x98, 0x98, 0x8e, 0xa8, 0x6b, 0x95, 0x6a, 0x86, 0x0b, 0xb4, 0x95, 0x58,
+    0x34, 0x12, 0xb0, 0xc0, 0xf8, 0x2d, 0x5b, 0x40, 0x51, 0x80, 0x07, 0x91,
+    0x31, 0x77, 0xd3, 0x06, 0xa5, 0xe5, 0x1f, 0xe2, 0xf8, 0x92, 0xe4, 0x23,
+    0x2b, 0xf0, 0x4c, 0xa9, 0xa5, 0x6c, 0x6f, 0xaf, 0xaf, 0xbf, 0x97, 0xcf,
+    0x46, 0xf2, 0x8d, 0x61, 0x0e, 0x73, 0xcd, 0xc5, 0xde, 0xda, 0x50, 0x82,
+    0x61, 0x6d, 0xb1, 0xa2, 0xbe, 0x6b, 0x99, 0xcd, 0x5b, 0x99, 0x8f, 0x66,
+    0xab, 0x11, 0x78, 0xcc, 0xdb, 0x66, 0x98, 0xca, 0x19, 0x92, 0xf4, 0x05,
+    0xae, 0xe6, 0xf3, 0xe7, 0xf0, 0x30, 0x28, 0x31, 0x74, 0xff, 0xe2, 0xb3,
+    0x3a, 0x4f, 0x79, 0xe7, 0x2a, 0x9f, 0xe3, 0x41, 0xb2, 0x88, 0xc8, 0x8f,
+    0x77, 0x57, 0x42, 0x65, 0xdb, 0x07, 0xf6, 0x5f, 0xb8, 0x34, 0x17, 0xe3,
+    0x8d, 0x22, 0x5b, 0x88, 0x94, 0x60, 0x97, 0x32, 0x3d, 0x8a, 0x51, 0x9d,
+    0xb5, 0xac, 0xd7, 0x99, 0x96, 0x23, 0x6d, 0xc9, 0xab, 0x61, 0x41, 0x8f,
+    0x72, 0x1b, 0xf8, 0x84, 0xd9, 0x57, 0x88, 0x68, 0x3d, 0x73, 0x5f, 0xb1,
+    0x18, 0x5c, 0x3a, 0x35, 0xd2, 0xc5, 0xb7, 0x29, 0xc7, 0x95, 0xdd, 0x21,
+    0xc0, 0x78, 0x49, 0xf3, 0x24, 0xe0, 0x4c, 0x5c, 0x32, 0x08, 0xb7, 0x00,
+    0x43, 0x70, 0x5a, 0x95, 0x23, 0x91, 0xf5, 0xb7, 0x61, 0x85, 0x6f, 0xb3,
+    0xa4, 0x6b, 0x05, 0x9d, 0x39, 0xa3, 0xb1, 0x1c, 0x61, 0xc5, 0xa5, 0xe7,
+    0x9a, 0xe9, 0x5d, 0xaa, 0xca, 0x11, 0xd8, 0x4b, 0xa4, 0x9c, 0x18, 0x4e,
+    0x2b, 0x2d, 0x75, 0xc1, 0x12, 0x20, 0xe4, 0x66, 0xa5, 0x59, 0x67, 0x4b,
+    0xcc, 0x52, 0x2d, 0xfa, 0xaa, 0xa4, 0xe9, 0xfc, 0x79, 0xd7, 0xff, 0x03,
+    0x3e, 0xec, 0xba, 0x97, 0x37, 0x52, 0xc1, 0x57, 0x31, 0x8e, 0x57, 0x0c,
+    0x54, 0x92, 0x9c, 0x25, 0x5c, 0xfa, 0x9f, 0xa5, 0x36, 0x18, 0xd0, 0xaa,
+    0xf3, 0x3b, 0x5b, 0x59, 0xbd, 0x33, 0x5e, 0x7d, 0x74, 0x7c, 0xaf, 0xe9,
+    0x54, 0x80, 0xc4, 0xb4, 0xa1, 0x24, 0x9e, 0x23, 0x0d, 0xbf, 0x4e, 0x0f,
+    0xaf, 0xa5, 0x16, 0xcb, 0x3b, 0xfa, 0x33, 0xa5, 0x68, 0xa6, 0x64, 0x48,
+    0x2f, 0x5e, 0xfa, 0x64, 0x4e, 0xe3, 0x27, 0x4f, 0x13, 0xe6, 0x37, 0xf6,
+    0xb9, 0x63, 0x4b, 0xdc, 0x49, 0x3c, 0x5e, 0x9e, 0x06, 0xea, 0xac, 0xa3,
+    0xdf, 0x6c, 0x49, 0xfb, 0xa1, 0x01, 0x4f, 0x6f, 0x74, 0x1f, 0xd3, 0x26,
+    0xa1, 0x92, 0x3e, 0xe0, 0x73, 0xd6, 0x3b, 0x67, 0x13, 0x53, 0x2e, 0xcb,
+    0xbc, 0x83, 0xd0, 0x6e, 0x28, 0xb1, 0xcb, 0xd9, 0x66, 0xe0, 0x33, 0x59,
+    0x45, 0xd3, 0x13, 0xc2, 0x48, 0xd5, 0x9e, 0x88, 0xba, 0x75, 0x7b, 0xb1,
+    0xfe, 0x6f, 0xec, 0xde, 0xff, 0x14, 0x59, 0x75, 0xbf, 0x1a, 0x74, 0x47,
+    0xc5, 0xd8, 0xe8, 0x1b, 0x3c, 0x86, 0xd7, 0x1f, 0x99, 0x11, 0xd3, 0x29,
+    0xfd, 0x5d, 0x22, 0x7e, 0x03, 0x78, 0xed, 0x62, 0x0e, 0xbe, 0x6d, 0x75,
+    0xf4, 0xa8, 0x6e, 0xc7, 0x21, 0x76, 0xc5, 0xa0, 0x0c, 0xaa, 0x58, 0x78,
+    0x7e, 0x6e, 0xfc, 0x1e, 0x2a, 0x1c, 0xdd, 0xe5, 0x78, 0x08, 0xbd, 0xdb,
+    0xea, 0x8f, 0x8a, 0xa5, 0xbf, 0x93, 0xfe, 0x0f, 0x03, 0xa1, 0xc8, 0x64,
+    0x9f, 0x4a,
+
+    /* Third Packet: 1-RTT */
+    0x48,               /* Short, 1-RTT, Spin=0, KP=0, PN Length=2 bytes */
+    0x3e, 0x28,         /* PN (0) */
+    0xb9, 0xdb, 0x61, 0xf8, 0x8b, 0x3a, 0xef, 0x26, 0x69, 0xf2, 0x57, 0xc6,
+    0x84, 0x25, 0x6b, 0x77, 0xbe, 0x8c, 0x43, 0x32, 0xf3, 0x9a, 0xd1, 0x85,
+    0x14, 0xbc, 0x89, 0x3b, 0x9c, 0xf3, 0xfc, 0x00, 0xa1, 0x3a, 0xc3, 0xc4,
+    0x1e, 0xdf, 0xd0, 0x11, 0x70, 0xd9, 0x02, 0x7a, 0xd4, 0xef, 0x86, 0x67,
+    0xb1, 0x1e, 0x5d, 0xe3, 0x7f, 0x82, 0x14, 0x52, 0xa5, 0x8a, 0x89, 0xa7,
+    0x98, 0x75, 0x2f, 0x8a, 0x00, 0xf3, 0xbd, 0x49, 0x26, 0x4d, 0x0c, 0xc7,
+    0x38, 0xe7, 0x91, 0x85, 0xc9, 0x21, 0x6a, 0x1c, 0xc4, 0xa3, 0x0e, 0xd8,
+    0xfe, 0xb1, 0x25, 0x1a,
+};
+
+static const QUIC_PKT_HDR script_6a_expect_hdr = {
+    QUIC_PKT_TYPE_INITIAL,
+    0,          /* Spin Bit */
+    0,          /* Key Phase */
+    2,          /* PN Length */
+    0,          /* Partial */
+    1,          /* Fixed */
+    1,          /* Version */
+    {0, {0}},                           /* DCID */
+    {4, {0x36, 0xf4, 0x75, 0x2d}},      /* SCID */
+    {0},        /* PN */
+    NULL, 0,    /* Token/Token Len */
+    428, NULL
+};
+
+static const unsigned char script_6a_body[] = {
+    0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00,
+    0x40, 0x5a, 0x02, 0x00, 0x00, 0x56, 0x03, 0x03, 0xc3, 0x45, 0xe8, 0xb8,
+    0xf9, 0x7c, 0x9f, 0x5d, 0xcf, 0x66, 0x25, 0xe4, 0x91, 0x0e, 0xb0, 0x5a,
+    0x14, 0xce, 0xaf, 0xea, 0x83, 0x12, 0xde, 0x68, 0xd9, 0x31, 0xf2, 0x23,
+    0x11, 0x3a, 0x15, 0xcb, 0x00, 0x13, 0x02, 0x00, 0x00, 0x2e, 0x00, 0x2b,
+    0x00, 0x02, 0x03, 0x04, 0x00, 0x33, 0x00, 0x24, 0x00, 0x1d, 0x00, 0x20,
+    0xab, 0xd3, 0xc6, 0x9f, 0x36, 0xd3, 0x52, 0x93, 0x87, 0xee, 0x92, 0x01,
+    0xa2, 0xd6, 0x9a, 0x5e, 0x61, 0x43, 0xcc, 0x4a, 0xcc, 0x7a, 0xcd, 0x83,
+    0xb2, 0xd9, 0xad, 0xd1, 0x14, 0xdc, 0x84, 0x61,
+};
+
+static const QUIC_PKT_HDR script_6b_expect_hdr = {
+    QUIC_PKT_TYPE_HANDSHAKE,
+    0,          /* Spin Bit */
+    0,          /* Key Phase */
+    2,          /* PN Length */
+    0,          /* Partial */
+    1,          /* Fixed */
+    1,          /* Version */
+    {0, {0}},                           /* DCID */
+    {4, {0x36, 0xf4, 0x75, 0x2d}},      /* SCID */
+    {0},        /* PN */
+    NULL, 0,    /* Token/Token Len */
+    670, NULL
+};
+
+static const unsigned char script_6b_body[] = {
+    0x06, 0x00, 0x42, 0x9a, 0x08, 0x00, 0x00, 0x80, 0x00, 0x7e, 0x00, 0x10,
+    0x00, 0x08, 0x00, 0x06, 0x05, 0x64, 0x75, 0x6d, 0x6d, 0x79, 0x00, 0x39,
+    0x00, 0x6e, 0x47, 0xfa, 0x05, 0x5a, 0xe0, 0xec, 0x4a, 0xf3, 0x05, 0x04,
+    0x80, 0x08, 0x00, 0x00, 0x06, 0x04, 0x80, 0x08, 0x00, 0x00, 0x07, 0x04,
+    0x80, 0x08, 0x00, 0x00, 0x04, 0x04, 0x80, 0x0c, 0x00, 0x00, 0x08, 0x02,
+    0x40, 0x64, 0x09, 0x02, 0x40, 0x64, 0x01, 0x04, 0x80, 0x00, 0x75, 0x30,
+    0x03, 0x02, 0x45, 0xac, 0x0b, 0x01, 0x1a, 0x0c, 0x00, 0x02, 0x10, 0x35,
+    0xd7, 0x7d, 0x8b, 0xc5, 0xb1, 0x89, 0xb1, 0x5c, 0x23, 0x74, 0x50, 0xfd,
+    0x47, 0xfe, 0xd2, 0x00, 0x11, 0x96, 0x38, 0x27, 0xde, 0x7d, 0xfb, 0x2b,
+    0x38, 0x56, 0xe5, 0x2a, 0xb8, 0x6b, 0xfa, 0xaa, 0xde, 0x81, 0x0e, 0x01,
+    0x04, 0x0f, 0x04, 0x36, 0xf4, 0x75, 0x2d, 0x10, 0x04, 0xac, 0x88, 0x95,
+    0xbd, 0x20, 0x01, 0x00, 0x0b, 0x00, 0x01, 0x8f, 0x00, 0x00, 0x01, 0x8b,
+    0x00, 0x01, 0x86, 0x30, 0x82, 0x01, 0x82, 0x30, 0x82, 0x01, 0x29, 0xa0,
+    0x03, 0x02, 0x01, 0x02, 0x02, 0x14, 0x0a, 0x73, 0x0f, 0x86, 0x18, 0xf2,
+    0xc3, 0x30, 0x01, 0xd2, 0xc0, 0xc1, 0x62, 0x52, 0x13, 0xf1, 0x9c, 0x13,
+    0x39, 0xb5, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04,
+    0x03, 0x02, 0x30, 0x17, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04,
+    0x03, 0x0c, 0x0c, 0x6d, 0x61, 0x70, 0x61, 0x6b, 0x74, 0x2e, 0x6c, 0x6f,
+    0x63, 0x61, 0x6c, 0x30, 0x1e, 0x17, 0x0d, 0x32, 0x32, 0x30, 0x38, 0x30,
+    0x32, 0x31, 0x32, 0x30, 0x30, 0x31, 0x38, 0x5a, 0x17, 0x0d, 0x32, 0x32,
+    0x30, 0x39, 0x30, 0x31, 0x31, 0x32, 0x30, 0x30, 0x31, 0x38, 0x5a, 0x30,
+    0x17, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x0c,
+    0x6d, 0x61, 0x70, 0x61, 0x6b, 0x74, 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x6c,
+    0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02,
+    0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03,
+    0x42, 0x00, 0x04, 0x67, 0xf4, 0xd3, 0x8f, 0x15, 0x6d, 0xee, 0x85, 0xcc,
+    0x2a, 0x77, 0xfc, 0x0b, 0x8f, 0x9f, 0xcf, 0xa9, 0x95, 0x5d, 0x5b, 0xcd,
+    0xb7, 0x8b, 0xba, 0x31, 0x0a, 0x73, 0x62, 0xc5, 0xd0, 0x0e, 0x07, 0x90,
+    0xae, 0x38, 0x43, 0x79, 0xce, 0x5e, 0x33, 0xad, 0x31, 0xbf, 0x9f, 0x2a,
+    0x56, 0x83, 0xa5, 0x24, 0x16, 0xab, 0x0c, 0xf1, 0x64, 0xbe, 0xe4, 0x93,
+    0xb5, 0x89, 0xd6, 0x05, 0xe4, 0xf7, 0x7b, 0xa3, 0x53, 0x30, 0x51, 0x30,
+    0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x02, 0x64,
+    0x0f, 0x55, 0x69, 0x14, 0x91, 0x19, 0xed, 0xf9, 0x1a, 0xe9, 0x1d, 0xa5,
+    0x5a, 0xd0, 0x48, 0x96, 0x9f, 0x60, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d,
+    0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x02, 0x64, 0x0f, 0x55, 0x69,
+    0x14, 0x91, 0x19, 0xed, 0xf9, 0x1a, 0xe9, 0x1d, 0xa5, 0x5a, 0xd0, 0x48,
+    0x96, 0x9f, 0x60, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01,
+    0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x0a, 0x06, 0x08,
+    0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x47, 0x00, 0x30,
+    0x44, 0x02, 0x20, 0x0a, 0x82, 0x92, 0x6e, 0xd3, 0xc6, 0x66, 0xd9, 0xd3,
+    0x75, 0xff, 0x71, 0x3b, 0x61, 0x46, 0x21, 0x00, 0xe6, 0x21, 0x5d, 0x9c,
+    0x86, 0xe9, 0x65, 0x40, 0x4f, 0xeb, 0x70, 0x4f, 0x2c, 0xad, 0x00, 0x02,
+    0x20, 0x08, 0xc2, 0x07, 0x5d, 0x16, 0xfc, 0x54, 0x34, 0x2b, 0xb4, 0x18,
+    0x67, 0x44, 0x81, 0xc9, 0xa9, 0x67, 0x2e, 0xce, 0xa1, 0x02, 0x9f, 0x3b,
+    0xe5, 0x61, 0x16, 0x0b, 0x50, 0xf6, 0xa1, 0x50, 0x94, 0x00, 0x00, 0x0f,
+    0x00, 0x00, 0x4b, 0x04, 0x03, 0x00, 0x47, 0x30, 0x45, 0x02, 0x20, 0x78,
+    0x9e, 0xe0, 0x6a, 0x7a, 0xbd, 0xc3, 0x84, 0x3d, 0x25, 0x6a, 0x59, 0x23,
+    0x97, 0x52, 0x64, 0x4e, 0xb6, 0x9f, 0xcc, 0xd3, 0xd7, 0xa9, 0x29, 0x44,
+    0x75, 0x6d, 0x50, 0xfc, 0x22, 0xde, 0xd3, 0x02, 0x21, 0x00, 0xe5, 0x28,
+    0xd6, 0x5a, 0xd1, 0xec, 0x4a, 0xcc, 0x20, 0xb4, 0xea, 0x15, 0xfb, 0x8e,
+    0x73, 0xa8, 0x6b, 0xbb, 0x42, 0x70, 0x90, 0x08, 0x6e, 0x74, 0x6f, 0x5a,
+    0x05, 0xb5, 0x39, 0xee, 0x01, 0x04, 0x14, 0x00, 0x00, 0x30, 0xff, 0x9f,
+    0xb2, 0x1d, 0xcb, 0x4f, 0xfc, 0x7a, 0xac, 0xf4, 0x75, 0x24, 0x83, 0x5f,
+    0x8d, 0xa3, 0x3e, 0x9d, 0xef, 0x43, 0x67, 0x89, 0x5d, 0x55, 0xc7, 0xce,
+    0x80, 0xab, 0xc3, 0xc7, 0x74, 0xc7, 0xb2, 0x91, 0x27, 0xce, 0xd8, 0x5e,
+    0xc4, 0x4e, 0x96, 0x19, 0x68, 0x2d, 0xbe, 0x6f, 0x49, 0xfa,
+};
+
+static const QUIC_PKT_HDR script_6c_expect_hdr = {
+    QUIC_PKT_TYPE_1RTT,
+    0,          /* Spin Bit */
+    0,          /* Key Phase */
+    2,          /* PN Length */
+    0,          /* Partial */
+    1,          /* Fixed */
+    0,          /* Version */
+    {0, {0}},                           /* DCID */
+    {0, {0}},                           /* SCID */
+    {0},        /* PN */
+    NULL, 0,    /* Token/Token Len */
+    72, NULL
+};
+
+static const unsigned char script_6c_body[] = {
+    0x18, 0x03, 0x00, 0x04, 0xf2, 0x94, 0x49, 0xc3, 0x34, 0xa1, 0xf4, 0x0f,
+    0xcb, 0xb8, 0x03, 0x04, 0x1f, 0xc8, 0x69, 0xb9, 0x3b, 0xd5, 0xc6, 0x93,
+    0x18, 0x02, 0x00, 0x04, 0x9a, 0x4f, 0xec, 0x52, 0xde, 0xd2, 0xc8, 0xb7,
+    0x1c, 0x0c, 0xf3, 0x4e, 0x46, 0xf0, 0x6c, 0x54, 0x34, 0x1b, 0x0d, 0x98,
+    0x18, 0x01, 0x00, 0x04, 0xe3, 0x33, 0x9e, 0x59, 0x00, 0x69, 0xc3, 0xac,
+    0xfc, 0x58, 0x0e, 0xa4, 0xf4, 0xf3, 0x23, 0x1b, 0xd6, 0x8e, 0x5b, 0x08,
+};
+
+static const struct test_op script_6[] = {
+    OP_ADD_RX_DCID(empty_conn_id)
+    OP_PROVIDE_SECRET_INITIAL(script_6_c2s_init_dcid)
+    OP_INJECT_N(6)
+    OP_CHECK_PKT_N(6a)
+    OP_CHECK_NO_PKT() /* not got secret for next packet yet */
+    OP_PROVIDE_SECRET(QUIC_ENC_LEVEL_HANDSHAKE,
+                      QRL_SUITE_AES256GCM, script_6_handshake_secret)
+    OP_CHECK_PKT_N(6b)
+    OP_CHECK_NO_PKT() /* not got secret for next packet yet */
+    OP_PROVIDE_SECRET(QUIC_ENC_LEVEL_1RTT,
+                      QRL_SUITE_AES256GCM, script_6_1rtt_secret)
+    OP_CHECK_PKT_N(6c)
+    OP_CHECK_NO_PKT()
+
+    /* Try injecting the packet again */
+    OP_INJECT_N(6)
+    /*
+     * Initial packet is not output due to receiving a Handshake packet causing
+     * auto-discard of Initial keys
+     */
+    OP_CHECK_PKT_N(6b)
+    OP_CHECK_PKT_N(6c)
+    OP_CHECK_NO_PKT()
+    /* Try again with discarded keys */
+    OP_DISCARD_EL(QUIC_ENC_LEVEL_HANDSHAKE)
+    OP_INJECT_N(6)
+    OP_CHECK_PKT_N(6c)
+    OP_CHECK_NO_PKT()
+    /* Try again */
+    OP_INJECT_N(6)
+    OP_CHECK_PKT_N(6c)
+    OP_CHECK_NO_PKT()
+    /* Try again with discarded 1-RTT keys */
+    OP_DISCARD_EL(QUIC_ENC_LEVEL_1RTT)
+    OP_INJECT_N(6)
+    OP_CHECK_NO_PKT()
+
+    /* Recreate QRL, test reading packets received before key */
+    OP_SET_SCID_LEN(0)
+    OP_ADD_RX_DCID(empty_conn_id)
+    OP_INJECT_N(6)
+    OP_CHECK_NO_PKT()
+    OP_PROVIDE_SECRET_INITIAL(script_6_c2s_init_dcid)
+    OP_CHECK_PKT_N(6a)
+    OP_CHECK_NO_PKT()
+    OP_PROVIDE_SECRET(QUIC_ENC_LEVEL_HANDSHAKE,
+                      QRL_SUITE_AES256GCM, script_6_handshake_secret)
+    OP_CHECK_PKT_N(6b)
+    OP_CHECK_NO_PKT()
+    OP_PROVIDE_SECRET(QUIC_ENC_LEVEL_1RTT,
+                      QRL_SUITE_AES256GCM, script_6_1rtt_secret)
+    OP_CHECK_PKT_N(6c)
+    OP_CHECK_NO_PKT()
+
+    OP_END
+};
+
+/*
+ * 7. Real World - S2C Multiple Packets
+ *      - Initial, Handshake, 1-RTT (ChaCha20-Poly1305)
+ */
+static const QUIC_CONN_ID script_7_c2s_init_dcid = {
+    4, {0xfa, 0x5d, 0xd6, 0x80}
+};
+
+static const unsigned char script_7_handshake_secret[32] = {
+    0x85, 0x44, 0xa4, 0x02, 0x46, 0x5b, 0x2a, 0x92, 0x80, 0x71, 0xfd, 0x11,
+    0x89, 0x73, 0x84, 0xeb, 0x3e, 0x0d, 0x89, 0x4f, 0x71, 0xdc, 0x9c, 0xdd,
+    0x55, 0x77, 0x9e, 0x79, 0x7b, 0xeb, 0xfa, 0x86,
+};
+
+static const unsigned char script_7_1rtt_secret[32] = {
+    0x4a, 0x77, 0xb6, 0x0e, 0xfd, 0x90, 0xca, 0xbf, 0xc0, 0x1a, 0x64, 0x9f,
+    0xc0, 0x03, 0xd3, 0x8d, 0xc5, 0x41, 0x04, 0x50, 0xb1, 0x5b, 0x74, 0xe7,
+    0xe3, 0x99, 0x0c, 0xdf, 0x74, 0x61, 0x35, 0xe6,
+};
+
+static const unsigned char script_7_in[] = {
+    /* First Packet: Initial */
+    0xc2,                           /* Long, Initial, PN Length=2 bytes */
+    0x00, 0x00, 0x00, 0x01,         /* Version */
+    0x00,                           /* DCID */
+    0x04, 0x03, 0x45, 0x0c, 0x7a,   /* SCID */
+    0x00,                           /* Token Length */
+    0x41, 0xcb,                     /* Length (459) */
+    0x3c, 0xe0,                     /* PN (0) */
+    0x85, 0x05, 0xc2, 0x4d, 0x0f, 0xf3, 0x62, 0x51, 0x04, 0x33, 0xfa, 0xb5,
+    0xa3, 0x02, 0xbd, 0x5c, 0x22, 0x0c, 0x1d, 0xda, 0x06, 0xf1, 0xd7, 0xe0,
+    0xc8, 0x56, 0xb0, 0x3d, 0xc1, 0x49, 0x8c, 0xc2, 0x88, 0x5a, 0x0e, 0xd5,
+    0x67, 0x72, 0xec, 0xcc, 0x7a, 0x2b, 0x46, 0x17, 0x49, 0x4b, 0x28, 0x6a,
+    0x89, 0x71, 0xfd, 0x31, 0x9a, 0xa1, 0x97, 0x64, 0xe2, 0xbf, 0xa0, 0x6d,
+    0xf6, 0x76, 0x83, 0x28, 0xc4, 0xd5, 0x39, 0x87, 0x22, 0x7c, 0x11, 0x9a,
+    0x53, 0x66, 0xb4, 0x27, 0xf1, 0xab, 0x6f, 0x49, 0x43, 0x3f, 0x9a, 0x23,
+    0xd3, 0x53, 0x06, 0xe8, 0x14, 0xfd, 0xc0, 0x67, 0x1f, 0x88, 0x2a, 0xa8,
+    0xae, 0x5f, 0x05, 0x0a, 0xeb, 0x66, 0x72, 0x8c, 0x46, 0xcc, 0x54, 0x21,
+    0x5e, 0x14, 0xfe, 0x68, 0xc7, 0xf7, 0x60, 0x67, 0xb5, 0xa7, 0x0d, 0xf4,
+    0xe1, 0xff, 0x60, 0xe3, 0x11, 0x38, 0x92, 0x90, 0xc2, 0x48, 0x28, 0xbf,
+    0xf3, 0x85, 0x27, 0xfe, 0xbf, 0x42, 0x26, 0x1a, 0x4e, 0x78, 0xf1, 0xf0,
+    0x88, 0x16, 0x1b, 0x64, 0x5f, 0x66, 0x02, 0x0b, 0x45, 0x3d, 0x38, 0xd9,
+    0x09, 0xd5, 0xff, 0xc2, 0x68, 0x02, 0x2c, 0xc4, 0x3f, 0x60, 0x6e, 0x2f,
+    0x7f, 0x43, 0xf7, 0x1a, 0x37, 0xcc, 0xe0, 0xe0, 0x4b, 0x96, 0xc1, 0xb1,
+    0x8b, 0x1c, 0x7c, 0x6e, 0x80, 0xe3, 0x92, 0x9b, 0x86, 0x87, 0x1f, 0x9a,
+    0x6a, 0x62, 0x18, 0xf4, 0x86, 0xc2, 0x3e, 0x33, 0xa3, 0xbf, 0x43, 0x96,
+    0x6e, 0xff, 0x94, 0xaf, 0x6d, 0x23, 0x5c, 0x42, 0xed, 0xe7, 0xb9, 0x2c,
+    0x33, 0xb0, 0xc6, 0x3d, 0x44, 0x00, 0x0b, 0xa3, 0x39, 0xa8, 0xeb, 0x8c,
+    0x81, 0x1a, 0x99, 0x20, 0xbd, 0xfa, 0xf3, 0xf4, 0xf0, 0x11, 0xd8, 0x41,
+    0x31, 0x8d, 0xdc, 0x0d, 0x00, 0xa6, 0x31, 0x40, 0xc6, 0xc6, 0xad, 0x74,
+    0x93, 0x62, 0x1c, 0x55, 0xce, 0x5f, 0x8c, 0x5b, 0x3c, 0xcb, 0x25, 0x5e,
+    0xbf, 0xed, 0xbb, 0x3c, 0x97, 0x4b, 0x62, 0xe0, 0xba, 0xf1, 0xb0, 0x30,
+    0xbf, 0x35, 0x89, 0x7e, 0x25, 0x61, 0x54, 0x86, 0x52, 0x11, 0x86, 0x90,
+    0xc3, 0xf5, 0xad, 0xa0, 0x96, 0x30, 0xb2, 0xf0, 0xa6, 0x79, 0x39, 0x1c,
+    0x51, 0x42, 0xa1, 0x00, 0x6f, 0x55, 0x7d, 0xdc, 0xd0, 0x7c, 0xcf, 0x01,
+    0x88, 0x03, 0xd7, 0x2d, 0x65, 0x2b, 0x40, 0xee, 0xba, 0x10, 0xd8, 0x0c,
+    0x85, 0x14, 0xb7, 0x4d, 0x9e, 0x7d, 0x7c, 0xde, 0x7f, 0x0d, 0x0e, 0x3b,
+    0x3d, 0xe3, 0xd3, 0x63, 0xc2, 0xed, 0xc7, 0x41, 0xaf, 0x05, 0x85, 0x87,
+    0x46, 0x55, 0x7e, 0xbe, 0x14, 0x5b, 0x98, 0xae, 0x6e, 0x67, 0x1a, 0x65,
+    0xc6, 0xcf, 0xe1, 0x28, 0x50, 0x6b, 0xb4, 0xf6, 0xba, 0x63, 0xbc, 0xf1,
+    0xd7, 0xa4, 0x97, 0x2d, 0x4d, 0x04, 0x26, 0x96, 0xec, 0x0c, 0xd4, 0xae,
+    0x6a, 0xca, 0x7e, 0x65, 0xc5, 0x43, 0x7e, 0xf8, 0x77, 0x61, 0xd0, 0x2c,
+    0xe5, 0x37, 0x0a, 0xb3, 0x7a, 0x8c, 0x2a, 0xa1, 0xdc, 0x29, 0xdb, 0xec,
+    0xca, 0xdc, 0xfe, 0xdd, 0x38, 0xd2, 0x13, 0x9f, 0x94, 0x6d, 0x5b, 0x87,
+    0xf3, 0x15, 0xa8, 0xe5, 0xe9, 0x65, 0x1d, 0x4f, 0x92, 0x1b, 0xf4, 0xa6,
+    0xa4, 0xd6, 0x22, 0xfc, 0x26, 0x1b, 0x35, 0xa4, 0x1c, 0x88, 0x9f, 0x7d,
+    0xe0, 0x9a, 0x89, 0x0f, 0x6c, 0xc1, 0xda, 0x6e, 0x45, 0xce, 0x74, 0xb1,
+    0xff,
+
+    /* Second Packet: Handshake */
+    0xeb,                           /* Long, Handshake, PN Length=2 bytes */
+    0x00, 0x00, 0x00, 0x01,         /* Version */
+    0x00,                           /* DCID */
+    0x04, 0x03, 0x45, 0x0c, 0x7a,   /* SCID */
+    0x42, 0xa3,                     /* Length (675) */
+    0x43, 0x29,                     /* PN (0) */
+    0xff, 0xdb, 0xcf, 0x3c, 0x17, 0xcf, 0xdc, 0x42, 0x3a, 0x59, 0x88, 0xdb,
+    0x13, 0xef, 0x09, 0x3d, 0xf2, 0x24, 0xf3, 0xeb, 0xca, 0xb0, 0xe1, 0xa4,
+    0x67, 0x64, 0x65, 0x80, 0x5f, 0x73, 0x29, 0x69, 0x29, 0xba, 0x03, 0x77,
+    0x22, 0xc8, 0xa8, 0xd5, 0x21, 0xf2, 0xa2, 0x30, 0x7f, 0x86, 0x3a, 0x8a,
+    0xdd, 0x92, 0x33, 0xa6, 0x57, 0x21, 0x39, 0xdd, 0x34, 0xb4, 0x39, 0xa7,
+    0x6f, 0x0a, 0x14, 0xba, 0x9e, 0x3b, 0x3a, 0x6a, 0x4b, 0xc5, 0xda, 0x44,
+    0x82, 0xca, 0x52, 0x86, 0x68, 0x8a, 0x0c, 0x5e, 0xeb, 0x1e, 0x81, 0x43,
+    0x3a, 0x59, 0x2c, 0x26, 0x63, 0xa3, 0x89, 0x92, 0x80, 0xe9, 0x75, 0xc2,
+    0xdb, 0xb9, 0x58, 0x6d, 0xab, 0xfd, 0x21, 0xe0, 0x35, 0x79, 0x2e, 0x56,
+    0x7b, 0xfb, 0xb3, 0x7a, 0x05, 0x33, 0x0f, 0x13, 0xe5, 0xef, 0x04, 0x41,
+    0x69, 0x85, 0x91, 0x24, 0xce, 0xb5, 0x21, 0x8d, 0x0a, 0x13, 0xda, 0xae,
+    0x86, 0x2f, 0x25, 0x1f, 0x9c, 0x70, 0x8a, 0xaa, 0x05, 0xeb, 0x30, 0x93,
+    0x50, 0xc1, 0x39, 0xab, 0x99, 0x8a, 0x31, 0xc1, 0xc1, 0x5e, 0x39, 0xcf,
+    0x64, 0x3f, 0x9f, 0x5c, 0xa5, 0xa1, 0x88, 0xb2, 0x5f, 0x23, 0xcb, 0x76,
+    0xe5, 0xf3, 0x2d, 0xa0, 0xed, 0xad, 0xcf, 0x30, 0x05, 0x44, 0xdc, 0xa5,
+    0x81, 0xb1, 0x7f, 0x78, 0x0d, 0x4d, 0x96, 0xa3, 0xcb, 0xcb, 0x45, 0xcf,
+    0x5f, 0x22, 0xb8, 0x93, 0x2b, 0x16, 0xe0, 0x1c, 0x53, 0x34, 0x76, 0x3b,
+    0x7b, 0x78, 0xa1, 0x46, 0x40, 0x43, 0x4b, 0x0e, 0x1c, 0xfd, 0xcf, 0x01,
+    0xf1, 0x2c, 0xee, 0xd0, 0xbd, 0x9f, 0x44, 0xd2, 0xd7, 0x13, 0xf9, 0x65,
+    0x82, 0xf5, 0x42, 0xec, 0x9f, 0x5d, 0x51, 0x5a, 0x7b, 0xf2, 0x39, 0xbb,
+    0xa6, 0x19, 0x5c, 0x73, 0x95, 0x65, 0x5b, 0x64, 0x2f, 0xda, 0x50, 0xd0,
+    0x02, 0x34, 0x3f, 0x35, 0xc1, 0xd6, 0x31, 0x3b, 0xcf, 0x3f, 0x81, 0x8d,
+    0xe0, 0x40, 0xfd, 0x6d, 0x32, 0x68, 0xa4, 0xf2, 0x4e, 0x3a, 0x4a, 0x42,
+    0x2c, 0x07, 0x2d, 0x27, 0xa3, 0x34, 0xe7, 0x27, 0x87, 0x80, 0x76, 0xc0,
+    0xa0, 0x72, 0x05, 0xf2, 0x88, 0x81, 0xe3, 0x32, 0x00, 0x76, 0x8d, 0x24,
+    0x5c, 0x97, 0x2d, 0xd6, 0xb8, 0x34, 0xf8, 0x1c, 0x1a, 0x6d, 0xc7, 0x3f,
+    0xcf, 0x56, 0xae, 0xec, 0x26, 0x74, 0x53, 0x69, 0xcd, 0x7a, 0x97, 0x29,
+    0xab, 0x12, 0x7d, 0x75, 0xf8, 0x8d, 0x5b, 0xc0, 0x77, 0x20, 0xb6, 0x6a,
+    0x0b, 0xce, 0x98, 0x50, 0xca, 0x47, 0x42, 0x1e, 0x5d, 0xc3, 0x24, 0x5a,
+    0x47, 0x48, 0x3b, 0xa0, 0x9e, 0x43, 0xe9, 0x8d, 0x18, 0x23, 0xda, 0x6f,
+    0x8c, 0xda, 0xd0, 0x3e, 0xdb, 0x37, 0xff, 0xfc, 0x7e, 0x17, 0xbe, 0x42,
+    0xfd, 0xdb, 0x51, 0xb1, 0xa4, 0xfd, 0x9a, 0x20, 0x27, 0x24, 0x17, 0x04,
+    0x70, 0xb6, 0x21, 0x87, 0x88, 0xe9, 0xda, 0x63, 0xcb, 0xcb, 0x1d, 0xaf,
+    0x4a, 0x46, 0x76, 0x88, 0xa1, 0xf8, 0x48, 0x6c, 0x06, 0xb4, 0x62, 0x1a,
+    0x67, 0x18, 0xb0, 0x1d, 0x58, 0x6a, 0xfe, 0x1f, 0xf1, 0x48, 0xff, 0xcb,
+    0xa4, 0xd1, 0xa8, 0x12, 0x1f, 0x45, 0x94, 0x2f, 0x55, 0x80, 0x6a, 0x06,
+    0xcc, 0x7b, 0xb0, 0xcc, 0xb8, 0x06, 0x52, 0x16, 0xe3, 0x6e, 0x7e, 0xb0,
+    0x42, 0xfd, 0x3b, 0x7e, 0x0a, 0x42, 0x7b, 0x73, 0xaf, 0x2c, 0xf3, 0xbd,
+    0xe5, 0x72, 0x8c, 0x16, 0xb2, 0xd7, 0x7a, 0x11, 0xb6, 0x9f, 0xd1, 0x69,
+    0xc1, 0x1a, 0xe0, 0x26, 0x26, 0x13, 0xe2, 0x75, 0xf5, 0x74, 0xae, 0x3f,
+    0xee, 0x1e, 0x09, 0x63, 0x5a, 0x30, 0x19, 0xa5, 0x59, 0x48, 0x90, 0x9b,
+    0x46, 0x56, 0xd8, 0x6f, 0x6b, 0x76, 0x82, 0x32, 0xc7, 0x29, 0x76, 0x2e,
+    0x32, 0xb6, 0x23, 0x99, 0xeb, 0x92, 0x5d, 0xc4, 0x4c, 0xa1, 0xe9, 0x26,
+    0x37, 0x9a, 0x7d, 0x4c, 0x16, 0x9c, 0x18, 0xe9, 0xc0, 0xff, 0x48, 0x79,
+    0xb1, 0x7b, 0x0b, 0x1e, 0x6f, 0xb1, 0x77, 0xa5, 0xd2, 0xc6, 0x9a, 0xa9,
+    0xfc, 0xd1, 0x0f, 0x69, 0xf3, 0xe0, 0x49, 0x70, 0x57, 0x80, 0x86, 0xa7,
+    0x3f, 0x54, 0xa8, 0x60, 0xfb, 0xe4, 0x06, 0xa3, 0x13, 0xb9, 0x2f, 0xa7,
+    0x37, 0x80, 0x0c, 0x43, 0xac, 0x2f, 0xae, 0x6e, 0x62, 0x2b, 0x53, 0xe4,
+    0xfe, 0x58, 0xd7, 0x8b, 0x96, 0xdc, 0xe6, 0xd3, 0x86, 0xb8, 0xd6, 0x42,
+    0x5b, 0x68, 0x03, 0x48, 0x3f, 0xcd, 0xee, 0x39, 0x8b, 0xc4, 0x53, 0x30,
+    0x87, 0x48, 0x2a, 0x01, 0x9d, 0x6f, 0x8e, 0x36, 0x75, 0x73, 0xef, 0x77,
+    0x3a, 0x82, 0xd8, 0x4c, 0x0e, 0x7f, 0xb3, 0x8f, 0x16, 0xd1, 0x10, 0xcf,
+    0x2f, 0xa3, 0xdf, 0x65, 0xba, 0x91, 0x79, 0xf6, 0x93, 0x60, 0x08, 0xe5,
+    0xdb, 0x73, 0x02, 0x7a, 0x0b, 0x0e, 0xcc, 0x3b, 0x1f, 0x08, 0x2d, 0x51,
+    0x3e, 0x87, 0x48, 0xd3, 0xd3, 0x75, 0xc2, 0x28, 0xa3, 0xf3, 0x02, 0xde,
+    0x8f, 0xa6, 0xbd, 0xb3, 0x19, 0xa0, 0xdb, 0x48, 0x51, 0x03, 0x5f, 0x98,
+    0xbe,
+
+    /* Third Packet: 1-RTT */
+    0x5c,               /* Short, 1-RTT, Spin=0, KP=0, PN Length=2 bytes */
+    0x4f, 0x33,         /* PN (0) */
+    0x16, 0x75, 0x98, 0x67, 0x04, 0x16, 0x61, 0xe3, 0x00, 0xb7, 0x9d, 0x5c,
+    0x53, 0x4c, 0x26, 0x90, 0x92, 0x8e, 0x0e, 0xc0, 0x9c, 0x6d, 0x8b, 0xac,
+    0x15, 0x6d, 0x89, 0x74, 0x2f, 0xe7, 0x84, 0xe3, 0x46, 0x46, 0x8c, 0xc1,
+    0x21, 0x7c, 0x44, 0xa5, 0x00, 0x29, 0xca, 0xf2, 0x11, 0x18, 0xe0, 0x04,
+    0x40, 0x55, 0xd2, 0xa7, 0xe5, 0x9d, 0x22, 0xa2, 0x2a, 0x6c, 0x03, 0x87,
+    0xa3, 0xa3, 0xfa, 0xf5, 0x6c, 0xd7, 0x7d, 0xae, 0x3f, 0x28, 0x01, 0xae,
+    0x06, 0x11, 0x69, 0x67, 0x90, 0x57, 0x5a, 0xd0, 0xeb, 0xdd, 0xac, 0xbd,
+    0x7f, 0x33, 0x86, 0xbb,
+};
+
+static const QUIC_PKT_HDR script_7a_expect_hdr = {
+    QUIC_PKT_TYPE_INITIAL,
+    0,          /* Spin Bit */
+    0,          /* Key Phase */
+    2,          /* PN Length */
+    0,          /* Partial */
+    1,          /* Fixed */
+    1,          /* Version */
+    {0, {0}},                           /* DCID */
+    {4, {0x03, 0x45, 0x0c, 0x7a}},      /* SCID */
+    {0},        /* PN */
+    NULL, 0,    /* Token/Token Len */
+    441, NULL
+};
+
+static const unsigned char script_7a_body[] = {
+    0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06,
+    0x00, 0x40, 0x5a, 0x02, 0x00, 0x00, 0x56, 0x03, 0x03, 0xd5, 0xfb, 0x6a,
+    0x81, 0x1c, 0xdb, 0xa2, 0x5c, 0x11, 0x31, 0xda, 0x15, 0x28, 0x97, 0x94,
+    0x83, 0xfd, 0x9d, 0x91, 0x0e, 0x87, 0x71, 0x46, 0x64, 0xb4, 0xd9, 0x9e,
+    0xbd, 0xa8, 0x48, 0x32, 0xbf, 0x00, 0x13, 0x03, 0x00, 0x00, 0x2e, 0x00,
+    0x2b, 0x00, 0x02, 0x03, 0x04, 0x00, 0x33, 0x00, 0x24, 0x00, 0x1d, 0x00,
+    0x20, 0xef, 0xbb, 0x46, 0xe9, 0xb4, 0xf6, 0x54, 0xc4, 0x07, 0x71, 0xdc,
+    0x50, 0xd5, 0x69, 0x40, 0xbc, 0x85, 0x7f, 0xf9, 0x48, 0x14, 0xe3, 0xd6,
+    0x08, 0xa9, 0x0b, 0xfd, 0xbe, 0xf1, 0x57, 0x21, 0x34,
+};
+
+static const QUIC_PKT_HDR script_7b_expect_hdr = {
+    QUIC_PKT_TYPE_HANDSHAKE,
+    0,          /* Spin Bit */
+    0,          /* Key Phase */
+    2,          /* PN Length */
+    0,          /* Partial */
+    1,          /* Fixed */
+    1,          /* Version */
+    {0, {0}},                           /* DCID */
+    {4, {0x03, 0x45, 0x0c, 0x7a}},      /* SCID */
+    {0},        /* PN */
+    NULL, 0,    /* Token/Token Len */
+    657, NULL
+};
+
+static const unsigned char script_7b_body[] = {
+    0x06, 0x00, 0x42, 0x8d, 0x08, 0x00, 0x00, 0x82, 0x00, 0x80, 0x00, 0x10,
+    0x00, 0x08, 0x00, 0x06, 0x05, 0x64, 0x75, 0x6d, 0x6d, 0x79, 0x00, 0x39,
+    0x00, 0x70, 0x46, 0x0a, 0x0d, 0xdc, 0x59, 0xf0, 0x4e, 0xb2, 0x2c, 0xac,
+    0x69, 0x6a, 0xc9, 0x77, 0xa9, 0x99, 0x05, 0x04, 0x80, 0x08, 0x00, 0x00,
+    0x06, 0x04, 0x80, 0x08, 0x00, 0x00, 0x07, 0x04, 0x80, 0x08, 0x00, 0x00,
+    0x04, 0x04, 0x80, 0x0c, 0x00, 0x00, 0x08, 0x02, 0x40, 0x64, 0x09, 0x02,
+    0x40, 0x64, 0x01, 0x04, 0x80, 0x00, 0x75, 0x30, 0x03, 0x02, 0x45, 0xac,
+    0x0b, 0x01, 0x1a, 0x0c, 0x00, 0x02, 0x10, 0x42, 0xf0, 0xed, 0x09, 0x07,
+    0x5b, 0xd9, 0x5a, 0xb2, 0x39, 0x5d, 0x73, 0x2c, 0x57, 0x1f, 0x50, 0x00,
+    0x0b, 0xe0, 0x3e, 0xf3, 0xd6, 0x91, 0x6f, 0x9c, 0xcc, 0x31, 0xf7, 0xa5,
+    0x0e, 0x01, 0x04, 0x0f, 0x04, 0x03, 0x45, 0x0c, 0x7a, 0x10, 0x04, 0xfa,
+    0x5d, 0xd6, 0x80, 0x20, 0x01, 0x00, 0x0b, 0x00, 0x01, 0x8f, 0x00, 0x00,
+    0x01, 0x8b, 0x00, 0x01, 0x86, 0x30, 0x82, 0x01, 0x82, 0x30, 0x82, 0x01,
+    0x29, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x14, 0x0a, 0x73, 0x0f, 0x86,
+    0x18, 0xf2, 0xc3, 0x30, 0x01, 0xd2, 0xc0, 0xc1, 0x62, 0x52, 0x13, 0xf1,
+    0x9c, 0x13, 0x39, 0xb5, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce,
+    0x3d, 0x04, 0x03, 0x02, 0x30, 0x17, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03,
+    0x55, 0x04, 0x03, 0x0c, 0x0c, 0x6d, 0x61, 0x70, 0x61, 0x6b, 0x74, 0x2e,
+    0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x30, 0x1e, 0x17, 0x0d, 0x32, 0x32, 0x30,
+    0x38, 0x30, 0x32, 0x31, 0x32, 0x30, 0x30, 0x31, 0x38, 0x5a, 0x17, 0x0d,
+    0x32, 0x32, 0x30, 0x39, 0x30, 0x31, 0x31, 0x32, 0x30, 0x30, 0x31, 0x38,
+    0x5a, 0x30, 0x17, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x03,
+    0x0c, 0x0c, 0x6d, 0x61, 0x70, 0x61, 0x6b, 0x74, 0x2e, 0x6c, 0x6f, 0x63,
+    0x61, 0x6c, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce,
+    0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01,
+    0x07, 0x03, 0x42, 0x00, 0x04, 0x67, 0xf4, 0xd3, 0x8f, 0x15, 0x6d, 0xee,
+    0x85, 0xcc, 0x2a, 0x77, 0xfc, 0x0b, 0x8f, 0x9f, 0xcf, 0xa9, 0x95, 0x5d,
+    0x5b, 0xcd, 0xb7, 0x8b, 0xba, 0x31, 0x0a, 0x73, 0x62, 0xc5, 0xd0, 0x0e,
+    0x07, 0x90, 0xae, 0x38, 0x43, 0x79, 0xce, 0x5e, 0x33, 0xad, 0x31, 0xbf,
+    0x9f, 0x2a, 0x56, 0x83, 0xa5, 0x24, 0x16, 0xab, 0x0c, 0xf1, 0x64, 0xbe,
+    0xe4, 0x93, 0xb5, 0x89, 0xd6, 0x05, 0xe4, 0xf7, 0x7b, 0xa3, 0x53, 0x30,
+    0x51, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
+    0x02, 0x64, 0x0f, 0x55, 0x69, 0x14, 0x91, 0x19, 0xed, 0xf9, 0x1a, 0xe9,
+    0x1d, 0xa5, 0x5a, 0xd0, 0x48, 0x96, 0x9f, 0x60, 0x30, 0x1f, 0x06, 0x03,
+    0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x02, 0x64, 0x0f,
+    0x55, 0x69, 0x14, 0x91, 0x19, 0xed, 0xf9, 0x1a, 0xe9, 0x1d, 0xa5, 0x5a,
+    0xd0, 0x48, 0x96, 0x9f, 0x60, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13,
+    0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x0a,
+    0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x47,
+    0x00, 0x30, 0x44, 0x02, 0x20, 0x0a, 0x82, 0x92, 0x6e, 0xd3, 0xc6, 0x66,
+    0xd9, 0xd3, 0x75, 0xff, 0x71, 0x3b, 0x61, 0x46, 0x21, 0x00, 0xe6, 0x21,
+    0x5d, 0x9c, 0x86, 0xe9, 0x65, 0x40, 0x4f, 0xeb, 0x70, 0x4f, 0x2c, 0xad,
+    0x00, 0x02, 0x20, 0x08, 0xc2, 0x07, 0x5d, 0x16, 0xfc, 0x54, 0x34, 0x2b,
+    0xb4, 0x18, 0x67, 0x44, 0x81, 0xc9, 0xa9, 0x67, 0x2e, 0xce, 0xa1, 0x02,
+    0x9f, 0x3b, 0xe5, 0x61, 0x16, 0x0b, 0x50, 0xf6, 0xa1, 0x50, 0x94, 0x00,
+    0x00, 0x0f, 0x00, 0x00, 0x4c, 0x04, 0x03, 0x00, 0x48, 0x30, 0x46, 0x02,
+    0x21, 0x00, 0xaa, 0x18, 0x61, 0x93, 0xdf, 0xbb, 0x79, 0xe7, 0x34, 0x7e,
+    0x2e, 0x61, 0x13, 0x8c, 0xa0, 0x33, 0xfb, 0x33, 0xca, 0xfc, 0xd2, 0x45,
+    0xb0, 0xc7, 0x89, 0x3d, 0xf1, 0xd6, 0x54, 0x94, 0x05, 0xb6, 0x02, 0x21,
+    0x00, 0xef, 0x6c, 0xb6, 0xf2, 0x00, 0xb2, 0x32, 0xb1, 0xf3, 0x3f, 0x59,
+    0xf5, 0xc8, 0x18, 0xbe, 0x39, 0xbb, 0x27, 0xf8, 0x67, 0xac, 0xcb, 0x63,
+    0xa4, 0x29, 0xfb, 0x8e, 0x88, 0x0f, 0xe5, 0xe9, 0x7e, 0x14, 0x00, 0x00,
+    0x20, 0xfc, 0x2c, 0x4c, 0xa7, 0x77, 0x24, 0x79, 0x29, 0xa8, 0x82, 0x1a,
+    0x4d, 0x58, 0x9d, 0x82, 0xe2, 0x09, 0x36, 0x63, 0x0e, 0x0b, 0x55, 0x51,
+    0x80, 0x93, 0x40, 0xda, 0x41, 0x33, 0x08, 0x10, 0x2c,
+};
+
+static const QUIC_PKT_HDR script_7c_expect_hdr = {
+    QUIC_PKT_TYPE_1RTT,
+    0,          /* Spin Bit */
+    0,          /* Key Phase */
+    2,          /* PN Length */
+    0,          /* Partial */
+    1,          /* Fixed */
+    0,          /* Version */
+    {0, {0}},                           /* DCID */
+    {0, {0}},                           /* SCID */
+    {0},        /* PN */
+    NULL, 0,    /* Token/Token Len */
+    72, NULL
+};
+
+static const unsigned char script_7c_body[] = {
+    0x18, 0x03, 0x00, 0x04, 0xf7, 0x75, 0x72, 0xa2, 0xfd, 0x17, 0xd4, 0x82,
+    0x8e, 0xe9, 0x5b, 0xce, 0xed, 0xec, 0x88, 0xb9, 0x73, 0xbf, 0x36, 0x9f,
+    0x18, 0x02, 0x00, 0x04, 0x5f, 0x43, 0x96, 0xe4, 0x15, 0xdc, 0x56, 0x6b,
+    0x67, 0x4c, 0x36, 0xb2, 0xe2, 0x77, 0xdc, 0x6e, 0xb9, 0x2c, 0x0d, 0x79,
+    0x18, 0x01, 0x00, 0x04, 0xcb, 0x83, 0x4a, 0xf4, 0x8d, 0x7b, 0x69, 0x90,
+    0xaf, 0x0d, 0xd2, 0x38, 0xa4, 0xf1, 0x94, 0xff, 0x63, 0x24, 0xd3, 0x7a,
+};
+
+static const struct test_op script_7[] = {
+    OP_ADD_RX_DCID(empty_conn_id)
+    OP_PROVIDE_SECRET_INITIAL(script_7_c2s_init_dcid)
+    OP_INJECT_N(7)
+    OP_CHECK_PKT_N(7a)
+    OP_CHECK_NO_PKT() /* not got secret for next packet yet */
+    OP_PROVIDE_SECRET(QUIC_ENC_LEVEL_HANDSHAKE,
+                      QRL_SUITE_CHACHA20POLY1305, script_7_handshake_secret)
+    OP_CHECK_PKT_N(7b)
+    OP_CHECK_NO_PKT() /* not got secret for next packet yet */
+    OP_PROVIDE_SECRET(QUIC_ENC_LEVEL_1RTT,
+                      QRL_SUITE_CHACHA20POLY1305, script_7_1rtt_secret)
+    OP_CHECK_PKT_N(7c)
+    OP_CHECK_NO_PKT()
+
+    /* Try injecting the packet again */
+    OP_INJECT_N(7)
+    /*
+     * Initial packet is not output due to receiving a Handshake packet causing
+     * auto-discard of Initial keys
+     */
+    OP_CHECK_PKT_N(7b)
+    OP_CHECK_PKT_N(7c)
+    OP_CHECK_NO_PKT()
+    /* Try again with discarded keys */
+    OP_DISCARD_EL(QUIC_ENC_LEVEL_HANDSHAKE)
+    OP_INJECT_N(7)
+    OP_CHECK_PKT_N(7c)
+    OP_CHECK_NO_PKT()
+    /* Try again */
+    OP_INJECT_N(7)
+    OP_CHECK_PKT_N(7c)
+    OP_CHECK_NO_PKT()
+    /* Try again with discarded 1-RTT keys */
+    OP_DISCARD_EL(QUIC_ENC_LEVEL_1RTT)
+    OP_INJECT_N(7)
+    OP_CHECK_NO_PKT()
+
+    /* Recreate QRL, test reading packets received before key */
+    OP_SET_SCID_LEN(0)
+    OP_ADD_RX_DCID(empty_conn_id)
+    OP_INJECT_N(7)
+    OP_CHECK_NO_PKT()
+    OP_PROVIDE_SECRET_INITIAL(script_7_c2s_init_dcid)
+    OP_CHECK_PKT_N(7a)
+    OP_CHECK_NO_PKT()
+    OP_PROVIDE_SECRET(QUIC_ENC_LEVEL_HANDSHAKE,
+                      QRL_SUITE_CHACHA20POLY1305, script_7_handshake_secret)
+    OP_CHECK_PKT_N(7b)
+    OP_CHECK_NO_PKT()
+    OP_PROVIDE_SECRET(QUIC_ENC_LEVEL_1RTT,
+                      QRL_SUITE_CHACHA20POLY1305, script_7_1rtt_secret)
+    OP_CHECK_PKT_N(7c)
+    OP_CHECK_NO_PKT()
+
+    OP_END
+};
+
+static const struct test_op *scripts[] = {
+    script_1,
+    script_2,
+    script_3,
+    script_4,
+    script_5,
+    script_6,
+    script_7
+};
+
+static int cmp_pkt_hdr(const QUIC_PKT_HDR *a, const QUIC_PKT_HDR *b,
+                       const unsigned char *b_data, size_t b_len,
+                       int cmp_data)
+{
+    int ok = 1;
+
+    if (b_data == NULL) {
+        b_data = b->data;
+        b_len  = b->len;
+    }
+
+    if (!TEST_int_eq(a->type, b->type)
+        || !TEST_int_eq(a->spin_bit, b->spin_bit)
+        || !TEST_int_eq(a->key_phase, b->key_phase)
+        || !TEST_int_eq(a->pn_len, b->pn_len)
+        || !TEST_int_eq(a->partial, b->partial)
+        || !TEST_int_eq(a->fixed, b->fixed)
+        || !TEST_uint_eq(a->version, b->version)
+        || !TEST_true(ossl_quic_conn_id_eq(&a->dst_conn_id, &b->dst_conn_id))
+        || !TEST_true(ossl_quic_conn_id_eq(&a->src_conn_id, &b->src_conn_id))
+        || !TEST_mem_eq(a->pn, sizeof(a->pn), b->pn, sizeof(b->pn))
+        || !TEST_size_t_eq(a->token_len, b->token_len)
+        || !TEST_uint64_t_eq(a->len, b->len))
+        ok = 0;
+
+    if (a->token_len > 0 && b->token_len > 0
+        && !TEST_mem_eq(a->token, a->token_len, b->token, b->token_len))
+        ok = 0;
+
+    if ((a->token_len == 0 && !TEST_ptr_null(a->token))
+        || (b->token_len == 0 && !TEST_ptr_null(b->token)))
+        ok = 0;
+
+    if (cmp_data && !TEST_mem_eq(a->data, a->len, b_data, b_len))
+        ok = 0;
+
+    return ok;
+}
+
+struct state {
+    QUIC_DEMUX     *demux;
+    OSSL_QRL       *qrl;
+    OSSL_QRL_ARGS   args;
+};
+
+static void state_teardown(struct state *s)
+{
+    if (s->qrl != NULL) {
+        ossl_qrl_free(s->qrl);
+        s->qrl = NULL;
+    }
+
+    if (s->demux != NULL) {
+        ossl_quic_demux_free(s->demux);
+        s->demux = NULL;
+    }
+}
+
+static int state_ensure(struct state *s)
+{
+    if (s->demux == NULL
+        && !TEST_ptr(s->demux = ossl_quic_demux_new(NULL,
+                                                    s->args.short_conn_id_len,
+                                                    1500)))
+        return 0;
+
+    s->args.rx_demux = s->demux;
+
+    if (s->qrl == NULL
+        && !TEST_ptr(s->qrl = ossl_qrl_new(&s->args)))
+        return 0;
+
+    return 1;
+}
+
+static int run_script(const struct test_op *script)
+{
+    int testresult = 0, pkt_outstanding = 0;
+    struct state s = {0};
+    size_t i;
+    OSSL_QRL_RX_PKT pkt = {0};
+    const struct test_op *op = script;
+
+    for (; op->op != TEST_OP_END; ++op)
+        switch (op->op) {
+            case TEST_OP_SET_SCID_LEN:
+                state_teardown(&s);
+                s.args.short_conn_id_len = op->enc_level;
+                break;
+            case TEST_OP_SET_INIT_LARGEST_PN:
+                state_teardown(&s);
+                for (i = 0; i < QUIC_PN_SPACE_NUM; ++i)
+                    s.args.rx_init_largest_pn[i] = op->largest_pn;
+                break;
+            case TEST_OP_ADD_RX_DCID:
+                if (!TEST_true(state_ensure(&s)))
+                    goto err;
+                if (!TEST_true(ossl_qrl_add_dst_conn_id(s.qrl, op->dcid)))
+                    goto err;
+                break;
+            case TEST_OP_PROVIDE_SECRET:
+                if (!TEST_true(state_ensure(&s)))
+                    goto err;
+                if (!TEST_true(ossl_qrl_provide_rx_secret(s.qrl, op->enc_level,
+                                                          op->suite_id,
+                                                          op->buf,
+                                                          op->buf_len)))
+                    goto err;
+                break;
+            case TEST_OP_PROVIDE_SECRET_INITIAL:
+                if (!TEST_true(state_ensure(&s)))
+                    goto err;
+                if (!TEST_true(ossl_qrl_provide_rx_secret_initial(s.qrl,
+                                                                  op->dcid)))
+                    goto err;
+                break;
+            case TEST_OP_DISCARD_EL:
+                if (!TEST_true(state_ensure(&s)))
+                    goto err;
+                if (!TEST_true(ossl_qrl_discard_enc_level(s.qrl, op->enc_level)))
+                    goto err;
+                break;
+            case TEST_OP_INJECT:
+                if (!TEST_true(state_ensure(&s)))
+                    goto err;
+                if (!TEST_true(ossl_quic_demux_inject(s.demux,
+                                                      op->buf, op->buf_len,
+                                                      NULL, NULL)))
+                    goto err;
+                break;
+            case TEST_OP_CHECK_PKT:
+                if (!TEST_true(state_ensure(&s)))
+                    goto err;
+
+                if (!TEST_true(ossl_qrl_read_pkt(s.qrl, &pkt)))
+                    goto err;
+
+                pkt_outstanding = 1;
+                if (!TEST_ptr(pkt.hdr))
+                    goto err;
+
+                if (!TEST_mem_eq(pkt.hdr->data, pkt.hdr->len,
+                                 op->buf, op->buf_len))
+                    goto err;
+
+                if (!TEST_true(cmp_pkt_hdr(pkt.hdr, op->hdr,
+                                           op->buf, op->buf_len, 1)))
+                    goto err;
+
+                ossl_qrl_release_pkt(s.qrl, pkt.handle);
+                pkt_outstanding = 0;
+                break;
+            case TEST_OP_CHECK_NO_PKT:
+                if (!TEST_true(state_ensure(&s)))
+                    goto err;
+
+                if (!TEST_false(ossl_qrl_read_pkt(s.qrl, &pkt)))
+                    goto err;
+
+                break;
+            default:
+                OPENSSL_assert(0);
+                goto err;
+        }
+
+    testresult = 1;
+err:
+    if (pkt_outstanding)
+        ossl_qrl_release_pkt(s.qrl, pkt.handle);
+    state_teardown(&s);
+    return testresult;
+}
+
+static int test_script(int idx)
+{
+    return run_script(scripts[idx]);
+}
+
+/* Packet Header Tests */
+struct pkt_hdr_test {
+    QUIC_PKT_HDR hdr;
+    const unsigned char *expected;
+    size_t expected_len;
+    const unsigned char *payload;
+    size_t payload_len;
+    size_t short_conn_id_len;
+    /*
+     * Minimum number of bytes which should be required for a successful decode.
+     * SIZE_MAX if should never decode successfully.
+     */
+    size_t min_success_len;
+    size_t pn_offset, sample_offset;
+};
+
+/* Packet Header Test 1: INITIAL With SCID */
+static const unsigned char pkt_hdr_test_1_expected[] = {
+    0xc1,                     /* Long|Fixed, Type=Initial, PN Len=2 */
+    0x00, 0x00, 0x00, 0x01,   /* Version */
+    0x00,                     /* DCID Length */
+    0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5, /* SCID Length, SCID */
+    0x00,                     /* Token Length */
+    0x15,                     /* Length=21 */
+    0x33, 0x44,               /* Encoded PN */
+    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, /* Payload */
+    0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+    0x20, 0x21, 0x22
+};
+
+static const unsigned char pkt_hdr_test_1_payload[] = {
+    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+    0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+    0x20, 0x21, 0x22
+};
+
+static const struct pkt_hdr_test pkt_hdr_test_1 = {
+    {
+        QUIC_PKT_TYPE_INITIAL,  /* type */
+        0,                      /* spin bit */
+        0,                      /* key phase */
+        2,                      /* PN length */
+        0,                      /* partial */
+        1,                      /* fixed */
+        1,                      /* version */
+        { 0, {0} },             /* DCID */
+        { 8, {0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5 } }, /* SCID */
+        { 0x33, 0x44 },         /* PN */
+        NULL, 0,                /* Token/Token Len */
+        19, NULL                /* Len/Data */
+    },
+    pkt_hdr_test_1_expected, OSSL_NELEM(pkt_hdr_test_1_expected),
+    pkt_hdr_test_1_payload,  OSSL_NELEM(pkt_hdr_test_1_payload),
+    0, sizeof(pkt_hdr_test_1_expected),
+    17, 21
+};
+
+/* Packet Header Test 2: INITIAL With SCID and Token */
+static const unsigned char pkt_hdr_test_2_expected[] = {
+    0xc1,                     /* Long|Fixed, Type=Initial, PN Len=2 */
+    0x00, 0x00, 0x00, 0x01,   /* Version */
+    0x00,                     /* DCID Length */
+    0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5, /* SCID Length, SCID */
+    0x07,                     /* Token Length */
+    0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96,
+    0x15,                     /* Length=21 */
+    0x33, 0x44,               /* Encoded PN */
+    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, /* Payload */
+    0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+    0x20, 0x21, 0x22
+};
+
+static const unsigned char pkt_hdr_test_2_payload[] = {
+    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+    0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+    0x20, 0x21, 0x22
+};
+
+static const unsigned char pkt_hdr_test_2_token[] = {
+    0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96
+};
+
+static const struct pkt_hdr_test pkt_hdr_test_2 = {
+    {
+        QUIC_PKT_TYPE_INITIAL,  /* type */
+        0,                      /* spin bit */
+        0,                      /* key phase */
+        2,                      /* PN length */
+        0,                      /* partial */
+        1,                      /* fixed */
+        1,                      /* version */
+        { 0, {0} },             /* DCID */
+        { 8, {0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5 } }, /* SCID */
+        { 0x33, 0x44 },         /* PN */
+        pkt_hdr_test_2_token, sizeof(pkt_hdr_test_2_token), /* Token */
+        19, NULL                /* Len/Data */
+    },
+    pkt_hdr_test_2_expected, OSSL_NELEM(pkt_hdr_test_2_expected),
+    pkt_hdr_test_2_payload,  OSSL_NELEM(pkt_hdr_test_2_payload),
+    0, sizeof(pkt_hdr_test_2_expected),
+    24, 28
+};
+
+/* Packet Header Test 3: INITIAL With DCID and SCID and Token */
+static const unsigned char pkt_hdr_test_3_expected[] = {
+    0xc1,                     /* Long|Fixed, Type=Initial, PN Len=2 */
+    0x00, 0x00, 0x00, 0x01,   /* Version */
+    0x03,                     /* DCID Length */
+    0x70, 0x71, 0x72,         /* DCID */
+    0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5, /* SCID Length, SCID */
+    0x06,                     /* Token Length */
+    0x91, 0x92, 0x93, 0x94, 0x95, 0x96,
+    0x15,                     /* Length=21 */
+    0x33, 0x44,               /* Encoded PN */
+    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, /* Payload */
+    0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+    0x20, 0x21, 0x22
+};
+
+static const unsigned char pkt_hdr_test_3_payload[] = {
+    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+    0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+    0x20, 0x21, 0x22
+};
+
+static const unsigned char pkt_hdr_test_3_token[] = {
+    0x91, 0x92, 0x93, 0x94, 0x95, 0x96
+};
+
+static const struct pkt_hdr_test pkt_hdr_test_3 = {
+    {
+        QUIC_PKT_TYPE_INITIAL,  /* type */
+        0,                      /* spin bit */
+        0,                      /* key phase */
+        2,                      /* PN length */
+        0,                      /* partial */
+        1,                      /* fixed */
+        1,                      /* version */
+        { 3, {0x70, 0x71, 0x72} },                                /* DCID */
+        { 8, {0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5 } }, /* SCID */
+        { 0x33, 0x44 },         /* PN */
+        pkt_hdr_test_3_token, sizeof(pkt_hdr_test_3_token), /* Token */
+        19, NULL                /* Len/Data */
+    },
+    pkt_hdr_test_3_expected, OSSL_NELEM(pkt_hdr_test_3_expected),
+    pkt_hdr_test_3_payload,  OSSL_NELEM(pkt_hdr_test_3_payload),
+    0, sizeof(pkt_hdr_test_3_expected),
+    26, 30
+};
+
+/* Packet Header Test 4: 0-RTT */
+static const unsigned char pkt_hdr_test_4_expected[] = {
+    0xd0,                     /* Long|Fixed, Type=0-RTT, PN Len=1 */
+    0x00, 0x00, 0x00, 0x01,   /* Version */
+    0x03,                     /* DCID Length */
+    0x70, 0x71, 0x72,         /* DCID */
+    0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5, /* SCID Length, SCID */
+    0x14,                     /* Length=20 */
+    0x33,                     /* Encoded PN */
+    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, /* Payload */
+    0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+    0x20, 0x21, 0x22
+};
+
+static const unsigned char pkt_hdr_test_4_payload[] = {
+    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+    0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+    0x20, 0x21, 0x22
+};
+
+static const struct pkt_hdr_test pkt_hdr_test_4 = {
+    {
+        QUIC_PKT_TYPE_0RTT,     /* type */
+        0,                      /* spin bit */
+        0,                      /* key phase */
+        1,                      /* PN length */
+        0,                      /* partial */
+        1,                      /* fixed */
+        1,                      /* version */
+        { 3, {0x70, 0x71, 0x72} },                                /* DCID */
+        { 8, {0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5 } }, /* SCID */
+        { 0x33 },               /* PN */
+        NULL, 0,                /* Token */
+        19, NULL                /* Len/Data */
+    },
+    pkt_hdr_test_4_expected, OSSL_NELEM(pkt_hdr_test_4_expected),
+    pkt_hdr_test_4_payload,  OSSL_NELEM(pkt_hdr_test_4_payload),
+    0, sizeof(pkt_hdr_test_4_expected),
+    19, 23
+};
+
+/* Packet Header Test 5: Handshake */
+static const unsigned char pkt_hdr_test_5_expected[] = {
+    0xe0,                     /* Long|Fixed, Type=Handshake, PN Len=1 */
+    0x00, 0x00, 0x00, 0x01,   /* Version */
+    0x03,                     /* DCID Length */
+    0x70, 0x71, 0x72,         /* DCID */
+    0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5, /* SCID Length, SCID */
+    0x14,                     /* Length=20 */
+    0x33,                     /* Encoded PN */
+    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, /* Payload */
+    0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+    0x20, 0x21, 0x22
+};
+
+static const unsigned char pkt_hdr_test_5_payload[] = {
+    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+    0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+    0x20, 0x21, 0x22
+};
+
+static const struct pkt_hdr_test pkt_hdr_test_5 = {
+    {
+        QUIC_PKT_TYPE_HANDSHAKE,    /* type */
+        0,                          /* spin bit */
+        0,                          /* key phase */
+        1,                          /* PN length */
+        0,                          /* partial */
+        1,                      /* fixed */
+        1,                          /* version */
+        { 3, {0x70, 0x71, 0x72} },                                /* DCID */
+        { 8, {0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5 } }, /* SCID */
+        { 0x33 },                   /* PN */
+        NULL, 0,                    /* Token */
+        19, NULL                    /* Len/Data */
+    },
+    pkt_hdr_test_5_expected, OSSL_NELEM(pkt_hdr_test_5_expected),
+    pkt_hdr_test_5_payload,  OSSL_NELEM(pkt_hdr_test_5_payload),
+    0, sizeof(pkt_hdr_test_5_expected),
+    19, 23
+};
+
+/* Packet Header Test 6: Retry */
+static const unsigned char pkt_hdr_test_6_expected[] = {
+    0xf0,                     /* Long|Fixed, Type=Retry */
+    0x00, 0x00, 0x00, 0x01,   /* Version */
+    0x03,                     /* DCID Length */
+    0x70, 0x71, 0x72,         /* DCID */
+    0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5, /* SCID Length, SCID */
+    0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,       /* Retry Token */
+    0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
+    0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f              /* Retry Integrity Tag */
+};
+
+static const unsigned char pkt_hdr_test_6_payload[] = {
+    0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,       /* Retry Token */
+    0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
+    0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f              /* Retry Integrity Tag */
+};
+
+static const struct pkt_hdr_test pkt_hdr_test_6 = {
+    {
+        QUIC_PKT_TYPE_RETRY,        /* type */
+        0,                          /* spin bit */
+        0,                          /* key phase */
+        0,                          /* PN length */
+        0,                          /* partial */
+        1,                          /* fixed */
+        1,                          /* version */
+        { 3, {0x70, 0x71, 0x72} },                                /* DCID */
+        { 8, {0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5 } }, /* SCID */
+        { 0 },                      /* PN */
+        NULL, 0,                    /* Token */
+        24, NULL                    /* Len/Data */
+    },
+    pkt_hdr_test_6_expected, OSSL_NELEM(pkt_hdr_test_6_expected),
+    pkt_hdr_test_6_payload,  OSSL_NELEM(pkt_hdr_test_6_payload),
+    0, 21,
+    SIZE_MAX, SIZE_MAX
+};
+
+/* Packet Header Test 7: 1-RTT */
+static const unsigned char pkt_hdr_test_7_expected[] = {
+    0x42,                     /* Short|Fixed, Type=1-RTT, PN Len=3 */
+    0x70, 0x71, 0x72,         /* DCID */
+    0x50, 0x51, 0x52,         /* PN */
+    0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99,
+    0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1
+};
+
+static const unsigned char pkt_hdr_test_7_payload[] = {
+    0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99,
+    0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1
+};
+
+static const struct pkt_hdr_test pkt_hdr_test_7 = {
+    {
+        QUIC_PKT_TYPE_1RTT,         /* type */
+        0,                          /* spin bit */
+        0,                          /* key phase */
+        3,                          /* PN length */
+        0,                          /* partial */
+        1,                          /* fixed */
+        0,                          /* version */
+        { 3, {0x70, 0x71, 0x72} },  /* DCID */
+        { 0, {0} },                 /* SCID */
+        { 0x50, 0x51, 0x52 },       /* PN */
+        NULL, 0,                    /* Token */
+        18, NULL                    /* Len/Data */
+    },
+    pkt_hdr_test_7_expected, OSSL_NELEM(pkt_hdr_test_7_expected),
+    pkt_hdr_test_7_payload,  OSSL_NELEM(pkt_hdr_test_7_payload),
+    3, 21,
+    4, 8
+};
+
+/* Packet Header Test 8: 1-RTT with Spin Bit */
+static const unsigned char pkt_hdr_test_8_expected[] = {
+    0x62,                     /* Short|Fixed, Type=1-RTT, PN Len=3, Spin=1 */
+    0x70, 0x71, 0x72,         /* DCID */
+    0x50, 0x51, 0x52,         /* PN */
+    0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99,
+    0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1
+};
+
+static const unsigned char pkt_hdr_test_8_payload[] = {
+    0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99,
+    0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1
+};
+
+static const struct pkt_hdr_test pkt_hdr_test_8 = {
+    {
+        QUIC_PKT_TYPE_1RTT,         /* type */
+        1,                          /* spin bit */
+        0,                          /* key phase */
+        3,                          /* PN length */
+        0,                          /* partial */
+        1,                          /* fixed */
+        0,                          /* version */
+        { 3, {0x70, 0x71, 0x72} },  /* DCID */
+        { 0, {0} },                 /* SCID */
+        { 0x50, 0x51, 0x52 },       /* PN */
+        NULL, 0,                    /* Token */
+        18, NULL                    /* Len/Data */
+    },
+    pkt_hdr_test_8_expected, OSSL_NELEM(pkt_hdr_test_8_expected),
+    pkt_hdr_test_8_payload,  OSSL_NELEM(pkt_hdr_test_8_payload),
+    3, 21,
+    4, 8
+};
+
+/* Packet Header Test 9: 1-RTT with Key Phase Bit */
+static const unsigned char pkt_hdr_test_9_expected[] = {
+    0x46,                     /* Short|Fixed, Type=1-RTT, PN Len=3, Key Phase=1 */
+    0x70, 0x71, 0x72,         /* DCID */
+    0x50, 0x51, 0x52,         /* PN */
+    0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99,
+    0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1
+};
+
+static const unsigned char pkt_hdr_test_9_payload[] = {
+    0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99,
+    0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1
+};
+
+static const struct pkt_hdr_test pkt_hdr_test_9 = {
+    {
+        QUIC_PKT_TYPE_1RTT,         /* type */
+        0,                          /* spin bit */
+        1,                          /* key phase */
+        3,                          /* PN length */
+        0,                          /* partial */
+        1,                          /* fixed */
+        0,                          /* version */
+        { 3, {0x70, 0x71, 0x72} },  /* DCID */
+        { 0, {0} },                 /* SCID */
+        { 0x50, 0x51, 0x52 },       /* PN */
+        NULL, 0,                    /* Token */
+        18, NULL                    /* Len/Data */
+    },
+    pkt_hdr_test_9_expected, OSSL_NELEM(pkt_hdr_test_9_expected),
+    pkt_hdr_test_9_payload,  OSSL_NELEM(pkt_hdr_test_9_payload),
+    3, 21,
+    4, 8
+};
+
+/* Packet Header Test 10: Handshake with 4-Byte PN */
+static const unsigned char pkt_hdr_test_10_expected[] = {
+    0xe3,                     /* Long|Fixed, Type=Handshake, PN Len=4 */
+    0x00, 0x00, 0x00, 0x01,   /* Version */
+    0x03,                     /* DCID Length */
+    0x70, 0x71, 0x72,         /* DCID */
+    0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5, /* SCID Length, SCID */
+    0x17,                     /* Length=20 */
+    0x33, 0x44, 0x55, 0x66,   /* Encoded PN */
+    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, /* Payload */
+    0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+    0x20, 0x21, 0x22
+};
+
+static const unsigned char pkt_hdr_test_10_payload[] = {
+    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+    0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+    0x20, 0x21, 0x22
+};
+
+static const struct pkt_hdr_test pkt_hdr_test_10 = {
+    {
+        QUIC_PKT_TYPE_HANDSHAKE,    /* type */
+        0,                          /* spin bit */
+        0,                          /* key phase */
+        4,                          /* PN length */
+        0,                          /* partial */
+        1,                          /* fixed */
+        1,                          /* version */
+        { 3, {0x70, 0x71, 0x72} },                                /* DCID */
+        { 8, {0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5 } }, /* SCID */
+        { 0x33, 0x44, 0x55, 0x66 }, /* PN */
+        NULL, 0,                    /* Token */
+        19, NULL                    /* Len/Data */
+    },
+    pkt_hdr_test_10_expected, OSSL_NELEM(pkt_hdr_test_10_expected),
+    pkt_hdr_test_10_payload,  OSSL_NELEM(pkt_hdr_test_10_payload),
+    0, sizeof(pkt_hdr_test_10_expected),
+    19, 23
+};
+
+/* Packet Header Test 11: 1-RTT with 4-Byte PN */
+static const unsigned char pkt_hdr_test_11_expected[] = {
+    0x43,                     /* Short|Fixed, Type=1-RTT, PN Len=4 */
+    0x70, 0x71, 0x72,         /* DCID */
+    0x50, 0x51, 0x52, 0x53,   /* PN */
+    0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99,
+    0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1
+};
+
+static const unsigned char pkt_hdr_test_11_payload[] = {
+    0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99,
+    0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1
+};
+
+static const struct pkt_hdr_test pkt_hdr_test_11 = {
+    {
+        QUIC_PKT_TYPE_1RTT,         /* type */
+        0,                          /* spin bit */
+        0,                          /* key phase */
+        4,                          /* PN length */
+        0,                          /* partial */
+        1,                          /* fixed */
+        0,                          /* version */
+        { 3, {0x70, 0x71, 0x72} },  /* DCID */
+        { 0, {0} },                 /* SCID */
+        { 0x50, 0x51, 0x52, 0x53 }, /* PN */
+        NULL, 0,                    /* Token */
+        18, NULL                    /* Len/Data */
+    },
+    pkt_hdr_test_11_expected, OSSL_NELEM(pkt_hdr_test_11_expected),
+    pkt_hdr_test_11_payload,  OSSL_NELEM(pkt_hdr_test_11_payload),
+    3, 21,
+    4, 8
+};
+
+/* Packet Header Test 12: Version Negotiation */
+static const unsigned char pkt_hdr_test_12_expected[] = {
+    0xc0,                       /* Long|Fixed, Type=Version Neg */
+    0x00, 0x00, 0x00, 0x00,     /* Version (0) */
+    0x03, 0x70, 0x71, 0x72,     /* DCID */
+    0x02, 0x81, 0x82,           /* SCID */
+    0x11, 0x22, 0x33, 0x44      /* One Version */
+};
+
+static const unsigned char pkt_hdr_test_12_payload[] = {
+    0x11, 0x22, 0x33, 0x44
+};
+
+static const struct pkt_hdr_test pkt_hdr_test_12 = {
+    {
+        QUIC_PKT_TYPE_VERSION_NEG,  /* type */
+        0,                          /* spin bit */
+        0,                          /* key phase */
+        0,                          /* PN length */
+        0,                          /* partial */
+        1,                          /* fixed */
+        0,                          /* version */
+        { 3, {0x70, 0x71, 0x72} },  /* DCID */
+        { 2, {0x81, 0x82} },        /* SCID */
+        { 0 },                      /* PN */
+        NULL, 0,                    /* Token */
+        4, NULL                     /* Len/Data */
+    },
+    pkt_hdr_test_12_expected, OSSL_NELEM(pkt_hdr_test_12_expected),
+    pkt_hdr_test_12_payload,  OSSL_NELEM(pkt_hdr_test_12_payload),
+    0, 12,
+    SIZE_MAX, SIZE_MAX
+};
+
+/* Packet Header Test 13: Version Negotiation without Fixed Bit */
+static const unsigned char pkt_hdr_test_13_expected[] = {
+    0x80,                       /* Long|Fixed, Type=Version Neg */
+    0x00, 0x00, 0x00, 0x00,     /* Version (0) */
+    0x03, 0x70, 0x71, 0x72,     /* DCID */
+    0x02, 0x81, 0x82,           /* SCID */
+    0x11, 0x22, 0x33, 0x44      /* One Version */
+};
+
+static const unsigned char pkt_hdr_test_13_payload[] = {
+    0x11, 0x22, 0x33, 0x44
+};
+
+static const struct pkt_hdr_test pkt_hdr_test_13 = {
+    {
+        QUIC_PKT_TYPE_VERSION_NEG,  /* type */
+        0,                          /* spin bit */
+        0,                          /* key phase */
+        0,                          /* PN length */
+        0,                          /* partial */
+        0,                          /* fixed */
+        0,                          /* version */
+        { 3, {0x70, 0x71, 0x72} },  /* DCID */
+        { 2, {0x81, 0x82} },        /* SCID */
+        { 0 },                      /* PN */
+        NULL, 0,                    /* Token */
+        4, NULL                     /* Len/Data */
+    },
+    pkt_hdr_test_13_expected, OSSL_NELEM(pkt_hdr_test_13_expected),
+    pkt_hdr_test_13_payload,  OSSL_NELEM(pkt_hdr_test_13_payload),
+    0, 12,
+    SIZE_MAX, SIZE_MAX
+};
+
+/* Packet Header Test 14: 1-RTT - Malformed - No Fixed Bit */
+static const unsigned char pkt_hdr_test_14_expected[] = {
+    0x02,                     /* Fixed, Type=1-RTT, PN Len=3 */
+    0x70, 0x71, 0x72,         /* DCID */
+    0x50, 0x51, 0x52,         /* PN */
+    0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99,
+    0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1
+};
+
+static const struct pkt_hdr_test pkt_hdr_test_14 = {
+    { 0 },
+    pkt_hdr_test_14_expected, OSSL_NELEM(pkt_hdr_test_14_expected),
+    NULL, 0,
+    3, SIZE_MAX,
+    4, 8
+};
+
+/* Packet Header Test 15: Handshake - Malformed - No Fixed Bit */
+static const unsigned char pkt_hdr_test_15_expected[] = {
+    0xa0,                     /* Long, Type=Handshake, PN Len=1 */
+    0x00, 0x00, 0x00, 0x01,   /* Version */
+    0x03,                     /* DCID Length */
+    0x70, 0x71, 0x72,         /* DCID */
+    0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5, /* SCID Length, SCID */
+    0x14,                     /* Length=20 */
+    0x33,                     /* Encoded PN */
+    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, /* Payload */
+    0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+    0x20, 0x21, 0x22
+};
+
+static const struct pkt_hdr_test pkt_hdr_test_15 = {
+    { 0 },
+    pkt_hdr_test_15_expected, OSSL_NELEM(pkt_hdr_test_15_expected),
+    NULL, 0,
+    0, SIZE_MAX,
+    19, 23
+};
+
+/* Packet Header Test 16: Handshake - Malformed - Wrong Version */
+static const unsigned char pkt_hdr_test_16_expected[] = {
+    0xe0,                     /* Long|Fixed, Type=Handshake, PN Len=1 */
+    0x00, 0x00, 0x00, 0x02,   /* Version */
+    0x03,                     /* DCID Length */
+    0x70, 0x71, 0x72,         /* DCID */
+    0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5, /* SCID Length, SCID */
+    0x14,                     /* Length=20 */
+    0x33,                     /* Encoded PN */
+    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, /* Payload */
+    0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+    0x20, 0x21, 0x22
+};
+
+static const struct pkt_hdr_test pkt_hdr_test_16 = {
+    { 0 },
+    pkt_hdr_test_16_expected, OSSL_NELEM(pkt_hdr_test_16_expected),
+    NULL, 0,
+    0, SIZE_MAX,
+    19, 23
+};
+
+static const struct pkt_hdr_test *const pkt_hdr_tests[] = {
+    &pkt_hdr_test_1,
+    &pkt_hdr_test_2,
+    &pkt_hdr_test_3,
+    &pkt_hdr_test_4,
+    &pkt_hdr_test_5,
+    &pkt_hdr_test_6,
+    &pkt_hdr_test_7,
+    &pkt_hdr_test_8,
+    &pkt_hdr_test_9,
+    &pkt_hdr_test_10,
+    &pkt_hdr_test_11,
+    &pkt_hdr_test_12,
+    &pkt_hdr_test_13,
+    &pkt_hdr_test_14,
+    &pkt_hdr_test_15,
+    &pkt_hdr_test_16
+};
+
+#define HPR_REPEAT_COUNT 4
+#define HPR_CIPHER_COUNT 3
+
+/*
+ * Count of number of times we observed an unchanged (u) or changed (c) bit in
+ * each header-protectable bit over all test suites.
+ */
+static unsigned int counts_u[HPR_CIPHER_COUNT][37] = {0};
+static unsigned int counts_c[HPR_CIPHER_COUNT][37] = {0};
+
+static int test_wire_pkt_hdr_actual(int tidx, int repeat, int cipher,
+                                    size_t trunc_len)
+{
+    int testresult = 0;
+    const struct pkt_hdr_test *t = pkt_hdr_tests[tidx];
+    QUIC_PKT_HDR hdr = {0};
+    QUIC_PKT_HDR_PTRS ptrs = {0}, wptrs = {0};
+    PACKET pkt = {0};
+    WPACKET wpkt = {0};
+    BUF_MEM *buf = NULL;
+    size_t l = 0, i, j;
+    QUIC_HDR_PROTECTOR hpr = {0};
+    unsigned char hpr_key[32] = {0,1,2,3,4,5,6,7};
+    int have_hpr = 0, hpr_cipher_id, hpr_key_len;
+    unsigned char *hbuf = NULL;
+    int is_trunc = trunc_len < t->expected_len;
+    int expect_fail = trunc_len < t->min_success_len;
+    hpr_key[8] = (unsigned char)tidx;
+    hpr_key[9] = (unsigned char)repeat;
+
+    switch (cipher) {
+        case 0:
+            hpr_cipher_id = QUIC_HDR_PROT_CIPHER_AES_128;
+            hpr_key_len   = 16;
+            break;
+        case 1:
+            hpr_cipher_id = QUIC_HDR_PROT_CIPHER_AES_256;
+            hpr_key_len   = 32;
+            break;
+        case 2:
+            hpr_cipher_id = QUIC_HDR_PROT_CIPHER_CHACHA;
+            hpr_key_len   = 32;
+            break;
+        default:
+            goto err;
+    }
+
+    if (!TEST_ptr(buf = BUF_MEM_new()))
+        goto err;
+
+    if (!TEST_true(WPACKET_init(&wpkt, buf)))
+        goto err;
+
+    if (!TEST_true(PACKET_buf_init(&pkt, t->expected, trunc_len)))
+        goto err;
+
+    if (!TEST_int_eq(ossl_quic_wire_decode_pkt_hdr(&pkt, t->short_conn_id_len,
+                                                   0, &hdr, &ptrs),
+                     !expect_fail))
+        goto err;
+
+    if (!expect_fail && !is_trunc) {
+        if (!TEST_true(cmp_pkt_hdr(&hdr, &t->hdr, t->payload, t->payload_len, 1)))
+            goto err;
+
+        if (!TEST_ptr_eq(ptrs.raw_start, t->expected))
+            goto err;
+
+        if (t->pn_offset == SIZE_MAX) {
+            if (!TEST_ptr_null(ptrs.raw_pn))
+                goto err;
+        } else {
+            if (!TEST_ptr_eq(ptrs.raw_pn, t->expected + t->pn_offset))
+                goto err;
+        }
+
+        if (t->sample_offset != SIZE_MAX) {
+            if (!TEST_ptr_eq(ptrs.raw_sample, t->expected + t->sample_offset))
+                goto err;
+            if (!TEST_size_t_eq(ptrs.raw_sample_len,
+                                t->expected_len - t->sample_offset))
+                goto err;
+        }
+
+        if (!TEST_true(ossl_quic_wire_encode_pkt_hdr(&wpkt, t->short_conn_id_len, &hdr, &wptrs)))
+            goto err;
+
+        if (!TEST_true(WPACKET_memcpy(&wpkt, t->payload, t->payload_len)))
+            goto err;
+
+        if (!TEST_true(WPACKET_get_total_written(&wpkt, &l)))
+            goto err;
+
+        if (!TEST_mem_eq(buf->data, l, t->expected, t->expected_len))
+            goto err;
+
+        /* Test header protection. */
+        if (t->sample_offset != SIZE_MAX) { /* if packet type has protection */
+            if (!TEST_true(ossl_quic_hdr_protector_init(&hpr, NULL, NULL,
+                                                        hpr_cipher_id,
+                                                        hpr_key,
+                                                        hpr_key_len)))
+                goto err;
+
+            have_hpr = 1;
+
+            /*
+             * Copy into a duplicate buffer to test header protection by
+             * comparing it against the original.
+             */
+            hbuf = OPENSSL_malloc(t->expected_len);
+            if (!TEST_ptr(hbuf))
+                goto err;
+
+            memcpy(hbuf, t->expected, t->expected_len);
+
+            /* Fixup pointers to new buffer and encrypt. */
+            ptrs.raw_pn         = hbuf + (ptrs.raw_pn     - ptrs.raw_start);
+            ptrs.raw_sample     = hbuf + (ptrs.raw_sample - ptrs.raw_start);
+            ptrs.raw_start      = hbuf;
+            if (!TEST_true(ossl_quic_hdr_protector_encrypt(&hpr, &ptrs)))
+                goto err;
+
+            /* Ensure that bytes which should not have changed did not change */
+            for (i = 0; i < t->expected_len; ++i) {
+                unsigned char d = t->expected[i] ^ hbuf[i], rej_mask = 0xff;
+                size_t jrel = 0;
+                if (i == 0) {
+                    /* Bits in first byte which must not change */
+                    rej_mask = (t->hdr.type == QUIC_PKT_TYPE_1RTT) ? ~0x1f : ~0xf;
+                } else if (i >= t->pn_offset && i < t->pn_offset + t->hdr.pn_len) {
+                    /* PN bytes change */
+                    rej_mask = 0;
+                    jrel = 5 + (i - t->pn_offset) * 8;
+                }
+
+                if (rej_mask != 0xff)
+                    for (j = 0; j < 8; ++j) {
+                        if (((1U << j) & rej_mask) != 0)
+                            /*
+                             * Bit unrelated to header protection, do not record
+                             * stats about it.
+                             */
+                            continue;
+
+                        OPENSSL_assert(jrel + j < OSSL_NELEM(counts_u[cipher]));
+                        if ((d & (1U << j)) != 0)
+                            ++counts_c[cipher][jrel + j]; /* bit did change */
+                        else
+                            ++counts_u[cipher][jrel + j]; /* bit did not change */
+                    }
+
+                /* Bits in rej_mask must not change */
+                if (!TEST_int_eq(d & rej_mask, 0))
+                    goto err;
+            }
+
+            /* Decrypt and check matches original. */
+            if (!TEST_true(ossl_quic_hdr_protector_decrypt(&hpr, &ptrs)))
+                goto err;
+
+            if (!TEST_mem_eq(hbuf, t->expected_len, t->expected, t->expected_len))
+                goto err;
+        }
+    }
+
+    testresult = 1;
+err:
+    if (have_hpr)
+        ossl_quic_hdr_protector_destroy(&hpr);
+    WPACKET_finish(&wpkt);
+    BUF_MEM_free(buf);
+    OPENSSL_free(hbuf);
+    return testresult;
+}
+
+static int test_wire_pkt_hdr_inner(int tidx, int repeat, int cipher)
+{
+    int testresult = 0;
+    const struct pkt_hdr_test *t = pkt_hdr_tests[tidx];
+    size_t i;
+
+    /* Test with entire packet */
+    if (!TEST_true(test_wire_pkt_hdr_actual(tidx, repeat, cipher,
+                                            t->expected_len)))
+        goto err;
+
+    /* Now repeat for every possible truncation of the packet */
+    for (i = 0; i < t->expected_len; ++i)
+        if (!TEST_true(test_wire_pkt_hdr_actual(tidx, repeat, cipher, i)))
+            goto err;
+
+    testresult = 1;
+err:
+    return testresult;
+}
+
+static int test_hdr_prot_stats(void)
+{
+    int testresult = 0;
+    size_t i, cipher;
+
+    /*
+     * Test that, across all previously executed tests for each header
+     * protection cipher, every bit which can have header protection applied a)
+     * was changed in at least one test of applying header protection, and b)
+     * was unchanged in at least one test of applying header protection.
+     */
+    for (cipher = 0; cipher < HPR_CIPHER_COUNT; ++cipher)
+        for (i = 0; i < OSSL_NELEM(counts_u[0]); ++i) {
+            if (!TEST_true(counts_u[cipher][i]))
+                goto err;
+            if (!TEST_true(counts_c[cipher][i]))
+                goto err;
+        }
+
+    testresult = 1;
+err:
+    return testresult;
+}
+
+#define NUM_WIRE_PKT_HDR_TESTS \
+    (OSSL_NELEM(pkt_hdr_tests) * HPR_REPEAT_COUNT * HPR_CIPHER_COUNT)
+
+static int test_wire_pkt_hdr(int idx)
+{
+    int tidx, repeat, cipher;
+
+    if (idx == NUM_WIRE_PKT_HDR_TESTS)
+        return test_hdr_prot_stats();
+
+    cipher = idx % HPR_CIPHER_COUNT;
+    idx /= HPR_CIPHER_COUNT;
+
+    repeat = idx % HPR_REPEAT_COUNT;
+    idx /= HPR_REPEAT_COUNT;
+
+    tidx = idx;
+
+    return test_wire_pkt_hdr_inner(tidx, repeat, cipher);
+}
+
+int setup_tests(void)
+{
+    ADD_ALL_TESTS(test_script, OSSL_NELEM(scripts));
+    /*
+     * Each instance of this test is executed multiple times to get enough
+     * statistical coverage for our statistical test, as well as for each
+     * supported key type.
+     *
+     * We call the statistical test as the last index in the wire_pkt_hdr
+     * test rather than as a separate case, as it needs to execute last
+     * and otherwise random test ordering will cause itt to randomly fail.
+     */
+    ADD_ALL_TESTS(test_wire_pkt_hdr, NUM_WIRE_PKT_HDR_TESTS + 1);
+    return 1;
+}
index 10c7835e0cf1982f7f37ba73216de0bfa609e45a..b10b14014fe49b222b3544ee45f22e5f209ebbc6 100644 (file)
@@ -9,6 +9,7 @@
 
 #include "internal/packet.h"
 #include "internal/quic_wire.h"
+#include "internal/quic_wire_pkt.h"
 #include "testutil.h"
 
 struct encode_test_case {
@@ -1357,9 +1358,71 @@ err:
     return testresult;
 }
 
+/* Packet Header PN Encoding Tests */
+struct pn_test {
+    QUIC_PN         pn, tx_largest_acked, rx_largest_pn;
+    char            expected_len;
+    unsigned char   expected_bytes[4];
+};
+
+static const struct pn_test pn_tests[] = {
+    /* RFC 9000 Section A.2 */
+    { 0xac5c02, 0xabe8b3, 0xabe8b3, 2, {0x5c,0x02} },
+    { 0xace8fe, 0xabe8b3, 0xabe8b3, 3, {0xac,0xe8,0xfe} },
+    /* RFC 9000 Section A.3 */
+    { 0xa82f9b32, 0xa82f30ea, 0xa82f30ea, 2, {0x9b,0x32} },
+    /* Boundary Cases */
+    { 1, 0, 0, 1, {0x01} },
+    { 256, 255, 255, 1, {0x00} },
+    { 257, 255, 255, 1, {0x01} },
+    { 256, 128, 128, 1, {0x00} },
+    { 256, 127, 127, 2, {0x01,0x00} },
+    { 65536, 32768, 32768, 2, {0x00,0x00} },
+    { 65537, 32769, 32769, 2, {0x00,0x01} },
+    { 65536, 32767, 32767, 3, {0x01,0x00,0x00} },
+    { 65537, 32768, 32768, 3, {0x01,0x00,0x01} },
+    { 16777216, 8388608, 8388608, 3, {0x00,0x00,0x00} },
+    { 16777217, 8388609, 8388609, 3, {0x00,0x00,0x01} },
+    { 16777216, 8388607, 8388607, 4, {0x01,0x00,0x00,0x00} },
+    { 16777217, 8388608, 8388608, 4, {0x01,0x00,0x00,0x01} },
+    { 4294967296, 2147483648, 2147483648, 4, {0x00,0x00,0x00,0x00} },
+    { 4294967297, 2147483648, 2147483648, 4, {0x00,0x00,0x00,0x01} },
+};
+
+static int test_wire_pkt_hdr_pn(int tidx)
+{
+    int testresult = 0;
+    const struct pn_test *t = &pn_tests[tidx];
+    unsigned char buf[4];
+    int pn_len;
+    QUIC_PN res_pn;
+
+    pn_len = ossl_quic_wire_determine_pn_len(t->pn, t->tx_largest_acked);
+    if (!TEST_int_eq(pn_len, (int)t->expected_len))
+        goto err;
+
+    if (!TEST_true(ossl_quic_wire_encode_pkt_hdr_pn(t->pn, buf, pn_len)))
+        goto err;
+
+    if (!TEST_mem_eq(t->expected_bytes, t->expected_len, buf, pn_len))
+        goto err;
+
+    if (!TEST_true(ossl_quic_wire_decode_pkt_hdr_pn(buf, pn_len,
+                                                    t->rx_largest_pn, &res_pn)))
+        goto err;
+
+    if (!TEST_uint64_t_eq(res_pn, t->pn))
+        goto err;
+
+    testresult = 1;
+err:
+    return testresult;
+}
+
 int setup_tests(void)
 {
-    ADD_ALL_TESTS(test_wire_encode, OSSL_NELEM(encode_cases));
-    ADD_ALL_TESTS(test_wire_ack,    OSSL_NELEM(ack_cases));
+    ADD_ALL_TESTS(test_wire_encode,     OSSL_NELEM(encode_cases));
+    ADD_ALL_TESTS(test_wire_ack,        OSSL_NELEM(ack_cases));
+    ADD_ALL_TESTS(test_wire_pkt_hdr_pn, OSSL_NELEM(pn_tests));
     return 1;
 }
diff --git a/test/recipes/70-test_quic_record.t b/test/recipes/70-test_quic_record.t
new file mode 100644 (file)
index 0000000..3fd7820
--- /dev/null
@@ -0,0 +1,19 @@
+#! /usr/bin/env perl
+# 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
+
+use OpenSSL::Test;
+use OpenSSL::Test::Utils;
+
+setup("test_quic_record");
+
+plan skip_all => "QUIC protocol is not supported by this OpenSSL build"
+    if disabled('quic');
+
+plan tests => 1;
+
+ok(run(test(["quic_record_test"])));