+
+/*
+ * Prepend frame data into a packet. To be called from a packet_plain_listener
+ * callback
+ */
+int qtest_fault_prepend_frame(QTEST_FAULT *fault, const unsigned char *frame,
+ size_t frame_len)
+{
+ unsigned char *buf;
+ size_t old_len;
+
+ /*
+ * Alloc'd size should always be non-zero, so if this fails we've been
+ * incorrectly called
+ */
+ if (fault->pplainbuf_alloc == 0)
+ return 0;
+
+ /* Cast below is safe because we allocated the buffer */
+ buf = (unsigned char *)fault->pplainio.buf;
+ old_len = fault->pplainio.buf_len;
+
+ /* Extend the size of the packet by the size of the new frame */
+ if (!TEST_true(qtest_fault_resize_plain_packet(fault,
+ old_len + frame_len)))
+ return 0;
+
+ memmove(buf + frame_len, buf, old_len);
+ memcpy(buf, frame, frame_len);
+
+ return 1;
+}
+
+static int handshake_mutate(const unsigned char *msgin, size_t msginlen,
+ unsigned char **msgout, size_t *msgoutlen,
+ void *arg)
+{
+ QTEST_FAULT *fault = arg;
+ unsigned char *buf;
+ unsigned long payloadlen;
+ unsigned int msgtype;
+ PACKET pkt;
+
+ buf = OPENSSL_malloc(msginlen + GROWTH_ALLOWANCE);
+ if (buf == NULL)
+ return 0;
+
+ fault->handbuf = buf;
+ fault->handbuflen = msginlen;
+ fault->handbufalloc = msginlen + GROWTH_ALLOWANCE;
+ memcpy(buf, msgin, msginlen);
+
+ if (!PACKET_buf_init(&pkt, buf, msginlen)
+ || !PACKET_get_1(&pkt, &msgtype)
+ || !PACKET_get_net_3(&pkt, &payloadlen)
+ || PACKET_remaining(&pkt) != payloadlen)
+ return 0;
+
+ /* Parse specific message types */
+ switch (msgtype) {
+ case SSL3_MT_ENCRYPTED_EXTENSIONS:
+ {
+ QTEST_ENCRYPTED_EXTENSIONS ee;
+
+ if (fault->encextcb == NULL)
+ break;
+
+ /*
+ * The EncryptedExtensions message is very simple. It just has an
+ * extensions block in it and nothing else.
+ */
+ ee.extensions = (unsigned char *)PACKET_data(&pkt);
+ ee.extensionslen = payloadlen;
+ if (!fault->encextcb(fault, &ee, payloadlen, fault->encextcbarg))
+ return 0;
+ }
+
+ default:
+ /* No specific handlers for these message types yet */
+ break;
+ }
+
+ if (fault->handshakecb != NULL
+ && !fault->handshakecb(fault, buf, fault->handbuflen,
+ fault->handshakecbarg))
+ return 0;
+
+ *msgout = buf;
+ *msgoutlen = fault->handbuflen;
+
+ return 1;
+}
+
+static void handshake_finish(void *arg)
+{
+ QTEST_FAULT *fault = arg;
+
+ OPENSSL_free(fault->handbuf);
+ fault->handbuf = NULL;
+}
+
+int qtest_fault_set_handshake_listener(QTEST_FAULT *fault,
+ qtest_fault_on_handshake_cb handshakecb,
+ void *handshakecbarg)
+{
+ fault->handshakecb = handshakecb;
+ fault->handshakecbarg = handshakecbarg;
+
+ return ossl_quic_tserver_set_handshake_mutator(fault->qtserv,
+ handshake_mutate,
+ handshake_finish,
+ fault);
+}
+
+int qtest_fault_set_hand_enc_ext_listener(QTEST_FAULT *fault,
+ qtest_fault_on_enc_ext_cb encextcb,
+ void *encextcbarg)
+{
+ fault->encextcb = encextcb;
+ fault->encextcbarg = encextcbarg;
+
+ return ossl_quic_tserver_set_handshake_mutator(fault->qtserv,
+ handshake_mutate,
+ handshake_finish,
+ fault);
+}
+
+/* To be called from a handshake_listener callback */
+int qtest_fault_resize_handshake(QTEST_FAULT *fault, size_t newlen)
+{
+ unsigned char *buf;
+ size_t oldlen = fault->handbuflen;
+
+ /*
+ * Alloc'd size should always be non-zero, so if this fails we've been
+ * incorrectly called
+ */
+ if (fault->handbufalloc == 0)
+ return 0;
+
+ if (newlen > fault->handbufalloc) {
+ /* This exceeds our growth allowance. Fail */
+ return 0;
+ }
+
+ buf = (unsigned char *)fault->handbuf;
+
+ if (newlen > oldlen) {
+ /* Extend packet with 0 bytes */
+ memset(buf + oldlen, 0, newlen - oldlen);
+ } /* else we're truncating or staying the same */
+
+ fault->handbuflen = newlen;
+ return 1;
+}
+
+/* To be called from message specific listener callbacks */
+int qtest_fault_resize_message(QTEST_FAULT *fault, size_t newlen)
+{
+ /* First resize the underlying message */
+ if (!qtest_fault_resize_handshake(fault, newlen + SSL3_HM_HEADER_LENGTH))
+ return 0;
+
+ /* Fixup the handshake message header */
+ fault->handbuf[1] = (unsigned char)((newlen >> 16) & 0xff);
+ fault->handbuf[2] = (unsigned char)((newlen >> 8) & 0xff);
+ fault->handbuf[3] = (unsigned char)((newlen ) & 0xff);
+
+ return 1;
+}
+
+int qtest_fault_delete_extension(QTEST_FAULT *fault,
+ unsigned int exttype, unsigned char *ext,
+ size_t *extlen)
+{
+ PACKET pkt, sub, subext;
+ unsigned int type;
+ const unsigned char *start, *end;
+ size_t newlen;
+ size_t msglen = fault->handbuflen;
+
+ if (!PACKET_buf_init(&pkt, ext, *extlen))
+ return 0;
+
+ /* Extension block starts with 2 bytes for extension block length */
+ if (!PACKET_as_length_prefixed_2(&pkt, &sub))
+ return 0;
+
+ do {
+ start = PACKET_data(&sub);
+ if (!PACKET_get_net_2(&sub, &type)
+ || !PACKET_get_length_prefixed_2(&sub, &subext))
+ return 0;
+ } while (type != exttype);
+
+ /* Found it */
+ end = PACKET_data(&sub);
+
+ /*
+ * If we're not the last extension we need to move the rest earlier. The
+ * cast below is safe because we own the underlying buffer and we're no
+ * longer making PACKET calls.
+ */
+ if (end < ext + *extlen)
+ memmove((unsigned char *)start, end, end - start);
+
+ /*
+ * Calculate new extensions payload length =
+ * Original length
+ * - 2 extension block length bytes
+ * - length of removed extension
+ */
+ newlen = *extlen - 2 - (end - start);
+
+ /* Fixup the length bytes for the extension block */
+ ext[0] = (unsigned char)((newlen >> 8) & 0xff);
+ ext[1] = (unsigned char)((newlen ) & 0xff);
+
+ /*
+ * Length of the whole extension block is the new payload length plus the
+ * 2 bytes for the length
+ */
+ *extlen = newlen + 2;
+
+ /* We can now resize the message */
+ if ((size_t)(end - start) + SSL3_HM_HEADER_LENGTH > msglen)
+ return 0; /* Should not happen */
+ msglen -= (end - start) + SSL3_HM_HEADER_LENGTH;
+ if (!qtest_fault_resize_message(fault, msglen))
+ return 0;
+
+ return 1;
+}
+
+#define BIO_TYPE_CIPHER_PACKET_FILTER (0x80 | BIO_TYPE_FILTER)
+
+static BIO_METHOD *pcipherbiometh = NULL;
+
+# define BIO_MSG_N(array, stride, n) (*(BIO_MSG *)((char *)(array) + (n)*(stride)))
+
+static int pcipher_sendmmsg(BIO *b, BIO_MSG *msg, size_t stride,
+ size_t num_msg, uint64_t flags,
+ size_t *num_processed)
+{
+ QTEST_FAULT *fault;
+ BIO *next = BIO_next(b);
+ ossl_ssize_t ret = 0;
+ size_t i = 0, tmpnump;
+ QUIC_PKT_HDR hdr;
+ PACKET pkt;
+ unsigned char *tmpdata;
+
+ if (next == NULL)
+ return 0;
+
+ fault = BIO_get_data(b);
+ if (fault == NULL
+ || (fault->pciphercb == NULL && fault->datagramcb == NULL))
+ return BIO_sendmmsg(next, msg, stride, num_msg, flags, num_processed);
+
+ if (num_msg == 0) {
+ *num_processed = 0;
+ return 1;
+ }
+
+ for (i = 0; i < num_msg; ++i) {
+ fault->msg = BIO_MSG_N(msg, stride, i);
+
+ /* Take a copy of the data so that callbacks can modify it */
+ tmpdata = OPENSSL_malloc(fault->msg.data_len + GROWTH_ALLOWANCE);
+ if (tmpdata == NULL)
+ return 0;
+ memcpy(tmpdata, fault->msg.data, fault->msg.data_len);
+ fault->msg.data = tmpdata;
+ fault->msgalloc = fault->msg.data_len + GROWTH_ALLOWANCE;
+
+ if (fault->pciphercb != NULL) {
+ if (!PACKET_buf_init(&pkt, fault->msg.data, fault->msg.data_len))
+ return 0;
+
+ do {
+ if (!ossl_quic_wire_decode_pkt_hdr(&pkt,
+ /*
+ * TODO(QUIC SERVER):
+ * Needs to be set to the actual short header CID length
+ * when testing the server implementation.
+ */
+ 0,
+ 1,
+ 0, &hdr, NULL))
+ goto out;
+
+ /*
+ * hdr.data is const - but its our buffer so casting away the
+ * const is safe
+ */
+ if (!fault->pciphercb(fault, &hdr, (unsigned char *)hdr.data,
+ hdr.len, fault->pciphercbarg))
+ goto out;
+
+ /*
+ * At the moment modifications to hdr by the callback
+ * are ignored. We might need to rewrite the QUIC header to
+ * enable tests to change this. We also don't yet have a
+ * mechanism for the callback to change the encrypted data
+ * length. It's not clear if that's needed or not.
+ */
+ } while (PACKET_remaining(&pkt) > 0);
+ }
+
+ if (fault->datagramcb != NULL
+ && !fault->datagramcb(fault, &fault->msg, stride,
+ fault->datagramcbarg))
+ goto out;
+
+ if (!BIO_sendmmsg(next, &fault->msg, stride, 1, flags, &tmpnump)) {
+ *num_processed = i;
+ goto out;
+ }
+
+ OPENSSL_free(fault->msg.data);
+ fault->msg.data = NULL;
+ fault->msgalloc = 0;
+ }
+
+ *num_processed = i;
+out:
+ ret = i > 0;
+ OPENSSL_free(fault->msg.data);
+ fault->msg.data = NULL;
+ return ret;
+}
+
+static long pcipher_ctrl(BIO *b, int cmd, long larg, void *parg)
+{
+ BIO *next = BIO_next(b);
+
+ if (next == NULL)
+ return -1;
+
+ return BIO_ctrl(next, cmd, larg, parg);
+}
+
+BIO_METHOD *qtest_get_bio_method(void)
+{
+ BIO_METHOD *tmp;
+
+ if (pcipherbiometh != NULL)
+ return pcipherbiometh;
+
+ tmp = BIO_meth_new(BIO_TYPE_CIPHER_PACKET_FILTER, "Cipher Packet Filter");
+
+ if (!TEST_ptr(tmp))
+ return NULL;
+
+ if (!TEST_true(BIO_meth_set_sendmmsg(tmp, pcipher_sendmmsg))
+ || !TEST_true(BIO_meth_set_ctrl(tmp, pcipher_ctrl)))
+ goto err;
+
+ pcipherbiometh = tmp;
+ tmp = NULL;
+ err:
+ BIO_meth_free(tmp);
+ return pcipherbiometh;
+}
+
+int qtest_fault_set_packet_cipher_listener(QTEST_FAULT *fault,
+ qtest_fault_on_packet_cipher_cb pciphercb,
+ void *pciphercbarg)
+{
+ fault->pciphercb = pciphercb;
+ fault->pciphercbarg = pciphercbarg;
+
+ return 1;
+}
+
+int qtest_fault_set_datagram_listener(QTEST_FAULT *fault,
+ qtest_fault_on_datagram_cb datagramcb,
+ void *datagramcbarg)
+{
+ fault->datagramcb = datagramcb;
+ fault->datagramcbarg = datagramcbarg;
+
+ return 1;
+}
+
+/* To be called from a datagram_listener callback */
+int qtest_fault_resize_datagram(QTEST_FAULT *fault, size_t newlen)
+{
+ if (newlen > fault->msgalloc)
+ return 0;
+
+ if (newlen > fault->msg.data_len)
+ memset((unsigned char *)fault->msg.data + fault->msg.data_len, 0,
+ newlen - fault->msg.data_len);
+
+ fault->msg.data_len = newlen;
+
+ return 1;
+}