static int tls_dump_puts(BIO *bp, const char *str);
/* Choose a sufficiently large type likely to be unused for this custom BIO */
-#define BIO_TYPE_TLS_DUMP_FILTER (0x80 | BIO_TYPE_FILTER)
+#define BIO_TYPE_TLS_DUMP_FILTER (0x80 | BIO_TYPE_FILTER)
#define BIO_TYPE_MEMPACKET_TEST 0x81
#define BIO_TYPE_ALWAYS_RETRY 0x82
+#define BIO_TYPE_MAYBE_RETRY (0x83 | BIO_TYPE_FILTER)
static BIO_METHOD *method_tls_dump = NULL;
static BIO_METHOD *meth_mem = NULL;
static BIO_METHOD *meth_always_retry = NULL;
+static BIO_METHOD *meth_maybe_retry = NULL;
static int retry_err = -1;
/* Note: Not thread safe! */
return retry_err;
}
+struct maybe_retry_data_st {
+ unsigned int retrycnt;
+};
+
+static int maybe_retry_new(BIO *bi);
+static int maybe_retry_free(BIO *a);
+static int maybe_retry_write(BIO *b, const char *in, int inl);
+static long maybe_retry_ctrl(BIO *b, int cmd, long num, void *ptr);
+
+const BIO_METHOD *bio_s_maybe_retry(void)
+{
+ if (meth_maybe_retry == NULL) {
+ if (!TEST_ptr(meth_maybe_retry = BIO_meth_new(BIO_TYPE_MAYBE_RETRY,
+ "Maybe Retry"))
+ || !TEST_true(BIO_meth_set_write(meth_maybe_retry,
+ maybe_retry_write))
+ || !TEST_true(BIO_meth_set_ctrl(meth_maybe_retry,
+ maybe_retry_ctrl))
+ || !TEST_true(BIO_meth_set_create(meth_maybe_retry,
+ maybe_retry_new))
+ || !TEST_true(BIO_meth_set_destroy(meth_maybe_retry,
+ maybe_retry_free)))
+ return NULL;
+ }
+ return meth_maybe_retry;
+}
+
+void bio_s_maybe_retry_free(void)
+{
+ BIO_meth_free(meth_maybe_retry);
+}
+
+static int maybe_retry_new(BIO *bio)
+{
+ struct maybe_retry_data_st *data = OPENSSL_zalloc(sizeof(*data));
+
+ if (data == NULL)
+ return 0;
+
+ BIO_set_data(bio, data);
+ BIO_set_init(bio, 1);
+ return 1;
+}
+
+static int maybe_retry_free(BIO *bio)
+{
+ struct maybe_retry_data_st *data = BIO_get_data(bio);
+
+ OPENSSL_free(data);
+ BIO_set_data(bio, NULL);
+ BIO_set_init(bio, 0);
+ return 1;
+}
+
+static int maybe_retry_write(BIO *bio, const char *in, int inl)
+{
+ struct maybe_retry_data_st *data = BIO_get_data(bio);
+
+ if (data == NULL)
+ return -1;
+
+ if (data->retrycnt == 0) {
+ BIO_set_retry_write(bio);
+ return -1;
+ }
+ data->retrycnt--;
+
+ return BIO_write(BIO_next(bio), in, inl);
+}
+
+static long maybe_retry_ctrl(BIO *bio, int cmd, long num, void *ptr)
+{
+ struct maybe_retry_data_st *data = BIO_get_data(bio);
+
+ if (data == NULL)
+ return 0;
+
+ switch (cmd) {
+ case MAYBE_RETRY_CTRL_SET_RETRY_AFTER_CNT:
+ data->retrycnt = num;
+ return 1;
+
+ case BIO_CTRL_FLUSH:
+ if (data->retrycnt == 0) {
+ BIO_set_retry_write(bio);
+ return -1;
+ }
+ data->retrycnt--;
+ /* fall through */
+ default:
+ return BIO_ctrl(BIO_next(bio), cmd, num, ptr);
+ }
+}
+
int create_ssl_ctx_pair(OSSL_LIB_CTX *libctx, const SSL_METHOD *sm,
const SSL_METHOD *cm, int min_proto_version,
int max_proto_version, SSL_CTX **sctx, SSL_CTX **cctx,
void bio_s_always_retry_free(void);
void set_always_retry_err_val(int err);
+/*
+ * Maybe retry BIO ctrls. We make them large enough to not clash with standard
+ * BIO ctrl codes.
+ */
+#define MAYBE_RETRY_CTRL_SET_RETRY_AFTER_CNT (1 << 15)
+
+const BIO_METHOD *bio_s_maybe_retry(void);
+void bio_s_maybe_retry_free(void);
+
/* Packet types - value 0 is reserved */
#define INJECT_PACKET 1
#define INJECT_PACKET_IGNORE_REC_SEQ 2
return testresult;
}
+/*
+ * Test that receiving retries when writing application data works as expected
+ */
+static int test_data_retry(void)
+{
+ SSL_CTX *cctx = NULL, *sctx = NULL;
+ SSL *clientssl = NULL, *serverssl = NULL;
+ int testresult = 0;
+ unsigned char inbuf[1200], outbuf[1200];
+ size_t i;
+ BIO *tmp = NULL;
+ BIO *bretry = BIO_new(bio_s_maybe_retry());
+ size_t written, readbytes, totread = 0;
+
+ if (!TEST_ptr(bretry))
+ goto end;
+
+ for (i = 0; i < sizeof(inbuf); i++)
+ inbuf[i] = i;
+ memset(outbuf, 0, sizeof(outbuf));
+
+ if (!TEST_true(create_ssl_ctx_pair(libctx, TLS_server_method(),
+ TLS_client_method(), 0, 0, &sctx, &cctx,
+ cert, privkey)))
+ goto end;
+
+ if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl, &clientssl, NULL,
+ NULL)))
+ goto end;
+
+ if (!TEST_true(create_ssl_connection(serverssl, clientssl, SSL_ERROR_NONE)))
+ goto end;
+
+ /* Smallest possible max send fragment is 512 */
+ if (!TEST_true(SSL_set_max_send_fragment(clientssl, 512)))
+ goto end;
+
+ tmp = SSL_get_wbio(clientssl);
+ if (!TEST_ptr(tmp))
+ goto end;
+ if (!TEST_true(BIO_up_ref(tmp)))
+ goto end;
+ BIO_push(bretry, tmp);
+ tmp = NULL;
+ SSL_set0_wbio(clientssl, bretry);
+ if (!BIO_up_ref(bretry)) {
+ bretry = NULL;
+ goto end;
+ }
+
+ for (i = 0; i < 3; i++) {
+ /* We expect this call to make no progress and indicate retry */
+ if (!TEST_false(SSL_write_ex(clientssl, inbuf, sizeof(inbuf), &written)))
+ goto end;
+ if (!TEST_int_eq(SSL_get_error(clientssl, 0), SSL_ERROR_WANT_WRITE))
+ goto end;
+
+ /* Allow one write to progess, but the next one to signal retry */
+ if (!TEST_true(BIO_ctrl(bretry, MAYBE_RETRY_CTRL_SET_RETRY_AFTER_CNT, 1,
+ NULL)))
+ goto end;
+
+ if (i == 2)
+ break;
+
+ /*
+ * This call will hopefully make progress but will still indicate retry
+ * because there is more data than will fit into a single record.
+ */
+ if (!TEST_false(SSL_write_ex(clientssl, inbuf, sizeof(inbuf), &written)))
+ goto end;
+ if (!TEST_int_eq(SSL_get_error(clientssl, 0), SSL_ERROR_WANT_WRITE))
+ goto end;
+ }
+
+ /* The final call should write the last chunk of data and succeed */
+ if (!TEST_true(SSL_write_ex(clientssl, inbuf, sizeof(inbuf), &written)))
+ goto end;
+ /* Read all the data available */
+ while (SSL_read_ex(serverssl, outbuf + totread, sizeof(outbuf) - totread,
+ &readbytes))
+ totread += readbytes;
+ if (!TEST_mem_eq(inbuf, sizeof(inbuf), outbuf, totread))
+ goto end;
+
+ testresult = 1;
+end:
+ SSL_free(serverssl);
+ SSL_free(clientssl);
+ SSL_CTX_free(sctx);
+ SSL_CTX_free(cctx);
+ BIO_free_all(bretry);
+ BIO_free(tmp);
+ return testresult;
+}
+
OPT_TEST_DECLARE_USAGE("certfile privkeyfile srpvfile tmpfile provider config dhfile\n")
int setup_tests(void)
ADD_ALL_TESTS(test_version, 6);
ADD_TEST(test_rstate_string);
ADD_ALL_TESTS(test_handshake_retry, 16);
+ ADD_TEST(test_data_retry);
return 1;
err:
OPENSSL_free(privkey8192);
bio_s_mempacket_test_free();
bio_s_always_retry_free();
+ bio_s_maybe_retry_free();
OSSL_PROVIDER_unload(defctxnull);
OSSL_LIB_CTX_free(libctx);
}