diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4061ca5e73..b378784d21 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: runs-on: macos-12 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 }} @@ -84,9 +84,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 }} @@ -112,9 +112,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 }} @@ -140,9 +140,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 }} @@ -175,9 +175,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 }} @@ -206,7 +206,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 @@ -231,9 +231,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 }} @@ -263,9 +263,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 }} @@ -304,7 +304,7 @@ jobs: contents: read steps: - name: Clone repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install dependencies run: | apk update @@ -324,9 +324,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 }} @@ -360,9 +360,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 }} @@ -396,9 +396,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 }} @@ -429,9 +429,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 }} @@ -512,9 +512,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 }} @@ -542,9 +542,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 }} @@ -592,7 +592,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: | @@ -625,7 +625,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' @@ -643,7 +643,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' @@ -661,7 +661,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/.github/workflows/pr-desc-lint.yml b/.github/workflows/pr-desc-lint.yml index 73c32d71c3..ee2c429ff1 100644 --- a/.github/workflows/pr-desc-lint.yml +++ b/.github/workflows/pr-desc-lint.yml @@ -23,34 +23,43 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - pr_description=$(gh pr view https://github.com/${GITHUB_REPOSITORY}/pull/${{ github.event.pull_request.number }} --json body -q ".body") - error_occurred=0 + pr_link="https://github.com/${GITHUB_REPOSITORY}/pull/${{ github.event.pull_request.number }}" + + echo "$pr_link" + + pr_description=$(gh pr view $pr_link --json body -q ".body") + + if [ -z "$pr_description" ]; then + echo "Failed to fetch the PR description" + exit 1 + fi + # Define minimum character count for each section - MIN_CHARS=25 + MIN_CHARS=10 # Extract contents - # Extract contents - what_changed=$(echo "$pr_description" | sed -n -e '/\*What was changed?\*/,/\*/p' | sed '$d' | sed '1d') - why_changed=$(echo "$pr_description" | sed -n -e '/\*Why was it changed?\*/,/\*/p' | sed '$d' | sed '1d') - how_changed=$(echo "$pr_description" | sed -n -e '/\*How was it changed?\*/,/\*/p' | sed '$d' | sed '1d') - testing_done=$(echo "$pr_description" | sed -n -e '/\*What testing was done for the changes?\*/,/\*/p' | sed '$d' | sed '1d') - + what_changed=$(echo "$pr_description" | sed -n -e '/\*What was changed?\*/,/\*Why was it changed?\*/p' | sed '$d' | sed '1d') + why_changed=$(echo "$pr_description" | sed -n -e '/\*Why was it changed?\*/,/\*How was it changed?\*/p' | sed '$d' | sed '1d') + how_changed=$(echo "$pr_description" | sed -n -e '/\*How was it changed?\*/,/\*What testing was done for the changes?\*/p' | sed '$d' | sed '1d') + testing_done=$(echo "$pr_description" | sed -n -e '/\*What testing was done for the changes?\*/,/By submitting this pull request/p' | sed '$d' | sed '1d') + + error_occurred=0 if [[ ${#what_changed} -lt $MIN_CHARS ]]; then - echo "PR description for what changed section is either missing or too short." + echo "PR description for what changed section is either missing or too short. Required: ${MIN_CHARS}, Current: ${what_changed}" error_occurred=1 fi if [[ ${#why_changed} -lt $MIN_CHARS ]]; then - echo "PR description for why it changed section is either missing or too short." + echo "PR description for why it changed section is either missing or too short. Required: ${MIN_CHARS}, Current: ${why_changed}" error_occurred=1 fi if [[ ${#how_changed} -lt $MIN_CHARS ]]; then - echo "PR description for how was it changed section is either missing or too short." + echo "PR description for how was it changed section is either missing or too short. Required: ${MIN_CHARS}, Current: ${how_changed}" error_occurred=1 fi if [[ ${#testing_done} -lt $MIN_CHARS ]]; then - echo "PR description for testing section are either missing or too short." + echo "PR description for testing section are either missing or too short. Required: ${MIN_CHARS}, Current: ${testing_done}" error_occurred=1 fi if [[ $error_occurred -eq 1 ]]; then - exit 1 + exit 1 fi diff --git a/src/include/com/amazonaws/kinesis/video/webrtcclient/Include.h b/src/include/com/amazonaws/kinesis/video/webrtcclient/Include.h index db7ec16d68..8e4a239001 100644 --- a/src/include/com/amazonaws/kinesis/video/webrtcclient/Include.h +++ b/src/include/com/amazonaws/kinesis/video/webrtcclient/Include.h @@ -836,7 +836,6 @@ typedef enum { RTC_CODEC_ALAW = 5, //!< ALAW audio codec RTC_CODEC_UNKNOWN = 6, RTC_CODEC_H265 = 7, //!< H265 video codec - // 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 0c86fb7d40..f9e78b8748 100644 --- a/src/source/Ice/IceAgent.c +++ b/src/source/Ice/IceAgent.c @@ -90,7 +90,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 9df02023f6..b3e0de03e4 100644 --- a/src/source/PeerConnection/SessionDescription.c +++ b/src/source/PeerConnection/SessionDescription.c @@ -958,6 +958,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)); @@ -982,8 +983,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 @@ -1184,18 +1189,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; @@ -1244,7 +1252,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 @@ -1261,7 +1268,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) { @@ -1324,7 +1330,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)); @@ -1339,7 +1344,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 ce9a81d5f9..63b88ec7fc 100644 --- a/src/source/PeerConnection/SessionDescription.h +++ b/src/source/PeerConnection/SessionDescription.h @@ -83,6 +83,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 dbb5547629..2ac27c316b 100644 --- a/tst/SdpApiTest.cpp +++ b/tst/SdpApiTest.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "WebRTCClientTestFixture.h" @@ -647,7 +648,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)); }); } @@ -760,6 +761,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)