Skip to content

Commit 3a784d2

Browse files
authored
Merge pull request #1923 from private-octopus/test-loop-after-reset
Try to repro issue #1922
2 parents 8a65adb + 6864cd6 commit 3a784d2

File tree

5 files changed

+280
-13
lines changed

5 files changed

+280
-13
lines changed

UnitTest1/unittest1.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1979,6 +1979,12 @@ namespace UnitTest1
19791979
Assert::AreEqual(ret, 0);
19801980
}
19811981

1982+
TEST_METHOD(reset_loop)
1983+
{
1984+
int ret = reset_loop_test();
1985+
1986+
Assert::AreEqual(ret, 0);
1987+
}
19821988
TEST_METHOD(initial_pto)
19831989
{
19841990
int ret = initial_pto_test();

picoquic/frames.c

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1366,19 +1366,43 @@ picoquic_stream_head_t* picoquic_find_ready_stream_path(picoquic_cnx_t* cnx, pic
13661366
* the current selection is validated */
13671367
break;
13681368
}
1369-
has_data = (cnx->maxdata_remote > cnx->data_sent && stream->sent_offset < stream->maxdata_remote && (stream->is_active ||
1370-
(stream->send_queue != NULL && stream->send_queue->length > stream->send_queue->offset) ||
1371-
(stream->fin_requested && !stream->fin_sent)));
1372-
if (has_data && path_x != NULL && stream->affinity_path != path_x && stream->affinity_path != NULL) {
1373-
/* Only consider the streams that meet path affinity requirements */
1369+
1370+
/* The tests for "have data" should excatly replicate the tests in
1371+
* the formating of a stream frame */
1372+
if (stream->stop_sending_requested && !stream->stop_sending_sent) {
1373+
/* will send a stop sending frame.
1374+
* this takes precedence over FIFO vs round-robin processing */
1375+
found_stream = stream;
1376+
has_data = 1;
1377+
break;
1378+
}
1379+
else if (stream->reset_sent) {
1380+
/* No data will be sent after a reset */
13741381
has_data = 0;
13751382
}
1376-
if ((stream->reset_requested && !stream->reset_sent) ||
1377-
(stream->stop_sending_requested && !stream->stop_sending_sent)) {
1378-
/* urgent action is needed, this takes precedence over FIFO vs round-robin processing */
1383+
else if (stream->reset_requested) {
1384+
/* will queue a reset frame --
1385+
* this takes precedence over FIFO vs round-robin processing */
13791386
found_stream = stream;
1387+
has_data = 1;
13801388
break;
1381-
} else if (has_data) {
1389+
}
1390+
else if (cnx->maxdata_remote > cnx->data_sent && stream->sent_offset < stream->maxdata_remote && (stream->is_active ||
1391+
(stream->send_queue != NULL && stream->send_queue->length > stream->send_queue->offset) ||
1392+
(stream->fin_requested && !stream->fin_sent))) {
1393+
has_data = 1;
1394+
}
1395+
else {
1396+
has_data = 0;
1397+
}
1398+
1399+
/* implement affinity scheduling */
1400+
if (has_data && path_x != NULL && stream->affinity_path != path_x && stream->affinity_path != NULL) {
1401+
/* Only consider the streams that meet path affinity requirements */
1402+
has_data = 0;
1403+
}
1404+
1405+
if (has_data) {
13821406
/* Check that this stream is actually available for sending data */
13831407
if (stream->sent_offset == 0) {
13841408
if (IS_CLIENT_STREAM_ID(stream->stream_id) == cnx->client_mode) {
@@ -1651,17 +1675,18 @@ uint8_t * picoquic_format_stream_frame(picoquic_cnx_t* cnx, picoquic_stream_head
16511675
}
16521676
}
16531677

1678+
if (stream->stop_sending_requested && !stream->stop_sending_sent) {
1679+
return picoquic_format_stop_sending_frame(stream, bytes, bytes_max, more_data, is_pure_ack);
1680+
}
1681+
16541682
if (stream->reset_sent) {
16551683
/* No data will be sent after a reset */
16561684
return bytes;
16571685
}
1658-
else if (stream->reset_requested && !stream->reset_sent) {
1686+
else if (stream->reset_requested) {
16591687
return picoquic_format_stream_reset_frame(cnx, stream, bytes, bytes_max, more_data, is_pure_ack);
16601688
}
16611689

1662-
if (stream->stop_sending_requested && !stream->stop_sending_sent) {
1663-
return picoquic_format_stop_sending_frame(stream, bytes, bytes_max, more_data, is_pure_ack);
1664-
}
16651690

16661691
if (!stream->is_active &&
16671692
(stream->send_queue == NULL || stream->send_queue->length <= stream->send_queue->offset) &&

picoquic_t/picoquic_t.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,7 @@ static const picoquic_test_def_t test_table[] = {
325325
{ "reset_need_max", reset_need_max_test },
326326
{ "reset_need_reset", reset_need_reset_test },
327327
{ "reset_need_stop", reset_need_stop_test },
328+
{ "reset_loop_test", reset_loop_test },
328329
{ "initial_pto", initial_pto_test },
329330
{ "initial_pto_srv", initial_pto_srv_test },
330331
{ "ready_to_send", ready_to_send_test },

picoquictest/edge_cases.c

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1641,5 +1641,239 @@ int crypto_hs_offset_test()
16411641
ret = crypto_hs_offset_test_one(pc[i]);
16421642
}
16431643

1644+
return ret;
1645+
}
1646+
1647+
/*
1648+
* Test what happens if there is data sent after reset,
1649+
* and possibly other shenanigans.
1650+
*
1651+
*/
1652+
1653+
static test_api_stream_desc_t test_scenario_reset_case[] = {
1654+
{ 4, 0, 128, 1000000 },
1655+
{ 8, 0, 128, 1000000 }
1656+
};
1657+
1658+
typedef struct st_reset_loop_callback_t {
1659+
uint64_t data_sent[4];
1660+
uint64_t data_received[4];
1661+
int fin_received[4];
1662+
int reset_received[4];
1663+
} reset_loop_callback_t;
1664+
1665+
1666+
int reset_loop_prepare_to_send(reset_loop_callback_t * cb, int stream_rank, void* context, size_t space)
1667+
{
1668+
int ret = 0;
1669+
uint8_t* buffer;
1670+
1671+
if (stream_rank >= 4) {
1672+
ret = -1;
1673+
}
1674+
else {
1675+
int is_fin = (cb->data_sent[stream_rank] + space > 1000000);
1676+
buffer = (uint8_t*)picoquic_provide_stream_data_buffer(context, space, is_fin, !is_fin);
1677+
1678+
if (buffer == NULL) {
1679+
ret = -1;
1680+
}
1681+
else {
1682+
memset(buffer, (uint8_t)('a' + stream_rank), space);
1683+
cb->data_sent[stream_rank] += space;
1684+
}
1685+
}
1686+
return ret;
1687+
}
1688+
1689+
int reset_loop_callback(picoquic_cnx_t* cnx,
1690+
uint64_t stream_id, uint8_t* bytes, size_t length,
1691+
picoquic_call_back_event_t fin_or_event, void* callback_ctx, void* v_stream_ctx)
1692+
{
1693+
1694+
int ret = 0;
1695+
int stream_rank = (int)((stream_id / 2) - 2) + cnx->client_mode;
1696+
reset_loop_callback_t* cb = (reset_loop_callback_t*)callback_ctx;
1697+
1698+
if (ret == 0) {
1699+
switch (fin_or_event) {
1700+
case picoquic_callback_stream_data:
1701+
case picoquic_callback_stream_fin:
1702+
cb->data_received[stream_rank] += length;
1703+
if (fin_or_event == picoquic_callback_stream_fin) {
1704+
cb->fin_received[stream_rank] += 1;
1705+
}
1706+
if (!cnx->client_mode && cb->data_sent[stream_rank] == 0) {
1707+
picoquic_mark_active_stream(cnx, stream_id, 1, cb);
1708+
}
1709+
break;
1710+
case picoquic_callback_prepare_to_send:
1711+
ret = reset_loop_prepare_to_send(cb, stream_rank, bytes, length);
1712+
break;
1713+
case picoquic_callback_datagram:
1714+
/* Receive data in a datagram */
1715+
break;
1716+
case picoquic_callback_prepare_datagram:
1717+
ret = -1;
1718+
break;
1719+
case picoquic_callback_stream_reset: /* Client reset stream #x */
1720+
cb->reset_received[stream_rank] += 1;
1721+
break;
1722+
case picoquic_callback_stop_sending: /* Client asks server to reset stream #x */
1723+
ret = picoquic_reset_stream(cnx, stream_id, 0);
1724+
break;
1725+
case picoquic_callback_stateless_reset: /* Received an error message */
1726+
case picoquic_callback_close: /* Received connection close */
1727+
case picoquic_callback_application_close: /* Received application close */
1728+
/* Remove the connection from the context, and then delete it */
1729+
picoquic_set_callback(cnx, NULL, NULL);
1730+
break;
1731+
case picoquic_callback_version_negotiation:
1732+
/* The server should never receive a version negotiation response */
1733+
break;
1734+
case picoquic_callback_stream_gap:
1735+
/* This callback is never used. */
1736+
break;
1737+
case picoquic_callback_almost_ready:
1738+
case picoquic_callback_ready:
1739+
break;
1740+
case picoquic_callback_datagram_acked:
1741+
/* Ack for packet carrying datagram-object received from peer */
1742+
case picoquic_callback_datagram_lost:
1743+
/* Packet carrying datagram-object probably lost */
1744+
case picoquic_callback_datagram_spurious:
1745+
/* Packet carrying datagram-object was not really lost */
1746+
break;
1747+
case picoquic_callback_pacing_changed:
1748+
/* Notification of rate change from congestion controller */
1749+
break;
1750+
default:
1751+
/* unexpected */
1752+
break;
1753+
}
1754+
}
1755+
1756+
if (ret != 0) {
1757+
DBG_PRINTF("Reset loop callback returns %d, event %d", ret, fin_or_event);
1758+
}
1759+
1760+
return ret;
1761+
}
1762+
1763+
int reset_loop_test()
1764+
{
1765+
uint64_t simulated_time = 0;
1766+
uint64_t loss_mask = 0;
1767+
uint64_t timeout;
1768+
uint64_t test_stream = 8;
1769+
reset_loop_callback_t cb = { 0 };
1770+
picoquic_stream_head_t* stream = NULL;
1771+
picoquic_test_tls_api_ctx_t* test_ctx = NULL;
1772+
int ret = tls_api_init_ctx(&test_ctx, PICOQUIC_INTERNAL_TEST_VERSION_1,
1773+
PICOQUIC_TEST_SNI, PICOQUIC_TEST_ALPN, &simulated_time, NULL, NULL, 0, 0, 0);
1774+
1775+
if (ret == 0) {
1776+
uint8_t bogus_data[4] = { 0 };
1777+
picoquic_set_default_callback(test_ctx->qserver, reset_loop_callback, &cb);
1778+
picoquic_set_callback(test_ctx->cnx_client, reset_loop_callback, &cb);
1779+
picoquic_start_client_cnx(test_ctx->cnx_client);
1780+
picoquic_add_to_stream(test_ctx->cnx_client, 4, bogus_data, 4, 0);
1781+
picoquic_add_to_stream(test_ctx->cnx_client, 8, bogus_data, 4, 0);
1782+
}
1783+
1784+
if (ret == 0) {
1785+
ret = tls_api_connection_loop(test_ctx, &loss_mask, 0, &simulated_time);
1786+
}
1787+
1788+
/* Prepare to send data */
1789+
if (ret == 0) {
1790+
picoquic_mark_active_stream(test_ctx->cnx_client, 4, 1, &cb);
1791+
picoquic_mark_active_stream(test_ctx->cnx_client, 8, 1, &cb);
1792+
/* set priorities */
1793+
if (ret == 0) {
1794+
ret = picoquic_set_stream_priority(test_ctx->cnx_client, 4, 8);
1795+
}
1796+
if (ret == 0) {
1797+
ret = picoquic_set_stream_priority(test_ctx->cnx_client, 8, 8);
1798+
}
1799+
}
1800+
1801+
/* Perform a few rounds of sending loop, but not enough to send all the data */
1802+
if (ret == 0) {
1803+
timeout = simulated_time + 100000;
1804+
ret = tls_api_wait_for_timeout(test_ctx, &simulated_time, timeout);
1805+
}
1806+
1807+
/* trigger a reset of tst stream */
1808+
if (ret == 0) {
1809+
ret = picoquic_reset_stream(test_ctx->cnx_server, test_stream, 0);
1810+
}
1811+
1812+
/* set priorities */
1813+
if (ret == 0) {
1814+
ret = picoquic_set_stream_priority(test_ctx->cnx_client, 4, 9);
1815+
}
1816+
if (ret == 0) {
1817+
ret = picoquic_set_stream_priority(test_ctx->cnx_client, 8, 7);
1818+
}
1819+
if (ret == 0) {
1820+
ret = picoquic_set_stream_priority(test_ctx->cnx_server, 4, 9);
1821+
}
1822+
if (ret == 0) {
1823+
ret = picoquic_set_stream_priority(test_ctx->cnx_server, 8, 7);
1824+
}
1825+
1826+
/* make sure that reset is sent */
1827+
if (ret == 0) {
1828+
timeout = simulated_time + 100000;
1829+
for (int i = 0; ret == 0 && i < 16; i++) {
1830+
int was_active = 0;
1831+
ret = tls_api_one_sim_round(test_ctx, &simulated_time, timeout, &was_active);
1832+
if (ret == 0) {
1833+
stream = picoquic_find_stream(test_ctx->cnx_server, test_stream);
1834+
if (stream == NULL) {
1835+
ret = -1;
1836+
break;
1837+
}
1838+
else if (stream->reset_sent) {
1839+
break;
1840+
}
1841+
}
1842+
}
1843+
}
1844+
if (ret == 0 && (stream == NULL || !stream->reset_sent)) {
1845+
DBG_PRINTF("Could not reset stream %" PRIu64, test_stream);
1846+
ret = -1;
1847+
}
1848+
1849+
if (ret == 0) {
1850+
/* add data to the stream to elicit some bad behavior */
1851+
uint8_t bogus_data[4] = { 1, 2, 3, 4 };
1852+
if (picoquic_add_to_stream(test_ctx->cnx_server, test_stream, bogus_data, 4, 1) == 0) {
1853+
DBG_PRINTF("Adding on stream %" PRIu64 " after reset should be forbidden", test_stream);
1854+
ret = -1;
1855+
}
1856+
}
1857+
1858+
if (ret == 0) {
1859+
/* add data to the stream to elicit some bad behavior */
1860+
uint8_t bogus_context[4] = { 0, 0, 0, 0 };
1861+
if (picoquic_mark_active_stream(test_ctx->cnx_server, test_stream, 1, bogus_context) == 0) {
1862+
DBG_PRINTF("Marking stream %" PRIu64 " active after reset should be forbidden", test_stream);
1863+
ret = -1;
1864+
}
1865+
}
1866+
1867+
/* Do a loop to check the behavior */
1868+
if (ret == 0) {
1869+
timeout = simulated_time + 2000000;
1870+
ret = tls_api_wait_for_timeout(test_ctx, &simulated_time, timeout);
1871+
}
1872+
1873+
if (test_ctx != NULL) {
1874+
tls_api_delete_ctx(test_ctx);
1875+
test_ctx = NULL;
1876+
}
1877+
16441878
return ret;
16451879
}

picoquictest/picoquictest.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ int reset_extra_stop_test();
265265
int reset_need_max_test();
266266
int reset_need_reset_test();
267267
int reset_need_stop_test();
268+
int reset_loop_test();
268269
int initial_pto_test();
269270
int initial_pto_srv_test();
270271
int ready_to_send_test();

0 commit comments

Comments
 (0)