Skip to content

Commit

Permalink
Merge branch 'private-octopus:master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
hfstco authored Sep 7, 2024
2 parents 67c63cd + f20ad61 commit c227dc7
Show file tree
Hide file tree
Showing 18 changed files with 1,043 additions and 55 deletions.
3 changes: 2 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ else()
endif()

project(picoquic
VERSION 1.1.23.1
VERSION 1.1.24.0
DESCRIPTION "picoquic library"
LANGUAGES C CXX)

Expand Down Expand Up @@ -426,6 +426,7 @@ if(BUILD_TESTING AND picoquic_BUILD_TESTS)

add_executable(picoquic_sample
sample/sample.c
sample/sample_background.c
sample/sample_client.c
sample/sample_server.c)
target_link_libraries(picoquic_sample PRIVATE picoquic-log picoquic-core)
Expand Down
7 changes: 7 additions & 0 deletions UnitTest1/unittest1.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1869,6 +1869,13 @@ namespace UnitTest1
Assert::AreEqual(ret, 0);
}

TEST_METHOD(crypto_hs_offset)
{
int ret = crypto_hs_offset_test();

Assert::AreEqual(ret, 0);
}

TEST_METHOD(cubic)
{
int ret = cubic_test();
Expand Down
87 changes: 87 additions & 0 deletions doc/managing-memory-copies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Managing Memory Copies

Picoquic is designed to limit the number of memory copies and
related memory allocations when sending and receiving data.

## Memory copies when sending data

Application data is sent as either data frames or stream frames.
The sending process will format packets, send them, and
also keep them in memory for possible repetitions.

The typical flow would be:

1. The application makes the data available, either by calling the API `picoquic_add_to_stream`
which allocates memory and keep a copy, or by signalling that data is available on a stream
using `picoquic_mark_active_stream`, or by signalling that datagrams are ready to send
using `picoquic_mark_datagram_ready`.
2. The socket loop wakes up and calls the API `picoquic_prepare_next_packet_ex`
to ask the stack to prepare the next packet in the QUIC context. It provides
a data buffer in which the packet will be copied before being sent.
3. The QUIC context selects the next avalaible connection, and will
call the function `picoquic_prepare_packet_ex` to prepare the next packet for that connection.
4. That function allocates a packet container of type `picoquic_packet_t`, which will
contain the formatted packet.
5. The formatting happens in the functions called from there, which will copy
a set of QUIC frames in the packet, including datagram or stream data frames.
6. The content of the stream frames is either copied from data previously queued
using the `picoquic_add_to_stream` API, or copied directly from the application
memory using a callback `picoquic_callback_prepare_to_send` for streams, or
`picoquic_callback_prepare_datagram` for datagrams.
7. When the packet is ready, the stack encrypt it. The clear text in the `packet` structure
is left untouched, and the encrypted bytes are copied into the "send" buffer
passed in `picoquic_prepare_next_packet_ex` call. That buffer will be sent to
the peer through a socket call.
8. The clear text packet is attached to the retransmission queue, waiting for acknowledgement.

### Handling packet losses

In most cases, the clear text packet will be detached from the retransmission queue when the
acknowledgement is received. In some cases, the acknowledgement is not received, and
the data will have to be resent. For stream data, this will involve copying the stream data
from the old copy into a new packet.

### Recycling packets

When packets are acknowledged, the `picoquic_packet_t` element is "recycled". It is added
to a queue of empty packets managed in the Quic context, unless that queue has already
reached its maximum size, in which case the packets are freed. New packet structures are
only allocated when no recycled packet is available.

## Memory copies when receiving data.

The picoquic stack receives encrypted packets from the network, and delivers
decrypted data to the application.

The typical flow would be:

1. A new network packet is received from the socket, and is passed to the
stack through a call to `picoquic_incoming_packet`.
2. The stack allocates a data node container of type `picoquic_stream_data_node_t`.
3. The header is analyzed and the packet is decrypted, with the clear text data
is stored into the data node.
4. The decrypted packet is parsed, and the frames that it contained are processed.
The content of datagram frames is passed to the application through the
callback `picoquic_callback_datagram`. The processing of stream data frames varies,
because stream data must be delivered in order.
5. If the stream data is arriving in order, the data is delivered immediately,
through the callback `picoquic_callback_stream_data` or `picoquic_callback_stream_fin`.
6. If the data cannot be delivered immediately, it needs to be kept in memory
until the holes in the stream have been filled. The processing varies
depending on the number of frames in the packet.
7. If the stream data frame is the last frame in the packet, the data node
structured in queued to the stream. Else, a new data node is allocated,
the stream data frame is copied to it, and that new data node is queued
to the stream.
8. At the end of this process, if the data node was not queued to a stream it
is recycled.

### Managing out of order delivery

When stream data frames arrive out of order, one data node is queued for
each incoming frame. When a hole filling frame arrives, the data is delivered
through the callback `picoquic_callback_stream_data` and the data node
is recycled. The data nodes will also be recycled if the stream is reset
or the connection is closed.


31 changes: 31 additions & 0 deletions doc/sample.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# The picoquic code sample

The picoquic sample code in the "sample" folder builds a single executable, `picoquic_sample`.
This is a simple file transfer program that can be
instantiated in client or server mode. The program can be instantiated
as either:
~~~
picoquic_sample client server_name port folder *queried_file
~~~
or:
~~~
picoquic_sample server port cert_file private_key_file folder
~~~
The client opens a quic connection to the server, and then fetches
the listed files. The client opens one bidir client stream for each
file, writes the requested file name in the stream data, and then
marks the stream as finished. The server reads the file name, and
if the named file is present in the server's folder, sends the file
content on the same stream, marking the fin of the stream when all
bytes are sent. If the file is not available, the server resets the
stream. If the client receives the file, it writes its content in the
client's folder.

Server or client close the connection if it remains inactive for
more than 10 seconds.

The purpose of the sample is not to provide example of using the
picoquic API to build a simple application. The current code is
limited: it does not use the "configuration" API to set parameters
of the picoquic endpoint, and it does not demonstrate how to run
picoquic in a background thread.
21 changes: 15 additions & 6 deletions picoquic/frames.c
Original file line number Diff line number Diff line change
Expand Up @@ -2262,14 +2262,23 @@ const uint8_t* picoquic_decode_crypto_hs_frame(picoquic_cnx_t* cnx, const uint8_
picoquic_connection_error(cnx, PICOQUIC_TRANSPORT_FRAME_FORMAT_ERROR, picoquic_frame_type_crypto_hs);
} else {
picoquic_stream_head_t* stream = &cnx->tls_stream[epoch];
int new_data_available;
int ret = picoquic_queue_network_input(cnx->quic, &stream->stream_data_tree, stream->consumed_offset,
offset, data_bytes, (size_t)data_length, picoquic_is_last_stream_frame(bytes+data_length, bytes_max),
received_data, &new_data_available);
if (ret != 0) {
picoquic_connection_error(cnx, (int64_t)ret, picoquic_frame_type_crypto_hs);

if (stream->consumed_offset < offset &&
stream->consumed_offset + PICOQUIC_MAX_CRYPTO_BUFFER_GAP < offset + data_length) {
picoquic_connection_error(cnx, PICOQUIC_TRANSPORT_CRYPTO_BUFFER_EXCEEDED, picoquic_frame_type_crypto_hs);
bytes = NULL;
}
else {
int new_data_available;
int ret = picoquic_queue_network_input(cnx->quic, &stream->stream_data_tree, stream->consumed_offset,
offset, data_bytes, (size_t)data_length, picoquic_is_last_stream_frame(bytes + data_length, bytes_max),
received_data, &new_data_available);

if (ret != 0) {
picoquic_connection_error(cnx, (int64_t)ret, picoquic_frame_type_crypto_hs);
bytes = NULL;
}
}
}

return bytes;
Expand Down
3 changes: 2 additions & 1 deletion picoquic/picoquic.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
extern "C" {
#endif

#define PICOQUIC_VERSION "1.1.23.1"
#define PICOQUIC_VERSION "1.1.24.0"
#define PICOQUIC_ERROR_CLASS 0x400
#define PICOQUIC_ERROR_DUPLICATE (PICOQUIC_ERROR_CLASS + 1)
#define PICOQUIC_ERROR_AEAD_CHECK (PICOQUIC_ERROR_CLASS + 3)
Expand Down Expand Up @@ -137,6 +137,7 @@ extern "C" {
#define PICOQUIC_RESET_SECRET_SIZE 16
#define PICOQUIC_RESET_PACKET_PAD_SIZE 23
#define PICOQUIC_RESET_PACKET_MIN_SIZE (PICOQUIC_RESET_PACKET_PAD_SIZE + PICOQUIC_RESET_SECRET_SIZE)
#define PICOQUIC_MAX_CRYPTO_BUFFER_GAP 16384

#define PICOQUIC_LOG_PACKET_MAX_SEQUENCE 100

Expand Down
7 changes: 4 additions & 3 deletions picoquic/sender.c
Original file line number Diff line number Diff line change
Expand Up @@ -2002,7 +2002,8 @@ int picoquic_prepare_packet_client_init(picoquic_cnx_t* cnx, picoquic_path_t * p
*is_initial_sent = (packet->ptype == picoquic_packet_initial);
}
else if (ret == 0 && is_cleartext_mode && tls_ready == 0
&& cnx->first_misc_frame == NULL && !cnx->ack_ctx[pc].act[0].ack_needed && !force_handshake_padding) {
&& picoquic_find_first_misc_frame(cnx, pc) == NULL
&& !cnx->ack_ctx[pc].act[0].ack_needed && !force_handshake_padding) {
/* when in a clear text mode, only send packets if there is
* actually something to send, or resend. */

Expand All @@ -2026,7 +2027,7 @@ int picoquic_prepare_packet_client_init(picoquic_cnx_t* cnx, picoquic_path_t * p
if ((tls_ready == 0 || path_x->cwin <= path_x->bytes_in_transit || cnx->quic->cwin_max <= path_x->bytes_in_transit)
&& (cnx->cnx_state == picoquic_state_client_almost_ready
|| picoquic_is_ack_needed(cnx, current_time, next_wake_time, pc, 0) == 0)
&& cnx->first_misc_frame == NULL && !force_handshake_padding) {
&& picoquic_find_first_misc_frame(cnx, pc) == NULL && !force_handshake_padding) {
length = 0;
}
else {
Expand Down Expand Up @@ -3169,7 +3170,7 @@ int picoquic_prepare_packet_almost_ready(picoquic_cnx_t* cnx, picoquic_path_t* p
* several RTT.
*/
if (length <= header_length && cnx->client_mode &&
cnx->first_misc_frame == NULL &&
picoquic_find_first_misc_frame(cnx, pc) == NULL &&
(length = picoquic_retransmit_needed(cnx, pc, path_x, current_time, next_wake_time, packet,
send_buffer_min_max, &header_length)) > 0) {
/* Check whether it makes sense to add an ACK at the end of the retransmission */
Expand Down
3 changes: 2 additions & 1 deletion picoquic_t/picoquic_t.c
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ static const picoquic_test_def_t test_table[] = {
{ "ready_to_skip", ready_to_skip_test },
{ "ready_to_zfin", ready_to_zfin_test },
{ "ready_to_zero", ready_to_zero_test },
{ "crypto_hs_offset", crypto_hs_offset_test },
{ "cubic", cubic_test },
{ "cubic_jitter", cubic_jitter_test },
{ "fastcc", fastcc_test },
Expand Down Expand Up @@ -532,8 +533,8 @@ int usage(char const * argv0)
fprintf(stderr, " -C ccc Use nnn stress clients in parallel.\n");
fprintf(stderr, " -c nnn ccc Run connection stress for nnn minutes, ccc connections.\n");
fprintf(stderr, " -d ppp uuu dir Run connection ddoss for ppp packets, uuu usec intervals,\n");
fprintf(stderr, " -F nnn Run the corrupt file fuzzer nnn times,\n");
fprintf(stderr, " logs in dir. No logs if dir=\"-\"");
fprintf(stderr, " -F nnn Run the corrupt file fuzzer nnn times,\n");
fprintf(stderr, " -n Disable debug prints.\n");
fprintf(stderr, " -r Retry failed tests with debug print enabled.\n");
fprintf(stderr, " -h Print this help message\n");
Expand Down
79 changes: 79 additions & 0 deletions picoquictest/edge_cases.c
Original file line number Diff line number Diff line change
Expand Up @@ -1393,5 +1393,84 @@ int initial_pto_test()
test_ctx = NULL;
}

return ret;
}

/* Test of out of order crypto packets.
* We test that by injecting a crypto handshake frame with an
* offset of 64K in the crypto context. There will be three tests,
* for initial, handshake and 1 rtt contexts.
*/


int crypto_hs_offset_test_one(picoquic_packet_context_enum pc)
{
int ret = 0;
picoquic_test_tls_api_ctx_t* test_ctx = NULL;
size_t length = 0;
uint64_t simulated_time = 0;
picoquic_connection_id_t initial_cid = { { 0xC0, 0xFF, 0x5E, 0x40, 0, 0, 0, 0}, 8 };
uint8_t bad_crypto_hs[] = { picoquic_frame_type_crypto_hs, 0x80, 0x01, 0, 0, 4, 1, 2, 3, 4 };

initial_cid.id[4] = (uint8_t)pc;

/* Create a client. */
ret = tls_api_init_ctx_ex(&test_ctx, PICOQUIC_INTERNAL_TEST_VERSION_1,
PICOQUIC_TEST_SNI, PICOQUIC_TEST_ALPN, &simulated_time, NULL, NULL, 0, 1, 0, &initial_cid);
if (ret != 0) {
DBG_PRINTF("Cannot initialize context, ret = 0x%x", ret);
}
else {
/* Set the binlog */
picoquic_set_binlog(test_ctx->qserver, ".");
/* start the client connection */
ret = picoquic_start_client_cnx(test_ctx->cnx_client);
}

if (ret == 0) {
/* Inject the made up packet */
ret = picoquic_queue_misc_frame(test_ctx->cnx_client, bad_crypto_hs, sizeof(bad_crypto_hs), 1, pc);
}


/* Try to establish the connection */
if (ret == 0) {
if (wait_client_connection_ready(test_ctx, &simulated_time) == 0) {
if (test_ctx->cnx_server != NULL) {
if (test_ctx->cnx_server->cnx_state != picoquic_state_handshake_failure &&
test_ctx->cnx_server->cnx_state < picoquic_state_disconnecting) {
/* Should wait for ready state */
DBG_PRINTF("Unexpected success, pc=%d\n", pc);
ret = -1;
}
}
}
}

if (ret == 0 && test_ctx->cnx_client->remote_error != PICOQUIC_TRANSPORT_CRYPTO_BUFFER_EXCEEDED) {
DBG_PRINTF("For pc=%d, expected error 0x%x, got 0x%x\n", pc,
PICOQUIC_TRANSPORT_CRYPTO_BUFFER_EXCEEDED, test_ctx->cnx_client->remote_error);
ret = -1;
}

if (test_ctx != NULL) {
tls_api_delete_ctx(test_ctx);
test_ctx = NULL;
}

return ret;
}

int crypto_hs_offset_test()
{
picoquic_packet_context_enum pc[] = { picoquic_packet_context_initial,
picoquic_packet_context_handshake, picoquic_packet_context_application };
size_t nb_pc = sizeof(pc) / sizeof(picoquic_packet_context_enum);
int ret = 0;

for (size_t i = 0; i < nb_pc && ret == 0; i++) {
ret = crypto_hs_offset_test_one(pc[i]);
}

return ret;
}
1 change: 1 addition & 0 deletions picoquictest/picoquictest.h
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ int ready_to_send_test();
int ready_to_skip_test();
int ready_to_zero_test();
int ready_to_zfin_test();
int crypto_hs_offset_test();
int cubic_test();
int cubic_jitter_test();
int satellite_basic_test();
Expand Down
2 changes: 1 addition & 1 deletion picoquictest/stresstest.c
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ static picosplay_node_t *stress_client_node_create(void * value)

void * stress_client_node_value(picosplay_node_t * node)
{
return (void*)((char*)node - offsetof(struct st_picoquic_stress_client_t, client_node));
return (node == NULL)?NULL:(void*)((char*)node - offsetof(struct st_picoquic_stress_client_t, client_node));
}

static void stress_client_node_delete(void * tree, picosplay_node_t * node)
Expand Down
4 changes: 4 additions & 0 deletions sample/picoquic_sample.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,13 @@ extern "C" {
#define PICOQUIC_SAMPLE_CLIENT_QLOG_DIR ".";
#define PICOQUIC_SAMPLE_SERVER_QLOG_DIR ".";

#define PICOQUIC_SAMPLE_BACKGROUND_MAX_FILES 32

int picoquic_sample_client(char const* server_name, int server_port, char const* default_dir,
int nb_files, char const** file_names);

int picoquic_sample_background(char const* server_name, int server_port, char const* default_dir);

int picoquic_sample_server(int server_port, const char* pem_cert, const char* pem_key, const char * default_dir);

#ifdef __cplusplus
Expand Down
14 changes: 13 additions & 1 deletion sample/sample.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ static void usage(char const * sample_name)
{
fprintf(stderr, "Usage:\n");
fprintf(stderr, " %s client server_name port folder *queried_file\n", sample_name);
fprintf(stderr, " %s background server_name port folder\n", sample_name);
fprintf(stderr, "or :\n");
fprintf(stderr, " %s server port cert_file private_key_file folder\n", sample_name);
exit(1);
Expand All @@ -65,6 +66,7 @@ int get_port(char const* sample_name, char const* port_arg)

return server_port;
}

int main(int argc, char** argv)
{
int exit_code = 0;
Expand All @@ -88,8 +90,18 @@ int main(int argc, char** argv)
exit_code = picoquic_sample_client(argv[2], server_port, argv[4], nb_files, file_names);
}
}
else if (strcmp(argv[1], "background") == 0) {
if (argc != 5) {
usage(argv[0]);
}
else {
int server_port = get_port(argv[0], argv[3]);

exit_code = picoquic_sample_background(argv[2], server_port, argv[4]);
}
}
else if (strcmp(argv[1], "server") == 0) {
if (argc < 5) {
if (argc != 6) {
usage(argv[0]);
}
else {
Expand Down
Loading

0 comments on commit c227dc7

Please sign in to comment.