Skip to content

Commit

Permalink
Improve the QUIC_RSTREAM implementation
Browse files Browse the repository at this point in the history
Add API calls to avoid copying data when reading
These are ossl_quic_rstream_get_record() and
ossl_quic_rstream_release_record().

Add side storage for the stream frame data.
When there are too many packets referenced by the
receiving stream the function ossl_quic_rstream_move_to_rbuf()
can be called to move the data to a ring buffer.

Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Hugo Landau <hlandau@openssl.org>
(Merged from #19794)
  • Loading branch information
t8m committed Mar 6, 2023
1 parent ac21c17 commit 2113ea5
Show file tree
Hide file tree
Showing 8 changed files with 652 additions and 195 deletions.
85 changes: 85 additions & 0 deletions include/internal/quic_sf_list.h
Expand Up @@ -47,18 +47,103 @@ typedef struct sframe_list_st {
size_t num_frames;
/* Offset of data not yet dropped */
uint64_t offset;
/* Is head locked ? */
int head_locked;
} SFRAME_LIST;

/*
* Initializes the stream frame list fl.
*/
void ossl_sframe_list_init(SFRAME_LIST *fl);

/*
* Destroys the stream frame list fl releasing any data
* still present inside it.
*/
void ossl_sframe_list_destroy(SFRAME_LIST *fl);

/*
* Insert a stream frame data into the list.
* The data covers an offset range (range.start is inclusive,
* range.end is exclusive).
* fin should be set if this is the final frame of the stream.
* Returns an error if a frame cannot be inserted - due to
* STREAM_FRAME allocation error, or in case of erroneous
* fin flag (this is an ossl_assert() check so a caller must
* check it on its own too).
*/
int ossl_sframe_list_insert(SFRAME_LIST *fl, UINT_RANGE *range,
OSSL_QRX_PKT *pkt,
const unsigned char *data, int fin);

/*
* Iterator to peek at the contiguous frames at the beginning
* of the frame list fl.
* The *data covers an offset range (range.start is inclusive,
* range.end is exclusive).
* *fin is set if this is the final frame of the stream.
* Opaque iterator *iter can be used to peek at the subsequent
* frame if there is any without any gap before it.
* Returns 1 on success.
* Returns 0 if there is no further contiguous frame. In that
* case *fin is set, if the end of the stream is reached.
*/
int ossl_sframe_list_peek(const SFRAME_LIST *fl, void **iter,
UINT_RANGE *range, const unsigned char **data,
int *fin);

/*
* Drop all frames up to the offset limit.
* Also unlocks the head frame if locked.
* Returns 1 on success.
* Returns 0 when trying to drop frames at offsets that were not
* received yet. (ossl_assert() is used to check, so this is an invalid call.)
*/
int ossl_sframe_list_drop_frames(SFRAME_LIST *fl, uint64_t limit);

/*
* Locks and returns the head frame of fl if it is readable - read offset is
* at the beginning or middle of the frame.
* range is set to encompass the not yet read part of the head frame,
* data pointer is set to appropriate offset within the frame if the read
* offset points in the middle of the frame,
* fin is set to 1 if the head frame is also the tail frame.
* Returns 1 on success, 0 if there is no readable data or the head
* frame is already locked.
*/
int ossl_sframe_list_lock_head(SFRAME_LIST *fl, UINT_RANGE *range,
const unsigned char **data,
int *fin);

/*
* Just returns whether the head frame is locked by previous
* ossl_sframe_list_lock_head() call.
*/
int ossl_sframe_list_is_head_locked(SFRAME_LIST *fl);

/*
* Callback function type to write stream frame data to some
* side storage before the packet containing the frame data
* is released.
* It should return 1 on success or 0 if there is not enough
* space available in the side storage.
*/
typedef int (sframe_list_write_at_cb)(uint64_t logical_offset,
const unsigned char *buf,
size_t buf_len,
void *cb_arg);

/*
* Move the frame data in all the stream frames in the list fl
* from the packets to the side storage using the write_at_cb
* callback.
* Returns 1 if all the calls to the callback return 1.
* If the callback returns 0, the function stops processing further
* frames and returns 0.
*/
int ossl_sframe_list_move_data(SFRAME_LIST *fl,
sframe_list_write_at_cb *write_at_cb,
void *cb_arg);
# endif

#endif
53 changes: 52 additions & 1 deletion include/internal/quic_stream.h
Expand Up @@ -308,9 +308,11 @@ typedef struct quic_rstream_st QUIC_RSTREAM;
* controller and statistics module. They can be NULL for unit testing.
* If they are non-NULL, the `rxfc` is called when receive stream data
* is read by application. `statm` is queried for current rtt.
* `rbuf_size` is the initial size of the ring buffer to be used
* when ossl_quic_rstream_move_to_rbuf() is called.
*/
QUIC_RSTREAM *ossl_quic_rstream_new(QUIC_RXFC *rxfc,
OSSL_STATM *statm);
OSSL_STATM *statm, size_t rbuf_size);

/*
* Frees a QUIC_RSTREAM and any associated storage.
Expand Down Expand Up @@ -357,6 +359,55 @@ int ossl_quic_rstream_peek(QUIC_RSTREAM *qrs, unsigned char *buf, size_t size,
*/
int ossl_quic_rstream_available(QUIC_RSTREAM *qrs, size_t *avail, int *fin);

/*
* Sets *record to the beginning of the first readable stream data chunk and
* *reclen to the size of the chunk. *fin is set to 1 if the end of the
* chunk is the last of the stream data chunks.
* If there is no record available *record is set to NULL and *rec_len to 0;
* ossl_quic_rstream_release_record() should not be called in that case.
* Returns 1 on success (including calls if no record is available, or
* after end of the stream - in that case *fin will be set to 1 and
* *rec_len to 0), 0 on error.
* It is an error to call ossl_quic_rstream_get_record() multiple times
* without calling ossl_quic_rstream_release_record() in between.
*/
int ossl_quic_rstream_get_record(QUIC_RSTREAM *qrs,
const unsigned char **record, size_t *rec_len,
int *fin);

/*
* Releases (possibly partially) the record returned by
* previous ossl_quic_rstream_get_record() call.
* read_len between previously returned *rec_len and SIZE_MAX indicates
* release of the whole record. Otherwise only part of the record is
* released. The remaining part of the record is unlocked, another
* call to ossl_quic_rstream_get_record() is needed to obtain further
* stream data.
* Returns 1 on success, 0 on error.
* It is an error to call ossl_quic_rstream_release_record() multiple
* times without calling ossl_quic_rstream_get_record() in between.
*/
int ossl_quic_rstream_release_record(QUIC_RSTREAM *qrs, size_t read_len);

/*
* Moves received frame data from decrypted packets to ring buffer.
* This should be called when there are too many decrypted packets allocated.
* Returns 1 on success, 0 when it was not possible to release all
* referenced packets due to an insufficient size of the ring buffer.
* Exception is the packet from the record returned previously by
* ossl_quic_rstream_get_record() - that one will be always skipped.
*/
int ossl_quic_rstream_move_to_rbuf(QUIC_RSTREAM *qrs);

/*
* Resizes the internal ring buffer to a new `rbuf_size` size.
* Returns 1 on success, 0 on error.
* Possible error conditions are an allocation failure, trying to resize
* the ring buffer when ossl_quic_rstream_get_record() was called and
* not yet released, or trying to resize the ring buffer to a smaller size
* than currently occupied.
*/
int ossl_quic_rstream_resize_rbuf(QUIC_RSTREAM *qrs, size_t rbuf_size);
# endif

#endif

0 comments on commit 2113ea5

Please sign in to comment.