diff --git a/.github/workflows/stream_list.yml b/.github/workflows/stream_list.yml new file mode 100644 index 000000000..7da047b6b --- /dev/null +++ b/.github/workflows/stream_list.yml @@ -0,0 +1,41 @@ +name: Stream List CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + + runs-on: ${{ matrix.os }} + + env: + CTEST_OUTPUT_ON_FAILURE: 1 + + steps: + + - uses: actions/checkout@v2 + + - name: Create Build Environment + run: cmake -E make_directory ${{github.workspace}}/build + + - name: Configure CMake + working-directory: ${{github.workspace}}/build + shell: bash + run: cmake $GITHUB_WORKSPACE -DBUILD_WITH_SANITIZERS=TRUE -DCMAKE_C_FLAGS:STRING="-DSRTP_NO_STREAM_LIST -DSRTP_USE_TEST_STREAM_LIST" + + - name: Build + working-directory: ${{github.workspace}}/build + shell: bash + run: cmake --build . -t srtp_driver + + - name: Test + working-directory: ${{github.workspace}}/build + shell: bash + run: ctest -R srtp_driver diff --git a/include/srtp_priv.h b/include/srtp_priv.h index 48dc65c7d..8e7848989 100644 --- a/include/srtp_priv.h +++ b/include/srtp_priv.h @@ -66,6 +66,7 @@ extern "C" { typedef struct srtp_stream_ctx_t_ srtp_stream_ctx_t; typedef srtp_stream_ctx_t *srtp_stream_t; +typedef struct srtp_stream_list_ctx_t_ *srtp_stream_list_t; /* * the following declarations are libSRTP internal functions @@ -96,12 +97,6 @@ srtp_err_status_t srtp_steam_init_all_master_keys( srtp_master_key_t **keys, const unsigned int max_master_keys); -/* - * srtp_stream_init(s, p) initializes the srtp_stream_t s to - * use the policy at the location p - */ -srtp_err_status_t srtp_stream_init(srtp_stream_t srtp, const srtp_policy_t *p); - /* * libsrtp internal datatypes */ @@ -149,14 +144,21 @@ typedef struct srtp_stream_ctx_t_ { int *enc_xtn_hdr; int enc_xtn_hdr_count; uint32_t pending_roc; - struct srtp_stream_ctx_t_ *next; /* linked list of streams */ + /* + The next and prev pointers are here to allow for a stream list to be + implemented as an intrusive doubly-linked list (the former being the + default). Other stream list implementations can ignore these fields or use + them for some other purpose specific to the stream list implementation. + */ + struct srtp_stream_ctx_t_ *next; + struct srtp_stream_ctx_t_ *prev; } strp_stream_ctx_t_; /* * an srtp_ctx_t holds a stream list and a service description */ typedef struct srtp_ctx_t_ { - struct srtp_stream_ctx_t_ *stream_list; /* linked list of streams */ + srtp_stream_list_t stream_list; /* linked list of streams */ struct srtp_stream_ctx_t_ *stream_template; /* act as template for other */ /* streams */ void *user_data; /* user custom data */ diff --git a/include/stream_list_priv.h b/include/stream_list_priv.h new file mode 100644 index 000000000..f49cd551d --- /dev/null +++ b/include/stream_list_priv.h @@ -0,0 +1,122 @@ +/* + * stream_list_priv.h + * + * list of SRTP streams, keyed by SSRC + * + * Alba Mendez + */ +/* + * + * Copyright (c) 2001-2017, Cisco Systems, Inc. + * Copyright (c) 2022, Dolby Laboratories, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * Neither the name of the Cisco Systems, Inc. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef SRTP_STREAM_LIST_PRIV_H +#define SRTP_STREAM_LIST_PRIV_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * srtp_stream_list_t holds a list of srtp_stream_t, each identified + * by their SSRC. + * + * the API was extracted to allow downstreams to override its + * implementation by defining the `SRTP_NO_STREAM_LIST` preprocessor + * directive, which removes the default implementation of these + * functions. if this is done, the `next` & `prev` fields are free for + * the implementation to use. + * + * this is still an internal interface; there is no stability + * guarantee--downstreams should watch this file for changes in + * signatures or semantics. + */ + +/** + * allocate and initialize a stream list instance + */ +srtp_err_status_t srtp_stream_list_alloc(srtp_stream_list_t *list_ptr); + +/** + * deallocate a stream list instance + * + * the list must be empty or else an error is returned. + */ +srtp_err_status_t srtp_stream_list_dealloc(srtp_stream_list_t list); + +/** + * insert a stream into the list + * + * returns srtp_err_status_alloc_fail if insertion failed due to unavailable + * capacity in the list. if operation succeeds, srtp_err_status_ok is returned + * + * if another stream with the same SSRC already exists in the list, + * behavior is undefined. if the SSRC field is mutated while the + * stream is inserted, further operations have undefined behavior + */ +srtp_err_status_t srtp_stream_list_insert(srtp_stream_list_t list, + srtp_stream_t stream); + +/* + * look up the stream corresponding to the specified SSRC and return it. + * if no such SSRC is found, NULL is returned. + */ +srtp_stream_t srtp_stream_list_get(srtp_stream_list_t list, uint32_t ssrc); + +/** + * remove the stream from the list. + * + * The stream to be removed is referenced "by value", i.e., by the pointer to be + * removed from the list. This pointer is obtained using `srtp_stream_list_get` + * or as callback parameter in `srtp_stream_list_for_each`. + */ +void srtp_stream_list_remove(srtp_stream_list_t list, srtp_stream_t stream); + +/** + * iterate through all stored streams. while iterating, it is allowed to delete + * the current element; any other mutation to the list is undefined behavior. + * returning non-zero from callback aborts the iteration. + */ +void srtp_stream_list_for_each(srtp_stream_list_t list, + int (*callback)(srtp_stream_t, void *), + void *data); + +#ifdef __cplusplus +} +#endif + +#endif /* SRTP_STREAM_LIST_PRIV_H */ diff --git a/srtp/srtp.c b/srtp/srtp.c index 8483210a6..933f58f96 100644 --- a/srtp/srtp.c +++ b/srtp/srtp.c @@ -46,6 +46,7 @@ #include "config.h" #include "srtp_priv.h" +#include "stream_list_priv.h" #include "crypto_types.h" #include "err.h" #include "alloc.h" /* for srtp_crypto_alloc() */ @@ -279,6 +280,47 @@ static srtp_err_status_t srtp_stream_dealloc( return srtp_err_status_ok; } +/* try to insert stream in list or deallocate it */ +static srtp_err_status_t srtp_insert_or_dealloc_stream(srtp_stream_list_t list, + srtp_stream_t stream, + srtp_stream_t template) +{ + srtp_err_status_t status = srtp_stream_list_insert(list, stream); + /* on failure, ownership wasn't transferred and we need to deallocate */ + if (status) { + srtp_stream_dealloc(stream, template); + } + return status; +} + +struct remove_and_dealloc_streams_data { + srtp_err_status_t status; + srtp_stream_list_t list; + srtp_stream_t template; +}; + +static int remove_and_dealloc_streams_cb(srtp_stream_t stream, void *data) +{ + struct remove_and_dealloc_streams_data *d = + (struct remove_and_dealloc_streams_data *)data; + srtp_stream_list_remove(d->list, stream); + d->status = srtp_stream_dealloc(stream, d->template); + if (d->status) { + return 1; + } + return 0; +} + +static srtp_err_status_t srtp_remove_and_dealloc_streams( + srtp_stream_list_t list, + srtp_stream_t template) +{ + struct remove_and_dealloc_streams_data data = { srtp_err_status_ok, list, + template }; + srtp_stream_list_for_each(list, remove_and_dealloc_streams_cb, &data); + return data.status; +} + static srtp_err_status_t srtp_valid_policy(const srtp_policy_t *p) { if (p != NULL && p->deprecated_ekt != NULL) { @@ -553,6 +595,7 @@ static srtp_err_status_t srtp_stream_clone( /* defensive coding */ str->next = NULL; + str->prev = NULL; return srtp_err_status_ok; } @@ -1257,8 +1300,8 @@ srtp_err_status_t srtp_stream_init_keys(srtp_stream_ctx_t *srtp, return srtp_err_status_ok; } -srtp_err_status_t srtp_stream_init(srtp_stream_ctx_t *srtp, - const srtp_policy_t *p) +static srtp_err_status_t srtp_stream_init(srtp_stream_ctx_t *srtp, + const srtp_policy_t *p) { srtp_err_status_t err; @@ -2058,9 +2101,12 @@ static srtp_err_status_t srtp_unprotect_aead(srtp_ctx_t *ctx, return status; } - /* add new stream to the head of the stream_list */ - new_stream->next = ctx->stream_list; - ctx->stream_list = new_stream; + /* add new stream to the list */ + status = srtp_insert_or_dealloc_stream(ctx->stream_list, new_stream, + ctx->stream_template); + if (status) { + return status; + } /* set stream (the pointer used in this function) */ stream = new_stream; @@ -2149,9 +2195,12 @@ srtp_err_status_t srtp_protect_mki(srtp_ctx_t *ctx, if (status) return status; - /* add new stream to the head of the stream_list */ - new_stream->next = ctx->stream_list; - ctx->stream_list = new_stream; + /* add new stream to the list */ + status = srtp_insert_or_dealloc_stream(ctx->stream_list, new_stream, + ctx->stream_template); + if (status) { + return status; + } /* set direction to outbound */ new_stream->direction = dir_srtp_sender; @@ -2745,12 +2794,16 @@ srtp_err_status_t srtp_unprotect_mki(srtp_ctx_t *ctx, */ status = srtp_stream_clone(ctx->stream_template, hdr->ssrc, &new_stream); - if (status) + if (status) { return status; + } - /* add new stream to the head of the stream_list */ - new_stream->next = ctx->stream_list; - ctx->stream_list = new_stream; + /* add new stream to the list */ + status = srtp_insert_or_dealloc_stream(ctx->stream_list, new_stream, + ctx->stream_template); + if (status) { + return status; + } /* set stream (the pointer used in this function) */ stream = new_stream; @@ -2808,32 +2861,13 @@ srtp_err_status_t srtp_shutdown(void) return srtp_err_status_ok; } -/* - * srtp_get_stream(ssrc) returns a pointer to the stream corresponding - * to ssrc, or NULL if no stream exists for that ssrc - * - * this is an internal function - */ - srtp_stream_ctx_t *srtp_get_stream(srtp_t srtp, uint32_t ssrc) { - srtp_stream_ctx_t *stream; - - /* walk down list until ssrc is found */ - stream = srtp->stream_list; - while (stream != NULL) { - if (stream->ssrc == ssrc) - return stream; - stream = stream->next; - } - - /* we haven't found our ssrc, so return a null */ - return NULL; + return srtp_stream_list_get(srtp->stream_list, ssrc); } srtp_err_status_t srtp_dealloc(srtp_t session) { - srtp_stream_ctx_t *stream; srtp_err_status_t status; /* @@ -2842,21 +2876,25 @@ srtp_err_status_t srtp_dealloc(srtp_t session) * memory and just return an error */ - /* walk list of streams, deallocating as we go */ - stream = session->stream_list; - while (stream != NULL) { - srtp_stream_t next = stream->next; - status = srtp_stream_dealloc(stream, session->stream_template); - if (status) - return status; - stream = next; + /* deallocate streams */ + status = srtp_remove_and_dealloc_streams(session->stream_list, + session->stream_template); + if (status) { + return status; } /* deallocate stream template, if there is one */ if (session->stream_template != NULL) { status = srtp_stream_dealloc(session->stream_template, NULL); - if (status) + if (status) { return status; + } + } + + /* deallocate stream list */ + status = srtp_stream_list_dealloc(session->stream_list); + if (status) { + return status; } /* deallocate session context */ @@ -2919,8 +2957,11 @@ srtp_err_status_t srtp_add_stream(srtp_t session, const srtp_policy_t *policy) session->stream_template->direction = dir_srtp_receiver; break; case (ssrc_specific): - tmp->next = session->stream_list; - session->stream_list = tmp; + status = srtp_insert_or_dealloc_stream(session->stream_list, tmp, + session->stream_template); + if (status) { + return status; + } break; case (ssrc_undefined): default: @@ -2952,13 +2993,23 @@ srtp_err_status_t srtp_create(srtp_t *session, /* handle for session */ return srtp_err_status_alloc_fail; *session = ctx; + ctx->stream_template = NULL; + ctx->stream_list = NULL; + ctx->user_data = NULL; + + /* allocate stream list */ + stat = srtp_stream_list_alloc(&ctx->stream_list); + if (stat) { + /* clean up everything */ + srtp_dealloc(*session); + *session = NULL; + return stat; + } + /* * loop over elements in the policy list, allocating and * initializing a stream for each element */ - ctx->stream_template = NULL; - ctx->stream_list = NULL; - ctx->user_data = NULL; while (policy != NULL) { stat = srtp_add_stream(ctx, policy); if (stat) { @@ -2977,28 +3028,20 @@ srtp_err_status_t srtp_create(srtp_t *session, /* handle for session */ srtp_err_status_t srtp_remove_stream(srtp_t session, uint32_t ssrc) { - srtp_stream_ctx_t *stream, *last_stream; + srtp_stream_ctx_t *stream; srtp_err_status_t status; /* sanity check arguments */ if (session == NULL) return srtp_err_status_bad_param; - /* find stream in list; complain if not found */ - last_stream = stream = session->stream_list; - while ((stream != NULL) && (ssrc != stream->ssrc)) { - last_stream = stream; - stream = stream->next; - } - if (stream == NULL) + /* find and remove stream from the list */ + stream = srtp_stream_list_get(session->stream_list, ssrc); + if (stream == NULL) { return srtp_err_status_no_ctx; + } - /* remove stream from the list */ - if (last_stream == stream) - /* stream was first in list */ - session->stream_list = stream->next; - else - last_stream->next = stream->next; + srtp_stream_list_remove(session->stream_list, stream); /* deallocate the stream */ status = srtp_stream_dealloc(stream, session->stream_template); @@ -3035,12 +3078,70 @@ srtp_err_status_t srtp_update(srtp_t session, const srtp_policy_t *policy) return srtp_err_status_ok; } +struct update_template_stream_data { + srtp_err_status_t status; + srtp_t session; + srtp_stream_t new_stream_template; + srtp_stream_list_t new_stream_list; +}; + +static int update_template_stream_cb(srtp_stream_t stream, void *raw_data) +{ + struct update_template_stream_data *data = + (struct update_template_stream_data *)raw_data; + srtp_t session = data->session; + uint32_t ssrc = stream->ssrc; + srtp_xtd_seq_num_t old_index; + srtp_rdb_t old_rtcp_rdb; + + /* old / non-template streams are copied unchanged */ + if (stream->session_keys[0].rtp_auth != + session->stream_template->session_keys[0].rtp_auth) { + srtp_stream_list_remove(session->stream_list, stream); + data->status = srtp_insert_or_dealloc_stream( + data->new_stream_list, stream, session->stream_template); + if (data->status) { + return 1; + } + return 0; + } + + /* save old extendard seq */ + old_index = stream->rtp_rdbx.index; + old_rtcp_rdb = stream->rtcp_rdb; + + /* remove stream */ + data->status = srtp_remove_stream(session, ssrc); + if (data->status) { + return 1; + } + + /* allocate and initialize a new stream */ + data->status = srtp_stream_clone(data->new_stream_template, ssrc, &stream); + if (data->status) { + return 1; + } + + /* add new stream to the head of the new_stream_list */ + data->status = srtp_insert_or_dealloc_stream(data->new_stream_list, stream, + data->new_stream_template); + if (data->status) { + return 1; + } + + /* restore old extended seq */ + stream->rtp_rdbx.index = old_index; + stream->rtcp_rdb = old_rtcp_rdb; + + return 0; +} + static srtp_err_status_t update_template_streams(srtp_t session, const srtp_policy_t *policy) { srtp_err_status_t status; srtp_stream_t new_stream_template; - srtp_stream_t new_stream_list = NULL; + srtp_stream_list_t new_stream_list; status = srtp_valid_policy(policy); if (status != srtp_err_status_ok) { @@ -3064,77 +3165,37 @@ static srtp_err_status_t update_template_streams(srtp_t session, return status; } - /* for all old templated streams */ - for (;;) { - srtp_stream_t stream; - uint32_t ssrc; - srtp_xtd_seq_num_t old_index; - srtp_rdb_t old_rtcp_rdb; - - stream = session->stream_list; - while ((stream != NULL) && - (stream->session_keys[0].rtp_auth != - session->stream_template->session_keys[0].rtp_auth)) { - stream = stream->next; - } - if (stream == NULL) { - /* no more templated streams */ - break; - } - - /* save old extendard seq */ - ssrc = stream->ssrc; - old_index = stream->rtp_rdbx.index; - old_rtcp_rdb = stream->rtcp_rdb; - - /* remove stream */ - status = srtp_remove_stream(session, ssrc); - if (status) { - /* free new allocations */ - while (new_stream_list != NULL) { - srtp_stream_t next = new_stream_list->next; - srtp_stream_dealloc(new_stream_list, new_stream_template); - new_stream_list = next; - } - srtp_stream_dealloc(new_stream_template, NULL); - return status; - } - - /* allocate and initialize a new stream */ - status = srtp_stream_clone(new_stream_template, ssrc, &stream); - if (status) { - /* free new allocations */ - while (new_stream_list != NULL) { - srtp_stream_t next = new_stream_list->next; - srtp_stream_dealloc(new_stream_list, new_stream_template); - new_stream_list = next; - } - srtp_stream_dealloc(new_stream_template, NULL); - return status; - } - - /* add new stream to the head of the new_stream_list */ - stream->next = new_stream_list; - new_stream_list = stream; - - /* restore old extended seq */ - stream->rtp_rdbx.index = old_index; - stream->rtcp_rdb = old_rtcp_rdb; + /* allocate new stream list */ + status = srtp_stream_list_alloc(&new_stream_list); + if (status) { + srtp_crypto_free(new_stream_template); + return status; } - /* dealloc old template */ + + /* process streams */ + struct update_template_stream_data data = { srtp_err_status_ok, session, + new_stream_template, + new_stream_list }; + srtp_stream_list_for_each(session->stream_list, update_template_stream_cb, + &data); + if (data.status) { + /* free new allocations */ + srtp_remove_and_dealloc_streams(new_stream_list, new_stream_template); + srtp_stream_list_dealloc(new_stream_list); + srtp_stream_dealloc(new_stream_template, NULL); + return data.status; + } + + /* dealloc old list / template */ + srtp_remove_and_dealloc_streams(session->stream_list, + session->stream_template); + srtp_stream_list_dealloc(session->stream_list); srtp_stream_dealloc(session->stream_template, NULL); - /* set new template */ + + /* set new list / template */ session->stream_template = new_stream_template; - /* add new list */ - if (new_stream_list) { - srtp_stream_t tail = new_stream_list; - while (tail->next) { - tail = tail->next; - } - tail->next = session->stream_list; - session->stream_list = new_stream_list; - } - return status; + session->stream_list = new_stream_list; + return srtp_err_status_ok; } static srtp_err_status_t update_stream(srtp_t session, @@ -3902,9 +3963,12 @@ static srtp_err_status_t srtp_unprotect_rtcp_aead( return status; } - /* add new stream to the head of the stream_list */ - new_stream->next = ctx->stream_list; - ctx->stream_list = new_stream; + /* add new stream to the list */ + status = srtp_insert_or_dealloc_stream(ctx->stream_list, new_stream, + ctx->stream_template); + if (status) { + return status; + } /* set stream (the pointer used in this function) */ stream = new_stream; @@ -3968,9 +4032,12 @@ srtp_err_status_t srtp_protect_rtcp_mki(srtp_t ctx, if (status) return status; - /* add new stream to the head of the stream_list */ - new_stream->next = ctx->stream_list; - ctx->stream_list = new_stream; + /* add new stream to the list */ + status = srtp_insert_or_dealloc_stream(ctx->stream_list, new_stream, + ctx->stream_template); + if (status) { + return status; + } /* set stream (the pointer used in this function) */ stream = new_stream; @@ -4415,9 +4482,12 @@ srtp_err_status_t srtp_unprotect_rtcp_mki(srtp_t ctx, if (status) return status; - /* add new stream to the head of the stream_list */ - new_stream->next = ctx->stream_list; - ctx->stream_list = new_stream; + /* add new stream to the list */ + status = srtp_insert_or_dealloc_stream(ctx->stream_list, new_stream, + ctx->stream_template); + if (status) { + return status; + } /* set stream (the pointer used in this function) */ stream = new_stream; @@ -4598,6 +4668,32 @@ srtp_err_status_t stream_get_protect_trailer_length(srtp_stream_ctx_t *stream, return srtp_err_status_ok; } +struct get_protect_trailer_length_data { + uint32_t found_stream; /* whether at least one matching stream was found */ + uint32_t length; /* maximum trailer length found so far */ + uint32_t is_rtp; + uint32_t use_mki; + uint32_t mki_index; +}; + +static int get_protect_trailer_length_cb(srtp_stream_t stream, void *raw_data) +{ + struct get_protect_trailer_length_data *data = + (struct get_protect_trailer_length_data *)raw_data; + uint32_t temp_length; + + if (stream_get_protect_trailer_length(stream, data->is_rtp, data->use_mki, + data->mki_index, + &temp_length) == srtp_err_status_ok) { + data->found_stream = 1; + if (temp_length > data->length) { + data->length = temp_length; + } + } + + return 0; +} + srtp_err_status_t get_protect_trailer_length(srtp_t session, uint32_t is_rtp, uint32_t use_mki, @@ -4605,38 +4701,29 @@ srtp_err_status_t get_protect_trailer_length(srtp_t session, uint32_t *length) { srtp_stream_ctx_t *stream; + struct get_protect_trailer_length_data data = { 0, 0, is_rtp, use_mki, + mki_index }; if (session == NULL) { return srtp_err_status_bad_param; } - if (session->stream_template == NULL && session->stream_list == NULL) { - return srtp_err_status_bad_param; - } - - *length = 0; - stream = session->stream_template; if (stream != NULL) { + data.found_stream = 1; stream_get_protect_trailer_length(stream, is_rtp, use_mki, mki_index, - length); + &data.length); } - stream = session->stream_list; + srtp_stream_list_for_each(session->stream_list, + get_protect_trailer_length_cb, &data); - while (stream != NULL) { - uint32_t temp_length; - if (stream_get_protect_trailer_length(stream, is_rtp, use_mki, - mki_index, &temp_length) == - srtp_err_status_ok) { - if (temp_length > *length) { - *length = temp_length; - } - } - stream = stream->next; + if (!data.found_stream) { + return srtp_err_status_bad_param; } + *length = data.length; return srtp_err_status_ok; } @@ -4750,3 +4837,92 @@ srtp_err_status_t srtp_get_stream_roc(srtp_t session, return srtp_err_status_ok; } + +#ifndef SRTP_NO_STREAM_LIST + +/* in the default implementation, we have an intrusive doubly-linked list */ +struct srtp_stream_list_ctx_t_ { + /* a stub stream that just holds pointers to the beginning and end of the + * list */ + srtp_stream_ctx_t data; +} srtp_stream_list_ctx_t_; + +srtp_err_status_t srtp_stream_list_alloc(srtp_stream_list_t *list_ptr) +{ + srtp_stream_list_t list = + srtp_crypto_alloc(sizeof(srtp_stream_list_ctx_t_)); + if (list == NULL) { + return srtp_err_status_alloc_fail; + } + + list->data.next = NULL; + list->data.prev = NULL; + + *list_ptr = list; + return srtp_err_status_ok; +} + +srtp_err_status_t srtp_stream_list_dealloc(srtp_stream_list_t list) +{ + /* list must be empty */ + if (list->data.next) { + return srtp_err_status_fail; + } + srtp_crypto_free(list); + return srtp_err_status_ok; +} + +srtp_err_status_t srtp_stream_list_insert(srtp_stream_list_t list, + srtp_stream_t stream) +{ + /* insert at the head of the list */ + stream->next = list->data.next; + if (stream->next != NULL) { + stream->next->prev = stream; + } + list->data.next = stream; + stream->prev = &(list->data); + + return srtp_err_status_ok; +} + +srtp_stream_t srtp_stream_list_get(srtp_stream_list_t list, uint32_t ssrc) +{ + /* walk down list until ssrc is found */ + srtp_stream_t stream = list->data.next; + while (stream != NULL) { + if (stream->ssrc == ssrc) { + return stream; + } + stream = stream->next; + } + + /* we haven't found our ssrc, so return a null */ + return NULL; +} + +void srtp_stream_list_remove(srtp_stream_list_t list, + srtp_stream_t stream_to_remove) +{ + (void)list; + + stream_to_remove->prev->next = stream_to_remove->next; + if (stream_to_remove->next != NULL) { + stream_to_remove->next->prev = stream_to_remove->prev; + } +} + +void srtp_stream_list_for_each(srtp_stream_list_t list, + int (*callback)(srtp_stream_t, void *), + void *data) +{ + srtp_stream_t stream = list->data.next; + while (stream != NULL) { + srtp_stream_t tmp = stream; + stream = stream->next; + if (callback(tmp, data)) + break; + } +} + +#endif diff --git a/test/srtp_driver.c b/test/srtp_driver.c index 20957a60e..42229be78 100644 --- a/test/srtp_driver.c +++ b/test/srtp_driver.c @@ -49,6 +49,7 @@ #include "getopt_s.h" /* for local getopt() */ #include "srtp_priv.h" +#include "stream_list_priv.h" #include "util.h" #ifdef HAVE_NETINET_IN_H @@ -124,6 +125,8 @@ char *srtp_packet_to_string(srtp_hdr_t *hdr, int packet_len); double mips_estimate(int num_trials, int *ignore); +srtp_err_status_t srtp_stream_list_test(void); + #define TEST_MKI_ID_SIZE 4 extern uint8_t test_key[46]; @@ -153,15 +156,17 @@ srtp_master_key_t *test_keys[2] = { void usage(char *prog_name) { - printf("usage: %s [ -t ][ -c ][ -v ][ -o ][-d ]* [ -l ]\n" - " -t run timing test\n" - " -r run rejection timing test\n" - " -c run codec timing test\n" - " -v run validation tests\n" - " -o output logging to stdout\n" - " -d turn on debugging module \n" - " -l list debugging modules\n", - prog_name); + printf( + "usage: %s [ -t ][ -c ][ -v ][ -s ][ -o ][-d ]* [ -l ]\n" + " -t run timing test\n" + " -r run rejection timing test\n" + " -c run codec timing test\n" + " -v run validation tests\n" + " -s run stream list tests only\n" + " -o output logging to stdout\n" + " -d turn on debugging module \n" + " -l list debugging modules\n", + prog_name); exit(1); } @@ -217,6 +222,7 @@ int main(int argc, char *argv[]) unsigned do_rejection_test = 0; unsigned do_codec_timing = 0; unsigned do_validation = 0; + unsigned do_stream_list = 0; unsigned do_list_mods = 0; unsigned do_log_stdout = 0; srtp_err_status_t status; @@ -251,7 +257,7 @@ int main(int argc, char *argv[]) /* process input arguments */ while (1) { - q = getopt_s(argc, argv, "trcvold:"); + q = getopt_s(argc, argv, "trcvsold:"); if (q == -1) { break; } @@ -267,6 +273,10 @@ int main(int argc, char *argv[]) break; case 'v': do_validation = 1; + do_stream_list = 1; + break; + case 's': + do_stream_list = 1; break; case 'o': do_log_stdout = 1; @@ -287,7 +297,7 @@ int main(int argc, char *argv[]) } if (!do_validation && !do_timing_test && !do_codec_timing && - !do_list_mods && !do_rejection_test) { + !do_list_mods && !do_rejection_test && !do_stream_list) { usage(argv[0]); } @@ -597,6 +607,16 @@ int main(int argc, char *argv[]) } } + if (do_stream_list) { + printf("testing srtp_stream_list..."); + if (srtp_stream_list_test() == srtp_err_status_ok) { + printf("passed\n"); + } else { + printf("failed\n"); + exit(1); + } + } + if (do_timing_test) { const srtp_policy_t **policy = policy_array; @@ -1467,13 +1487,75 @@ srtp_err_status_t srtcp_test(const srtp_policy_t *policy, int mki_index) return srtp_err_status_ok; } +struct srtp_session_print_stream_data { + // set by callback to indicate failure + srtp_err_status_t status; + // indicates if it is the template stream or a regular stream + int is_template; +}; + +int srtp_session_print_stream(srtp_stream_t stream, void *raw_data) +{ + static const char *serv_descr[4] = { "none", "confidentiality", + "authentication", + "confidentiality and authentication" }; + static const char *direction[3] = { "unknown", "outbound", "inbound" }; + + struct srtp_session_print_stream_data *data = + (struct srtp_session_print_stream_data *)raw_data; + srtp_session_keys_t *session_keys = &stream->session_keys[0]; + char ssrc_text[32]; + + if (!data->is_template && stream->rtp_services > sec_serv_conf_and_auth) { + data->status = srtp_err_status_bad_param; + return 1; + } + + if (data->is_template) { + snprintf(ssrc_text, sizeof(ssrc_text), "any %s", + direction[stream->direction]); + } else { + snprintf(ssrc_text, sizeof(ssrc_text), "0x%08x", stream->ssrc); + } + + printf("# SSRC: %s\r\n" + "# rtp cipher: %s\r\n" + "# rtp auth: %s\r\n" + "# rtp services: %s\r\n" + "# rtcp cipher: %s\r\n" + "# rtcp auth: %s\r\n" + "# rtcp services: %s\r\n" + "# window size: %lu\r\n" + "# tx rtx allowed:%s\r\n", + ssrc_text, session_keys->rtp_cipher->type->description, + session_keys->rtp_auth->type->description, + serv_descr[stream->rtp_services], + session_keys->rtcp_cipher->type->description, + session_keys->rtcp_auth->type->description, + serv_descr[stream->rtcp_services], + srtp_rdbx_get_window_size(&stream->rtp_rdbx), + stream->allow_repeat_tx ? "true" : "false"); + + printf("# Encrypted extension headers: "); + if (stream->enc_xtn_hdr && stream->enc_xtn_hdr_count > 0) { + int *enc_xtn_hdr = stream->enc_xtn_hdr; + int count = stream->enc_xtn_hdr_count; + while (count > 0) { + printf("%d ", *enc_xtn_hdr); + enc_xtn_hdr++; + count--; + } + printf("\n"); + } else { + printf("none\n"); + } + + return 0; +} + srtp_err_status_t srtp_session_print_policy(srtp_t srtp) { - char *serv_descr[4] = { "none", "confidentiality", "authentication", - "confidentiality and authentication" }; - char *direction[3] = { "unknown", "outbound", "inbound" }; - srtp_stream_t stream; - srtp_session_keys_t *session_keys = NULL; + struct srtp_session_print_stream_data data = { srtp_err_status_ok, 0 }; /* sanity checking */ if (srtp == NULL) { @@ -1482,86 +1564,16 @@ srtp_err_status_t srtp_session_print_policy(srtp_t srtp) /* if there's a template stream, print it out */ if (srtp->stream_template != NULL) { - stream = srtp->stream_template; - session_keys = &stream->session_keys[0]; - printf("# SSRC: any %s\r\n" - "# rtp cipher: %s\r\n" - "# rtp auth: %s\r\n" - "# rtp services: %s\r\n" - "# rtcp cipher: %s\r\n" - "# rtcp auth: %s\r\n" - "# rtcp services: %s\r\n" - "# window size: %lu\r\n" - "# tx rtx allowed:%s\r\n", - direction[stream->direction], - session_keys->rtp_cipher->type->description, - session_keys->rtp_auth->type->description, - serv_descr[stream->rtp_services], - session_keys->rtcp_cipher->type->description, - session_keys->rtcp_auth->type->description, - serv_descr[stream->rtcp_services], - srtp_rdbx_get_window_size(&stream->rtp_rdbx), - stream->allow_repeat_tx ? "true" : "false"); - - printf("# Encrypted extension headers: "); - if (stream->enc_xtn_hdr && stream->enc_xtn_hdr_count > 0) { - int *enc_xtn_hdr = stream->enc_xtn_hdr; - int count = stream->enc_xtn_hdr_count; - while (count > 0) { - printf("%d ", *enc_xtn_hdr); - enc_xtn_hdr++; - count--; - } - printf("\n"); - } else { - printf("none\n"); - } + data.is_template = 1; + srtp_session_print_stream(srtp->stream_template, &data); } /* loop over streams in session, printing the policy of each */ - stream = srtp->stream_list; - while (stream != NULL) { - if (stream->rtp_services > sec_serv_conf_and_auth) { - return srtp_err_status_bad_param; - } - session_keys = &stream->session_keys[0]; - - printf("# SSRC: 0x%08x\r\n" - "# rtp cipher: %s\r\n" - "# rtp auth: %s\r\n" - "# rtp services: %s\r\n" - "# rtcp cipher: %s\r\n" - "# rtcp auth: %s\r\n" - "# rtcp services: %s\r\n" - "# window size: %lu\r\n" - "# tx rtx allowed:%s\r\n", - stream->ssrc, session_keys->rtp_cipher->type->description, - session_keys->rtp_auth->type->description, - serv_descr[stream->rtp_services], - session_keys->rtcp_cipher->type->description, - session_keys->rtcp_auth->type->description, - serv_descr[stream->rtcp_services], - srtp_rdbx_get_window_size(&stream->rtp_rdbx), - stream->allow_repeat_tx ? "true" : "false"); - - printf("# Encrypted extension headers: "); - if (stream->enc_xtn_hdr && stream->enc_xtn_hdr_count > 0) { - int *enc_xtn_hdr = stream->enc_xtn_hdr; - int count = stream->enc_xtn_hdr_count; - while (count > 0) { - printf("%d ", *enc_xtn_hdr); - enc_xtn_hdr++; - count--; - } - printf("\n"); - } else { - printf("none\n"); - } + data.is_template = 0; + srtp_stream_list_for_each(srtp->stream_list, srtp_session_print_stream, + &data); - /* advance to next stream in the list */ - stream = stream->next; - } - return srtp_err_status_ok; + return data.status; } srtp_err_status_t srtp_print_policy(const srtp_policy_t *policy) @@ -4287,3 +4299,240 @@ const srtp_policy_t wildcard_policy = { 0, /* list of encrypted extension headers is empty */ NULL }; + +static srtp_stream_t stream_list_test_create_stream(uint32_t ssrc) +{ + srtp_stream_t stream = malloc(sizeof(srtp_stream_ctx_t)); + stream->ssrc = ssrc; + return stream; +} + +static void stream_list_test_free_stream(srtp_stream_t stream) +{ + free(stream); +} + +int stream_list_test_count_cb(srtp_stream_t stream, void *data) +{ + int *count = (int *)data; + (*count)++; + (void)stream; + return 0; +} + +struct remove_one_data { + uint32_t ssrc; + srtp_stream_list_t list; +}; + +int stream_list_test_remove_one_cb(srtp_stream_t stream, void *data) +{ + struct remove_one_data *d = (struct remove_one_data *)data; + if (stream->ssrc == d->ssrc) { + srtp_stream_list_remove(d->list, stream); + stream_list_test_free_stream(stream); + return 1; + } + return 0; +} + +int stream_list_test_remove_all_cb(srtp_stream_t stream, void *data) +{ + srtp_stream_list_t *list = (srtp_stream_list_t *)data; + srtp_stream_list_remove(*list, stream); + stream_list_test_free_stream(stream); + return 0; +} + +srtp_err_status_t srtp_stream_list_test(void) +{ + srtp_stream_list_t list; + + if (srtp_stream_list_alloc(&list)) { + return srtp_err_status_fail; + } + + /* add 4 streams*/ + if (srtp_stream_list_insert(list, stream_list_test_create_stream(1))) { + return srtp_err_status_fail; + } + if (srtp_stream_list_insert(list, stream_list_test_create_stream(2))) { + return srtp_err_status_fail; + } + if (srtp_stream_list_insert(list, stream_list_test_create_stream(3))) { + return srtp_err_status_fail; + } + if (srtp_stream_list_insert(list, stream_list_test_create_stream(4))) { + return srtp_err_status_fail; + } + + /* find */ + if (srtp_stream_list_get(list, 3) == NULL) { + return srtp_err_status_fail; + } + if (srtp_stream_list_get(list, 1) == NULL) { + return srtp_err_status_fail; + } + if (srtp_stream_list_get(list, 2) == NULL) { + return srtp_err_status_fail; + } + if (srtp_stream_list_get(list, 4) == NULL) { + return srtp_err_status_fail; + } + + /* find not in list */ + if (srtp_stream_list_get(list, 5)) { + return srtp_err_status_fail; + } + + /* for each */ + int count = 0; + srtp_stream_list_for_each(list, stream_list_test_count_cb, &count); + if (count != 4) { + return srtp_err_status_fail; + } + + /* remove */ + srtp_stream_t stream = srtp_stream_list_get(list, 3); + if (stream == NULL) { + return srtp_err_status_fail; + } + srtp_stream_list_remove(list, stream); + stream_list_test_free_stream(stream); + + /* find after remove */ + if (srtp_stream_list_get(list, 3)) { + return srtp_err_status_fail; + } + + /* recount */ + count = 0; + srtp_stream_list_for_each(list, stream_list_test_count_cb, &count); + if (count != 3) { + return srtp_err_status_fail; + } + + /* remove one in for each */ + struct remove_one_data data = { 2, list }; + srtp_stream_list_for_each(list, stream_list_test_remove_one_cb, &data); + + /* find after remove */ + if (srtp_stream_list_get(list, 2)) { + return srtp_err_status_fail; + } + + /* recount */ + count = 0; + srtp_stream_list_for_each(list, stream_list_test_count_cb, &count); + if (count != 2) { + return srtp_err_status_fail; + } + + /* destroy non empty list */ + if (srtp_stream_list_dealloc(list) == srtp_err_status_ok) { + return srtp_err_status_fail; + } + + /* remove all in for each */ + srtp_stream_list_for_each(list, stream_list_test_remove_all_cb, &list); + + /* recount */ + count = 0; + srtp_stream_list_for_each(list, stream_list_test_count_cb, &count); + if (count != 0) { + return srtp_err_status_fail; + } + + /* destroy empty list */ + if (srtp_stream_list_dealloc(list)) { + return srtp_err_status_fail; + } + + return srtp_err_status_ok; +} + +#ifdef SRTP_USE_TEST_STREAM_LIST + +/* + * A srtp_stream_list_ctx_t implementation using a single linked list + * that does not use the internal next / prev fields. + */ + +struct test_list_node { + srtp_stream_t stream; + struct test_list_node *next; +}; +struct srtp_stream_list_ctx_t_ { + struct test_list_node *head; +}; + +srtp_err_status_t srtp_stream_list_alloc(srtp_stream_list_t *list_ptr) +{ + struct srtp_stream_list_ctx_t_ *l = + malloc(sizeof(struct srtp_stream_list_ctx_t_)); + l->head = NULL; + *list_ptr = l; + return srtp_err_status_ok; +} + +srtp_err_status_t srtp_stream_list_dealloc(srtp_stream_list_t list) +{ + struct test_list_node *node = list->head; + if (node) { + return srtp_err_status_fail; + } + free(list); + + return srtp_err_status_ok; +} + +srtp_err_status_t srtp_stream_list_insert(srtp_stream_list_t list, + srtp_stream_t stream) +{ + struct test_list_node *node = malloc(sizeof(struct test_list_node)); + node->stream = stream; + node->next = list->head; + list->head = node; + + return srtp_err_status_ok; +} + +srtp_stream_t srtp_stream_list_get(srtp_stream_list_t list, uint32_t ssrc) +{ + struct test_list_node *node = list->head; + while (node != NULL) { + if (node->stream->ssrc == ssrc) + return node->stream; + node = node->next; + } + return NULL; +} + +void srtp_stream_list_remove(srtp_stream_list_t list, srtp_stream_t stream) +{ + struct test_list_node **node = &(list->head); + while ((*node) != NULL) { + if ((*node)->stream->ssrc == stream->ssrc) { + struct test_list_node *tmp = (*node); + (*node) = tmp->next; + free(tmp); + return; + } + node = &(*node)->next; + } +} + +void srtp_stream_list_for_each(srtp_stream_list_t list, + int (*callback)(srtp_stream_t, void *), + void *data) +{ + struct test_list_node *node = list->head; + while (node != NULL) { + struct test_list_node *tmp = node; + node = node->next; + if (callback(tmp->stream, data)) + break; + } +} + +#endif