From 282117244be1c0f12a24ce795cad41401a91bec1 Mon Sep 17 00:00:00 2001 From: Divya Sampath Kumar Date: Thu, 8 Feb 2024 10:52:22 -0800 Subject: [PATCH] Reduce hash table size for SDP transceivers (#1914) * Modify hash table sizes * Add logs * Fix hash table size to be set according to number of m-lines and unique supported codecs * CI branch * Update actions to use node20 * Unit test for fake transceiver * Cleanup * Fix build failure * Fix unit test * Initialize to 0 --- .github/workflows/ci.yml | 70 +++---- .../kinesis/video/webrtcclient/Include.h | 2 + src/source/Ice/IceAgent.c | 2 +- .../PeerConnection/SessionDescription.c | 20 +- .../PeerConnection/SessionDescription.h | 2 + src/source/Sdp/Deserialize.c | 2 +- tst/SdpApiTest.cpp | 193 +++++++++++++++++- 7 files changed, 245 insertions(+), 46 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1da9a704dd..7a5bfe9dd6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: runs-on: macos-latest steps: - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install clang-format run: | brew install clang-format @@ -33,9 +33,9 @@ jobs: contents: read steps: - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} aws-region: ${{ secrets.AWS_REGION }} @@ -59,9 +59,9 @@ jobs: contents: read steps: - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} aws-region: ${{ secrets.AWS_REGION }} @@ -83,9 +83,9 @@ jobs: contents: read steps: - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} aws-region: ${{ secrets.AWS_REGION }} @@ -108,9 +108,9 @@ jobs: contents: read steps: - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} aws-region: ${{ secrets.AWS_REGION }} @@ -136,9 +136,9 @@ jobs: contents: read steps: - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} aws-region: ${{ secrets.AWS_REGION }} @@ -171,9 +171,9 @@ jobs: contents: read steps: - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} aws-region: ${{ secrets.AWS_REGION }} @@ -202,7 +202,7 @@ jobs: # AWS_KVS_LOG_LEVEL: 2 # steps: # - name: Clone repository - # uses: actions/checkout@v3 + # uses: actions/checkout@v4 # - name: Install dependencies # run: | # sudo apt clean && sudo apt update @@ -227,9 +227,9 @@ jobs: contents: read steps: - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} aws-region: ${{ secrets.AWS_REGION }} @@ -259,9 +259,9 @@ jobs: contents: read steps: - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} aws-region: ${{ secrets.AWS_REGION }} @@ -300,7 +300,7 @@ jobs: contents: read steps: - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install dependencies run: | apk update @@ -320,9 +320,9 @@ jobs: contents: read steps: - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} aws-region: ${{ secrets.AWS_REGION }} @@ -356,9 +356,9 @@ jobs: contents: read steps: - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} aws-region: ${{ secrets.AWS_REGION }} @@ -392,9 +392,9 @@ jobs: contents: read steps: - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} aws-region: ${{ secrets.AWS_REGION }} @@ -425,9 +425,9 @@ jobs: contents: read steps: - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} aws-region: ${{ secrets.AWS_REGION }} @@ -508,9 +508,9 @@ jobs: contents: read steps: - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} aws-region: ${{ secrets.AWS_REGION }} @@ -538,9 +538,9 @@ jobs: contents: read steps: - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v2 + uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} aws-region: ${{ secrets.AWS_REGION }} @@ -587,7 +587,7 @@ jobs: # AWS_KVS_LOG_LEVEL: 7 # steps: # - name: Clone repository - # uses: actions/checkout@v3 + # uses: actions/checkout@v4 # - name: Move cloned repo # shell: powershell # run: | @@ -620,7 +620,7 @@ jobs: sudo apt clean && sudo apt update sudo apt-get -y install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu binutils-aarch64-linux-gnu - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Build Repository run: | sudo sh -c 'echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6' @@ -638,7 +638,7 @@ jobs: sudo apt clean && sudo apt update sudo apt-get -y install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu binutils-aarch64-linux-gnu - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Build Repository run: | sudo sh -c 'echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6' @@ -656,7 +656,7 @@ jobs: sudo apt clean && sudo apt update sudo apt-get -y install gcc-arm-linux-gnueabi g++-arm-linux-gnueabi binutils-arm-linux-gnueabi - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Build Repository run: | sudo sh -c 'echo 0 > /proc/sys/net/ipv6/conf/all/disable_ipv6' diff --git a/src/include/com/amazonaws/kinesis/video/webrtcclient/Include.h b/src/include/com/amazonaws/kinesis/video/webrtcclient/Include.h index 3fcadf5693..cb2ff39ed9 100644 --- a/src/include/com/amazonaws/kinesis/video/webrtcclient/Include.h +++ b/src/include/com/amazonaws/kinesis/video/webrtcclient/Include.h @@ -835,6 +835,8 @@ typedef enum { RTC_CODEC_MULAW = 4, //!< MULAW audio codec RTC_CODEC_ALAW = 5, //!< ALAW audio codec RTC_CODEC_UNKNOWN = 6, + // RTC_CODEC_MAX **MUST** be the last enum in the list **ALWAYS** and not assigned a value + RTC_CODEC_MAX //!< Placeholder for max number of supported codecs } RTC_CODEC; /** diff --git a/src/source/Ice/IceAgent.c b/src/source/Ice/IceAgent.c index b806f13453..99f078288e 100644 --- a/src/source/Ice/IceAgent.c +++ b/src/source/Ice/IceAgent.c @@ -88,7 +88,7 @@ STATUS createIceAgent(PCHAR username, PCHAR password, PIceAgentCallbacks pIceAge // Pre-allocate stun packets - // no other attribtues needed: https://tools.ietf.org/html/rfc8445#section-11 + // no other attributes needed: https://tools.ietf.org/html/rfc8445#section-11 CHK_STATUS(createStunPacket(STUN_PACKET_TYPE_BINDING_INDICATION, NULL, &pIceAgent->pBindingIndication)); CHK_STATUS(hashTableCreateWithParams(ICE_HASH_TABLE_BUCKET_COUNT, ICE_HASH_TABLE_BUCKET_LENGTH, &pIceAgent->requestTimestampDiagnostics)); diff --git a/src/source/PeerConnection/SessionDescription.c b/src/source/PeerConnection/SessionDescription.c index 4b0ab961c7..3ec55159f6 100644 --- a/src/source/PeerConnection/SessionDescription.c +++ b/src/source/PeerConnection/SessionDescription.c @@ -886,6 +886,7 @@ STATUS populateSessionDescriptionMedia(PKvsPeerConnection pKvsPeerConnection, PS PCHAR pDtlsRole = NULL; PHashTable pUnknownCodecPayloadTypesTable = NULL, pUnknownCodecRtpmapTable = NULL; UINT32 unknownCodecHashTableKey = 0; + UINT32 unknownHashTableBucketCount = 0; CHK_STATUS(dtlsSessionGetLocalCertificateFingerprint(pKvsPeerConnection->pDtlsSession, certificateFingerprint, CERTIFICATE_FINGERPRINT_LENGTH)); @@ -910,8 +911,12 @@ STATUS populateSessionDescriptionMedia(PKvsPeerConnection pKvsPeerConnection, PS } } else { pDtlsRole = DTLS_ROLE_ACTIVE; - CHK_STATUS(hashTableCreate(&pUnknownCodecPayloadTypesTable)); - CHK_STATUS(hashTableCreate(&pUnknownCodecRtpmapTable)); + unknownHashTableBucketCount = + pRemoteSessionDescription->mediaCount < MIN_HASH_BUCKET_COUNT ? MIN_HASH_BUCKET_COUNT : pRemoteSessionDescription->mediaCount; + CHK_STATUS(hashTableCreateWithParams(unknownHashTableBucketCount, CODEC_RTPMAP_PAYLOAD_TYPES_HASH_TABLE_BUCKET_LENGTH, + &pUnknownCodecPayloadTypesTable)); + CHK_STATUS( + hashTableCreateWithParams(unknownHashTableBucketCount, CODEC_RTPMAP_PAYLOAD_TYPES_HASH_TABLE_BUCKET_LENGTH, &pUnknownCodecRtpmapTable)); // this function creates a list of transceivers corresponding to each m-line and adds it answerTransceivers // if an m-line does not have a corresponding transceiver created by the user, we create a fake transceiver @@ -1112,18 +1117,21 @@ STATUS findTransceiversByRemoteDescription(PKvsPeerConnection pKvsPeerConnection PRtcMediaStreamTrack pRtcMediaStreamTrack; RtcMediaStreamTrack track; - CHK_STATUS(hashTableCreate(&pSeenTransceivers)); // to be used by findCodecInTransceivers + // pSeenTranceivers is populated only with codec types supported by the SDK. And if already populated, it is not added again. + // Hence, it is sufficient if the hash table count is set to minimum or required transceivers + UINT32 seenTranceiversHashBucketCnt = RTC_CODEC_MAX < MIN_HASH_BUCKET_COUNT ? MIN_HASH_BUCKET_COUNT : RTC_CODEC_MAX; + CHK_STATUS(hashTableCreateWithParams(seenTranceiversHashBucketCnt, CODEC_RTPMAP_PAYLOAD_TYPES_HASH_TABLE_BUCKET_LENGTH, &pSeenTransceivers)); // sample m-lines // m=audio 9 UDP/TLS/RTP/SAVPF 111 63 103 104 9 0 8 106 105 13 110 112 113 126 // m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 102 121 127 120 125 107 108 109 124 119 123 117 35 36 114 115 116 62 118 // this loop iterates over all the m-lines + for (currentMedia = 0; currentMedia < pRemoteSessionDescription->mediaCount; currentMedia++) { pMediaDescription = &(pRemoteSessionDescription->mediaDescriptions[currentMedia]); foundMediaSectionWithCodec = FALSE; count = 0; MEMSET(firstCodec, 0x00, MAX_PAYLOAD_TYPE_LENGTH); - // Scan the media section name for any codecs we support // sample attributeValue=audio 9 UDP/TLS/RTP/SAVPF 111 63 103 104 9 0 8 106 105 13 110 112 113 126 attributeValue = pMediaDescription->mediaName; @@ -1172,7 +1180,6 @@ STATUS findTransceiversByRemoteDescription(PKvsPeerConnection pKvsPeerConnection attributeValue = end + 1; } } while (end != NULL && !foundMediaSectionWithCodec); - // get the first payload type from codecs in case we need to use it to generate a fake transceiver to respond to an m-line // if we don't have a user-created one corresponding to an m-line // we can respond to an m-line by including any one codec the offer had @@ -1189,7 +1196,6 @@ STATUS findTransceiversByRemoteDescription(PKvsPeerConnection pKvsPeerConnection for (currentAttribute = 0; currentAttribute < pMediaDescription->mediaAttributesCount && !foundMediaSectionWithCodec; currentAttribute++) { attributeValue = pMediaDescription->sdpAttributes[currentAttribute].attributeValue; rtcCodec = RTC_CODEC_UNKNOWN; - // check for supported codec in rtpmap values only if an a-line contains the keyword "rtpmap" to save string comparisons if (STRNCMP(RTPMAP_VALUE, pMediaDescription->sdpAttributes[currentAttribute].attributeName, 6) == 0) { if (STRSTR(attributeValue, H264_VALUE) != NULL) { @@ -1249,7 +1255,6 @@ STATUS findTransceiversByRemoteDescription(PKvsPeerConnection pKvsPeerConnection CHK_STATUS(doubleListInsertItemTail(pKvsPeerConnection->pAnswerTransceivers, (UINT64) pKvsRtpFakeTransceiver)); CHK_STATUS(STRTOUI32(firstCodec, firstCodec + tokenLen, 10, &codec)); - // Insert (int)(firstCodec) and rtpMapValue into the hashtables with the same key so they can be retrieved later during serialization CHK_STATUS(hashTableContains(pUnknownCodecPayloadTypesTable, (UINT64) codec, &containsPayloadType)); CHK_STATUS(hashTableContains(pUnknownCodecRtpmapTable, (UINT64) rtpMapValue, &containsRtpMap)); @@ -1264,7 +1269,6 @@ STATUS findTransceiversByRemoteDescription(PKvsPeerConnection pKvsPeerConnection } CleanUp: - CHK_STATUS(hashTableFree(pSeenTransceivers)); CHK_LOG_ERR(retStatus); diff --git a/src/source/PeerConnection/SessionDescription.h b/src/source/PeerConnection/SessionDescription.h index 15a6b45d11..1e5db4a931 100644 --- a/src/source/PeerConnection/SessionDescription.h +++ b/src/source/PeerConnection/SessionDescription.h @@ -78,6 +78,8 @@ extern "C" { #define TWCC_SDP_ATTR "transport-cc" #define TWCC_EXT_URL "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01" +#define CODEC_RTPMAP_PAYLOAD_TYPES_HASH_TABLE_BUCKET_LENGTH 2 + STATUS setPayloadTypesFromOffer(PHashTable, PHashTable, PSessionDescription); STATUS setPayloadTypesForOffer(PHashTable); diff --git a/src/source/Sdp/Deserialize.c b/src/source/Sdp/Deserialize.c index c67d684140..2fbf731239 100644 --- a/src/source/Sdp/Deserialize.c +++ b/src/source/Sdp/Deserialize.c @@ -5,7 +5,7 @@ STATUS parseMediaName(PSessionDescription pSessionDescription, PCHAR pch, UINT32 { ENTERS(); STATUS retStatus = STATUS_SUCCESS; - CHK(pSessionDescription->mediaCount < MAX_SDP_SESSION_MEDIA_COUNT, STATUS_BUFFER_TOO_SMALL); + CHK(pSessionDescription->mediaCount < MAX_SDP_SESSION_MEDIA_COUNT, STATUS_SESSION_DESCRIPTION_MAX_MEDIA_COUNT); STRNCPY(pSessionDescription->mediaDescriptions[pSessionDescription->mediaCount].mediaName, (pch + SDP_ATTRIBUTE_LENGTH), MIN(MAX_SDP_MEDIA_NAME_LENGTH, lineLen - SDP_ATTRIBUTE_LENGTH)); diff --git a/tst/SdpApiTest.cpp b/tst/SdpApiTest.cpp index 15046430d8..6f956215da 100644 --- a/tst/SdpApiTest.cpp +++ b/tst/SdpApiTest.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "WebRTCClientTestFixture.h" @@ -588,7 +589,7 @@ a=group:BUNDLE 0 1 2 3 SessionDescription sessionDescription; MEMSET(&sessionDescription, 0x00, SIZEOF(SessionDescription)); // as log as Sdp.h MAX_SDP_SESSION_MEDIA_COUNT 5 this should fail instead of overwriting memory - EXPECT_EQ(STATUS_BUFFER_TOO_SMALL, deserializeSessionDescription(&sessionDescription, (PCHAR) sdp)); + EXPECT_EQ(STATUS_SESSION_DESCRIPTION_MAX_MEDIA_COUNT, deserializeSessionDescription(&sessionDescription, (PCHAR) sdp)); }); } @@ -701,6 +702,196 @@ a=group:BUNDLE audio video data }); } +// Test out unknown codec, unknown rtpmap and seen tranceiver hash map for correct sizing +TEST_F(SdpApiTest, fakeTransceiverTest) +{ + auto offerBase = std::string(R"(v=0 +o=- 2414510623331460048 2 IN IP4 127.0.0.1 +s=- +t=0 0 +a=group:BUNDLE 0 1 +a=extmap-allow-mixed +a=msid-semantic: WMS 2e3ca9ff-0c7e-4b9d-9471-2ce80de74b84)"); + + auto audioSdp = std::string(R"(m=audio 9 UDP/TLS/RTP/SAVPF 111 +c=IN IP4 0.0.0.0 +a=rtcp:9 IN IP4 0.0.0.0 +a=ice-ufrag:tEm4 +a=ice-pwd:MHYra0wZc3cAECKFPlnoRpon +a=ice-options:trickle +a=fingerprint:sha-256 87:E6:EC:59:93:76:9F:42:7D:15:17:F6:8F:C4:29:AB:EA:3F:28:B6:DF:F8:14:2F:96:62:2F:16:98:F5:76:E5 +a=setup:actpass +a=mid:0 +a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level +a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time +a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01 +a=sendrecv +a=msid:2e3ca9ff-0c7e-4b9d-9471-2ce80de74b84 757d07a0-892a-46e7-a13d-b43fc3ef68c7 +a=rtcp-mux +a=rtpmap:111 opus/48000/2 +a=rtcp-fb:111 transport-cc +a=fmtp:111 minptime=10;useinbandfec=1 +a=ssrc:331864867 cname:jyxeGEm09Qe6m8dq +a=ssrc:331864867 msid:2e3ca9ff-0c7e-4b9d-9471-2ce80de74b84 757d07a0-892a-46e7-a13d-b43fc3ef68c7 + )"); + + auto videoSdp = std::string(R"(m=video 9 UDP/TLS/RTP/SAVPF 96 97 102 103 104 105 +c=IN IP4 0.0.0.0 +a=rtcp:9 IN IP4 0.0.0.0 +a=ice-ufrag:tEm4 +a=ice-pwd:MHYra0wZc3cAECKFPlnoRpon +a=ice-options:trickle +a=fingerprint:sha-256 37:C4:5C:9C:C9:DA:56:22:47:1F:8C:93:E1:A1:51:A8:15:94:78:1D:89:26:69:44:65:6C:C3:83:96:10:32:43 +a=setup:actpass +a=mid:1 +a=sendrecv +a=msid:2e3ca9ff-0c7e-4b9d-9471-2ce80de74b84 8c1b020b-e6ab-4002-8450-b816ebff0219 +a=rtcp-mux +a=rtcp-rsize +a=rtpmap:96 VP8/90000 +a=rtpmap:97 rtx/90000 +a=fmtp:97 apt=96 +a=rtpmap:102 H264/90000 +a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f +a=rtpmap:103 rtx/90000 +a=fmtp:103 apt=102 +a=rtpmap:104 H264/90000 +a=fmtp:104 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f +a=rtpmap:105 rtx/90000 +a=fmtp:105 apt=104 +a=ssrc-group:FID 2039979579 916070044 +a=ssrc:2039979579 cname:jyxeGEm09Qe6m8dq +a=ssrc:2039979579 msid:2e3ca9ff-0c7e-4b9d-9471-2ce80de74b84 8c1b020b-e6ab-4002-8450-b816ebff0219 +a=ssrc:916070044 cname:jyxeGEm09Qe6m8dq +a=ssrc:916070044 msid:2e3ca9ff-0c7e-4b9d-9471-2ce80de74b84 8c1b020b-e6ab-4002-8450-b816ebff0219 + )"); + + auto unsupportedVideoSdp = std::string(R"(m=video 9 UDP/TLS/RTP/SAVPF 200 230 250 270 +c=IN IP4 0.0.0.0 +a=rtcp:9 IN IP4 0.0.0.0 +a=ice-ufrag:tEm4 +a=ice-pwd:MHYra0wZc3cAECKFPlnoRpon +a=ice-options:trickle +a=fingerprint:sha-256 37:C4:5C:9C:C9:DA:56:22:47:1F:8C:93:E1:A1:51:A8:15:94:78:1D:89:26:69:44:65:6C:C3:83:96:10:32:43 +a=setup:actpass +a=mid:1 +a=sendrecv +a=msid:2e3ca9ff-0c7e-4b9d-9471-2ce80de74b84 8c1b020b-e6ab-4002-8450-b816ebff0219 +a=rtcp-mux +a=rtcp-rsize +a=rtpmap:200 abc/90000 +a=rtpmap:230 abc/90000 +a=fmtp:230 apt=0 +a=rtpmap:250 xyz/90000 +a=rtpmap:270 xyz/90000 +a=fmtp:270 apt=100 +a=ssrc:916070099 msid:2e3ca9ff-0c7e-4b9d-9471-2ce80de74b84 8c1b020b-e6ab-4002-8450-b816ebff0219 + )"); + + auto unsupportedAudioSdp = std::string(R"(m=audio 9 UDP/TLS/RTP/SAVPF 500 +c=IN IP4 0.0.0.0 +a=rtcp:9 IN IP4 0.0.0.0 +a=ice-ufrag:tEm4 +a=ice-pwd:MHYra0wZc3cAECKFPlnoRpon +a=ice-options:trickle +a=fingerprint:sha-256 87:E6:EC:59:93:76:9F:42:7D:15:17:F6:8F:C4:29:AB:EA:3F:28:B6:DF:F8:14:2F:96:62:2F:16:98:F5:76:E5 +a=setup:actpass +a=mid:0 +a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level +a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time +a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01 +a=sendrecv +a=msid:2e3ca9ff-0c7e-4b9d-9471-2ce80de74b84 757d07a0-892a-46e7-a13d-b43fc3ef68c7 +a=rtcp-mux +a=rtpmap:111 opus/48000/2 +a=rtcp-fb:111 transport-cc +a=fmtp:111 minptime=10;useinbandfec=1 +a=ssrc:331864867 cname:jyxeGEm09Qe6m8dq +a=ssrc:331864867 msid:2e3ca9ff-0c7e-4b9d-9471-2ce80de74b84 757d07a0-892a-46e7-a13d-b43fc3ef68c7 + )"); + + offerBase += "\n"; + offerBase += audioSdp; + offerBase += "\n"; + offerBase += videoSdp; + offerBase += "\n"; + offerBase += unsupportedVideoSdp; + offerBase += "\n"; + offerBase += unsupportedVideoSdp; + offerBase += "\n"; + offerBase += unsupportedAudioSdp; + offerBase += "\n"; + + assertLFAndCRLF((PCHAR) offerBase.c_str(), offerBase.size(), [](PCHAR sdp) { + RtcConfiguration configuration{}; + PRtcPeerConnection pRtcPeerConnection = nullptr; + RtcMediaStreamTrack track1{}; + RtcMediaStreamTrack track2{}; + PRtcRtpTransceiver transceiver1 = nullptr; + PRtcRtpTransceiver transceiver2 = nullptr; + RtcSessionDescriptionInit offerSdp{}; + RtcSessionDescriptionInit answerSdp{}; + + SNPRINTF(configuration.iceServers[0].urls, MAX_ICE_CONFIG_URI_LEN, KINESIS_VIDEO_STUN_URL, TEST_DEFAULT_REGION, TEST_DEFAULT_STUN_URL_POSTFIX); + + track1.kind = MEDIA_STREAM_TRACK_KIND_AUDIO; + track1.codec = RTC_CODEC_OPUS; + STRNCPY(track1.streamId, "audioStream1", MAX_MEDIA_STREAM_ID_LEN); + STRNCPY(track1.trackId, "audioTrack1", MAX_MEDIA_STREAM_TRACK_ID_LEN); + + track2.kind = MEDIA_STREAM_TRACK_KIND_AUDIO; + track2.codec = RTC_CODEC_OPUS; + STRNCPY(track2.streamId, "videoStream1", MAX_MEDIA_STREAM_ID_LEN); + STRNCPY(track2.trackId, "videoTrack1", MAX_MEDIA_STREAM_TRACK_ID_LEN); + + offerSdp.type = SDP_TYPE_OFFER; + STRNCPY(offerSdp.sdp, (PCHAR) sdp, MAX_SESSION_DESCRIPTION_INIT_SDP_LEN); + + EXPECT_EQ(STATUS_SUCCESS, createPeerConnection(&configuration, &pRtcPeerConnection)); + EXPECT_EQ(STATUS_SUCCESS, addSupportedCodec(pRtcPeerConnection, RTC_CODEC_OPUS)); + EXPECT_EQ(STATUS_SUCCESS, addSupportedCodec(pRtcPeerConnection, RTC_CODEC_H264_PROFILE_42E01F_LEVEL_ASYMMETRY_ALLOWED_PACKETIZATION_MODE)); + + EXPECT_EQ(STATUS_SUCCESS, addTransceiver(pRtcPeerConnection, &track1, nullptr, &transceiver1)); + EXPECT_EQ(STATUS_SUCCESS, addTransceiver(pRtcPeerConnection, &track2, nullptr, &transceiver2)); + + EXPECT_EQ(STATUS_SUCCESS, setRemoteDescription(pRtcPeerConnection, &offerSdp)); + EXPECT_EQ(STATUS_SUCCESS, createAnswer(pRtcPeerConnection, &answerSdp)); + + std::regex pattern("m=[^\\s]*"); + std::string answerSdpString = std::string(answerSdp.sdp); + auto words_begin = std::sregex_iterator(answerSdpString.begin(), answerSdpString.end(), pattern); + auto words_end = std::sregex_iterator(); + + int count = std::distance(words_begin, words_end); + EXPECT_EQ(count, 5); + EXPECT_PRED_FORMAT2(testing::IsSubstring, "fakeStream", answerSdp.sdp); + EXPECT_PRED_FORMAT2(testing::IsSubstring, "fakeTrack", answerSdp.sdp); + closePeerConnection(pRtcPeerConnection); + EXPECT_EQ(STATUS_SUCCESS, freePeerConnection(&pRtcPeerConnection)); + }); + + offerBase += unsupportedAudioSdp; + offerBase += "\n"; + + assertLFAndCRLF((PCHAR) offerBase.c_str(), offerBase.size(), [](PCHAR sdp) { + RtcConfiguration configuration{}; + PRtcPeerConnection pRtcPeerConnection = nullptr; + RtcSessionDescriptionInit offerSdp{}; + + SNPRINTF(configuration.iceServers[0].urls, MAX_ICE_CONFIG_URI_LEN, KINESIS_VIDEO_STUN_URL, TEST_DEFAULT_REGION, TEST_DEFAULT_STUN_URL_POSTFIX); + + offerSdp.type = SDP_TYPE_OFFER; + STRNCPY(offerSdp.sdp, (PCHAR) sdp, MAX_SESSION_DESCRIPTION_INIT_SDP_LEN); + + EXPECT_EQ(STATUS_SUCCESS, createPeerConnection(&configuration, &pRtcPeerConnection)); + + EXPECT_EQ(STATUS_SESSION_DESCRIPTION_MAX_MEDIA_COUNT, setRemoteDescription(pRtcPeerConnection, &offerSdp)); + closePeerConnection(pRtcPeerConnection); + EXPECT_EQ(STATUS_SUCCESS, freePeerConnection(&pRtcPeerConnection)); + }); +} + + // if offer (remote) contains video m-line only then answer (local) should contain video m-line only // even if local side has other transceivers, i.e. audio TEST_F(SdpApiTest, offerMediaMultipleDirections_validateAnswerCorrectMatchingDirections)