summaryrefslogtreecommitdiffstats
path: root/dom/media/webrtc
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/webrtc')
-rw-r--r--dom/media/webrtc/CubebDeviceEnumerator.cpp334
-rw-r--r--dom/media/webrtc/CubebDeviceEnumerator.h87
-rw-r--r--dom/media/webrtc/MediaEngine.h66
-rw-r--r--dom/media/webrtc/MediaEngineFake.cpp653
-rw-r--r--dom/media/webrtc/MediaEngineFake.h40
-rw-r--r--dom/media/webrtc/MediaEnginePrefs.h101
-rw-r--r--dom/media/webrtc/MediaEngineRemoteVideoSource.cpp907
-rw-r--r--dom/media/webrtc/MediaEngineRemoteVideoSource.h242
-rw-r--r--dom/media/webrtc/MediaEngineSource.cpp69
-rw-r--r--dom/media/webrtc/MediaEngineSource.h255
-rw-r--r--dom/media/webrtc/MediaEngineWebRTC.cpp299
-rw-r--r--dom/media/webrtc/MediaEngineWebRTC.h53
-rw-r--r--dom/media/webrtc/MediaEngineWebRTCAudio.cpp1329
-rw-r--r--dom/media/webrtc/MediaEngineWebRTCAudio.h295
-rw-r--r--dom/media/webrtc/MediaTrackConstraints.cpp560
-rw-r--r--dom/media/webrtc/MediaTrackConstraints.h371
-rw-r--r--dom/media/webrtc/MediaTransportChild.h38
-rw-r--r--dom/media/webrtc/MediaTransportParent.h69
-rw-r--r--dom/media/webrtc/PMediaTransport.ipdl105
-rw-r--r--dom/media/webrtc/PWebrtcGlobal.ipdl41
-rw-r--r--dom/media/webrtc/PeerIdentity.cpp80
-rw-r--r--dom/media/webrtc/PeerIdentity.h70
-rw-r--r--dom/media/webrtc/RTCCertificate.cpp438
-rw-r--r--dom/media/webrtc/RTCCertificate.h98
-rw-r--r--dom/media/webrtc/RTCIdentityProviderRegistrar.cpp70
-rw-r--r--dom/media/webrtc/RTCIdentityProviderRegistrar.h59
-rw-r--r--dom/media/webrtc/SineWaveGenerator.h58
-rw-r--r--dom/media/webrtc/WebrtcGlobal.h506
-rw-r--r--dom/media/webrtc/WebrtcIPCTraits.h89
-rw-r--r--dom/media/webrtc/common/CandidateInfo.h27
-rw-r--r--dom/media/webrtc/common/CommonTypes.h52
-rw-r--r--dom/media/webrtc/common/EncodingConstraints.h58
-rw-r--r--dom/media/webrtc/common/MediaEngineWrapper.h32
-rw-r--r--dom/media/webrtc/common/NullDeleter.h14
-rw-r--r--dom/media/webrtc/common/NullTransport.h56
-rw-r--r--dom/media/webrtc/common/Wrapper.h157
-rw-r--r--dom/media/webrtc/common/YuvStamper.cpp394
-rw-r--r--dom/media/webrtc/common/YuvStamper.h77
-rw-r--r--dom/media/webrtc/common/browser_logging/CSFLog.cpp85
-rw-r--r--dom/media/webrtc/common/browser_logging/CSFLog.h58
-rw-r--r--dom/media/webrtc/common/browser_logging/WebRtcLog.cpp162
-rw-r--r--dom/media/webrtc/common/browser_logging/WebRtcLog.h17
-rw-r--r--dom/media/webrtc/common/csf_common.h90
-rw-r--r--dom/media/webrtc/common/moz.build23
-rw-r--r--dom/media/webrtc/common/time_profiling/timecard.c112
-rw-r--r--dom/media/webrtc/common/time_profiling/timecard.h74
-rw-r--r--dom/media/webrtc/jsapi/MediaTransportHandler.cpp1727
-rw-r--r--dom/media/webrtc/jsapi/MediaTransportHandler.h167
-rw-r--r--dom/media/webrtc/jsapi/MediaTransportHandlerIPC.cpp414
-rw-r--r--dom/media/webrtc/jsapi/MediaTransportHandlerIPC.h96
-rw-r--r--dom/media/webrtc/jsapi/MediaTransportParent.cpp240
-rw-r--r--dom/media/webrtc/jsapi/PacketDumper.cpp124
-rw-r--r--dom/media/webrtc/jsapi/PacketDumper.h52
-rw-r--r--dom/media/webrtc/jsapi/PeerConnectionCtx.cpp650
-rw-r--r--dom/media/webrtc/jsapi/PeerConnectionCtx.h194
-rw-r--r--dom/media/webrtc/jsapi/PeerConnectionImpl.cpp4640
-rw-r--r--dom/media/webrtc/jsapi/PeerConnectionImpl.h969
-rw-r--r--dom/media/webrtc/jsapi/RTCDTMFSender.cpp159
-rw-r--r--dom/media/webrtc/jsapi/RTCDTMFSender.h78
-rw-r--r--dom/media/webrtc/jsapi/RTCDtlsTransport.cpp69
-rw-r--r--dom/media/webrtc/jsapi/RTCDtlsTransport.h43
-rw-r--r--dom/media/webrtc/jsapi/RTCRtpReceiver.cpp942
-rw-r--r--dom/media/webrtc/jsapi/RTCRtpReceiver.h198
-rw-r--r--dom/media/webrtc/jsapi/RTCRtpSender.cpp1654
-rw-r--r--dom/media/webrtc/jsapi/RTCRtpSender.h260
-rw-r--r--dom/media/webrtc/jsapi/RTCRtpTransceiver.cpp1080
-rw-r--r--dom/media/webrtc/jsapi/RTCRtpTransceiver.h243
-rw-r--r--dom/media/webrtc/jsapi/RTCSctpTransport.cpp53
-rw-r--r--dom/media/webrtc/jsapi/RTCSctpTransport.h65
-rw-r--r--dom/media/webrtc/jsapi/RTCStatsIdGenerator.cpp90
-rw-r--r--dom/media/webrtc/jsapi/RTCStatsIdGenerator.h42
-rw-r--r--dom/media/webrtc/jsapi/RTCStatsReport.cpp213
-rw-r--r--dom/media/webrtc/jsapi/RTCStatsReport.h205
-rw-r--r--dom/media/webrtc/jsapi/RemoteTrackSource.cpp73
-rw-r--r--dom/media/webrtc/jsapi/RemoteTrackSource.h64
-rw-r--r--dom/media/webrtc/jsapi/WebrtcGlobalChild.h42
-rw-r--r--dom/media/webrtc/jsapi/WebrtcGlobalInformation.cpp829
-rw-r--r--dom/media/webrtc/jsapi/WebrtcGlobalInformation.h102
-rw-r--r--dom/media/webrtc/jsapi/WebrtcGlobalParent.h52
-rw-r--r--dom/media/webrtc/jsapi/WebrtcGlobalStatsHistory.cpp282
-rw-r--r--dom/media/webrtc/jsapi/WebrtcGlobalStatsHistory.h88
-rw-r--r--dom/media/webrtc/jsapi/moz.build51
-rw-r--r--dom/media/webrtc/jsep/JsepCodecDescription.h1196
-rw-r--r--dom/media/webrtc/jsep/JsepSession.h287
-rw-r--r--dom/media/webrtc/jsep/JsepSessionImpl.cpp2457
-rw-r--r--dom/media/webrtc/jsep/JsepSessionImpl.h286
-rw-r--r--dom/media/webrtc/jsep/JsepTrack.cpp670
-rw-r--r--dom/media/webrtc/jsep/JsepTrack.h305
-rw-r--r--dom/media/webrtc/jsep/JsepTrackEncoding.h62
-rw-r--r--dom/media/webrtc/jsep/JsepTransceiver.h220
-rw-r--r--dom/media/webrtc/jsep/JsepTransport.h102
-rw-r--r--dom/media/webrtc/jsep/SsrcGenerator.cpp22
-rw-r--r--dom/media/webrtc/jsep/SsrcGenerator.h20
-rw-r--r--dom/media/webrtc/jsep/moz.build18
-rw-r--r--dom/media/webrtc/libwebrtcglue/AudioConduit.cpp975
-rw-r--r--dom/media/webrtc/libwebrtcglue/AudioConduit.h303
-rw-r--r--dom/media/webrtc/libwebrtcglue/CallWorkerThread.h116
-rw-r--r--dom/media/webrtc/libwebrtcglue/CodecConfig.h237
-rw-r--r--dom/media/webrtc/libwebrtcglue/GmpVideoCodec.cpp22
-rw-r--r--dom/media/webrtc/libwebrtcglue/GmpVideoCodec.h27
-rw-r--r--dom/media/webrtc/libwebrtcglue/MediaConduitControl.h77
-rw-r--r--dom/media/webrtc/libwebrtcglue/MediaConduitErrors.h46
-rw-r--r--dom/media/webrtc/libwebrtcglue/MediaConduitInterface.cpp151
-rw-r--r--dom/media/webrtc/libwebrtcglue/MediaConduitInterface.h495
-rw-r--r--dom/media/webrtc/libwebrtcglue/MediaDataCodec.cpp70
-rw-r--r--dom/media/webrtc/libwebrtcglue/MediaDataCodec.h32
-rw-r--r--dom/media/webrtc/libwebrtcglue/RtpRtcpConfig.h24
-rw-r--r--dom/media/webrtc/libwebrtcglue/RunningStat.h48
-rw-r--r--dom/media/webrtc/libwebrtcglue/SystemTime.cpp60
-rw-r--r--dom/media/webrtc/libwebrtcglue/SystemTime.h44
-rw-r--r--dom/media/webrtc/libwebrtcglue/TaskQueueWrapper.h182
-rw-r--r--dom/media/webrtc/libwebrtcglue/VideoConduit.cpp1902
-rw-r--r--dom/media/webrtc/libwebrtcglue/VideoConduit.h494
-rw-r--r--dom/media/webrtc/libwebrtcglue/VideoStreamFactory.cpp387
-rw-r--r--dom/media/webrtc/libwebrtcglue/VideoStreamFactory.h132
-rw-r--r--dom/media/webrtc/libwebrtcglue/WebrtcCallWrapper.cpp105
-rw-r--r--dom/media/webrtc/libwebrtcglue/WebrtcCallWrapper.h114
-rw-r--r--dom/media/webrtc/libwebrtcglue/WebrtcGmpVideoCodec.cpp1043
-rw-r--r--dom/media/webrtc/libwebrtcglue/WebrtcGmpVideoCodec.h507
-rw-r--r--dom/media/webrtc/libwebrtcglue/WebrtcImageBuffer.h53
-rw-r--r--dom/media/webrtc/libwebrtcglue/WebrtcMediaDataDecoderCodec.cpp209
-rw-r--r--dom/media/webrtc/libwebrtcglue/WebrtcMediaDataDecoderCodec.h70
-rw-r--r--dom/media/webrtc/libwebrtcglue/WebrtcMediaDataEncoderCodec.cpp518
-rw-r--r--dom/media/webrtc/libwebrtcglue/WebrtcMediaDataEncoderCodec.h78
-rw-r--r--dom/media/webrtc/libwebrtcglue/WebrtcVideoCodecFactory.cpp139
-rw-r--r--dom/media/webrtc/libwebrtcglue/WebrtcVideoCodecFactory.h124
-rw-r--r--dom/media/webrtc/libwebrtcglue/moz.build35
-rw-r--r--dom/media/webrtc/metrics.yaml358
-rw-r--r--dom/media/webrtc/moz.build132
-rw-r--r--dom/media/webrtc/sdp/HybridSdpParser.cpp88
-rw-r--r--dom/media/webrtc/sdp/HybridSdpParser.h38
-rw-r--r--dom/media/webrtc/sdp/ParsingResultComparer.cpp331
-rw-r--r--dom/media/webrtc/sdp/ParsingResultComparer.h57
-rw-r--r--dom/media/webrtc/sdp/RsdparsaSdp.cpp126
-rw-r--r--dom/media/webrtc/sdp/RsdparsaSdp.h72
-rw-r--r--dom/media/webrtc/sdp/RsdparsaSdpAttributeList.cpp1301
-rw-r--r--dom/media/webrtc/sdp/RsdparsaSdpAttributeList.h157
-rw-r--r--dom/media/webrtc/sdp/RsdparsaSdpGlue.cpp106
-rw-r--r--dom/media/webrtc/sdp/RsdparsaSdpGlue.h36
-rw-r--r--dom/media/webrtc/sdp/RsdparsaSdpInc.h510
-rw-r--r--dom/media/webrtc/sdp/RsdparsaSdpMediaSection.cpp253
-rw-r--r--dom/media/webrtc/sdp/RsdparsaSdpMediaSection.h71
-rw-r--r--dom/media/webrtc/sdp/RsdparsaSdpParser.cpp73
-rw-r--r--dom/media/webrtc/sdp/RsdparsaSdpParser.h34
-rw-r--r--dom/media/webrtc/sdp/Sdp.h166
-rw-r--r--dom/media/webrtc/sdp/SdpAttribute.cpp1562
-rw-r--r--dom/media/webrtc/sdp/SdpAttribute.h1907
-rw-r--r--dom/media/webrtc/sdp/SdpAttributeList.h90
-rw-r--r--dom/media/webrtc/sdp/SdpEnum.h64
-rw-r--r--dom/media/webrtc/sdp/SdpHelper.cpp801
-rw-r--r--dom/media/webrtc/sdp/SdpHelper.h109
-rw-r--r--dom/media/webrtc/sdp/SdpLog.cpp68
-rw-r--r--dom/media/webrtc/sdp/SdpLog.h17
-rw-r--r--dom/media/webrtc/sdp/SdpMediaSection.cpp197
-rw-r--r--dom/media/webrtc/sdp/SdpMediaSection.h317
-rw-r--r--dom/media/webrtc/sdp/SdpParser.h81
-rw-r--r--dom/media/webrtc/sdp/SdpPref.cpp107
-rw-r--r--dom/media/webrtc/sdp/SdpPref.h82
-rw-r--r--dom/media/webrtc/sdp/SdpTelemetry.cpp63
-rw-r--r--dom/media/webrtc/sdp/SdpTelemetry.h43
-rw-r--r--dom/media/webrtc/sdp/SipccSdp.cpp173
-rw-r--r--dom/media/webrtc/sdp/SipccSdp.h82
-rw-r--r--dom/media/webrtc/sdp/SipccSdpAttributeList.cpp1386
-rw-r--r--dom/media/webrtc/sdp/SipccSdpAttributeList.h145
-rw-r--r--dom/media/webrtc/sdp/SipccSdpMediaSection.cpp401
-rw-r--r--dom/media/webrtc/sdp/SipccSdpMediaSection.h101
-rw-r--r--dom/media/webrtc/sdp/SipccSdpParser.cpp88
-rw-r--r--dom/media/webrtc/sdp/SipccSdpParser.h35
-rw-r--r--dom/media/webrtc/sdp/moz.build48
-rw-r--r--dom/media/webrtc/sdp/rsdparsa_capi/Cargo.toml12
-rw-r--r--dom/media/webrtc/sdp/rsdparsa_capi/src/attribute.rs1472
-rw-r--r--dom/media/webrtc/sdp/rsdparsa_capi/src/lib.rs298
-rw-r--r--dom/media/webrtc/sdp/rsdparsa_capi/src/media_section.rs233
-rw-r--r--dom/media/webrtc/sdp/rsdparsa_capi/src/network.rs266
-rw-r--r--dom/media/webrtc/sdp/rsdparsa_capi/src/types.rs199
-rw-r--r--dom/media/webrtc/tests/crashtests/1770075.html8
-rw-r--r--dom/media/webrtc/tests/crashtests/1789908.html25
-rw-r--r--dom/media/webrtc/tests/crashtests/1799168.html16
-rw-r--r--dom/media/webrtc/tests/crashtests/1816708.html21
-rw-r--r--dom/media/webrtc/tests/crashtests/1821477.html16
-rw-r--r--dom/media/webrtc/tests/crashtests/crashtests.list7
-rw-r--r--dom/media/webrtc/tests/fuzztests/moz.build22
-rw-r--r--dom/media/webrtc/tests/fuzztests/sdp_parser_libfuzz.cpp30
-rw-r--r--dom/media/webrtc/tests/mochitests/NetworkPreparationChromeScript.js43
-rw-r--r--dom/media/webrtc/tests/mochitests/addTurnsSelfsignedCert.js32
-rw-r--r--dom/media/webrtc/tests/mochitests/blacksilence.js134
-rw-r--r--dom/media/webrtc/tests/mochitests/dataChannel.js352
-rw-r--r--dom/media/webrtc/tests/mochitests/head.js1445
-rw-r--r--dom/media/webrtc/tests/mochitests/helpers_from_wpt/sdp.js889
-rw-r--r--dom/media/webrtc/tests/mochitests/iceTestUtils.js302
-rw-r--r--dom/media/webrtc/tests/mochitests/identity/identityPcTest.js79
-rw-r--r--dom/media/webrtc/tests/mochitests/identity/idp-bad.js1
-rw-r--r--dom/media/webrtc/tests/mochitests/identity/idp-min.js24
-rw-r--r--dom/media/webrtc/tests/mochitests/identity/idp-redirect-http-trick.js3
-rw-r--r--dom/media/webrtc/tests/mochitests/identity/idp-redirect-http-trick.js^headers^2
-rw-r--r--dom/media/webrtc/tests/mochitests/identity/idp-redirect-http.js3
-rw-r--r--dom/media/webrtc/tests/mochitests/identity/idp-redirect-http.js^headers^2
-rw-r--r--dom/media/webrtc/tests/mochitests/identity/idp-redirect-https-double.js3
-rw-r--r--dom/media/webrtc/tests/mochitests/identity/idp-redirect-https-double.js^headers^2
-rw-r--r--dom/media/webrtc/tests/mochitests/identity/idp-redirect-https-odd-path.js3
-rw-r--r--dom/media/webrtc/tests/mochitests/identity/idp-redirect-https-odd-path.js^headers^2
-rw-r--r--dom/media/webrtc/tests/mochitests/identity/idp-redirect-https.js3
-rw-r--r--dom/media/webrtc/tests/mochitests/identity/idp-redirect-https.js^headers^2
-rw-r--r--dom/media/webrtc/tests/mochitests/identity/idp.js119
-rw-r--r--dom/media/webrtc/tests/mochitests/identity/idp.sjs18
-rw-r--r--dom/media/webrtc/tests/mochitests/identity/login.html31
-rw-r--r--dom/media/webrtc/tests/mochitests/identity/mochitest.ini47
-rw-r--r--dom/media/webrtc/tests/mochitests/identity/test_fingerprints.html91
-rw-r--r--dom/media/webrtc/tests/mochitests/identity/test_getIdentityAssertion.html101
-rw-r--r--dom/media/webrtc/tests/mochitests/identity/test_idpproxy.html178
-rw-r--r--dom/media/webrtc/tests/mochitests/identity/test_loginNeeded.html72
-rw-r--r--dom/media/webrtc/tests/mochitests/identity/test_peerConnection_asymmetricIsolation.html31
-rw-r--r--dom/media/webrtc/tests/mochitests/identity/test_peerConnection_peerIdentity.html21
-rw-r--r--dom/media/webrtc/tests/mochitests/identity/test_setIdentityProvider.html67
-rw-r--r--dom/media/webrtc/tests/mochitests/identity/test_setIdentityProviderWithErrors.html57
-rw-r--r--dom/media/webrtc/tests/mochitests/mediaStreamPlayback.js241
-rw-r--r--dom/media/webrtc/tests/mochitests/mochitest.ini65
-rw-r--r--dom/media/webrtc/tests/mochitests/mochitest_datachannel.ini52
-rw-r--r--dom/media/webrtc/tests/mochitests/mochitest_getusermedia.ini105
-rw-r--r--dom/media/webrtc/tests/mochitests/mochitest_peerconnection.ini311
-rw-r--r--dom/media/webrtc/tests/mochitests/network.js16
-rw-r--r--dom/media/webrtc/tests/mochitests/nonTrickleIce.js97
-rw-r--r--dom/media/webrtc/tests/mochitests/parser_rtp.js131
-rw-r--r--dom/media/webrtc/tests/mochitests/pc.js2495
-rw-r--r--dom/media/webrtc/tests/mochitests/peerconnection_audio_forced_sample_rate.js32
-rw-r--r--dom/media/webrtc/tests/mochitests/sdpUtils.js398
-rw-r--r--dom/media/webrtc/tests/mochitests/simulcast.js232
-rw-r--r--dom/media/webrtc/tests/mochitests/stats.js1596
-rw-r--r--dom/media/webrtc/tests/mochitests/templates.js615
-rw-r--r--dom/media/webrtc/tests/mochitests/test_1488832.html37
-rw-r--r--dom/media/webrtc/tests/mochitests/test_1717318.html26
-rw-r--r--dom/media/webrtc/tests/mochitests/test_a_noOp.html32
-rw-r--r--dom/media/webrtc/tests/mochitests/test_dataChannel_basicAudio.html25
-rw-r--r--dom/media/webrtc/tests/mochitests/test_dataChannel_basicAudioVideo.html26
-rw-r--r--dom/media/webrtc/tests/mochitests/test_dataChannel_basicAudioVideoCombined.html26
-rw-r--r--dom/media/webrtc/tests/mochitests/test_dataChannel_basicAudioVideoNoBundle.html27
-rw-r--r--dom/media/webrtc/tests/mochitests/test_dataChannel_basicDataOnly.html24
-rw-r--r--dom/media/webrtc/tests/mochitests/test_dataChannel_basicVideo.html25
-rw-r--r--dom/media/webrtc/tests/mochitests/test_dataChannel_bug1013809.html27
-rw-r--r--dom/media/webrtc/tests/mochitests/test_dataChannel_dataOnlyBufferedAmountLow.html25
-rw-r--r--dom/media/webrtc/tests/mochitests/test_dataChannel_dtlsVersions.html38
-rw-r--r--dom/media/webrtc/tests/mochitests/test_dataChannel_hostnameObfuscation.html59
-rw-r--r--dom/media/webrtc/tests/mochitests/test_dataChannel_noOffer.html33
-rw-r--r--dom/media/webrtc/tests/mochitests/test_dataChannel_stats.html50
-rw-r--r--dom/media/webrtc/tests/mochitests/test_defaultAudioConstraints.html80
-rw-r--r--dom/media/webrtc/tests/mochitests/test_enumerateDevices.html141
-rw-r--r--dom/media/webrtc/tests/mochitests/test_enumerateDevices_getUserMediaFake.html63
-rw-r--r--dom/media/webrtc/tests/mochitests/test_enumerateDevices_iframe.html28
-rw-r--r--dom/media/webrtc/tests/mochitests/test_enumerateDevices_iframe_pre_gum.html22
-rw-r--r--dom/media/webrtc/tests/mochitests/test_enumerateDevices_legacy.html147
-rw-r--r--dom/media/webrtc/tests/mochitests/test_enumerateDevices_navigation.html54
-rw-r--r--dom/media/webrtc/tests/mochitests/test_fingerprinting_resistance.html112
-rw-r--r--dom/media/webrtc/tests/mochitests/test_forceSampleRate.html23
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_GC_MediaStream.html59
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_active_autoplay.html61
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_addTrackRemoveTrack.html169
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_addtrack_removetrack_events.html110
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_audioCapture.html104
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_audioConstraints.html93
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_audioConstraints_concurrentIframes.html157
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_audioConstraints_concurrentStreams.html123
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_basicAudio.html27
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_basicAudio_loopback.html99
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_basicScreenshare.html260
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_basicTabshare.html67
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_basicVideo.html30
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_basicVideoAudio.html30
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_basicVideo_playAfterLoadedmetadata.html42
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_basicWindowshare.html39
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_bug1223696.html54
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_callbacks.html35
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_constraints.html166
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_cubebDisabled.html42
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_cubebDisabledFakeStreams.html43
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_getTrackById.html50
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_gumWithinGum.html38
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_loadedmetadata.html39
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaElementCapture_audio.html116
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaElementCapture_tracks.html179
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaElementCapture_video.html91
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaStreamClone.html258
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaStreamConstructors.html171
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaStreamTrackClone.html170
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_nonDefaultRate.html37
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_peerIdentity.html51
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_permission.html104
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_permission_iframe.html30
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_playAudioTwice.html25
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_playVideoAudioTwice.html26
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_playVideoTwice.html26
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_scarySources.html51
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_spinEventLoop.html28
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_trackCloneCleanup.html32
-rw-r--r--dom/media/webrtc/tests/mochitests/test_getUserMedia_trackEnded.html68
-rw-r--r--dom/media/webrtc/tests/mochitests/test_groupId.html53
-rw-r--r--dom/media/webrtc/tests/mochitests/test_multi_mics.html66
-rw-r--r--dom/media/webrtc/tests/mochitests/test_ondevicechange.html180
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_addAudioTrackToExistingVideoStream.html55
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_addDataChannel.html33
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_addDataChannelNoBundle.html44
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_addSecondAudioStream.html45
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_addSecondAudioStreamNoBundle.html53
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_addSecondVideoStream.html53
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_addSecondVideoStreamNoBundle.html60
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_addtrack_removetrack_events.html75
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_answererAddSecondAudioStream.html32
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_audioChannels.html102
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_audioCodecs.html81
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_audioContributingSources.html144
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_audioRenegotiationInactiveAnswer.html69
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_audioSynchronizationSources.html95
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_audioSynchronizationSourcesUnidirectional.html54
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudio.html25
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioDynamicPtMissingRtpmap.html36
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelay.html47
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTCP.html42
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTCPWithStun300.html54
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTLS.html41
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayWithStun300.html53
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATSrflx.html44
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNoisyUDPBlock.html41
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioPcmaPcmuOnly.html39
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioRelayPolicy.html83
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioRequireEOC.html35
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVerifyRtpHeaderExtensions.html63
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideo.html24
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoCombined.html24
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoNoBundle.html25
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoNoBundleNoRtcpMux.html39
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoNoRtcpMux.html38
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoTransceivers.html31
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoVerifyExtmap.html97
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoVerifyExtmapSendonly.html97
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoVerifyTooLongMidFails.html47
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudio_forced_higher_rate.html19
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudio_forced_lower_rate.html19
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_basicH264Video.html26
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_basicScreenshare.html56
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_basicVideo.html23
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_basicVideoVerifyRtpHeaderExtensions.html82
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_basicWindowshare.html25
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_bug1013809.html25
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_bug1042791.html36
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_bug1227781.html37
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_bug1512281.html47
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_bug1773067.html32
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_bug822674.html26
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_bug825703.html140
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_bug827843.html50
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_bug834153.html36
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_callbacks.html86
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_captureStream_canvas_2d.html81
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_captureStream_canvas_2d_noSSRC.html83
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_captureStream_canvas_webgl.html130
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_capturedVideo.html81
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_certificates.html185
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_checkPacketDumpHook.html107
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_close.html134
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_closeDuringIce.html79
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_codecNegotiationFailure.html111
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_constructedStream.html67
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_disabledVideoPreNegotiation.html45
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_encodingsNegotiation.html85
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_errorCallbacks.html55
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_extmapRenegotiation.html325
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_forwarding_basicAudioVideoCombined.html41
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_gatherWithSetConfiguration.html450
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_gatherWithStun300.html269
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_gatherWithStun300IPv6.html283
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_glean.html488
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_iceFailure.html84
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_insertDTMF.html76
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_localReofferRollback.html44
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_localRollback.html47
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_maxFsConstraint.html112
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_multiple_captureStream_canvas_2d.html115
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_noTrickleAnswer.html25
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_noTrickleOffer.html25
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_noTrickleOfferAnswer.html26
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_nonDefaultRate.html200
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_offerRequiresReceiveAudio.html23
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_offerRequiresReceiveVideo.html23
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_offerRequiresReceiveVideoAudio.html23
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_portRestrictions.html63
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_promiseSendOnly.html61
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_recordReceiveTrack.html101
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_relayOnly.html60
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_remoteReofferRollback.html50
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_remoteRollback.html51
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_removeAudioTrack.html57
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_removeThenAddAudioTrack.html87
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_removeThenAddAudioTrackNoBundle.html76
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_removeThenAddVideoTrack.html98
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_removeThenAddVideoTrackNoBundle.html89
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_removeVideoTrack.html64
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_renderAfterRenegotiation.html89
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_replaceNullTrackThenRenegotiateAudio.html53
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_replaceNullTrackThenRenegotiateVideo.html63
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_replaceTrack.html187
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_replaceTrack_camera.html48
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_replaceTrack_disabled.html60
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_replaceTrack_microphone.html46
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_replaceVideoThenRenegotiate.html74
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_restartIce.html41
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceBadAnswer.html58
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalAndRemoteRollback.html82
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalAndRemoteRollbackNoSubsequentRestart.html77
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalRollback.html76
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalRollbackNoSubsequentRestart.html60
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoBundle.html43
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoBundleNoRtcpMux.html44
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoRtcpMux.html43
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_restrictBandwidthTargetBitrate.html29
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_restrictBandwidthWithTias.html30
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_rtcp_rsize.html81
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_scaleResolution.html119
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_scaleResolution_oldSetParameters.html122
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_sender_and_receiver_stats.html73
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_setLocalAnswerInHaveLocalOffer.html34
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_setLocalAnswerInStable.html34
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_setLocalOfferInHaveRemoteOffer.html31
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters.html470
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_maxFramerate.html63
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_maxFramerate_oldSetParameters.html60
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_oldSetParameters.html86
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_scaleResolutionDownBy.html98
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_scaleResolutionDownBy_oldSetParameters.html96
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_setRemoteAnswerInHaveRemoteOffer.html34
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_setRemoteAnswerInStable.html34
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_setRemoteOfferInHaveLocalOffer.html37
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastAnswer.html121
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastAnswer_lowResFirst.html113
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastAnswer_lowResFirst_oldSetParameters.html115
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastAnswer_oldSetParameters.html115
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOddResolution.html183
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOddResolution_oldSetParameters.html172
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOffer.html109
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOffer_lowResFirst.html109
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOffer_lowResFirst_oldSetParameters.html112
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOffer_oldSetParameters.html112
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_stats.html42
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_stats_jitter.html58
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_stats_oneway.html65
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_stats_relayProtocol.html58
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_stereoFmtpPref.html61
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_syncSetDescription.html53
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_telephoneEventFirst.html56
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_threeUnbundledConnections.html134
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_throwInCallbacks.html83
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_toJSON.html39
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_trackDisabling.html108
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_trackDisabling_clones.html162
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_trackless_sender_stats.html56
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioStreams.html23
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioTracksInOneStream.html37
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioVideoStreams.html26
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioVideoStreamsCombined.html70
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioVideoStreamsCombinedNoBundle.html107
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_twoVideoStreams.html23
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_twoVideoTracksInOneStream.html37
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_verifyAudioAfterRenegotiation.html99
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_verifyDescriptions.html58
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_verifyVideoAfterRenegotiation.html123
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_videoCodecs.html142
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_videoRenegotiationInactiveAnswer.html95
-rw-r--r--dom/media/webrtc/tests/mochitests/test_peerConnection_webAudio.html43
-rw-r--r--dom/media/webrtc/tests/mochitests/test_selftest.html37
-rw-r--r--dom/media/webrtc/tests/mochitests/test_setSinkId.html83
-rw-r--r--dom/media/webrtc/tests/mochitests/test_setSinkId_default_addTrack.html52
-rw-r--r--dom/media/webrtc/tests/mochitests/test_setSinkId_preMutedElement.html100
-rw-r--r--dom/media/webrtc/tests/mochitests/test_unfocused_pref.html49
-rw-r--r--dom/media/webrtc/tests/mochitests/turnConfig.js16
-rw-r--r--dom/media/webrtc/third_party_build/README.md17
-rw-r--r--dom/media/webrtc/third_party_build/build_no_op_commits.sh126
-rw-r--r--dom/media/webrtc/third_party_build/commit-build-file-changes.sh47
-rw-r--r--dom/media/webrtc/third_party_build/default_config_env42
-rw-r--r--dom/media/webrtc/third_party_build/detect_upstream_revert.sh93
-rw-r--r--dom/media/webrtc/third_party_build/elm_arcconfig.patch10
-rw-r--r--dom/media/webrtc/third_party_build/elm_rebase.sh247
-rw-r--r--dom/media/webrtc/third_party_build/extract-for-git.py145
-rw-r--r--dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh256
-rw-r--r--dom/media/webrtc/third_party_build/fetch_github_repo.py122
-rw-r--r--dom/media/webrtc/third_party_build/filter_git_changes.py72
-rw-r--r--dom/media/webrtc/third_party_build/gn-configs/README.md16
-rw-r--r--dom/media/webrtc/third_party_build/gn-configs/webrtc.json83
-rw-r--r--dom/media/webrtc/third_party_build/lookup_branch_head.py98
-rw-r--r--dom/media/webrtc/third_party_build/loop-ff.sh236
-rwxr-xr-xdom/media/webrtc/third_party_build/make_upstream_revert_noop.sh101
-rw-r--r--dom/media/webrtc/third_party_build/pre-warmed-milestone.cache1
-rw-r--r--dom/media/webrtc/third_party_build/prep_repo.sh93
-rw-r--r--dom/media/webrtc/third_party_build/push_official_branch.sh50
-rw-r--r--dom/media/webrtc/third_party_build/restore_elm_arcconfig.py27
-rw-r--r--dom/media/webrtc/third_party_build/restore_patch_stack.py107
-rw-r--r--dom/media/webrtc/third_party_build/run_operations.py78
-rw-r--r--dom/media/webrtc/third_party_build/save_patch_stack.py142
-rw-r--r--dom/media/webrtc/third_party_build/update_default_config.sh46
-rw-r--r--dom/media/webrtc/third_party_build/use_config_env.sh89
-rw-r--r--dom/media/webrtc/third_party_build/vendor-libwebrtc.py419
-rw-r--r--dom/media/webrtc/third_party_build/verify_vendoring.sh55
-rw-r--r--dom/media/webrtc/third_party_build/webrtc.mozbuild40
-rw-r--r--dom/media/webrtc/third_party_build/write_default_config.py104
-rw-r--r--dom/media/webrtc/transport/README45
-rw-r--r--dom/media/webrtc/transport/SrtpFlow.cpp259
-rw-r--r--dom/media/webrtc/transport/SrtpFlow.h69
-rw-r--r--dom/media/webrtc/transport/WebrtcTCPSocketWrapper.cpp123
-rw-r--r--dom/media/webrtc/transport/WebrtcTCPSocketWrapper.h69
-rw-r--r--dom/media/webrtc/transport/build/moz.build44
-rw-r--r--dom/media/webrtc/transport/common.build94
-rw-r--r--dom/media/webrtc/transport/dtlsidentity.cpp331
-rw-r--r--dom/media/webrtc/transport/dtlsidentity.h101
-rw-r--r--dom/media/webrtc/transport/fuzztest/moz.build31
-rw-r--r--dom/media/webrtc/transport/fuzztest/stun_parser_libfuzz.cpp35
-rw-r--r--dom/media/webrtc/transport/ipc/NrIceStunAddrMessageUtils.h54
-rw-r--r--dom/media/webrtc/transport/ipc/PStunAddrsParams.h33
-rw-r--r--dom/media/webrtc/transport/ipc/PStunAddrsRequest.ipdl35
-rw-r--r--dom/media/webrtc/transport/ipc/PWebrtcTCPSocket.ipdl42
-rw-r--r--dom/media/webrtc/transport/ipc/StunAddrsRequestChild.cpp45
-rw-r--r--dom/media/webrtc/transport/ipc/StunAddrsRequestChild.h64
-rw-r--r--dom/media/webrtc/transport/ipc/StunAddrsRequestParent.cpp262
-rw-r--r--dom/media/webrtc/transport/ipc/StunAddrsRequestParent.h82
-rw-r--r--dom/media/webrtc/transport/ipc/WebrtcProxyConfig.ipdlh23
-rw-r--r--dom/media/webrtc/transport/ipc/WebrtcTCPSocket.cpp785
-rw-r--r--dom/media/webrtc/transport/ipc/WebrtcTCPSocket.h104
-rw-r--r--dom/media/webrtc/transport/ipc/WebrtcTCPSocketCallback.h28
-rw-r--r--dom/media/webrtc/transport/ipc/WebrtcTCPSocketChild.cpp96
-rw-r--r--dom/media/webrtc/transport/ipc/WebrtcTCPSocketChild.h47
-rw-r--r--dom/media/webrtc/transport/ipc/WebrtcTCPSocketLog.cpp11
-rw-r--r--dom/media/webrtc/transport/ipc/WebrtcTCPSocketLog.h20
-rw-r--r--dom/media/webrtc/transport/ipc/WebrtcTCPSocketParent.cpp122
-rw-r--r--dom/media/webrtc/transport/ipc/WebrtcTCPSocketParent.h59
-rw-r--r--dom/media/webrtc/transport/ipc/moz.build54
-rw-r--r--dom/media/webrtc/transport/logging.h65
-rw-r--r--dom/media/webrtc/transport/m_cpp_utils.h25
-rw-r--r--dom/media/webrtc/transport/mdns_service/Cargo.toml14
-rw-r--r--dom/media/webrtc/transport/mdns_service/mdns_service.h28
-rw-r--r--dom/media/webrtc/transport/mdns_service/src/lib.rs843
-rw-r--r--dom/media/webrtc/transport/mediapacket.cpp145
-rw-r--r--dom/media/webrtc/transport/mediapacket.h117
-rw-r--r--dom/media/webrtc/transport/moz.build23
-rw-r--r--dom/media/webrtc/transport/nr_socket_proxy_config.cpp34
-rw-r--r--dom/media/webrtc/transport/nr_socket_proxy_config.h41
-rw-r--r--dom/media/webrtc/transport/nr_socket_prsock.cpp1785
-rw-r--r--dom/media/webrtc/transport/nr_socket_prsock.h320
-rw-r--r--dom/media/webrtc/transport/nr_socket_tcp.cpp310
-rw-r--r--dom/media/webrtc/transport/nr_socket_tcp.h117
-rw-r--r--dom/media/webrtc/transport/nr_timer.cpp256
-rw-r--r--dom/media/webrtc/transport/nricectx.cpp1106
-rw-r--r--dom/media/webrtc/transport/nricectx.h421
-rw-r--r--dom/media/webrtc/transport/nricemediastream.cpp709
-rw-r--r--dom/media/webrtc/transport/nricemediastream.h225
-rw-r--r--dom/media/webrtc/transport/nriceresolver.cpp234
-rw-r--r--dom/media/webrtc/transport/nriceresolver.h119
-rw-r--r--dom/media/webrtc/transport/nriceresolverfake.cpp174
-rw-r--r--dom/media/webrtc/transport/nriceresolverfake.h137
-rw-r--r--dom/media/webrtc/transport/nricestunaddr.cpp93
-rw-r--r--dom/media/webrtc/transport/nricestunaddr.h36
-rw-r--r--dom/media/webrtc/transport/nrinterfaceprioritizer.cpp258
-rw-r--r--dom/media/webrtc/transport/nrinterfaceprioritizer.h17
-rw-r--r--dom/media/webrtc/transport/rlogconnector.cpp186
-rw-r--r--dom/media/webrtc/transport/rlogconnector.h127
-rw-r--r--dom/media/webrtc/transport/runnable_utils.h222
-rw-r--r--dom/media/webrtc/transport/sigslot.h619
-rw-r--r--dom/media/webrtc/transport/simpletokenbucket.cpp60
-rw-r--r--dom/media/webrtc/transport/simpletokenbucket.h54
-rw-r--r--dom/media/webrtc/transport/srtp/README_MOZILLA7
-rw-r--r--dom/media/webrtc/transport/srtp/moz.build8
-rw-r--r--dom/media/webrtc/transport/stun_socket_filter.cpp432
-rw-r--r--dom/media/webrtc/transport/stun_socket_filter.h41
-rw-r--r--dom/media/webrtc/transport/test/TestSyncRunnable.cpp56
-rw-r--r--dom/media/webrtc/transport/test/buffered_stun_socket_unittest.cpp245
-rw-r--r--dom/media/webrtc/transport/test/dummysocket.h217
-rw-r--r--dom/media/webrtc/transport/test/gtest_ringbuffer_dumper.h78
-rw-r--r--dom/media/webrtc/transport/test/gtest_utils.h201
-rw-r--r--dom/media/webrtc/transport/test/ice_unittest.cpp4400
-rw-r--r--dom/media/webrtc/transport/test/moz.build104
-rw-r--r--dom/media/webrtc/transport/test/mtransport_test_utils.h57
-rw-r--r--dom/media/webrtc/transport/test/multi_tcp_socket_unittest.cpp501
-rw-r--r--dom/media/webrtc/transport/test/nrappkit_unittest.cpp123
-rw-r--r--dom/media/webrtc/transport/test/proxy_tunnel_socket_unittest.cpp277
-rw-r--r--dom/media/webrtc/transport/test/rlogconnector_unittest.cpp255
-rw-r--r--dom/media/webrtc/transport/test/runnable_utils_unittest.cpp353
-rw-r--r--dom/media/webrtc/transport/test/sctp_unittest.cpp381
-rw-r--r--dom/media/webrtc/transport/test/simpletokenbucket_unittest.cpp114
-rw-r--r--dom/media/webrtc/transport/test/sockettransportservice_unittest.cpp181
-rw-r--r--dom/media/webrtc/transport/test/stunserver.cpp652
-rw-r--r--dom/media/webrtc/transport/test/stunserver.h123
-rw-r--r--dom/media/webrtc/transport/test/test_nr_socket_ice_unittest.cpp409
-rw-r--r--dom/media/webrtc/transport/test/test_nr_socket_unittest.cpp800
-rw-r--r--dom/media/webrtc/transport/test/transport_unittests.cpp1400
-rw-r--r--dom/media/webrtc/transport/test/turn_unittest.cpp432
-rw-r--r--dom/media/webrtc/transport/test/webrtcproxychannel_unittest.cpp754
-rw-r--r--dom/media/webrtc/transport/test_nr_socket.cpp1135
-rw-r--r--dom/media/webrtc/transport/test_nr_socket.h370
-rw-r--r--dom/media/webrtc/transport/third_party/moz.build39
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/COPYRIGHT36
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/README74
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/moz.yaml117
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/nicer.gyp276
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/non-unified-build.patch40
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/crypto/nr_crypto.c67
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/crypto/nr_crypto.h52
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_candidate.c1052
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_candidate.h124
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_candidate_pair.c689
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_candidate_pair.h101
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_codeword.h41
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_component.c1786
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_component.h111
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_ctx.c1125
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_ctx.h188
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_handler.h84
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_media_stream.c1087
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_media_stream.h146
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_parser.c564
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_peer_ctx.c875
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_peer_ctx.h101
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_reg.h81
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_socket.c404
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_socket.h98
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/net/local_addr.c70
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/net/local_addr.h62
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/net/nr_interface_prioritizer.c88
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/net/nr_interface_prioritizer.h66
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/net/nr_resolver.c85
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/net/nr_resolver.h96
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/net/nr_socket.c187
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/net/nr_socket.h123
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/net/nr_socket_local.h41
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/net/nr_socket_multi_tcp.c642
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/net/nr_socket_multi_tcp.h53
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/net/nr_socket_wrapper.c84
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/net/nr_socket_wrapper.h63
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/net/transport_addr.c559
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/net/transport_addr.h128
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/net/transport_addr_reg.c230
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/net/transport_addr_reg.h46
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs-bsd.c110
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs-bsd.h13
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs-netlink.c285
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs-netlink.h45
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs-win32.c210
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs-win32.h13
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs.c176
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs.h43
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/stun/nr_socket_buffered_stun.c656
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/stun/nr_socket_buffered_stun.h66
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/stun/nr_socket_turn.c195
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/stun/nr_socket_turn.h48
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/stun/stun.h218
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_build.c611
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_build.h147
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_client_ctx.c888
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_client_ctx.h200
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_codec.c1550
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_codec.h78
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_hint.c245
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_hint.h44
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_msg.c364
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_msg.h208
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_proc.c554
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_proc.h53
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_reg.h58
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_server_ctx.c468
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_server_ctx.h80
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_util.c352
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_util.h62
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/stun/turn_client_ctx.c1277
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/stun/turn_client_ctx.h161
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/util/cb_args.c57
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/util/cb_args.h41
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/util/ice_util.c71
-rw-r--r--dom/media/webrtc/transport/third_party/nICEr/src/util/ice_util.h41
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/COPYRIGHT159
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/README133
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/README_MOZILLA21
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/nrappkit.gyp251
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/event/async_timer.h54
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/event/async_wait.h83
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/event/async_wait_int.h62
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/log/r_log.c696
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/log/r_log.h85
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/plugin/nr_plugin.h57
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/port/android/include/android_funcs.h62
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/port/android/include/csi_platform.h55
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/port/android/include/sys/ttycom.h38
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/port/android/port-impl.mk31
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/port/darwin/include/csi_platform.h57
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/port/generic/include/sys/queue.h562
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/port/linux/include/csi_platform.h55
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/port/linux/include/linux_funcs.h62
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/port/linux/include/sys/ttycom.h38
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/port/linux/port-impl.mk31
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/port/win32/include/csi_platform.h107
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/registry/c2ru.c320
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/registry/c2ru.h96
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/registry/registry.c604
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/registry/registry.h154
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/registry/registry_int.h97
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/registry/registry_local.c1168
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/registry/registry_vtbl.h96
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/registry/registrycb.c440
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/share/nr_api.h51
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/share/nr_common.h108
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/share/nr_reg_keys.h167
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/stats/nrstats.h118
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/byteorder.c73
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/byteorder.h47
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/hex.c109
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/hex.h47
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/assoc.h90
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/debug.c127
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/debug.h94
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_assoc.c539
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_assoc.h126
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_common.h100
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_crc32.c175
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_crc32.h14
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_data.c248
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_data.h108
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_defaults.h91
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_errors.c136
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_errors.h127
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_includes.h98
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_list.c273
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_list.h106
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_macros.h137
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_memory.c198
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_memory.h101
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_replace.c107
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_thread.h68
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_time.c235
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_time.h109
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_types.h213
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/p_buf.c215
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/p_buf.h72
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/util.c775
-rw-r--r--dom/media/webrtc/transport/third_party/nrappkit/src/util/util.h73
-rw-r--r--dom/media/webrtc/transport/transportflow.cpp74
-rw-r--r--dom/media/webrtc/transport/transportflow.h105
-rw-r--r--dom/media/webrtc/transport/transportlayer.cpp49
-rw-r--r--dom/media/webrtc/transport/transportlayer.h108
-rw-r--r--dom/media/webrtc/transport/transportlayerdtls.cpp1558
-rw-r--r--dom/media/webrtc/transport/transportlayerdtls.h187
-rw-r--r--dom/media/webrtc/transport/transportlayerice.cpp168
-rw-r--r--dom/media/webrtc/transport/transportlayerice.h60
-rw-r--r--dom/media/webrtc/transport/transportlayerlog.cpp48
-rw-r--r--dom/media/webrtc/transport/transportlayerlog.h38
-rw-r--r--dom/media/webrtc/transport/transportlayerloopback.cpp119
-rw-r--r--dom/media/webrtc/transport/transportlayerloopback.h109
-rw-r--r--dom/media/webrtc/transport/transportlayersrtp.cpp222
-rw-r--r--dom/media/webrtc/transport/transportlayersrtp.h43
-rw-r--r--dom/media/webrtc/transportbridge/MediaPipeline.cpp1655
-rw-r--r--dom/media/webrtc/transportbridge/MediaPipeline.h454
-rw-r--r--dom/media/webrtc/transportbridge/MediaPipelineFilter.cpp153
-rw-r--r--dom/media/webrtc/transportbridge/MediaPipelineFilter.h89
-rw-r--r--dom/media/webrtc/transportbridge/RtpLogger.cpp67
-rw-r--r--dom/media/webrtc/transportbridge/RtpLogger.h28
-rw-r--r--dom/media/webrtc/transportbridge/moz.build27
758 files changed, 150508 insertions, 0 deletions
diff --git a/dom/media/webrtc/CubebDeviceEnumerator.cpp b/dom/media/webrtc/CubebDeviceEnumerator.cpp
new file mode 100644
index 0000000000..b5e705ea7d
--- /dev/null
+++ b/dom/media/webrtc/CubebDeviceEnumerator.cpp
@@ -0,0 +1,334 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CubebDeviceEnumerator.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/media/MediaUtils.h"
+#include "nsThreadUtils.h"
+#ifdef XP_WIN
+# include "mozilla/mscom/EnsureMTA.h"
+#endif
+
+namespace mozilla {
+
+using namespace CubebUtils;
+using AudioDeviceSet = CubebDeviceEnumerator::AudioDeviceSet;
+
+/* static */
+static StaticRefPtr<CubebDeviceEnumerator> sInstance;
+static StaticMutex sInstanceMutex MOZ_UNANNOTATED;
+
+/* static */
+CubebDeviceEnumerator* CubebDeviceEnumerator::GetInstance() {
+ StaticMutexAutoLock lock(sInstanceMutex);
+ if (!sInstance) {
+ sInstance = new CubebDeviceEnumerator();
+ static bool clearOnShutdownSetup = []() -> bool {
+ auto setClearOnShutdown = []() -> void {
+ ClearOnShutdown(&sInstance, ShutdownPhase::XPCOMShutdownThreads);
+ };
+ if (NS_IsMainThread()) {
+ setClearOnShutdown();
+ } else {
+ SchedulerGroup::Dispatch(
+ TaskCategory::Other,
+ NS_NewRunnableFunction("CubebDeviceEnumerator::::GetInstance()",
+ std::move(setClearOnShutdown)));
+ }
+ return true;
+ }();
+ Unused << clearOnShutdownSetup;
+ }
+ return sInstance.get();
+}
+
+CubebDeviceEnumerator::CubebDeviceEnumerator()
+ : mMutex("CubebDeviceListMutex"),
+ mManualInputInvalidation(false),
+ mManualOutputInvalidation(false) {
+#ifdef XP_WIN
+ // Ensure the MTA thread exists and gets instantiated before the
+ // CubebDeviceEnumerator so that this instance will always gets destructed
+ // before the MTA thread gets shutdown.
+ mozilla::mscom::EnsureMTA([&]() -> void {
+#endif
+ int rv = cubeb_register_device_collection_changed(
+ GetCubebContext(), CUBEB_DEVICE_TYPE_OUTPUT,
+ &OutputAudioDeviceListChanged_s, this);
+ if (rv != CUBEB_OK) {
+ NS_WARNING(
+ "Could not register the audio output"
+ " device collection changed callback.");
+ mManualOutputInvalidation = true;
+ }
+ rv = cubeb_register_device_collection_changed(
+ GetCubebContext(), CUBEB_DEVICE_TYPE_INPUT,
+ &InputAudioDeviceListChanged_s, this);
+ if (rv != CUBEB_OK) {
+ NS_WARNING(
+ "Could not register the audio input"
+ " device collection changed callback.");
+ mManualInputInvalidation = true;
+ }
+#ifdef XP_WIN
+ });
+#endif
+}
+
+/* static */
+void CubebDeviceEnumerator::Shutdown() {
+ StaticMutexAutoLock lock(sInstanceMutex);
+ if (sInstance) {
+ sInstance = nullptr;
+ }
+}
+
+CubebDeviceEnumerator::~CubebDeviceEnumerator() {
+#ifdef XP_WIN
+ mozilla::mscom::EnsureMTA([&]() -> void {
+#endif
+ int rv = cubeb_register_device_collection_changed(
+ GetCubebContext(), CUBEB_DEVICE_TYPE_OUTPUT, nullptr, this);
+ if (rv != CUBEB_OK) {
+ NS_WARNING(
+ "Could not unregister the audio output"
+ " device collection changed callback.");
+ }
+ rv = cubeb_register_device_collection_changed(
+ GetCubebContext(), CUBEB_DEVICE_TYPE_INPUT, nullptr, this);
+ if (rv != CUBEB_OK) {
+ NS_WARNING(
+ "Could not unregister the audio input"
+ " device collection changed callback.");
+ }
+#ifdef XP_WIN
+ });
+#endif
+}
+
+RefPtr<const AudioDeviceSet>
+CubebDeviceEnumerator::EnumerateAudioInputDevices() {
+ return EnumerateAudioDevices(Side::INPUT);
+}
+
+RefPtr<const AudioDeviceSet>
+CubebDeviceEnumerator::EnumerateAudioOutputDevices() {
+ return EnumerateAudioDevices(Side::OUTPUT);
+}
+
+#ifndef ANDROID
+static uint16_t ConvertCubebType(cubeb_device_type aType) {
+ uint16_t map[] = {
+ nsIAudioDeviceInfo::TYPE_UNKNOWN, // CUBEB_DEVICE_TYPE_UNKNOWN
+ nsIAudioDeviceInfo::TYPE_INPUT, // CUBEB_DEVICE_TYPE_INPUT,
+ nsIAudioDeviceInfo::TYPE_OUTPUT // CUBEB_DEVICE_TYPE_OUTPUT
+ };
+ return map[aType];
+}
+
+static uint16_t ConvertCubebState(cubeb_device_state aState) {
+ uint16_t map[] = {
+ nsIAudioDeviceInfo::STATE_DISABLED, // CUBEB_DEVICE_STATE_DISABLED
+ nsIAudioDeviceInfo::STATE_UNPLUGGED, // CUBEB_DEVICE_STATE_UNPLUGGED
+ nsIAudioDeviceInfo::STATE_ENABLED // CUBEB_DEVICE_STATE_ENABLED
+ };
+ return map[aState];
+}
+
+static uint16_t ConvertCubebPreferred(cubeb_device_pref aPreferred) {
+ if (aPreferred == CUBEB_DEVICE_PREF_NONE) {
+ return nsIAudioDeviceInfo::PREF_NONE;
+ }
+ if (aPreferred == CUBEB_DEVICE_PREF_ALL) {
+ return nsIAudioDeviceInfo::PREF_ALL;
+ }
+
+ uint16_t preferred = 0;
+ if (aPreferred & CUBEB_DEVICE_PREF_MULTIMEDIA) {
+ preferred |= nsIAudioDeviceInfo::PREF_MULTIMEDIA;
+ }
+ if (aPreferred & CUBEB_DEVICE_PREF_VOICE) {
+ preferred |= nsIAudioDeviceInfo::PREF_VOICE;
+ }
+ if (aPreferred & CUBEB_DEVICE_PREF_NOTIFICATION) {
+ preferred |= nsIAudioDeviceInfo::PREF_NOTIFICATION;
+ }
+ return preferred;
+}
+
+static uint16_t ConvertCubebFormat(cubeb_device_fmt aFormat) {
+ uint16_t format = 0;
+ if (aFormat & CUBEB_DEVICE_FMT_S16LE) {
+ format |= nsIAudioDeviceInfo::FMT_S16LE;
+ }
+ if (aFormat & CUBEB_DEVICE_FMT_S16BE) {
+ format |= nsIAudioDeviceInfo::FMT_S16BE;
+ }
+ if (aFormat & CUBEB_DEVICE_FMT_F32LE) {
+ format |= nsIAudioDeviceInfo::FMT_F32LE;
+ }
+ if (aFormat & CUBEB_DEVICE_FMT_F32BE) {
+ format |= nsIAudioDeviceInfo::FMT_F32BE;
+ }
+ return format;
+}
+
+static RefPtr<AudioDeviceSet> GetDeviceCollection(Side aSide) {
+ RefPtr set = new AudioDeviceSet();
+ cubeb* context = GetCubebContext();
+ if (context) {
+ cubeb_device_collection collection = {nullptr, 0};
+# ifdef XP_WIN
+ mozilla::mscom::EnsureMTA([&]() -> void {
+# endif
+ if (cubeb_enumerate_devices(context,
+ aSide == Input ? CUBEB_DEVICE_TYPE_INPUT
+ : CUBEB_DEVICE_TYPE_OUTPUT,
+ &collection) == CUBEB_OK) {
+ for (unsigned int i = 0; i < collection.count; ++i) {
+ auto device = collection.device[i];
+ if (device.max_channels == 0) {
+ continue;
+ }
+ RefPtr<AudioDeviceInfo> info = new AudioDeviceInfo(
+ device.devid, NS_ConvertUTF8toUTF16(device.friendly_name),
+ NS_ConvertUTF8toUTF16(device.group_id),
+ NS_ConvertUTF8toUTF16(device.vendor_name),
+ ConvertCubebType(device.type), ConvertCubebState(device.state),
+ ConvertCubebPreferred(device.preferred),
+ ConvertCubebFormat(device.format),
+ ConvertCubebFormat(device.default_format), device.max_channels,
+ device.default_rate, device.max_rate, device.min_rate,
+ device.latency_hi, device.latency_lo);
+ set->AppendElement(std::move(info));
+ }
+ }
+ cubeb_device_collection_destroy(context, &collection);
+# ifdef XP_WIN
+ });
+# endif
+ }
+ return set;
+}
+#endif // non ANDROID
+
+RefPtr<const AudioDeviceSet> CubebDeviceEnumerator::EnumerateAudioDevices(
+ CubebDeviceEnumerator::Side aSide) {
+ MOZ_ASSERT(aSide == Side::INPUT || aSide == Side::OUTPUT);
+
+ RefPtr<const AudioDeviceSet>* devicesCache;
+ bool manualInvalidation = true;
+
+ if (aSide == Side::INPUT) {
+ devicesCache = &mInputDevices;
+ manualInvalidation = mManualInputInvalidation;
+ } else {
+ MOZ_ASSERT(aSide == Side::OUTPUT);
+ devicesCache = &mOutputDevices;
+ manualInvalidation = mManualOutputInvalidation;
+ }
+
+ cubeb* context = GetCubebContext();
+ if (!context) {
+ return new AudioDeviceSet();
+ }
+ if (!manualInvalidation) {
+ MutexAutoLock lock(mMutex);
+ if (*devicesCache) {
+ return *devicesCache;
+ }
+ }
+
+#ifdef ANDROID
+ cubeb_device_type type = CUBEB_DEVICE_TYPE_UNKNOWN;
+ uint32_t channels = 0;
+ nsAutoString name;
+ if (aSide == Side::INPUT) {
+ type = CUBEB_DEVICE_TYPE_INPUT;
+ channels = 1;
+ name = u"Default audio input device"_ns;
+ } else {
+ MOZ_ASSERT(aSide == Side::OUTPUT);
+ type = CUBEB_DEVICE_TYPE_OUTPUT;
+ channels = 2;
+ name = u"Default audio output device"_ns;
+ }
+ RefPtr devices = new AudioDeviceSet();
+ // Bug 1473346: enumerating devices is not supported on Android in cubeb,
+ // simply state that there is a single sink, that it is the default, and has
+ // a single channel. All the other values are made up and are not to be used.
+ // Bug 1660391: we can't use fluent here yet to get localized strings, so
+ // those are hard-coded en_US strings for now.
+ RefPtr<AudioDeviceInfo> info = new AudioDeviceInfo(
+ nullptr, name, u""_ns, u""_ns, type, CUBEB_DEVICE_STATE_ENABLED,
+ CUBEB_DEVICE_PREF_ALL, CUBEB_DEVICE_FMT_ALL, CUBEB_DEVICE_FMT_S16NE,
+ channels, 44100, 44100, 44100, 441, 128);
+ devices->AppendElement(std::move(info));
+#else
+ RefPtr devices = GetDeviceCollection(
+ (aSide == Side::INPUT) ? CubebUtils::Input : CubebUtils::Output);
+#endif
+ {
+ MutexAutoLock lock(mMutex);
+ *devicesCache = devices;
+ }
+ return devices;
+}
+
+already_AddRefed<AudioDeviceInfo> CubebDeviceEnumerator::DeviceInfoFromName(
+ const nsString& aName, Side aSide) {
+ RefPtr devices = EnumerateAudioDevices(aSide);
+ for (const RefPtr<AudioDeviceInfo>& device : *devices) {
+ if (device->Name().Equals(aName)) {
+ RefPtr<AudioDeviceInfo> other = device;
+ return other.forget();
+ }
+ }
+
+ return nullptr;
+}
+
+RefPtr<AudioDeviceInfo> CubebDeviceEnumerator::DefaultDevice(Side aSide) {
+ RefPtr devices = EnumerateAudioDevices(aSide);
+ for (const RefPtr<AudioDeviceInfo>& device : *devices) {
+ if (device->Preferred()) {
+ RefPtr<AudioDeviceInfo> other = device;
+ return other.forget();
+ }
+ }
+
+ return nullptr;
+}
+
+void CubebDeviceEnumerator::InputAudioDeviceListChanged_s(cubeb* aContext,
+ void* aUser) {
+ CubebDeviceEnumerator* self = reinterpret_cast<CubebDeviceEnumerator*>(aUser);
+ self->AudioDeviceListChanged(CubebDeviceEnumerator::Side::INPUT);
+}
+
+void CubebDeviceEnumerator::OutputAudioDeviceListChanged_s(cubeb* aContext,
+ void* aUser) {
+ CubebDeviceEnumerator* self = reinterpret_cast<CubebDeviceEnumerator*>(aUser);
+ self->AudioDeviceListChanged(CubebDeviceEnumerator::Side::OUTPUT);
+}
+
+void CubebDeviceEnumerator::AudioDeviceListChanged(Side aSide) {
+ MutexAutoLock lock(mMutex);
+ if (aSide == Side::INPUT) {
+ mInputDevices = nullptr;
+ mOnInputDeviceListChange.Notify();
+ } else {
+ MOZ_ASSERT(aSide == Side::OUTPUT);
+ mOutputDevices = nullptr;
+ mOnOutputDeviceListChange.Notify();
+ }
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/CubebDeviceEnumerator.h b/dom/media/webrtc/CubebDeviceEnumerator.h
new file mode 100644
index 0000000000..6b6499f728
--- /dev/null
+++ b/dom/media/webrtc/CubebDeviceEnumerator.h
@@ -0,0 +1,87 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CUBEBDEVICEENUMERATOR_H_
+#define CUBEBDEVICEENUMERATOR_H_
+
+#include "AudioDeviceInfo.h"
+#include "cubeb/cubeb.h"
+#include "MediaEventSource.h"
+#include "mozilla/Mutex.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+
+namespace media {
+template <typename T>
+class Refcountable;
+}
+
+// This class implements a cache for accessing the audio device list.
+// It can be accessed on any thread.
+class CubebDeviceEnumerator final {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CubebDeviceEnumerator)
+
+ static CubebDeviceEnumerator* GetInstance();
+ static void Shutdown();
+ using AudioDeviceSet = media::Refcountable<nsTArray<RefPtr<AudioDeviceInfo>>>;
+ // This method returns a list of all the input audio devices
+ // (sources) available on this machine.
+ // This method is safe to call from all threads.
+ RefPtr<const AudioDeviceSet> EnumerateAudioInputDevices();
+ // Similar for the audio audio devices (sinks). Also thread safe.
+ RefPtr<const AudioDeviceSet> EnumerateAudioOutputDevices();
+ // From a device name, return the info for this device, if it's a valid name,
+ // or nullptr otherwise.
+ // This method is safe to call from any thread.
+ enum class Side {
+ INPUT,
+ OUTPUT,
+ };
+ already_AddRefed<AudioDeviceInfo> DeviceInfoFromName(const nsString& aName,
+ Side aSide);
+ // Event source to listen for changes to the audio input device list on.
+ MediaEventSource<void>& OnAudioInputDeviceListChange() {
+ return mOnInputDeviceListChange;
+ }
+
+ // Event source to listen for changes to the audio output device list on.
+ MediaEventSource<void>& OnAudioOutputDeviceListChange() {
+ return mOnOutputDeviceListChange;
+ }
+
+ // Return the default device for a particular side.
+ RefPtr<AudioDeviceInfo> DefaultDevice(Side aSide);
+
+ private:
+ CubebDeviceEnumerator();
+ ~CubebDeviceEnumerator();
+ // Static functions called by cubeb when the audio device list changes
+ // (i.e. when a new device is made available, or non-available). This
+ // simply calls `AudioDeviceListChanged` below.
+ static void InputAudioDeviceListChanged_s(cubeb* aContext, void* aUser);
+ static void OutputAudioDeviceListChanged_s(cubeb* aContext, void* aUser);
+ // Invalidates the cached audio input device list, can be called on any
+ // thread.
+ void AudioDeviceListChanged(Side aSide);
+ RefPtr<const AudioDeviceSet> EnumerateAudioDevices(Side aSide);
+ // Synchronize access to mInputDevices and mOutputDevices;
+ Mutex mMutex MOZ_UNANNOTATED;
+ RefPtr<const AudioDeviceSet> mInputDevices;
+ RefPtr<const AudioDeviceSet> mOutputDevices;
+ // If mManual*Invalidation is true, then it is necessary to query the device
+ // list each time instead of relying on automatic invalidation of the cache by
+ // cubeb itself. Set in the constructor and then can be access on any thread.
+ bool mManualInputInvalidation;
+ bool mManualOutputInvalidation;
+ MediaEventProducer<void> mOnInputDeviceListChange;
+ MediaEventProducer<void> mOnOutputDeviceListChange;
+};
+
+typedef CubebDeviceEnumerator Enumerator;
+typedef CubebDeviceEnumerator::Side EnumeratorSide;
+} // namespace mozilla
+
+#endif // CUBEBDEVICEENUMERATOR_H_
diff --git a/dom/media/webrtc/MediaEngine.h b/dom/media/webrtc/MediaEngine.h
new file mode 100644
index 0000000000..d3e1ece452
--- /dev/null
+++ b/dom/media/webrtc/MediaEngine.h
@@ -0,0 +1,66 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MEDIAENGINE_H_
+#define MEDIAENGINE_H_
+
+#include "DOMMediaStream.h"
+#include "MediaEventSource.h"
+#include "MediaTrackGraph.h"
+#include "MediaTrackConstraints.h"
+#include "mozilla/dom/MediaStreamTrackBinding.h"
+#include "mozilla/dom/VideoStreamTrack.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ThreadSafeWeakPtr.h"
+
+namespace mozilla {
+
+namespace dom {
+class Blob;
+} // namespace dom
+
+class AllocationHandle;
+class MediaDevice;
+class MediaEngineSource;
+
+enum MediaSinkEnum {
+ Speaker,
+ Other,
+};
+
+enum { kVideoTrack = 1, kAudioTrack = 2, kTrackCount };
+
+class MediaEngine {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaEngine)
+ NS_DECL_OWNINGEVENTTARGET
+
+ void AssertIsOnOwningThread() const { NS_ASSERT_OWNINGTHREAD(MediaEngine); }
+
+ /**
+ * Populate an array of sources of the requested type in the nsTArray.
+ * Also include devices that are currently unavailable.
+ */
+ virtual void EnumerateDevices(dom::MediaSourceEnum, MediaSinkEnum,
+ nsTArray<RefPtr<MediaDevice>>*) = 0;
+
+ virtual void Shutdown() = 0;
+
+ virtual RefPtr<MediaEngineSource> CreateSource(
+ const MediaDevice* aDevice) = 0;
+
+ virtual MediaEventSource<void>& DeviceListChangeEvent() = 0;
+ /**
+ * Return true if devices returned from EnumerateDevices are emulated media
+ * devices.
+ */
+ virtual bool IsFake() const = 0;
+
+ protected:
+ virtual ~MediaEngine() = default;
+};
+
+} // namespace mozilla
+
+#endif /* MEDIAENGINE_H_ */
diff --git a/dom/media/webrtc/MediaEngineFake.cpp b/dom/media/webrtc/MediaEngineFake.cpp
new file mode 100644
index 0000000000..bba6c18694
--- /dev/null
+++ b/dom/media/webrtc/MediaEngineFake.cpp
@@ -0,0 +1,653 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MediaEngineFake.h"
+
+#include "AudioSegment.h"
+#include "DOMMediaStream.h"
+#include "ImageContainer.h"
+#include "ImageTypes.h"
+#include "MediaEnginePrefs.h"
+#include "MediaEngineSource.h"
+#include "MediaTrackGraph.h"
+#include "MediaTrackListener.h"
+#include "MediaTrackConstraints.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/MediaManager.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/SyncRunnable.h"
+#include "mozilla/UniquePtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsCOMPtr.h"
+#include "nsContentUtils.h"
+#include "nsITimer.h"
+#include "SineWaveGenerator.h"
+#include "Tracing.h"
+#include "VideoSegment.h"
+#include "VideoUtils.h"
+
+#ifdef MOZ_WIDGET_ANDROID
+# include "nsISupportsUtils.h"
+#endif
+
+#ifdef MOZ_WEBRTC
+# include "YuvStamper.h"
+#endif
+
+#define VIDEO_WIDTH_MIN 160
+#define VIDEO_WIDTH_MAX 4096
+#define VIDEO_HEIGHT_MIN 90
+#define VIDEO_HEIGHT_MAX 2160
+#define DEFAULT_AUDIO_TIMER_MS 10
+namespace mozilla {
+
+using namespace mozilla::gfx;
+using dom::MediaSourceEnum;
+using dom::MediaTrackConstraints;
+using dom::MediaTrackSettings;
+using dom::VideoFacingModeEnum;
+
+static nsString FakeVideoName() {
+ // For the purpose of testing we allow to change the name of the fake device
+ // by pref.
+ nsAutoString cameraNameFromPref;
+ nsresult rv;
+ auto getPref = [&]() {
+ rv = Preferences::GetString("media.getusermedia.fake-camera-name",
+ cameraNameFromPref);
+ };
+ if (NS_IsMainThread()) {
+ getPref();
+ } else {
+ // Here it is preferred a "hard" block, instead of "soft" block provided
+ // by sync dispatch, which allows the waiting thread to spin its event
+ // loop. The latter would allow multiple enumeration requests being
+ // processed out-of-order.
+ RefPtr runnable = NS_NewRunnableFunction(__func__, getPref);
+ SyncRunnable::DispatchToThread(GetMainThreadSerialEventTarget(), runnable);
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ return std::move(cameraNameFromPref);
+ }
+ return u"Default Video Device"_ns;
+}
+
+/**
+ * Fake video source.
+ */
+class MediaEngineFakeVideoSource : public MediaEngineSource {
+ public:
+ MediaEngineFakeVideoSource();
+
+ static nsString GetGroupId();
+
+ nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
+ const MediaEnginePrefs& aPrefs, uint64_t aWindowID,
+ const char** aOutBadConstraint) override;
+ void SetTrack(const RefPtr<MediaTrack>& aTrack,
+ const PrincipalHandle& aPrincipal) override;
+ nsresult Start() override;
+ nsresult Reconfigure(const dom::MediaTrackConstraints& aConstraints,
+ const MediaEnginePrefs& aPrefs,
+ const char** aOutBadConstraint) override;
+ nsresult Stop() override;
+ nsresult Deallocate() override;
+
+ uint32_t GetBestFitnessDistance(
+ const nsTArray<const NormalizedConstraintSet*>& aConstraintSets)
+ const override;
+ void GetSettings(dom::MediaTrackSettings& aOutSettings) const override;
+
+ bool IsFake() const override { return true; }
+
+ protected:
+ ~MediaEngineFakeVideoSource() = default;
+
+ /**
+ * Called by mTimer when it's time to generate a new frame.
+ */
+ void GenerateFrame();
+
+ nsCOMPtr<nsITimer> mTimer;
+
+ RefPtr<layers::ImageContainer> mImageContainer;
+
+ // Current state of this source.
+ MediaEngineSourceState mState = kReleased;
+ RefPtr<layers::Image> mImage;
+ RefPtr<SourceMediaTrack> mTrack;
+ PrincipalHandle mPrincipalHandle = PRINCIPAL_HANDLE_NONE;
+
+ MediaEnginePrefs mOpts;
+ int mCb = 16;
+ int mCr = 16;
+
+ // Main thread only.
+ const RefPtr<media::Refcountable<dom::MediaTrackSettings>> mSettings;
+};
+
+MediaEngineFakeVideoSource::MediaEngineFakeVideoSource()
+ : mTimer(nullptr),
+ mSettings(MakeAndAddRef<media::Refcountable<MediaTrackSettings>>()) {
+ mSettings->mWidth.Construct(
+ int32_t(MediaEnginePrefs::DEFAULT_43_VIDEO_WIDTH));
+ mSettings->mHeight.Construct(
+ int32_t(MediaEnginePrefs::DEFAULT_43_VIDEO_HEIGHT));
+ mSettings->mFrameRate.Construct(double(MediaEnginePrefs::DEFAULT_VIDEO_FPS));
+ mSettings->mFacingMode.Construct(
+ NS_ConvertASCIItoUTF16(dom::VideoFacingModeEnumValues::strings
+ [uint8_t(VideoFacingModeEnum::Environment)]
+ .value));
+}
+
+nsString MediaEngineFakeVideoSource::GetGroupId() {
+ return u"Fake Video Group"_ns;
+}
+
+uint32_t MediaEngineFakeVideoSource::GetBestFitnessDistance(
+ const nsTArray<const NormalizedConstraintSet*>& aConstraintSets) const {
+ AssertIsOnOwningThread();
+
+ uint64_t distance = 0;
+
+#ifdef MOZ_WEBRTC
+ // distance is read from first entry only
+ if (aConstraintSets.Length() >= 1) {
+ const auto* cs = aConstraintSets.ElementAt(0);
+ Maybe<nsString> facingMode = Nothing();
+ distance +=
+ MediaConstraintsHelper::FitnessDistance(facingMode, cs->mFacingMode);
+
+ if (cs->mWidth.mMax < VIDEO_WIDTH_MIN ||
+ cs->mWidth.mMin > VIDEO_WIDTH_MAX) {
+ distance += UINT32_MAX;
+ }
+
+ if (cs->mHeight.mMax < VIDEO_HEIGHT_MIN ||
+ cs->mHeight.mMin > VIDEO_HEIGHT_MAX) {
+ distance += UINT32_MAX;
+ }
+ }
+#endif
+
+ return uint32_t(std::min(distance, uint64_t(UINT32_MAX)));
+}
+
+void MediaEngineFakeVideoSource::GetSettings(
+ MediaTrackSettings& aOutSettings) const {
+ MOZ_ASSERT(NS_IsMainThread());
+ aOutSettings = *mSettings;
+}
+
+nsresult MediaEngineFakeVideoSource::Allocate(
+ const MediaTrackConstraints& aConstraints, const MediaEnginePrefs& aPrefs,
+ uint64_t aWindowID, const char** aOutBadConstraint) {
+ AssertIsOnOwningThread();
+
+ MOZ_ASSERT(mState == kReleased);
+
+ FlattenedConstraints c(aConstraints);
+
+ // emulator debug is very, very slow; reduce load on it with smaller/slower
+ // fake video
+ mOpts = aPrefs;
+ mOpts.mWidth =
+ c.mWidth.Get(aPrefs.mWidth ? aPrefs.mWidth :
+#ifdef DEBUG
+ MediaEnginePrefs::DEFAULT_43_VIDEO_WIDTH / 2
+#else
+ MediaEnginePrefs::DEFAULT_43_VIDEO_WIDTH
+#endif
+ );
+ mOpts.mHeight =
+ c.mHeight.Get(aPrefs.mHeight ? aPrefs.mHeight :
+#ifdef DEBUG
+ MediaEnginePrefs::DEFAULT_43_VIDEO_HEIGHT / 2
+#else
+ MediaEnginePrefs::DEFAULT_43_VIDEO_HEIGHT
+#endif
+ );
+ mOpts.mWidth =
+ std::max(VIDEO_WIDTH_MIN, std::min(mOpts.mWidth, VIDEO_WIDTH_MAX)) & ~1;
+ mOpts.mHeight =
+ std::max(VIDEO_HEIGHT_MIN, std::min(mOpts.mHeight, VIDEO_HEIGHT_MAX)) &
+ ~1;
+
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ __func__, [settings = mSettings, frameRate = mOpts.mFPS,
+ width = mOpts.mWidth, height = mOpts.mHeight]() {
+ settings->mFrameRate.Value() = frameRate;
+ settings->mWidth.Value() = width;
+ settings->mHeight.Value() = height;
+ }));
+
+ mState = kAllocated;
+ return NS_OK;
+}
+
+nsresult MediaEngineFakeVideoSource::Deallocate() {
+ AssertIsOnOwningThread();
+
+ MOZ_ASSERT(!mImage);
+ MOZ_ASSERT(mState == kStopped || mState == kAllocated);
+
+ if (mTrack) {
+ mTrack->End();
+ mTrack = nullptr;
+ mPrincipalHandle = PRINCIPAL_HANDLE_NONE;
+ }
+ mState = kReleased;
+ mImageContainer = nullptr;
+
+ return NS_OK;
+}
+
+static bool AllocateSolidColorFrame(layers::PlanarYCbCrData& aData, int aWidth,
+ int aHeight, int aY, int aCb, int aCr) {
+ MOZ_ASSERT(!(aWidth & 1));
+ MOZ_ASSERT(!(aHeight & 1));
+ // Allocate a single frame with a solid color
+ int yLen = aWidth * aHeight;
+ int cbLen = yLen >> 2;
+ int crLen = cbLen;
+ uint8_t* frame = (uint8_t*)malloc(yLen + cbLen + crLen);
+ if (!frame) {
+ return false;
+ }
+ memset(frame, aY, yLen);
+ memset(frame + yLen, aCb, cbLen);
+ memset(frame + yLen + cbLen, aCr, crLen);
+
+ aData.mYChannel = frame;
+ aData.mYStride = aWidth;
+ aData.mCbCrStride = aWidth >> 1;
+ aData.mCbChannel = frame + yLen;
+ aData.mCrChannel = aData.mCbChannel + cbLen;
+ aData.mPictureRect = IntRect(0, 0, aWidth, aHeight);
+ aData.mStereoMode = StereoMode::MONO;
+ aData.mYUVColorSpace = gfx::YUVColorSpace::BT601;
+ aData.mChromaSubsampling = gfx::ChromaSubsampling::HALF_WIDTH_AND_HEIGHT;
+ return true;
+}
+
+static void ReleaseFrame(layers::PlanarYCbCrData& aData) {
+ free(aData.mYChannel);
+}
+
+void MediaEngineFakeVideoSource::SetTrack(const RefPtr<MediaTrack>& aTrack,
+ const PrincipalHandle& aPrincipal) {
+ AssertIsOnOwningThread();
+
+ MOZ_ASSERT(mState == kAllocated);
+ MOZ_ASSERT(!mTrack);
+ MOZ_ASSERT(aTrack->AsSourceTrack());
+
+ mTrack = aTrack->AsSourceTrack();
+ mPrincipalHandle = aPrincipal;
+}
+
+nsresult MediaEngineFakeVideoSource::Start() {
+ AssertIsOnOwningThread();
+
+ MOZ_ASSERT(mState == kAllocated || mState == kStopped);
+ MOZ_ASSERT(mTrack, "SetTrack() must happen before Start()");
+
+ mTimer = NS_NewTimer(GetCurrentSerialEventTarget());
+ if (!mTimer) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mImageContainer) {
+ mImageContainer = MakeAndAddRef<layers::ImageContainer>(
+ layers::ImageContainer::ASYNCHRONOUS);
+ }
+
+ // Start timer for subsequent frames
+ uint32_t interval;
+#if defined(MOZ_WIDGET_ANDROID) && defined(DEBUG)
+ // emulator debug is very, very slow and has problems dealing with realtime
+ // audio inputs
+ interval = 10 * (1000 / mOpts.mFPS);
+#else
+ interval = 1000 / mOpts.mFPS;
+#endif
+ mTimer->InitWithNamedFuncCallback(
+ [](nsITimer* aTimer, void* aClosure) {
+ RefPtr<MediaEngineFakeVideoSource> source =
+ static_cast<MediaEngineFakeVideoSource*>(aClosure);
+ source->GenerateFrame();
+ },
+ this, interval, nsITimer::TYPE_REPEATING_SLACK,
+ "MediaEngineFakeVideoSource::GenerateFrame");
+
+ mState = kStarted;
+ return NS_OK;
+}
+
+nsresult MediaEngineFakeVideoSource::Stop() {
+ AssertIsOnOwningThread();
+
+ if (mState == kStopped || mState == kAllocated) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mState == kStarted);
+ MOZ_ASSERT(mTimer);
+ MOZ_ASSERT(mTrack);
+
+ mTimer->Cancel();
+ mTimer = nullptr;
+
+ mState = kStopped;
+
+ return NS_OK;
+}
+
+nsresult MediaEngineFakeVideoSource::Reconfigure(
+ const MediaTrackConstraints& aConstraints, const MediaEnginePrefs& aPrefs,
+ const char** aOutBadConstraint) {
+ return NS_OK;
+}
+
+void MediaEngineFakeVideoSource::GenerateFrame() {
+ AssertIsOnOwningThread();
+
+ // Update the target color
+ if (mCr <= 16) {
+ if (mCb < 240) {
+ mCb++;
+ } else {
+ mCr++;
+ }
+ } else if (mCb >= 240) {
+ if (mCr < 240) {
+ mCr++;
+ } else {
+ mCb--;
+ }
+ } else if (mCr >= 240) {
+ if (mCb > 16) {
+ mCb--;
+ } else {
+ mCr--;
+ }
+ } else {
+ mCr--;
+ }
+
+ // Allocate a single solid color image
+ RefPtr<layers::PlanarYCbCrImage> ycbcr_image =
+ mImageContainer->CreatePlanarYCbCrImage();
+ layers::PlanarYCbCrData data;
+ if (NS_WARN_IF(!AllocateSolidColorFrame(data, mOpts.mWidth, mOpts.mHeight,
+ 0x80, mCb, mCr))) {
+ return;
+ }
+
+#ifdef MOZ_WEBRTC
+ uint64_t timestamp = PR_Now();
+ YuvStamper::Encode(mOpts.mWidth, mOpts.mHeight, mOpts.mWidth, data.mYChannel,
+ reinterpret_cast<unsigned char*>(&timestamp),
+ sizeof(timestamp), 0, 0);
+#endif
+
+ bool setData = ycbcr_image->CopyData(data);
+ MOZ_ASSERT(setData);
+
+ // SetData copies data, so we can free the frame
+ ReleaseFrame(data);
+
+ if (!setData) {
+ return;
+ }
+
+ VideoSegment segment;
+ segment.AppendFrame(ycbcr_image.forget(),
+ gfx::IntSize(mOpts.mWidth, mOpts.mHeight),
+ mPrincipalHandle);
+ mTrack->AppendData(&segment);
+}
+
+// This class is created on the media thread, as part of Start(), then entirely
+// self-sustained until destruction, just forwarding calls to Pull().
+class AudioSourcePullListener : public MediaTrackListener {
+ public:
+ AudioSourcePullListener(RefPtr<SourceMediaTrack> aTrack,
+ const PrincipalHandle& aPrincipalHandle,
+ uint32_t aFrequency)
+ : mTrack(std::move(aTrack)),
+ mPrincipalHandle(aPrincipalHandle),
+ mSineGenerator(MakeUnique<SineWaveGenerator<int16_t>>(
+ mTrack->mSampleRate, aFrequency)) {
+ MOZ_COUNT_CTOR(AudioSourcePullListener);
+ }
+
+ MOZ_COUNTED_DTOR(AudioSourcePullListener)
+
+ void NotifyPull(MediaTrackGraph* aGraph, TrackTime aEndOfAppendedData,
+ TrackTime aDesiredTime) override;
+
+ const RefPtr<SourceMediaTrack> mTrack;
+ const PrincipalHandle mPrincipalHandle;
+ const UniquePtr<SineWaveGenerator<int16_t>> mSineGenerator;
+};
+
+/**
+ * Fake audio source.
+ */
+class MediaEngineFakeAudioSource : public MediaEngineSource {
+ public:
+ MediaEngineFakeAudioSource() = default;
+
+ static nsString GetUUID();
+ static nsString GetGroupId();
+
+ nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
+ const MediaEnginePrefs& aPrefs, uint64_t aWindowID,
+ const char** aOutBadConstraint) override;
+ void SetTrack(const RefPtr<MediaTrack>& aTrack,
+ const PrincipalHandle& aPrincipal) override;
+ nsresult Start() override;
+ nsresult Reconfigure(const dom::MediaTrackConstraints& aConstraints,
+ const MediaEnginePrefs& aPrefs,
+ const char** aOutBadConstraint) override;
+ nsresult Stop() override;
+ nsresult Deallocate() override;
+
+ bool IsFake() const override { return true; }
+
+ void GetSettings(dom::MediaTrackSettings& aOutSettings) const override;
+
+ protected:
+ ~MediaEngineFakeAudioSource() = default;
+
+ // Current state of this source.
+ MediaEngineSourceState mState = kReleased;
+ RefPtr<SourceMediaTrack> mTrack;
+ PrincipalHandle mPrincipalHandle = PRINCIPAL_HANDLE_NONE;
+ uint32_t mFrequency = 1000;
+ RefPtr<AudioSourcePullListener> mPullListener;
+};
+
+nsString MediaEngineFakeAudioSource::GetUUID() {
+ return u"B7CBD7C1-53EF-42F9-8353-73F61C70C092"_ns;
+}
+
+nsString MediaEngineFakeAudioSource::GetGroupId() {
+ return u"Fake Audio Group"_ns;
+}
+
+void MediaEngineFakeAudioSource::GetSettings(
+ MediaTrackSettings& aOutSettings) const {
+ MOZ_ASSERT(NS_IsMainThread());
+ aOutSettings.mAutoGainControl.Construct(false);
+ aOutSettings.mEchoCancellation.Construct(false);
+ aOutSettings.mNoiseSuppression.Construct(false);
+ aOutSettings.mChannelCount.Construct(1);
+}
+
+nsresult MediaEngineFakeAudioSource::Allocate(
+ const MediaTrackConstraints& aConstraints, const MediaEnginePrefs& aPrefs,
+ uint64_t aWindowID, const char** aOutBadConstraint) {
+ AssertIsOnOwningThread();
+
+ MOZ_ASSERT(mState == kReleased);
+
+ mFrequency = aPrefs.mFreq ? aPrefs.mFreq : 1000;
+
+ mState = kAllocated;
+ return NS_OK;
+}
+
+nsresult MediaEngineFakeAudioSource::Deallocate() {
+ AssertIsOnOwningThread();
+
+ MOZ_ASSERT(mState == kStopped || mState == kAllocated);
+
+ if (mTrack) {
+ mTrack->End();
+ mTrack = nullptr;
+ mPrincipalHandle = PRINCIPAL_HANDLE_NONE;
+ }
+ mState = kReleased;
+ return NS_OK;
+}
+
+void MediaEngineFakeAudioSource::SetTrack(const RefPtr<MediaTrack>& aTrack,
+ const PrincipalHandle& aPrincipal) {
+ AssertIsOnOwningThread();
+
+ MOZ_ASSERT(mState == kAllocated);
+ MOZ_ASSERT(!mTrack);
+ MOZ_ASSERT(aTrack->AsSourceTrack());
+
+ mTrack = aTrack->AsSourceTrack();
+ mPrincipalHandle = aPrincipal;
+}
+
+nsresult MediaEngineFakeAudioSource::Start() {
+ AssertIsOnOwningThread();
+
+ if (mState == kStarted) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mState == kAllocated || mState == kStopped);
+ MOZ_ASSERT(mTrack, "SetTrack() must happen before Start()");
+
+ if (!mPullListener) {
+ mPullListener = MakeAndAddRef<AudioSourcePullListener>(
+ mTrack, mPrincipalHandle, mFrequency);
+ }
+
+ mState = kStarted;
+
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ __func__, [track = mTrack, listener = mPullListener]() {
+ if (track->IsDestroyed()) {
+ return;
+ }
+ track->AddListener(listener);
+ track->SetPullingEnabled(true);
+ }));
+
+ return NS_OK;
+}
+
+nsresult MediaEngineFakeAudioSource::Stop() {
+ AssertIsOnOwningThread();
+
+ if (mState == kStopped || mState == kAllocated) {
+ return NS_OK;
+ }
+ MOZ_ASSERT(mState == kStarted);
+ mState = kStopped;
+
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ __func__, [track = mTrack, listener = std::move(mPullListener)]() {
+ if (track->IsDestroyed()) {
+ return;
+ }
+ track->RemoveListener(listener);
+ track->SetPullingEnabled(false);
+ }));
+ return NS_OK;
+}
+
+nsresult MediaEngineFakeAudioSource::Reconfigure(
+ const MediaTrackConstraints& aConstraints, const MediaEnginePrefs& aPrefs,
+ const char** aOutBadConstraint) {
+ return NS_OK;
+}
+
+void AudioSourcePullListener::NotifyPull(MediaTrackGraph* aGraph,
+ TrackTime aEndOfAppendedData,
+ TrackTime aDesiredTime) {
+ TRACE_COMMENT("SourceMediaTrack::NotifyPull", "SourceMediaTrack %p",
+ mTrack.get());
+ AudioSegment segment;
+ TrackTicks delta = aDesiredTime - aEndOfAppendedData;
+ CheckedInt<size_t> bufferSize(sizeof(int16_t));
+ bufferSize *= delta;
+ RefPtr<SharedBuffer> buffer = SharedBuffer::Create(bufferSize);
+ int16_t* dest = static_cast<int16_t*>(buffer->Data());
+ mSineGenerator->generate(dest, delta);
+ AutoTArray<const int16_t*, 1> channels;
+ channels.AppendElement(dest);
+ segment.AppendFrames(buffer.forget(), channels, delta, mPrincipalHandle);
+ mTrack->AppendData(&segment);
+}
+
+MediaEngineFake::MediaEngineFake() = default;
+MediaEngineFake::~MediaEngineFake() = default;
+
+void MediaEngineFake::EnumerateDevices(
+ MediaSourceEnum aMediaSource, MediaSinkEnum aMediaSink,
+ nsTArray<RefPtr<MediaDevice>>* aDevices) {
+ AssertIsOnOwningThread();
+ using IsScary = MediaDevice::IsScary;
+ using OsPromptable = MediaDevice::OsPromptable;
+
+ if (aMediaSink == MediaSinkEnum::Speaker) {
+ NS_WARNING("No default implementation for MediaSinkEnum::Speaker");
+ }
+
+ switch (aMediaSource) {
+ case MediaSourceEnum::Camera: {
+ nsString name = FakeVideoName();
+ aDevices->EmplaceBack(
+ new MediaDevice(this, aMediaSource, name, /*aRawId=*/name,
+ MediaEngineFakeVideoSource::GetGroupId(), IsScary::No,
+ OsPromptable::No));
+ return;
+ }
+ case MediaSourceEnum::Microphone:
+ aDevices->EmplaceBack(
+ new MediaDevice(this, aMediaSource, u"Default Audio Device"_ns,
+ MediaEngineFakeAudioSource::GetUUID(),
+ MediaEngineFakeAudioSource::GetGroupId(), IsScary::No,
+ OsPromptable::No));
+ return;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unsupported source type");
+ return;
+ }
+}
+
+RefPtr<MediaEngineSource> MediaEngineFake::CreateSource(
+ const MediaDevice* aMediaDevice) {
+ MOZ_ASSERT(aMediaDevice->mEngine == this);
+ switch (aMediaDevice->mMediaSource) {
+ case MediaSourceEnum::Camera:
+ return new MediaEngineFakeVideoSource();
+ case MediaSourceEnum::Microphone:
+ return new MediaEngineFakeAudioSource();
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unsupported source type");
+ return nullptr;
+ }
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/MediaEngineFake.h b/dom/media/webrtc/MediaEngineFake.h
new file mode 100644
index 0000000000..611529a067
--- /dev/null
+++ b/dom/media/webrtc/MediaEngineFake.h
@@ -0,0 +1,40 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MEDIAENGINEFAKE_H_
+#define MEDIAENGINEFAKE_H_
+
+#include "nsTArrayForwardDeclare.h"
+#include "MediaEngine.h"
+
+namespace mozilla {
+
+template <typename...>
+class MediaEventProducer;
+
+/**
+ * The fake implementation of the MediaEngine interface.
+ */
+class MediaEngineFake : public MediaEngine {
+ public:
+ MediaEngineFake();
+
+ void EnumerateDevices(dom::MediaSourceEnum, MediaSinkEnum,
+ nsTArray<RefPtr<MediaDevice>>*) override;
+ void Shutdown() override {}
+ RefPtr<MediaEngineSource> CreateSource(const MediaDevice* aDevice) override;
+
+ MediaEventSource<void>& DeviceListChangeEvent() override {
+ return mDeviceListChangeEvent;
+ }
+ bool IsFake() const override { return true; }
+
+ private:
+ ~MediaEngineFake();
+ MediaEventProducer<void> mDeviceListChangeEvent;
+};
+
+} // namespace mozilla
+
+#endif /* NSMEDIAENGINEFAKE_H_ */
diff --git a/dom/media/webrtc/MediaEnginePrefs.h b/dom/media/webrtc/MediaEnginePrefs.h
new file mode 100644
index 0000000000..6c9775a238
--- /dev/null
+++ b/dom/media/webrtc/MediaEnginePrefs.h
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MediaEnginePrefs_h
+#define MediaEnginePrefs_h
+
+#include <stdint.h>
+#include <string.h>
+
+namespace mozilla {
+
+/**
+ * Video source and friends.
+ */
+class MediaEnginePrefs {
+ public:
+ static const int DEFAULT_VIDEO_FPS = 30;
+ static const int DEFAULT_43_VIDEO_WIDTH = 640;
+ static const int DEFAULT_43_VIDEO_HEIGHT = 480;
+ static const int DEFAULT_169_VIDEO_WIDTH = 1280;
+ static const int DEFAULT_169_VIDEO_HEIGHT = 720;
+
+ MediaEnginePrefs()
+ : mWidth(0),
+ mHeight(0),
+ mFPS(0),
+ mFreq(0),
+ mAecOn(false),
+ mUseAecMobile(false),
+ mAgcOn(false),
+ mHPFOn(false),
+ mNoiseOn(false),
+ mTransientOn(false),
+ mResidualEchoOn(false),
+ mAgc2Forced(false),
+ mAgc(0),
+ mNoise(0),
+ mChannels(0) {}
+
+ int32_t mWidth;
+ int32_t mHeight;
+ int32_t mFPS;
+ int32_t mFreq; // for test tones (fake:true)
+ bool mAecOn;
+ bool mUseAecMobile;
+ bool mAgcOn;
+ bool mHPFOn;
+ bool mNoiseOn;
+ bool mTransientOn;
+ bool mResidualEchoOn;
+ bool mAgc2Forced;
+ int32_t mAgc;
+ int32_t mNoise;
+ int32_t mChannels;
+
+ bool operator==(const MediaEnginePrefs& aRhs) {
+ return memcmp(this, &aRhs, sizeof(MediaEnginePrefs)) == 0;
+ };
+
+ // mWidth and/or mHeight may be zero (=adaptive default), so use functions.
+
+ int32_t GetWidth(bool aHD = false) const {
+ return mWidth ? mWidth
+ : (mHeight ? (mHeight * GetDefWidth(aHD)) / GetDefHeight(aHD)
+ : GetDefWidth(aHD));
+ }
+
+ int32_t GetHeight(bool aHD = false) const {
+ return mHeight ? mHeight
+ : (mWidth ? (mWidth * GetDefHeight(aHD)) / GetDefWidth(aHD)
+ : GetDefHeight(aHD));
+ }
+
+ private:
+ static int32_t GetDefWidth(bool aHD = false) {
+ // It'd be nice if we could use the ternary operator here, but we can't
+ // because of bug 1002729.
+ if (aHD) {
+ return DEFAULT_169_VIDEO_WIDTH;
+ }
+
+ return DEFAULT_43_VIDEO_WIDTH;
+ }
+
+ static int32_t GetDefHeight(bool aHD = false) {
+ // It'd be nice if we could use the ternary operator here, but we can't
+ // because of bug 1002729.
+ if (aHD) {
+ return DEFAULT_169_VIDEO_HEIGHT;
+ }
+
+ return DEFAULT_43_VIDEO_HEIGHT;
+ }
+};
+
+} // namespace mozilla
+
+#endif // MediaEnginePrefs_h
diff --git a/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp b/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp
new file mode 100644
index 0000000000..ce9f4ad9a8
--- /dev/null
+++ b/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp
@@ -0,0 +1,907 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MediaEngineRemoteVideoSource.h"
+
+#include "CamerasChild.h"
+#include "MediaManager.h"
+#include "MediaTrackConstraints.h"
+#include "mozilla/dom/MediaTrackSettingsBinding.h"
+#include "mozilla/ErrorNames.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/RefPtr.h"
+#include "PerformanceRecorder.h"
+#include "Tracing.h"
+#include "VideoFrameUtils.h"
+#include "VideoUtils.h"
+#include "ImageContainer.h"
+#include "common_video/include/video_frame_buffer.h"
+#include "common_video/libyuv/include/webrtc_libyuv.h"
+
+namespace mozilla {
+
+extern LazyLogModule gMediaManagerLog;
+#define LOG(...) MOZ_LOG(gMediaManagerLog, LogLevel::Debug, (__VA_ARGS__))
+#define LOG_FRAME(...) \
+ MOZ_LOG(gMediaManagerLog, LogLevel::Verbose, (__VA_ARGS__))
+
+using dom::ConstrainLongRange;
+using dom::MediaSourceEnum;
+using dom::MediaTrackConstraints;
+using dom::MediaTrackConstraintSet;
+using dom::MediaTrackSettings;
+using dom::VideoFacingModeEnum;
+
+/* static */
+camera::CaptureEngine MediaEngineRemoteVideoSource::CaptureEngine(
+ MediaSourceEnum aMediaSource) {
+ switch (aMediaSource) {
+ case MediaSourceEnum::Browser:
+ return camera::BrowserEngine;
+ case MediaSourceEnum::Camera:
+ return camera::CameraEngine;
+ case MediaSourceEnum::Screen:
+ return camera::ScreenEngine;
+ case MediaSourceEnum::Window:
+ return camera::WinEngine;
+ default:
+ MOZ_CRASH();
+ }
+}
+
+static Maybe<VideoFacingModeEnum> GetFacingMode(const nsString& aDeviceName) {
+ // Set facing mode based on device name.
+#if defined(ANDROID)
+ // Names are generated. Example: "Camera 0, Facing back, Orientation 90"
+ //
+ // See media/webrtc/trunk/webrtc/modules/video_capture/android/java/src/org/
+ // webrtc/videoengine/VideoCaptureDeviceInfoAndroid.java
+
+ if (aDeviceName.Find(u"Facing back"_ns) != kNotFound) {
+ return Some(VideoFacingModeEnum::Environment);
+ }
+ if (aDeviceName.Find(u"Facing front"_ns) != kNotFound) {
+ return Some(VideoFacingModeEnum::User);
+ }
+#endif // ANDROID
+#ifdef XP_MACOSX
+ // Kludge to test user-facing cameras on OSX.
+ if (aDeviceName.Find(u"Face"_ns) != -1) {
+ return Some(VideoFacingModeEnum::User);
+ }
+#endif
+#ifdef XP_WIN
+ // The cameras' name of Surface book are "Microsoft Camera Front" and
+ // "Microsoft Camera Rear" respectively.
+
+ if (aDeviceName.Find(u"Front"_ns) != kNotFound) {
+ return Some(VideoFacingModeEnum::User);
+ }
+ if (aDeviceName.Find(u"Rear"_ns) != kNotFound) {
+ return Some(VideoFacingModeEnum::Environment);
+ }
+#endif // WINDOWS
+
+ return Nothing();
+}
+
+MediaEngineRemoteVideoSource::MediaEngineRemoteVideoSource(
+ const MediaDevice* aMediaDevice)
+ : mCapEngine(CaptureEngine(aMediaDevice->mMediaSource)),
+ mTrackingId(CaptureEngineToTrackingSourceStr(mCapEngine), 0),
+ mMutex("MediaEngineRemoteVideoSource::mMutex"),
+ mRescalingBufferPool(/* zero_initialize */ false,
+ /* max_number_of_buffers */ 1),
+ mSettingsUpdatedByFrame(MakeAndAddRef<media::Refcountable<AtomicBool>>()),
+ mSettings(MakeAndAddRef<media::Refcountable<MediaTrackSettings>>()),
+ mFirstFramePromise(mFirstFramePromiseHolder.Ensure(__func__)),
+ mMediaDevice(aMediaDevice),
+ mDeviceUUID(NS_ConvertUTF16toUTF8(aMediaDevice->mRawID)) {
+ LOG("%s", __PRETTY_FUNCTION__);
+ mSettings->mWidth.Construct(0);
+ mSettings->mHeight.Construct(0);
+ mSettings->mFrameRate.Construct(0);
+ if (mCapEngine == camera::CameraEngine) {
+ // Only cameras can have a facing mode.
+ Maybe<VideoFacingModeEnum> facingMode =
+ GetFacingMode(mMediaDevice->mRawName);
+ if (facingMode.isSome()) {
+ NS_ConvertASCIItoUTF16 facingString(
+ dom::VideoFacingModeEnumValues::GetString(*facingMode));
+ mSettings->mFacingMode.Construct(facingString);
+ mFacingMode.emplace(facingString);
+ }
+ }
+}
+
+MediaEngineRemoteVideoSource::~MediaEngineRemoteVideoSource() {
+ mFirstFramePromiseHolder.RejectIfExists(NS_ERROR_ABORT, __func__);
+}
+
+nsresult MediaEngineRemoteVideoSource::Allocate(
+ const MediaTrackConstraints& aConstraints, const MediaEnginePrefs& aPrefs,
+ uint64_t aWindowID, const char** aOutBadConstraint) {
+ LOG("%s", __PRETTY_FUNCTION__);
+ AssertIsOnOwningThread();
+
+ MOZ_ASSERT(mState == kReleased);
+
+ NormalizedConstraints constraints(aConstraints);
+ webrtc::CaptureCapability newCapability;
+ LOG("ChooseCapability(kFitness) for mCapability (Allocate) ++");
+ if (!ChooseCapability(constraints, aPrefs, newCapability, kFitness)) {
+ *aOutBadConstraint =
+ MediaConstraintsHelper::FindBadConstraint(constraints, mMediaDevice);
+ return NS_ERROR_FAILURE;
+ }
+ LOG("ChooseCapability(kFitness) for mCapability (Allocate) --");
+
+ mCaptureId =
+ camera::GetChildAndCall(&camera::CamerasChild::AllocateCapture,
+ mCapEngine, mDeviceUUID.get(), aWindowID);
+ if (mCaptureId < 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ {
+ MutexAutoLock lock(mMutex);
+ mState = kAllocated;
+ mCapability = newCapability;
+ mTrackingId =
+ TrackingId(CaptureEngineToTrackingSourceStr(mCapEngine), mCaptureId);
+ }
+
+ LOG("Video device %d allocated", mCaptureId);
+ return NS_OK;
+}
+
+nsresult MediaEngineRemoteVideoSource::Deallocate() {
+ LOG("%s", __PRETTY_FUNCTION__);
+ AssertIsOnOwningThread();
+
+ MOZ_ASSERT(mState == kStopped || mState == kAllocated);
+
+ if (mTrack) {
+ mTrack->End();
+ }
+
+ {
+ MutexAutoLock lock(mMutex);
+
+ mTrack = nullptr;
+ mPrincipal = PRINCIPAL_HANDLE_NONE;
+ mState = kReleased;
+ }
+
+ // Stop() has stopped capture synchronously on the media thread before we get
+ // here, so there are no longer any callbacks on an IPC thread accessing
+ // mImageContainer or mRescalingBufferPool.
+ mImageContainer = nullptr;
+ mRescalingBufferPool.Release();
+
+ LOG("Video device %d deallocated", mCaptureId);
+
+ if (camera::GetChildAndCall(&camera::CamerasChild::ReleaseCapture, mCapEngine,
+ mCaptureId)) {
+ // Failure can occur when the parent process is shutting down.
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+void MediaEngineRemoteVideoSource::SetTrack(const RefPtr<MediaTrack>& aTrack,
+ const PrincipalHandle& aPrincipal) {
+ LOG("%s", __PRETTY_FUNCTION__);
+ AssertIsOnOwningThread();
+
+ MOZ_ASSERT(mState == kAllocated);
+ MOZ_ASSERT(!mTrack);
+ MOZ_ASSERT(aTrack);
+ MOZ_ASSERT(aTrack->AsSourceTrack());
+
+ if (!mImageContainer) {
+ mImageContainer = MakeAndAddRef<layers::ImageContainer>(
+ layers::ImageContainer::ASYNCHRONOUS);
+ }
+
+ {
+ MutexAutoLock lock(mMutex);
+ mTrack = aTrack->AsSourceTrack();
+ mPrincipal = aPrincipal;
+ }
+}
+
+nsresult MediaEngineRemoteVideoSource::Start() {
+ LOG("%s", __PRETTY_FUNCTION__);
+ AssertIsOnOwningThread();
+
+ MOZ_ASSERT(mState == kAllocated || mState == kStopped);
+ MOZ_ASSERT(mTrack);
+
+ {
+ MutexAutoLock lock(mMutex);
+ mState = kStarted;
+ }
+
+ mSettingsUpdatedByFrame->mValue = false;
+
+ if (camera::GetChildAndCall(&camera::CamerasChild::StartCapture, mCapEngine,
+ mCaptureId, mCapability, this)) {
+ LOG("StartCapture failed");
+ MutexAutoLock lock(mMutex);
+ mState = kStopped;
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "MediaEngineRemoteVideoSource::SetLastCapability",
+ [settings = mSettings, updated = mSettingsUpdatedByFrame,
+ capEngine = mCapEngine, cap = mCapability]() mutable {
+ switch (capEngine) {
+ case camera::ScreenEngine:
+ case camera::WinEngine:
+ // Undo the hack where ideal and max constraints are crammed
+ // together in mCapability for consumption by low-level code. We
+ // don't actually know the real resolution yet, so report min(ideal,
+ // max) for now.
+ // TODO: This can be removed in bug 1453269.
+ cap.width = std::min(cap.width >> 16, cap.width & 0xffff);
+ cap.height = std::min(cap.height >> 16, cap.height & 0xffff);
+ break;
+ default:
+ break;
+ }
+
+ if (!updated->mValue) {
+ settings->mWidth.Value() = cap.width;
+ settings->mHeight.Value() = cap.height;
+ }
+ settings->mFrameRate.Value() = cap.maxFPS;
+ }));
+
+ return NS_OK;
+}
+
+nsresult MediaEngineRemoteVideoSource::FocusOnSelectedSource() {
+ LOG("%s", __PRETTY_FUNCTION__);
+ AssertIsOnOwningThread();
+
+ int result;
+ result = camera::GetChildAndCall(&camera::CamerasChild::FocusOnSelectedSource,
+ mCapEngine, mCaptureId);
+ return result == 0 ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult MediaEngineRemoteVideoSource::Stop() {
+ LOG("%s", __PRETTY_FUNCTION__);
+ AssertIsOnOwningThread();
+
+ if (mState == kStopped || mState == kAllocated) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mState == kStarted);
+
+ if (camera::GetChildAndCall(&camera::CamerasChild::StopCapture, mCapEngine,
+ mCaptureId)) {
+ // Failure can occur when the parent process is shutting down.
+ return NS_ERROR_FAILURE;
+ }
+
+ {
+ MutexAutoLock lock(mMutex);
+ mState = kStopped;
+ }
+
+ return NS_OK;
+}
+
+nsresult MediaEngineRemoteVideoSource::Reconfigure(
+ const MediaTrackConstraints& aConstraints, const MediaEnginePrefs& aPrefs,
+ const char** aOutBadConstraint) {
+ LOG("%s", __PRETTY_FUNCTION__);
+ AssertIsOnOwningThread();
+
+ NormalizedConstraints constraints(aConstraints);
+ webrtc::CaptureCapability newCapability;
+ LOG("ChooseCapability(kFitness) for mTargetCapability (Reconfigure) ++");
+ if (!ChooseCapability(constraints, aPrefs, newCapability, kFitness)) {
+ *aOutBadConstraint =
+ MediaConstraintsHelper::FindBadConstraint(constraints, mMediaDevice);
+ return NS_ERROR_INVALID_ARG;
+ }
+ LOG("ChooseCapability(kFitness) for mTargetCapability (Reconfigure) --");
+
+ if (mCapability == newCapability) {
+ return NS_OK;
+ }
+
+ bool started = mState == kStarted;
+ if (started) {
+ nsresult rv = Stop();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ nsAutoCString name;
+ GetErrorName(rv, name);
+ LOG("Video source %p for video device %d Reconfigure() failed "
+ "unexpectedly in Stop(). rv=%s",
+ this, mCaptureId, name.Data());
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ {
+ MutexAutoLock lock(mMutex);
+ // Start() applies mCapability on the device.
+ mCapability = newCapability;
+ }
+
+ if (started) {
+ nsresult rv = Start();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ nsAutoCString name;
+ GetErrorName(rv, name);
+ LOG("Video source %p for video device %d Reconfigure() failed "
+ "unexpectedly in Start(). rv=%s",
+ this, mCaptureId, name.Data());
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ return NS_OK;
+}
+
+size_t MediaEngineRemoteVideoSource::NumCapabilities() const {
+ AssertIsOnOwningThread();
+
+ if (!mCapabilities.IsEmpty()) {
+ return mCapabilities.Length();
+ }
+
+ int num = camera::GetChildAndCall(&camera::CamerasChild::NumberOfCapabilities,
+ mCapEngine, mDeviceUUID.get());
+ if (num > 0) {
+ mCapabilities.SetLength(num);
+ } else {
+ // The default for devices that don't return discrete capabilities: treat
+ // them as supporting all capabilities orthogonally. E.g. screensharing.
+ // CaptureCapability defaults key values to 0, which means accept any value.
+ mCapabilities.AppendElement(MakeUnique<webrtc::CaptureCapability>());
+ mCapabilitiesAreHardcoded = true;
+ }
+
+ return mCapabilities.Length();
+}
+
+webrtc::CaptureCapability& MediaEngineRemoteVideoSource::GetCapability(
+ size_t aIndex) const {
+ AssertIsOnOwningThread();
+ MOZ_RELEASE_ASSERT(aIndex < mCapabilities.Length());
+ if (!mCapabilities[aIndex]) {
+ mCapabilities[aIndex] = MakeUnique<webrtc::CaptureCapability>();
+ camera::GetChildAndCall(&camera::CamerasChild::GetCaptureCapability,
+ mCapEngine, mDeviceUUID.get(), aIndex,
+ mCapabilities[aIndex].get());
+ }
+ return *mCapabilities[aIndex];
+}
+
+const TrackingId& MediaEngineRemoteVideoSource::GetTrackingId() const {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mState != kReleased);
+ return mTrackingId;
+}
+
+int MediaEngineRemoteVideoSource::DeliverFrame(
+ uint8_t* aBuffer, const camera::VideoFrameProperties& aProps) {
+ // Cameras IPC thread - take great care with accessing members!
+
+ Maybe<int32_t> req_max_width;
+ Maybe<int32_t> req_max_height;
+ Maybe<int32_t> req_ideal_width;
+ Maybe<int32_t> req_ideal_height;
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mState == kStarted);
+ // TODO: These can be removed in bug 1453269.
+ const int32_t max_width = mCapability.width & 0xffff;
+ const int32_t max_height = mCapability.height & 0xffff;
+ const int32_t ideal_width = (mCapability.width >> 16) & 0xffff;
+ const int32_t ideal_height = (mCapability.height >> 16) & 0xffff;
+
+ req_max_width = max_width ? Some(max_width) : Nothing();
+ req_max_height = max_height ? Some(max_height) : Nothing();
+ req_ideal_width = ideal_width ? Some(ideal_width) : Nothing();
+ req_ideal_height = ideal_height ? Some(ideal_height) : Nothing();
+ if (!mFrameDeliveringTrackingId) {
+ mFrameDeliveringTrackingId = Some(mTrackingId);
+ }
+ }
+
+ // This is only used in the case of screen sharing, see bug 1453269.
+
+ if (aProps.rotation() == 90 || aProps.rotation() == 270) {
+ // This frame is rotated, so what was negotiated as width is now height,
+ // and vice versa.
+ std::swap(req_max_width, req_max_height);
+ std::swap(req_ideal_width, req_ideal_height);
+ }
+
+ int32_t dst_max_width =
+ std::min(aProps.width(), req_max_width.valueOr(aProps.width()));
+ int32_t dst_max_height =
+ std::min(aProps.height(), req_max_height.valueOr(aProps.height()));
+ // This logic works for both camera and screen sharing case.
+ // for camera case, req_ideal_width and req_ideal_height are absent.
+ int32_t dst_width = req_ideal_width.valueOr(aProps.width());
+ int32_t dst_height = req_ideal_height.valueOr(aProps.height());
+
+ if (!req_ideal_width && req_ideal_height) {
+ dst_width = *req_ideal_height * aProps.width() / aProps.height();
+ } else if (!req_ideal_height && req_ideal_width) {
+ dst_height = *req_ideal_width * aProps.height() / aProps.width();
+ }
+ dst_width = std::min(dst_width, dst_max_width);
+ dst_height = std::min(dst_height, dst_max_height);
+
+ // Apply scaling for screen sharing, see bug 1453269.
+ switch (mCapEngine) {
+ case camera::ScreenEngine:
+ case camera::WinEngine: {
+ // scale to average of portrait and landscape
+ float scale_width = (float)dst_width / (float)aProps.width();
+ float scale_height = (float)dst_height / (float)aProps.height();
+ float scale = (scale_width + scale_height) / 2;
+ // If both req_ideal_width & req_ideal_height are absent, scale is 1, but
+ // if one is present and the other not, scale precisely to the one present
+ if (!req_ideal_width) {
+ scale = scale_height;
+ } else if (!req_ideal_height) {
+ scale = scale_width;
+ }
+ dst_width = int32_t(scale * (float)aProps.width());
+ dst_height = int32_t(scale * (float)aProps.height());
+
+ // if scaled rectangle exceeds max rectangle, scale to minimum of portrait
+ // and landscape
+ if (dst_width > dst_max_width || dst_height > dst_max_height) {
+ scale_width = (float)dst_max_width / (float)dst_width;
+ scale_height = (float)dst_max_height / (float)dst_height;
+ scale = std::min(scale_width, scale_height);
+ dst_width = int32_t(scale * dst_width);
+ dst_height = int32_t(scale * dst_height);
+ }
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+
+ // Ensure width and height are at least two. Smaller frames can lead to
+ // problems with scaling and video encoding.
+ dst_width = std::max(2, dst_width);
+ dst_height = std::max(2, dst_height);
+
+ std::function<void()> callback_unused = []() {};
+ rtc::scoped_refptr<webrtc::I420BufferInterface> buffer =
+ webrtc::WrapI420Buffer(
+ aProps.width(), aProps.height(), aBuffer, aProps.yStride(),
+ aBuffer + aProps.yAllocatedSize(), aProps.uStride(),
+ aBuffer + aProps.yAllocatedSize() + aProps.uAllocatedSize(),
+ aProps.vStride(), callback_unused);
+
+ if ((dst_width != aProps.width() || dst_height != aProps.height()) &&
+ dst_width <= aProps.width() && dst_height <= aProps.height()) {
+ PerformanceRecorder<CopyVideoStage> rec("MERVS::CropAndScale"_ns,
+ *mFrameDeliveringTrackingId,
+ dst_width, dst_height);
+ // Destination resolution is smaller than source buffer. We'll rescale.
+ rtc::scoped_refptr<webrtc::I420Buffer> scaledBuffer =
+ mRescalingBufferPool.CreateI420Buffer(dst_width, dst_height);
+ if (!scaledBuffer) {
+ MOZ_ASSERT_UNREACHABLE(
+ "We might fail to allocate a buffer, but with this "
+ "being a recycling pool that shouldn't happen");
+ return 0;
+ }
+ scaledBuffer->CropAndScaleFrom(*buffer);
+ buffer = scaledBuffer;
+ rec.Record();
+ }
+
+ layers::PlanarYCbCrData data;
+ data.mYChannel = const_cast<uint8_t*>(buffer->DataY());
+ data.mYStride = buffer->StrideY();
+ MOZ_ASSERT(buffer->StrideU() == buffer->StrideV());
+ data.mCbCrStride = buffer->StrideU();
+ data.mCbChannel = const_cast<uint8_t*>(buffer->DataU());
+ data.mCrChannel = const_cast<uint8_t*>(buffer->DataV());
+ data.mPictureRect = gfx::IntRect(0, 0, buffer->width(), buffer->height());
+ data.mYUVColorSpace = gfx::YUVColorSpace::BT601;
+ data.mChromaSubsampling = gfx::ChromaSubsampling::HALF_WIDTH_AND_HEIGHT;
+
+ RefPtr<layers::PlanarYCbCrImage> image;
+ {
+ PerformanceRecorder<CopyVideoStage> rec(
+ "MERVS::Copy"_ns, *mFrameDeliveringTrackingId, dst_width, dst_height);
+ image = mImageContainer->CreatePlanarYCbCrImage();
+ if (!image->CopyData(data)) {
+ MOZ_ASSERT_UNREACHABLE(
+ "We might fail to allocate a buffer, but with this "
+ "being a recycling container that shouldn't happen");
+ return 0;
+ }
+ rec.Record();
+ }
+
+#ifdef DEBUG
+ static uint32_t frame_num = 0;
+ LOG_FRAME(
+ "frame %d (%dx%d)->(%dx%d); rotation %d, timeStamp %u, ntpTimeMs %" PRIu64
+ ", renderTimeMs %" PRIu64,
+ frame_num++, aProps.width(), aProps.height(), dst_width, dst_height,
+ aProps.rotation(), aProps.timeStamp(), aProps.ntpTimeMs(),
+ aProps.renderTimeMs());
+#endif
+
+ if (mImageSize.width != dst_width || mImageSize.height != dst_height) {
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "MediaEngineRemoteVideoSource::FrameSizeChange",
+ [settings = mSettings, updated = mSettingsUpdatedByFrame,
+ holder = std::move(mFirstFramePromiseHolder), dst_width,
+ dst_height]() mutable {
+ settings->mWidth.Value() = dst_width;
+ settings->mHeight.Value() = dst_height;
+ updated->mValue = true;
+ // Since mImageSize was initialized to (0,0), we end up here on the
+ // arrival of the first frame. We resolve the promise representing
+ // arrival of first frame, after correct settings values have been
+ // made available (Resolve() is idempotent if already resolved).
+ holder.ResolveIfExists(true, __func__);
+ }));
+ }
+
+ {
+ MutexAutoLock lock(mMutex);
+ MOZ_ASSERT(mState == kStarted);
+ VideoSegment segment;
+ mImageSize = image->GetSize();
+ segment.AppendFrame(image.forget(), mImageSize, mPrincipal);
+ mTrack->AppendData(&segment);
+ }
+
+ return 0;
+}
+
+uint32_t MediaEngineRemoteVideoSource::GetDistance(
+ const webrtc::CaptureCapability& aCandidate,
+ const NormalizedConstraintSet& aConstraints,
+ const DistanceCalculation aCalculate) const {
+ if (aCalculate == kFeasibility) {
+ return GetFeasibilityDistance(aCandidate, aConstraints);
+ }
+ return GetFitnessDistance(aCandidate, aConstraints);
+}
+
+uint32_t MediaEngineRemoteVideoSource::GetFitnessDistance(
+ const webrtc::CaptureCapability& aCandidate,
+ const NormalizedConstraintSet& aConstraints) const {
+ AssertIsOnOwningThread();
+
+ // Treat width|height|frameRate == 0 on capability as "can do any".
+ // This allows for orthogonal capabilities that are not in discrete steps.
+
+ typedef MediaConstraintsHelper H;
+ uint64_t distance =
+ uint64_t(H::FitnessDistance(mFacingMode, aConstraints.mFacingMode)) +
+ uint64_t(aCandidate.width ? H::FitnessDistance(int32_t(aCandidate.width),
+ aConstraints.mWidth)
+ : 0) +
+ uint64_t(aCandidate.height
+ ? H::FitnessDistance(int32_t(aCandidate.height),
+ aConstraints.mHeight)
+ : 0) +
+ uint64_t(aCandidate.maxFPS ? H::FitnessDistance(double(aCandidate.maxFPS),
+ aConstraints.mFrameRate)
+ : 0);
+ return uint32_t(std::min(distance, uint64_t(UINT32_MAX)));
+}
+
+uint32_t MediaEngineRemoteVideoSource::GetFeasibilityDistance(
+ const webrtc::CaptureCapability& aCandidate,
+ const NormalizedConstraintSet& aConstraints) const {
+ AssertIsOnOwningThread();
+
+ // Treat width|height|frameRate == 0 on capability as "can do any".
+ // This allows for orthogonal capabilities that are not in discrete steps.
+
+ typedef MediaConstraintsHelper H;
+ uint64_t distance =
+ uint64_t(H::FitnessDistance(mFacingMode, aConstraints.mFacingMode)) +
+ uint64_t(aCandidate.width
+ ? H::FeasibilityDistance(int32_t(aCandidate.width),
+ aConstraints.mWidth)
+ : 0) +
+ uint64_t(aCandidate.height
+ ? H::FeasibilityDistance(int32_t(aCandidate.height),
+ aConstraints.mHeight)
+ : 0) +
+ uint64_t(aCandidate.maxFPS
+ ? H::FeasibilityDistance(double(aCandidate.maxFPS),
+ aConstraints.mFrameRate)
+ : 0);
+ return uint32_t(std::min(distance, uint64_t(UINT32_MAX)));
+}
+
+// Find best capability by removing inferiors. May leave >1 of equal distance
+
+/* static */
+void MediaEngineRemoteVideoSource::TrimLessFitCandidates(
+ nsTArray<CapabilityCandidate>& aSet) {
+ uint32_t best = UINT32_MAX;
+ for (auto& candidate : aSet) {
+ if (best > candidate.mDistance) {
+ best = candidate.mDistance;
+ }
+ }
+ aSet.RemoveElementsBy(
+ [best](const auto& set) { return set.mDistance > best; });
+ MOZ_ASSERT(aSet.Length());
+}
+
+uint32_t MediaEngineRemoteVideoSource::GetBestFitnessDistance(
+ const nsTArray<const NormalizedConstraintSet*>& aConstraintSets) const {
+ AssertIsOnOwningThread();
+
+ size_t num = NumCapabilities();
+ nsTArray<CapabilityCandidate> candidateSet;
+ for (size_t i = 0; i < num; i++) {
+ candidateSet.AppendElement(CapabilityCandidate(GetCapability(i)));
+ }
+
+ bool first = true;
+ for (const NormalizedConstraintSet* ns : aConstraintSets) {
+ for (size_t i = 0; i < candidateSet.Length();) {
+ auto& candidate = candidateSet[i];
+ uint32_t distance = GetFitnessDistance(candidate.mCapability, *ns);
+ if (distance == UINT32_MAX) {
+ candidateSet.RemoveElementAt(i);
+ } else {
+ ++i;
+ if (first) {
+ candidate.mDistance = distance;
+ }
+ }
+ }
+ first = false;
+ }
+ if (!candidateSet.Length()) {
+ return UINT32_MAX;
+ }
+ TrimLessFitCandidates(candidateSet);
+ return candidateSet[0].mDistance;
+}
+
+static const char* ConvertVideoTypeToCStr(webrtc::VideoType aType) {
+ switch (aType) {
+ case webrtc::VideoType::kI420:
+ return "I420";
+ case webrtc::VideoType::kIYUV:
+ case webrtc::VideoType::kYV12:
+ return "YV12";
+ case webrtc::VideoType::kRGB24:
+ return "24BG";
+ case webrtc::VideoType::kABGR:
+ return "ABGR";
+ case webrtc::VideoType::kARGB:
+ return "ARGB";
+ case webrtc::VideoType::kARGB4444:
+ return "R444";
+ case webrtc::VideoType::kRGB565:
+ return "RGBP";
+ case webrtc::VideoType::kARGB1555:
+ return "RGBO";
+ case webrtc::VideoType::kYUY2:
+ return "YUY2";
+ case webrtc::VideoType::kUYVY:
+ return "UYVY";
+ case webrtc::VideoType::kMJPEG:
+ return "MJPG";
+ case webrtc::VideoType::kNV21:
+ return "NV21";
+ case webrtc::VideoType::kNV12:
+ return "NV12";
+ case webrtc::VideoType::kBGRA:
+ return "BGRA";
+ case webrtc::VideoType::kUnknown:
+ default:
+ return "unknown";
+ }
+}
+
+static void LogCapability(const char* aHeader,
+ const webrtc::CaptureCapability& aCapability,
+ uint32_t aDistance) {
+ LOG("%s: %4u x %4u x %2u maxFps, %s. Distance = %" PRIu32, aHeader,
+ aCapability.width, aCapability.height, aCapability.maxFPS,
+ ConvertVideoTypeToCStr(aCapability.videoType), aDistance);
+}
+
+bool MediaEngineRemoteVideoSource::ChooseCapability(
+ const NormalizedConstraints& aConstraints, const MediaEnginePrefs& aPrefs,
+ webrtc::CaptureCapability& aCapability,
+ const DistanceCalculation aCalculate) {
+ LOG("%s", __PRETTY_FUNCTION__);
+ AssertIsOnOwningThread();
+
+ if (MOZ_LOG_TEST(gMediaManagerLog, LogLevel::Debug)) {
+ LOG("ChooseCapability: prefs: %dx%d @%dfps", aPrefs.GetWidth(),
+ aPrefs.GetHeight(), aPrefs.mFPS);
+ MediaConstraintsHelper::LogConstraints(aConstraints);
+ if (!aConstraints.mAdvanced.empty()) {
+ LOG("Advanced array[%zu]:", aConstraints.mAdvanced.size());
+ for (auto& advanced : aConstraints.mAdvanced) {
+ MediaConstraintsHelper::LogConstraints(advanced);
+ }
+ }
+ }
+
+ switch (mCapEngine) {
+ case camera::ScreenEngine:
+ case camera::WinEngine: {
+ FlattenedConstraints c(aConstraints);
+ // The actual resolution to constrain around is not easy to find ahead of
+ // time (and may in fact change over time), so as a hack, we push ideal
+ // and max constraints down to desktop_capture_impl.cc and finish the
+ // algorithm there.
+ // TODO: This can be removed in bug 1453269.
+ aCapability.width =
+ (std::min(0xffff, c.mWidth.mIdeal.valueOr(0)) & 0xffff) << 16 |
+ (std::min(0xffff, c.mWidth.mMax) & 0xffff);
+ aCapability.height =
+ (std::min(0xffff, c.mHeight.mIdeal.valueOr(0)) & 0xffff) << 16 |
+ (std::min(0xffff, c.mHeight.mMax) & 0xffff);
+ aCapability.maxFPS =
+ c.mFrameRate.Clamp(c.mFrameRate.mIdeal.valueOr(aPrefs.mFPS));
+ return true;
+ }
+ case camera::BrowserEngine: {
+ FlattenedConstraints c(aConstraints);
+ aCapability.maxFPS =
+ c.mFrameRate.Clamp(c.mFrameRate.mIdeal.valueOr(aPrefs.mFPS));
+ return true;
+ }
+ default:
+ break;
+ }
+
+ nsTArray<CapabilityCandidate> candidateSet;
+ size_t num = NumCapabilities();
+ for (size_t i = 0; i < num; i++) {
+ candidateSet.AppendElement(CapabilityCandidate(GetCapability(i)));
+ }
+
+ if (mCapabilitiesAreHardcoded && mCapEngine == camera::CameraEngine) {
+ // We have a hardcoded capability, which means this camera didn't report
+ // discrete capabilities. It might still allow a ranged capability, so we
+ // add a couple of default candidates based on prefs and constraints.
+ // The chosen candidate will be propagated to StartCapture() which will fail
+ // for an invalid candidate.
+ MOZ_DIAGNOSTIC_ASSERT(mCapabilities.Length() == 1);
+ MOZ_DIAGNOSTIC_ASSERT(candidateSet.Length() == 1);
+ candidateSet.Clear();
+
+ FlattenedConstraints c(aConstraints);
+ // Reuse the code across both the low-definition (`false`) pref and
+ // the high-definition (`true`) pref.
+ // If there are constraints we try to satisfy them but we default to prefs.
+ // Note that since constraints are from content and can literally be
+ // anything we put (rather generous) caps on them.
+ for (bool isHd : {false, true}) {
+ webrtc::CaptureCapability cap;
+ int32_t prefWidth = aPrefs.GetWidth(isHd);
+ int32_t prefHeight = aPrefs.GetHeight(isHd);
+
+ cap.width = c.mWidth.Get(prefWidth);
+ cap.width = std::max(0, std::min(cap.width, 7680));
+
+ cap.height = c.mHeight.Get(prefHeight);
+ cap.height = std::max(0, std::min(cap.height, 4320));
+
+ cap.maxFPS = c.mFrameRate.Get(aPrefs.mFPS);
+ cap.maxFPS = std::max(0, std::min(cap.maxFPS, 480));
+
+ if (cap.width != prefWidth) {
+ // Width was affected by constraints.
+ // We'll adjust the height too so the aspect ratio is retained.
+ cap.height = cap.width * prefHeight / prefWidth;
+ } else if (cap.height != prefHeight) {
+ // Height was affected by constraints but not width.
+ // We'll adjust the width too so the aspect ratio is retained.
+ cap.width = cap.height * prefWidth / prefHeight;
+ }
+
+ if (candidateSet.Contains(cap, CapabilityComparator())) {
+ continue;
+ }
+ LogCapability("Hardcoded capability", cap, 0);
+ candidateSet.AppendElement(cap);
+ }
+ }
+
+ // First, filter capabilities by required constraints (min, max, exact).
+
+ for (size_t i = 0; i < candidateSet.Length();) {
+ auto& candidate = candidateSet[i];
+ candidate.mDistance =
+ GetDistance(candidate.mCapability, aConstraints, aCalculate);
+ LogCapability("Capability", candidate.mCapability, candidate.mDistance);
+ if (candidate.mDistance == UINT32_MAX) {
+ candidateSet.RemoveElementAt(i);
+ } else {
+ ++i;
+ }
+ }
+
+ if (candidateSet.IsEmpty()) {
+ LOG("failed to find capability match from %zu choices",
+ candidateSet.Length());
+ return false;
+ }
+
+ // Filter further with all advanced constraints (that don't overconstrain).
+
+ for (const auto& cs : aConstraints.mAdvanced) {
+ nsTArray<CapabilityCandidate> rejects;
+ for (size_t i = 0; i < candidateSet.Length();) {
+ if (GetDistance(candidateSet[i].mCapability, cs, aCalculate) ==
+ UINT32_MAX) {
+ rejects.AppendElement(candidateSet[i]);
+ candidateSet.RemoveElementAt(i);
+ } else {
+ ++i;
+ }
+ }
+ if (!candidateSet.Length()) {
+ candidateSet.AppendElements(std::move(rejects));
+ }
+ }
+ MOZ_ASSERT(
+ candidateSet.Length(),
+ "advanced constraints filtering step can't reduce candidates to zero");
+
+ // Remaining algorithm is up to the UA.
+
+ TrimLessFitCandidates(candidateSet);
+
+ // Any remaining multiples all have the same distance. A common case of this
+ // occurs when no ideal is specified. Lean toward defaults.
+ uint32_t sameDistance = candidateSet[0].mDistance;
+ {
+ MediaTrackConstraintSet prefs;
+ prefs.mWidth.Construct().SetAsLong() = aPrefs.GetWidth();
+ prefs.mHeight.Construct().SetAsLong() = aPrefs.GetHeight();
+ prefs.mFrameRate.Construct().SetAsDouble() = aPrefs.mFPS;
+ NormalizedConstraintSet normPrefs(prefs, false);
+
+ for (auto& candidate : candidateSet) {
+ candidate.mDistance =
+ GetDistance(candidate.mCapability, normPrefs, aCalculate);
+ }
+ TrimLessFitCandidates(candidateSet);
+ }
+
+ aCapability = candidateSet[0].mCapability;
+
+ LogCapability("Chosen capability", aCapability, sameDistance);
+ return true;
+}
+
+void MediaEngineRemoteVideoSource::GetSettings(
+ MediaTrackSettings& aOutSettings) const {
+ aOutSettings = *mSettings;
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/MediaEngineRemoteVideoSource.h b/dom/media/webrtc/MediaEngineRemoteVideoSource.h
new file mode 100644
index 0000000000..7e4f852701
--- /dev/null
+++ b/dom/media/webrtc/MediaEngineRemoteVideoSource.h
@@ -0,0 +1,242 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MEDIAENGINE_REMOTE_VIDEO_SOURCE_H_
+#define MEDIAENGINE_REMOTE_VIDEO_SOURCE_H_
+
+#include "prcvar.h"
+#include "prthread.h"
+
+#include "mozilla/Mutex.h"
+#include "nsCOMPtr.h"
+#include "nsThreadUtils.h"
+#include "DOMMediaStream.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsComponentManagerUtils.h"
+
+// Avoid warnings about redefinition of WARN_UNUSED_RESULT
+#include "ipc/IPCMessageUtils.h"
+#include "VideoUtils.h"
+#include "MediaEngineSource.h"
+#include "VideoSegment.h"
+#include "AudioSegment.h"
+#include "MediaTrackGraph.h"
+
+#include "MediaEngineWrapper.h"
+#include "mozilla/dom/MediaStreamTrackBinding.h"
+
+// Camera Access via IPC
+#include "CamerasChild.h"
+
+#include "NullTransport.h"
+
+// WebRTC includes
+#include "common_video/include/video_frame_buffer_pool.h"
+#include "modules/video_capture/video_capture_defines.h"
+
+namespace webrtc {
+using CaptureCapability = VideoCaptureCapability;
+}
+
+namespace mozilla {
+
+// Fitness distance is defined in
+// https://w3c.github.io/mediacapture-main/getusermedia.html#dfn-selectsettings
+
+// The main difference of feasibility and fitness distance is that if the
+// constraint is required ('max', or 'exact'), and the settings dictionary's
+// value for the constraint does not satisfy the constraint, the fitness
+// distance is positive infinity. Given a continuous space of settings
+// dictionaries comprising all discrete combinations of dimension and frame-rate
+// related properties, the feasibility distance is still in keeping with the
+// constraints algorithm.
+enum DistanceCalculation { kFitness, kFeasibility };
+
+/**
+ * The WebRTC implementation of the MediaEngine interface.
+ */
+class MediaEngineRemoteVideoSource : public MediaEngineSource,
+ public camera::FrameRelay {
+ ~MediaEngineRemoteVideoSource();
+
+ struct CapabilityCandidate {
+ explicit CapabilityCandidate(webrtc::CaptureCapability aCapability,
+ uint32_t aDistance = 0)
+ : mCapability(aCapability), mDistance(aDistance) {}
+
+ const webrtc::CaptureCapability mCapability;
+ uint32_t mDistance;
+ };
+
+ class CapabilityComparator {
+ public:
+ bool Equals(const CapabilityCandidate& aCandidate,
+ const webrtc::CaptureCapability& aCapability) const {
+ return aCandidate.mCapability == aCapability;
+ }
+ };
+
+ bool ChooseCapability(const NormalizedConstraints& aConstraints,
+ const MediaEnginePrefs& aPrefs,
+ webrtc::CaptureCapability& aCapability,
+ const DistanceCalculation aCalculate);
+
+ uint32_t GetDistance(const webrtc::CaptureCapability& aCandidate,
+ const NormalizedConstraintSet& aConstraints,
+ const DistanceCalculation aCalculate) const;
+
+ uint32_t GetFitnessDistance(
+ const webrtc::CaptureCapability& aCandidate,
+ const NormalizedConstraintSet& aConstraints) const;
+
+ uint32_t GetFeasibilityDistance(
+ const webrtc::CaptureCapability& aCandidate,
+ const NormalizedConstraintSet& aConstraints) const;
+
+ static void TrimLessFitCandidates(nsTArray<CapabilityCandidate>& aSet);
+
+ public:
+ explicit MediaEngineRemoteVideoSource(const MediaDevice* aMediaDevice);
+
+ // ExternalRenderer
+ int DeliverFrame(uint8_t* aBuffer,
+ const camera::VideoFrameProperties& aProps) override;
+
+ // MediaEngineSource
+ nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
+ const MediaEnginePrefs& aPrefs, uint64_t aWindowID,
+ const char** aOutBadConstraint) override;
+ nsresult Deallocate() override;
+ void SetTrack(const RefPtr<MediaTrack>& aTrack,
+ const PrincipalHandle& aPrincipal) override;
+ nsresult Start() override;
+ nsresult Reconfigure(const dom::MediaTrackConstraints& aConstraints,
+ const MediaEnginePrefs& aPrefs,
+ const char** aOutBadConstraint) override;
+ nsresult FocusOnSelectedSource() override;
+ nsresult Stop() override;
+
+ uint32_t GetBestFitnessDistance(
+ const nsTArray<const NormalizedConstraintSet*>& aConstraintSets)
+ const override;
+ void GetSettings(dom::MediaTrackSettings& aOutSettings) const override;
+
+ RefPtr<GenericNonExclusivePromise> GetFirstFramePromise() const override {
+ return mFirstFramePromise;
+ }
+
+ const TrackingId& GetTrackingId() const override;
+
+ static camera::CaptureEngine CaptureEngine(dom::MediaSourceEnum aMediaSource);
+
+ private:
+ /**
+ * Returns the number of capabilities for the underlying device.
+ *
+ * Guaranteed to return at least one capability.
+ */
+ size_t NumCapabilities() const;
+
+ /**
+ * Returns the capability with index `aIndex` for our assigned device.
+ *
+ * It is an error to call this with `aIndex >= NumCapabilities()`.
+ *
+ * The lifetime of the returned capability is the same as for this source.
+ */
+ webrtc::CaptureCapability& GetCapability(size_t aIndex) const;
+
+ int mCaptureId = -1;
+ const camera::CaptureEngine mCapEngine; // source of media (cam, screen etc)
+
+ // A tracking id used to uniquely identify the source of video frames.
+ // Set under mMutex on the owning thread. Accessed under one of the two.
+ TrackingId mTrackingId;
+
+ // Mirror of mTrackingId on the frame-delivering thread (Cameras IPC).
+ Maybe<TrackingId> mFrameDeliveringTrackingId;
+
+ // mMutex protects certain members on 3 threads:
+ // MediaManager, Cameras IPC and MediaTrackGraph.
+ Mutex mMutex MOZ_UNANNOTATED;
+
+ // Current state of this source.
+ // Set under mMutex on the owning thread. Accessed under one of the two.
+ MediaEngineSourceState mState = kReleased;
+
+ // The source track that we feed video data to.
+ // Set under mMutex on the owning thread. Accessed under one of the two.
+ RefPtr<SourceMediaTrack> mTrack;
+
+ // The PrincipalHandle that gets attached to the frames we feed to mTrack.
+ // Set under mMutex on the owning thread. Accessed under one of the two.
+ PrincipalHandle mPrincipal = PRINCIPAL_HANDLE_NONE;
+
+ // Set in Start() and Deallocate() on the owning thread.
+ // Accessed in DeliverFrame() on the camera IPC thread, guaranteed to happen
+ // after Start() and before the end of Stop().
+ RefPtr<layers::ImageContainer> mImageContainer;
+
+ // A buffer pool used to manage the temporary buffer used when rescaling
+ // incoming images. Cameras IPC thread only.
+ webrtc::VideoFrameBufferPool mRescalingBufferPool;
+
+ // The intrinsic size of the latest captured image, so we can feed black
+ // images of the same size while stopped.
+ // Set under mMutex on the Cameras IPC thread. Accessed under one of the two.
+ gfx::IntSize mImageSize = gfx::IntSize(0, 0);
+
+ struct AtomicBool {
+ Atomic<bool> mValue;
+ };
+
+ // True when resolution settings have been updated from a real frame's
+ // resolution. Threadsafe.
+ // TODO: This can be removed in bug 1453269.
+ const RefPtr<media::Refcountable<AtomicBool>> mSettingsUpdatedByFrame;
+
+ // The current settings of this source.
+ // Note that these may be different from the settings of the underlying device
+ // since we scale frames to avoid fingerprinting.
+ // Members are main thread only.
+ const RefPtr<media::Refcountable<dom::MediaTrackSettings>> mSettings;
+ MozPromiseHolder<GenericNonExclusivePromise> mFirstFramePromiseHolder;
+ RefPtr<GenericNonExclusivePromise> mFirstFramePromise;
+
+ // The capability currently chosen by constraints of the user of this source.
+ // Set under mMutex on the owning thread. Accessed under one of the two.
+ webrtc::CaptureCapability mCapability;
+
+ /**
+ * Capabilities that we choose between when applying constraints.
+ *
+ * This allows for memoization of capabilities as they're requested from the
+ * parent process.
+ *
+ * This is mutable so that the const methods NumCapabilities() and
+ * GetCapability() can reset it. Owning thread only.
+ */
+ mutable nsTArray<UniquePtr<webrtc::CaptureCapability>> mCapabilities;
+
+ /**
+ * True if mCapabilities only contains hardcoded capabilities. This can happen
+ * if the underlying device is not reporting any capabilities. These can be
+ * affected by constraints, so they're evaluated in ChooseCapability() rather
+ * than GetCapability().
+ *
+ * This is mutable so that the const methods NumCapabilities() and
+ * GetCapability() can reset it. Owning thread only.
+ */
+ mutable bool mCapabilitiesAreHardcoded = false;
+
+ const RefPtr<const MediaDevice> mMediaDevice;
+ const nsCString mDeviceUUID;
+ Maybe<nsString> mFacingMode;
+};
+
+} // namespace mozilla
+
+#endif /* MEDIAENGINE_REMOTE_VIDEO_SOURCE_H_ */
diff --git a/dom/media/webrtc/MediaEngineSource.cpp b/dom/media/webrtc/MediaEngineSource.cpp
new file mode 100644
index 0000000000..d0e5e23ae7
--- /dev/null
+++ b/dom/media/webrtc/MediaEngineSource.cpp
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MediaEngineSource.h"
+
+#include "mozilla/dom/MediaTrackSettingsBinding.h"
+
+namespace mozilla {
+
+using dom::MediaSourceEnum;
+using dom::MediaTrackSettings;
+
+// These need a definition somewhere because template
+// code is allowed to take their address, and they aren't
+// guaranteed to have one without this.
+const unsigned int MediaEngineSource::kMaxDeviceNameLength;
+const unsigned int MediaEngineSource::kMaxUniqueIdLength;
+
+/* static */
+bool MediaEngineSource::IsVideo(MediaSourceEnum aSource) {
+ switch (aSource) {
+ case MediaSourceEnum::Camera:
+ case MediaSourceEnum::Screen:
+ case MediaSourceEnum::Window:
+ case MediaSourceEnum::Browser:
+ return true;
+ case MediaSourceEnum::Microphone:
+ case MediaSourceEnum::AudioCapture:
+ case MediaSourceEnum::Other:
+ return false;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown type");
+ return false;
+ }
+}
+
+/* static */
+bool MediaEngineSource::IsAudio(MediaSourceEnum aSource) {
+ switch (aSource) {
+ case MediaSourceEnum::Microphone:
+ case MediaSourceEnum::AudioCapture:
+ return true;
+ case MediaSourceEnum::Camera:
+ case MediaSourceEnum::Screen:
+ case MediaSourceEnum::Window:
+ case MediaSourceEnum::Browser:
+ case MediaSourceEnum::Other:
+ return false;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown type");
+ return false;
+ }
+}
+
+bool MediaEngineSource::IsFake() const { return false; }
+
+nsresult MediaEngineSource::FocusOnSelectedSource() {
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+nsresult MediaEngineSource::TakePhoto(MediaEnginePhotoCallback* aCallback) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+MediaEngineSource::~MediaEngineSource() = default;
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/MediaEngineSource.h b/dom/media/webrtc/MediaEngineSource.h
new file mode 100644
index 0000000000..d32796b094
--- /dev/null
+++ b/dom/media/webrtc/MediaEngineSource.h
@@ -0,0 +1,255 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MediaEngineSource_h
+#define MediaEngineSource_h
+
+#include "MediaSegment.h"
+#include "MediaTrackConstraints.h"
+#include "mozilla/dom/MediaStreamTrackBinding.h"
+#include "mozilla/media/MediaUtils.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ThreadSafeWeakPtr.h"
+#include "nsStringFwd.h"
+#include "PerformanceRecorder.h"
+
+namespace mozilla {
+
+namespace dom {
+class Blob;
+struct MediaTrackSettings;
+} // namespace dom
+
+namespace ipc {
+class PrincipalInfo;
+} // namespace ipc
+
+class MediaEnginePhotoCallback;
+class MediaEnginePrefs;
+class MediaTrack;
+
+/**
+ * Callback interface for TakePhoto(). Either PhotoComplete() or PhotoError()
+ * should be called.
+ */
+class MediaEnginePhotoCallback {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaEnginePhotoCallback)
+
+ // aBlob is the image captured by MediaEngineSource. It is
+ // called on main thread.
+ virtual nsresult PhotoComplete(already_AddRefed<dom::Blob> aBlob) = 0;
+
+ // It is called on main thread. aRv is the error code.
+ virtual nsresult PhotoError(nsresult aRv) = 0;
+
+ protected:
+ virtual ~MediaEnginePhotoCallback() = default;
+};
+
+/**
+ * Lifecycle state of MediaEngineSource.
+ */
+enum MediaEngineSourceState {
+ kAllocated, // Allocated, not yet started.
+ kStarted, // Previously allocated or stopped, then started.
+ kStopped, // Previously started, then stopped.
+ kReleased // Not allocated.
+};
+
+/**
+ * The pure interface of a MediaEngineSource.
+ *
+ * Most sources are helped by the defaults implemented in MediaEngineSource.
+ */
+class MediaEngineSourceInterface {
+ public:
+ /**
+ * Return true if this is a fake source. I.e., if it is generating media
+ * itself rather than being an interface to underlying hardware.
+ */
+ virtual bool IsFake() const = 0;
+
+ /**
+ * Override w/a promise if source has frames, in order to potentially allow
+ * deferring success of source acquisition until first frame has arrived.
+ */
+ virtual RefPtr<GenericNonExclusivePromise> GetFirstFramePromise() const {
+ return nullptr;
+ }
+
+ /**
+ * Get an id uniquely identifying the source of video frames that this
+ * MediaEngineSource represents. This can be used in profiler markers to
+ * separate markers from different sources into different lanes.
+ */
+ virtual const TrackingId& GetTrackingId() const = 0;
+
+ /**
+ * Called by MediaEngine to allocate an instance of this source.
+ */
+ virtual nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
+ const MediaEnginePrefs& aPrefs, uint64_t aWindowID,
+ const char** aOutBadConstraint) = 0;
+
+ /**
+ * Called by MediaEngine when a MediaTrack has been provided for the source to
+ * feed data to.
+ *
+ * This must be called before Start.
+ */
+ virtual void SetTrack(const RefPtr<MediaTrack>& aTrack,
+ const PrincipalHandle& aPrincipal) = 0;
+
+ /**
+ * Called by MediaEngine to start feeding data to the track.
+ *
+ * NB: Audio sources handle the enabling of pulling themselves.
+ */
+ virtual nsresult Start() = 0;
+
+ /**
+ * This brings focus to the selected source, e.g. to bring a captured window
+ * to the front.
+ *
+ * We return one of the following:
+ * NS_OK - Success.
+ * NS_ERROR_NOT_AVAILABLE - For backends where focusing does not make sense.
+ * NS_ERROR_NOT_IMPLEMENTED - For backends where focusing makes sense, but
+ * is not yet implemented.
+ * NS_ERROR_FAILURE - Failures reported from underlying code.
+ */
+ virtual nsresult FocusOnSelectedSource() = 0;
+
+ /**
+ * Applies new constraints to the capability selection for the underlying
+ * device.
+ *
+ * Should the constraints lead to choosing a new capability while the device
+ * is actively being captured, the device will restart using the new
+ * capability.
+ *
+ * We return one of the following:
+ * NS_OK - Successful reconfigure.
+ * NS_ERROR_INVALID_ARG - Couldn't find a capability fitting aConstraints.
+ * See aBadConstraint for details.
+ * NS_ERROR_UNEXPECTED - Reconfiguring the underlying device failed
+ * unexpectedly. This leaves the device in a stopped
+ * state.
+ */
+ virtual nsresult Reconfigure(const dom::MediaTrackConstraints& aConstraints,
+ const MediaEnginePrefs& aPrefs,
+ const char** aOutBadConstraint) = 0;
+
+ /**
+ * Called by MediaEngine to stop feeding data to the track.
+ *
+ * Double-stopping is allowed and will return NS_OK. This is necessary
+ * sometimes during shutdown.
+ *
+ * NB: Audio sources handle the disabling of pulling themselves.
+ */
+ virtual nsresult Stop() = 0;
+
+ /**
+ * Called by MediaEngine to deallocate an underlying device.
+ */
+ virtual nsresult Deallocate() = 0;
+
+ /**
+ * If implementation of MediaEngineSource supports TakePhoto(), the picture
+ * should be returned via aCallback object. Otherwise, it returns
+ * NS_ERROR_NOT_IMPLEMENTED.
+ */
+ virtual nsresult TakePhoto(MediaEnginePhotoCallback* aCallback) = 0;
+
+ /**
+ * GetBestFitnessDistance returns the best distance the capture device can
+ * offer as a whole, given an accumulated number of ConstraintSets. Ideal
+ * values are considered in the first ConstraintSet only. Plain values are
+ * treated as Ideal in the first ConstraintSet. Plain values are treated as
+ * Exact in subsequent ConstraintSets. Infinity = UINT32_MAX e.g. device
+ * cannot satisfy accumulated ConstraintSets. A finite result may be used to
+ * calculate this device's ranking as a choice.
+ */
+ virtual uint32_t GetBestFitnessDistance(
+ const nsTArray<const NormalizedConstraintSet*>& aConstraintSets)
+ const = 0;
+
+ /**
+ * Returns the current settings of the underlying device.
+ *
+ * Note that this might not be the settings of the underlying hardware.
+ * In case of a camera where we intervene and scale frames to avoid
+ * leaking information from other documents than the current one,
+ * GetSettings() will return the scaled resolution. I.e., the
+ * device settings as seen by js.
+ */
+ virtual void GetSettings(dom::MediaTrackSettings& aOutSettings) const = 0;
+};
+
+/**
+ * Abstract base class for MediaEngineSources.
+ *
+ * Implements defaults for some common MediaEngineSourceInterface methods below.
+ * Also implements RefPtr support and an owning-thread model for thread safety
+ * checks in subclasses.
+ */
+class MediaEngineSource : public MediaEngineSourceInterface {
+ public:
+ // code inside webrtc.org assumes these sizes; don't use anything smaller
+ // without verifying it's ok
+ static const unsigned int kMaxDeviceNameLength = 128;
+ static const unsigned int kMaxUniqueIdLength = 256;
+
+ /**
+ * Returns true if the given source type is for video, false otherwise.
+ * Only call with real types.
+ */
+ static bool IsVideo(dom::MediaSourceEnum aSource);
+
+ /**
+ * Returns true if the given source type is for audio, false otherwise.
+ * Only call with real types.
+ */
+ static bool IsAudio(dom::MediaSourceEnum aSource);
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaEngineSource)
+ NS_DECL_OWNINGEVENTTARGET
+
+ void AssertIsOnOwningThread() const {
+ NS_ASSERT_OWNINGTHREAD(MediaEngineSource);
+ }
+
+ const TrackingId& GetTrackingId() const override {
+ static auto notImplementedId = TrackingId();
+ return notImplementedId;
+ }
+
+ // Not fake by default.
+ bool IsFake() const override;
+
+ // Returns NS_ERROR_NOT_AVAILABLE by default.
+ nsresult FocusOnSelectedSource() override;
+
+ // TakePhoto returns NS_ERROR_NOT_IMPLEMENTED by default,
+ // to tell the caller to fallback to other methods.
+ nsresult TakePhoto(MediaEnginePhotoCallback* aCallback) override;
+
+ // Returns a default distance of 0 for devices that don't have capabilities.
+ uint32_t GetBestFitnessDistance(
+ const nsTArray<const NormalizedConstraintSet*>& aConstraintSets)
+ const override {
+ return 0;
+ }
+
+ protected:
+ virtual ~MediaEngineSource();
+};
+
+} // namespace mozilla
+
+#endif /* MediaEngineSource_h */
diff --git a/dom/media/webrtc/MediaEngineWebRTC.cpp b/dom/media/webrtc/MediaEngineWebRTC.cpp
new file mode 100644
index 0000000000..cf638497a9
--- /dev/null
+++ b/dom/media/webrtc/MediaEngineWebRTC.cpp
@@ -0,0 +1,299 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MediaEngineWebRTC.h"
+
+#include "CamerasChild.h"
+#include "MediaEngineRemoteVideoSource.h"
+#include "MediaEngineWebRTCAudio.h"
+#include "MediaManager.h"
+#include "mozilla/Logging.h"
+
+// Pipewire detection support
+#if defined(WEBRTC_USE_PIPEWIRE)
+# include "mozilla/StaticPrefs_media.h"
+# include "modules/desktop_capture/desktop_capturer.h"
+#endif
+
+#define FAKE_ONDEVICECHANGE_EVENT_PERIOD_IN_MS 500
+
+static mozilla::LazyLogModule sGetUserMediaLog("GetUserMedia");
+#undef LOG
+#define LOG(args) MOZ_LOG(sGetUserMediaLog, mozilla::LogLevel::Debug, args)
+
+namespace mozilla {
+
+using AudioDeviceSet = CubebDeviceEnumerator::AudioDeviceSet;
+using camera::CamerasChild;
+using camera::GetChildAndCall;
+using dom::MediaSourceEnum;
+
+CubebDeviceEnumerator* GetEnumerator() {
+ return CubebDeviceEnumerator::GetInstance();
+}
+
+MediaEngineWebRTC::MediaEngineWebRTC() {
+ AssertIsOnOwningThread();
+
+ GetChildAndCall(
+ &CamerasChild::ConnectDeviceListChangeListener<MediaEngineWebRTC>,
+ &mCameraListChangeListener, AbstractThread::MainThread(), this,
+ &MediaEngineWebRTC::DeviceListChanged);
+ mMicrophoneListChangeListener =
+ GetEnumerator()->OnAudioInputDeviceListChange().Connect(
+ AbstractThread::MainThread(), this,
+ &MediaEngineWebRTC::DeviceListChanged);
+ mSpeakerListChangeListener =
+ GetEnumerator()->OnAudioOutputDeviceListChange().Connect(
+ AbstractThread::MainThread(), this,
+ &MediaEngineWebRTC::DeviceListChanged);
+}
+
+void MediaEngineWebRTC::EnumerateVideoDevices(
+ MediaSourceEnum aMediaSource, nsTArray<RefPtr<MediaDevice>>* aDevices) {
+ AssertIsOnOwningThread();
+ // flag sources with cross-origin exploit potential
+ bool scaryKind = (aMediaSource == MediaSourceEnum::Screen ||
+ aMediaSource == MediaSourceEnum::Browser);
+#if defined(WEBRTC_USE_PIPEWIRE)
+ bool canRequestOsLevelPrompt =
+ mozilla::StaticPrefs::media_webrtc_capture_allow_pipewire() &&
+ webrtc::DesktopCapturer::IsRunningUnderWayland() &&
+ (aMediaSource == MediaSourceEnum::Application ||
+ aMediaSource == MediaSourceEnum::Screen ||
+ aMediaSource == MediaSourceEnum::Window);
+#else
+ bool canRequestOsLevelPrompt = false;
+#endif
+ /*
+ * We still enumerate every time, in case a new device was plugged in since
+ * the last call. TODO: Verify that WebRTC actually does deal with hotplugging
+ * new devices (with or without new engine creation) and accordingly adjust.
+ * Enumeration is not neccessary if GIPS reports the same set of devices
+ * for a given instance of the engine.
+ */
+ int num;
+#if defined(_ARM64_) && defined(XP_WIN)
+ // There are problems with using DirectShow on versions of Windows before
+ // 19H1 on arm64. This disables the camera on older versions of Windows.
+ if (aMediaSource == MediaSourceEnum::Camera) {
+ typedef ULONG (*RtlGetVersionFn)(LPOSVERSIONINFOEXW);
+ RtlGetVersionFn RtlGetVersion;
+ RtlGetVersion = (RtlGetVersionFn)GetProcAddress(GetModuleHandleA("ntdll"),
+ "RtlGetVersion");
+ if (RtlGetVersion) {
+ OSVERSIONINFOEXW info;
+ info.dwOSVersionInfoSize = sizeof(info);
+ RtlGetVersion(&info);
+ // 19H1 is 18346
+ if (info.dwBuildNumber < 18346) {
+ return;
+ }
+ }
+ }
+#endif
+ camera::CaptureEngine capEngine =
+ MediaEngineRemoteVideoSource::CaptureEngine(aMediaSource);
+ num = GetChildAndCall(&CamerasChild::NumberOfCaptureDevices, capEngine);
+
+ for (int i = 0; i < num; i++) {
+ char deviceName[MediaEngineSource::kMaxDeviceNameLength];
+ char uniqueId[MediaEngineSource::kMaxUniqueIdLength];
+ bool scarySource = false;
+
+ // paranoia
+ deviceName[0] = '\0';
+ uniqueId[0] = '\0';
+ int error;
+
+ error = GetChildAndCall(&CamerasChild::GetCaptureDevice, capEngine, i,
+ deviceName, sizeof(deviceName), uniqueId,
+ sizeof(uniqueId), &scarySource);
+ if (error) {
+ LOG(("camera:GetCaptureDevice: Failed %d", error));
+ continue;
+ }
+#ifdef DEBUG
+ LOG((" Capture Device Index %d, Name %s", i, deviceName));
+
+ webrtc::CaptureCapability cap;
+ int numCaps = GetChildAndCall(&CamerasChild::NumberOfCapabilities,
+ capEngine, uniqueId);
+ LOG(("Number of Capabilities %d", numCaps));
+ for (int j = 0; j < numCaps; j++) {
+ if (GetChildAndCall(&CamerasChild::GetCaptureCapability, capEngine,
+ uniqueId, j, &cap) != 0) {
+ break;
+ }
+ LOG(("type=%d width=%d height=%d maxFPS=%d",
+ static_cast<int>(cap.videoType), cap.width, cap.height, cap.maxFPS));
+ }
+#endif
+
+ NS_ConvertUTF8toUTF16 name(deviceName);
+ NS_ConvertUTF8toUTF16 uuid(uniqueId);
+ // The remote video backend doesn't implement group id. We return the
+ // device name and higher layers will correlate this with the name of
+ // audio devices.
+ aDevices->EmplaceBack(new MediaDevice(
+ this, aMediaSource, name, uuid, uuid,
+ MediaDevice::IsScary(scaryKind || scarySource),
+ canRequestOsLevelPrompt ? MediaDevice::OsPromptable::Yes
+ : MediaDevice::OsPromptable::No));
+ }
+}
+
+void MediaEngineWebRTC::EnumerateMicrophoneDevices(
+ nsTArray<RefPtr<MediaDevice>>* aDevices) {
+ AssertIsOnOwningThread();
+
+ RefPtr<const AudioDeviceSet> devices =
+ GetEnumerator()->EnumerateAudioInputDevices();
+
+ DebugOnly<bool> foundPreferredDevice = false;
+
+ for (const auto& deviceInfo : *devices) {
+#ifndef ANDROID
+ MOZ_ASSERT(deviceInfo->DeviceID());
+#endif
+ LOG(("Cubeb device: type 0x%x, state 0x%x, name %s, id %p",
+ deviceInfo->Type(), deviceInfo->State(),
+ NS_ConvertUTF16toUTF8(deviceInfo->Name()).get(),
+ deviceInfo->DeviceID()));
+
+ if (deviceInfo->State() == CUBEB_DEVICE_STATE_ENABLED) {
+ MOZ_ASSERT(deviceInfo->Type() == CUBEB_DEVICE_TYPE_INPUT);
+ // Lie and provide the name as UUID
+ RefPtr device = new MediaDevice(this, deviceInfo, deviceInfo->Name());
+ if (deviceInfo->Preferred()) {
+#ifdef DEBUG
+ if (!foundPreferredDevice) {
+ foundPreferredDevice = true;
+ } else {
+ // This is possible on windows, there is a default communication
+ // device, and a default device:
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=1542739
+# ifndef XP_WIN
+ MOZ_ASSERT(!foundPreferredDevice,
+ "Found more than one preferred audio input device"
+ "while enumerating");
+# endif
+ }
+#endif
+ aDevices->InsertElementAt(0, std::move(device));
+ } else {
+ aDevices->AppendElement(std::move(device));
+ }
+ }
+ }
+}
+
+void MediaEngineWebRTC::EnumerateSpeakerDevices(
+ nsTArray<RefPtr<MediaDevice>>* aDevices) {
+ AssertIsOnOwningThread();
+
+ RefPtr<const AudioDeviceSet> devices =
+ GetEnumerator()->EnumerateAudioOutputDevices();
+
+#ifndef XP_WIN
+ DebugOnly<bool> preferredDeviceFound = false;
+#endif
+ for (const auto& deviceInfo : *devices) {
+ LOG(("Cubeb device: type 0x%x, state 0x%x, name %s, id %p",
+ deviceInfo->Type(), deviceInfo->State(),
+ NS_ConvertUTF16toUTF8(deviceInfo->Name()).get(),
+ deviceInfo->DeviceID()));
+ if (deviceInfo->State() == CUBEB_DEVICE_STATE_ENABLED) {
+ MOZ_ASSERT(deviceInfo->Type() == CUBEB_DEVICE_TYPE_OUTPUT);
+ nsString uuid(deviceInfo->Name());
+ // If, for example, input and output are in the same device, uuid
+ // would be the same for both which ends up to create the same
+ // deviceIDs (in JS).
+ uuid.Append(u"_Speaker"_ns);
+ RefPtr device = new MediaDevice(this, deviceInfo, uuid);
+ if (deviceInfo->Preferred()) {
+ // In windows is possible to have more than one preferred device
+#if defined(DEBUG) && !defined(XP_WIN)
+ MOZ_ASSERT(!preferredDeviceFound, "More than one preferred device");
+ preferredDeviceFound = true;
+#endif
+ aDevices->InsertElementAt(0, std::move(device));
+ } else {
+ aDevices->AppendElement(std::move(device));
+ }
+ }
+ }
+}
+
+void MediaEngineWebRTC::EnumerateDevices(
+ MediaSourceEnum aMediaSource, MediaSinkEnum aMediaSink,
+ nsTArray<RefPtr<MediaDevice>>* aDevices) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aMediaSource != MediaSourceEnum::Other ||
+ aMediaSink != MediaSinkEnum::Other);
+ if (MediaEngineSource::IsVideo(aMediaSource)) {
+ switch (aMediaSource) {
+ case MediaSourceEnum::Window:
+ // Since the mediaSource constraint is deprecated, treat the Window
+ // value as a request for getDisplayMedia-equivalent sharing: Combine
+ // window and fullscreen into a single list of choices. The other values
+ // are still useful for testing.
+ EnumerateVideoDevices(MediaSourceEnum::Window, aDevices);
+ EnumerateVideoDevices(MediaSourceEnum::Browser, aDevices);
+ EnumerateVideoDevices(MediaSourceEnum::Screen, aDevices);
+ break;
+ case MediaSourceEnum::Screen:
+ case MediaSourceEnum::Browser:
+ case MediaSourceEnum::Camera:
+ EnumerateVideoDevices(aMediaSource, aDevices);
+ break;
+ default:
+ MOZ_CRASH("No valid video source");
+ break;
+ }
+ } else if (aMediaSource == MediaSourceEnum::AudioCapture) {
+ aDevices->EmplaceBack(new MediaDevice(
+ this, aMediaSource, u"AudioCapture"_ns,
+ MediaEngineWebRTCAudioCaptureSource::GetUUID(),
+ MediaEngineWebRTCAudioCaptureSource::GetGroupId(),
+ MediaDevice::IsScary::No, MediaDevice::OsPromptable::No));
+ } else if (aMediaSource == MediaSourceEnum::Microphone) {
+ EnumerateMicrophoneDevices(aDevices);
+ }
+
+ if (aMediaSink == MediaSinkEnum::Speaker) {
+ EnumerateSpeakerDevices(aDevices);
+ }
+}
+
+RefPtr<MediaEngineSource> MediaEngineWebRTC::CreateSource(
+ const MediaDevice* aMediaDevice) {
+ MOZ_ASSERT(aMediaDevice->mEngine == this);
+ if (MediaEngineSource::IsVideo(aMediaDevice->mMediaSource)) {
+ return new MediaEngineRemoteVideoSource(aMediaDevice);
+ }
+ switch (aMediaDevice->mMediaSource) {
+ case MediaSourceEnum::AudioCapture:
+ return new MediaEngineWebRTCAudioCaptureSource(aMediaDevice);
+ case MediaSourceEnum::Microphone:
+ return new MediaEngineWebRTCMicrophoneSource(aMediaDevice);
+ default:
+ MOZ_CRASH("Unsupported source type");
+ return nullptr;
+ }
+}
+
+void MediaEngineWebRTC::Shutdown() {
+ AssertIsOnOwningThread();
+ mCameraListChangeListener.DisconnectIfExists();
+ mMicrophoneListChangeListener.DisconnectIfExists();
+ mSpeakerListChangeListener.DisconnectIfExists();
+
+ LOG(("%s", __FUNCTION__));
+ mozilla::camera::Shutdown();
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/MediaEngineWebRTC.h b/dom/media/webrtc/MediaEngineWebRTC.h
new file mode 100644
index 0000000000..918558dfab
--- /dev/null
+++ b/dom/media/webrtc/MediaEngineWebRTC.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MEDIAENGINEWEBRTC_H_
+#define MEDIAENGINEWEBRTC_H_
+
+#include "MediaEngine.h"
+#include "MediaEventSource.h"
+#include "MediaEngineSource.h"
+#include "nsTArray.h"
+#include "CubebDeviceEnumerator.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/MediaStreamTrackBinding.h"
+
+namespace mozilla {
+
+class MediaEngineWebRTC : public MediaEngine {
+ public:
+ MediaEngineWebRTC();
+
+ // Clients should ensure to clean-up sources video/audio sources
+ // before invoking Shutdown on this class.
+ void Shutdown() override;
+
+ void EnumerateDevices(dom::MediaSourceEnum, MediaSinkEnum,
+ nsTArray<RefPtr<MediaDevice>>*) override;
+ RefPtr<MediaEngineSource> CreateSource(const MediaDevice* aDevice) override;
+
+ MediaEventSource<void>& DeviceListChangeEvent() override {
+ return mDeviceListChangeEvent;
+ }
+ bool IsFake() const override { return false; }
+
+ private:
+ ~MediaEngineWebRTC() = default;
+ void EnumerateVideoDevices(dom::MediaSourceEnum,
+ nsTArray<RefPtr<MediaDevice>>*);
+ void EnumerateMicrophoneDevices(nsTArray<RefPtr<MediaDevice>>*);
+ void EnumerateSpeakerDevices(nsTArray<RefPtr<MediaDevice>>*);
+
+ void DeviceListChanged() { mDeviceListChangeEvent.Notify(); }
+
+ MediaEventListener mCameraListChangeListener;
+ MediaEventListener mMicrophoneListChangeListener;
+ MediaEventListener mSpeakerListChangeListener;
+ MediaEventProducer<void> mDeviceListChangeEvent;
+};
+
+} // namespace mozilla
+
+#endif /* NSMEDIAENGINEWEBRTC_H_ */
diff --git a/dom/media/webrtc/MediaEngineWebRTCAudio.cpp b/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
new file mode 100644
index 0000000000..64ed88c625
--- /dev/null
+++ b/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
@@ -0,0 +1,1329 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MediaEngineWebRTCAudio.h"
+
+#include <stdio.h>
+#include <algorithm>
+
+#include "AudioConverter.h"
+#include "MediaManager.h"
+#include "MediaTrackGraphImpl.h"
+#include "MediaTrackConstraints.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/ErrorNames.h"
+#include "nsIDUtils.h"
+#include "transport/runnable_utils.h"
+#include "Tracing.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/Logging.h"
+
+#include "common_audio/include/audio_util.h"
+#include "modules/audio_processing/include/audio_processing.h"
+
+using namespace webrtc;
+
+// These are restrictions from the webrtc.org code
+#define MAX_CHANNELS 2
+#define MONO 1
+#define MAX_SAMPLING_FREQ 48000 // Hz - multiple of 100
+
+namespace mozilla {
+
+using dom::MediaSourceEnum;
+
+extern LazyLogModule gMediaManagerLog;
+#define LOG(...) MOZ_LOG(gMediaManagerLog, LogLevel::Debug, (__VA_ARGS__))
+#define LOG_FRAME(...) \
+ MOZ_LOG(gMediaManagerLog, LogLevel::Verbose, (__VA_ARGS__))
+#define LOG_ERROR(...) MOZ_LOG(gMediaManagerLog, LogLevel::Error, (__VA_ARGS__))
+
+/**
+ * WebRTC Microphone MediaEngineSource.
+ */
+
+MediaEngineWebRTCMicrophoneSource::MediaEngineWebRTCMicrophoneSource(
+ const MediaDevice* aMediaDevice)
+ : mPrincipal(PRINCIPAL_HANDLE_NONE),
+ mDeviceInfo(aMediaDevice->mAudioDeviceInfo),
+ mDeviceMaxChannelCount(mDeviceInfo->MaxChannels()),
+ mSettings(new nsMainThreadPtrHolder<
+ media::Refcountable<dom::MediaTrackSettings>>(
+ "MediaEngineWebRTCMicrophoneSource::mSettings",
+ new media::Refcountable<dom::MediaTrackSettings>(),
+ // Non-strict means it won't assert main thread for us.
+ // It would be great if it did but we're already on the media thread.
+ /* aStrict = */ false)) {
+ MOZ_ASSERT(aMediaDevice->mMediaSource == MediaSourceEnum::Microphone);
+#ifndef ANDROID
+ MOZ_ASSERT(mDeviceInfo->DeviceID());
+#endif
+
+ // We'll init lazily as needed
+ mSettings->mEchoCancellation.Construct(0);
+ mSettings->mAutoGainControl.Construct(0);
+ mSettings->mNoiseSuppression.Construct(0);
+ mSettings->mChannelCount.Construct(0);
+
+ mState = kReleased;
+}
+
+nsresult MediaEngineWebRTCMicrophoneSource::EvaluateSettings(
+ const NormalizedConstraints& aConstraintsUpdate,
+ const MediaEnginePrefs& aInPrefs, MediaEnginePrefs* aOutPrefs,
+ const char** aOutBadConstraint) {
+ AssertIsOnOwningThread();
+
+ FlattenedConstraints c(aConstraintsUpdate);
+ MediaEnginePrefs prefs = aInPrefs;
+
+ prefs.mAecOn = c.mEchoCancellation.Get(aInPrefs.mAecOn);
+ prefs.mAgcOn = c.mAutoGainControl.Get(aInPrefs.mAgcOn && prefs.mAecOn);
+ prefs.mNoiseOn = c.mNoiseSuppression.Get(aInPrefs.mNoiseOn && prefs.mAecOn);
+
+ // Determine an actual channel count to use for this source. Three factors at
+ // play here: the device capabilities, the constraints passed in by content,
+ // and a pref that can force things (for testing)
+ int32_t maxChannels = static_cast<int32_t>(mDeviceInfo->MaxChannels());
+
+ // First, check channelCount violation wrt constraints. This fails in case of
+ // error.
+ if (c.mChannelCount.mMin > maxChannels) {
+ *aOutBadConstraint = "channelCount";
+ return NS_ERROR_FAILURE;
+ }
+ // A pref can force the channel count to use. If the pref has a value of zero
+ // or lower, it has no effect.
+ if (aInPrefs.mChannels <= 0) {
+ prefs.mChannels = maxChannels;
+ }
+
+ // Get the number of channels asked for by content, and clamp it between the
+ // pref and the maximum number of channels that the device supports.
+ prefs.mChannels = c.mChannelCount.Get(std::min(prefs.mChannels, maxChannels));
+ prefs.mChannels = std::max(1, std::min(prefs.mChannels, maxChannels));
+
+ LOG("Audio config: agc: %d, noise: %d, channels: %d",
+ prefs.mAgcOn ? prefs.mAgc : -1, prefs.mNoiseOn ? prefs.mNoise : -1,
+ prefs.mChannels);
+
+ *aOutPrefs = prefs;
+
+ return NS_OK;
+}
+
+nsresult MediaEngineWebRTCMicrophoneSource::Reconfigure(
+ const dom::MediaTrackConstraints& aConstraints,
+ const MediaEnginePrefs& aPrefs, const char** aOutBadConstraint) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(mTrack);
+
+ LOG("Mic source %p Reconfigure ", this);
+
+ NormalizedConstraints constraints(aConstraints);
+ MediaEnginePrefs outputPrefs;
+ nsresult rv =
+ EvaluateSettings(constraints, aPrefs, &outputPrefs, aOutBadConstraint);
+ if (NS_FAILED(rv)) {
+ if (aOutBadConstraint) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsAutoCString name;
+ GetErrorName(rv, name);
+ LOG("Mic source %p Reconfigure() failed unexpectedly. rv=%s", this,
+ name.Data());
+ Stop();
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ ApplySettings(outputPrefs);
+
+ mCurrentPrefs = outputPrefs;
+
+ return NS_OK;
+}
+
+void MediaEngineWebRTCMicrophoneSource::ApplySettings(
+ const MediaEnginePrefs& aPrefs) {
+ AssertIsOnOwningThread();
+
+ TRACE("ApplySettings");
+ MOZ_ASSERT(
+ mTrack,
+ "ApplySetting is to be called only after SetTrack has been called");
+
+ mAudioProcessingConfig.pipeline.multi_channel_render = true;
+ mAudioProcessingConfig.pipeline.multi_channel_capture = true;
+
+ mAudioProcessingConfig.echo_canceller.enabled = aPrefs.mAecOn;
+ mAudioProcessingConfig.echo_canceller.mobile_mode = aPrefs.mUseAecMobile;
+
+ if ((mAudioProcessingConfig.gain_controller1.enabled =
+ aPrefs.mAgcOn && !aPrefs.mAgc2Forced)) {
+ auto mode = static_cast<AudioProcessing::Config::GainController1::Mode>(
+ aPrefs.mAgc);
+ if (mode != AudioProcessing::Config::GainController1::kAdaptiveAnalog &&
+ mode != AudioProcessing::Config::GainController1::kAdaptiveDigital &&
+ mode != AudioProcessing::Config::GainController1::kFixedDigital) {
+ LOG_ERROR("AudioInputProcessing %p Attempt to set invalid AGC mode %d",
+ mInputProcessing.get(), static_cast<int>(mode));
+ mode = AudioProcessing::Config::GainController1::kAdaptiveDigital;
+ }
+#if defined(WEBRTC_IOS) || defined(ATA) || defined(WEBRTC_ANDROID)
+ if (mode == AudioProcessing::Config::GainController1::kAdaptiveAnalog) {
+ LOG_ERROR(
+ "AudioInputProcessing %p Invalid AGC mode kAdaptiveAnalog on "
+ "mobile",
+ mInputProcessing.get());
+ MOZ_ASSERT_UNREACHABLE(
+ "Bad pref set in all.js or in about:config"
+ " for the auto gain, on mobile.");
+ mode = AudioProcessing::Config::GainController1::kFixedDigital;
+ }
+#endif
+ mAudioProcessingConfig.gain_controller1.mode = mode;
+ }
+ mAudioProcessingConfig.gain_controller2.enabled =
+ mAudioProcessingConfig.gain_controller2.adaptive_digital.enabled =
+ aPrefs.mAgcOn && aPrefs.mAgc2Forced;
+
+ if ((mAudioProcessingConfig.noise_suppression.enabled = aPrefs.mNoiseOn)) {
+ auto level = static_cast<AudioProcessing::Config::NoiseSuppression::Level>(
+ aPrefs.mNoise);
+ if (level != AudioProcessing::Config::NoiseSuppression::kLow &&
+ level != AudioProcessing::Config::NoiseSuppression::kModerate &&
+ level != AudioProcessing::Config::NoiseSuppression::kHigh &&
+ level != AudioProcessing::Config::NoiseSuppression::kVeryHigh) {
+ LOG_ERROR(
+ "AudioInputProcessing %p Attempt to set invalid noise suppression "
+ "level %d",
+ mInputProcessing.get(), static_cast<int>(level));
+
+ level = AudioProcessing::Config::NoiseSuppression::kModerate;
+ }
+ mAudioProcessingConfig.noise_suppression.level = level;
+ }
+
+ mAudioProcessingConfig.transient_suppression.enabled = aPrefs.mTransientOn;
+
+ mAudioProcessingConfig.high_pass_filter.enabled = aPrefs.mHPFOn;
+
+ // See https://bugs.chromium.org/p/webrtc/issues/detail?id=11539 for more
+ // info. Our pref defaults to false, and if this is truly as unhelpful
+ // as the upstream bug claim, we could delete the pref that drive this:
+ // media.getusermedia.residual_echo_enabled. See Bug 1779498.
+ // mAudioProcessingConfig.residual_echo_detector.enabled =
+ // aPrefs.mResidualEchoOn;
+
+ RefPtr<MediaEngineWebRTCMicrophoneSource> that = this;
+ CubebUtils::AudioDeviceID deviceID = mDeviceInfo->DeviceID();
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ __func__, [this, that, deviceID, track = mTrack, prefs = aPrefs,
+ audioProcessingConfig = mAudioProcessingConfig] {
+ mSettings->mEchoCancellation.Value() = prefs.mAecOn;
+ mSettings->mAutoGainControl.Value() = prefs.mAgcOn;
+ mSettings->mNoiseSuppression.Value() = prefs.mNoiseOn;
+ mSettings->mChannelCount.Value() = prefs.mChannels;
+
+ class Message : public ControlMessage {
+ CubebUtils::AudioDeviceID mDeviceID;
+ const RefPtr<AudioInputProcessing> mInputProcessing;
+ const AudioProcessing::Config mAudioProcessingConfig;
+ const bool mPassThrough;
+ const uint32_t mRequestedInputChannelCount;
+
+ public:
+ Message(MediaTrack* aTrack, CubebUtils::AudioDeviceID aDeviceID,
+ AudioInputProcessing* aInputProcessing,
+ const AudioProcessing::Config& aAudioProcessingConfig,
+ bool aPassThrough, uint32_t aRequestedInputChannelCount)
+ : ControlMessage(aTrack),
+ mDeviceID(aDeviceID),
+ mInputProcessing(aInputProcessing),
+ mAudioProcessingConfig(aAudioProcessingConfig),
+ mPassThrough(aPassThrough),
+ mRequestedInputChannelCount(aRequestedInputChannelCount) {}
+
+ void Run() override {
+ mInputProcessing->ApplyConfig(mTrack->GraphImpl(),
+ mAudioProcessingConfig);
+ {
+ TRACE("SetRequestedInputChannelCount");
+ mInputProcessing->SetRequestedInputChannelCount(
+ mTrack->GraphImpl(), mDeviceID, mRequestedInputChannelCount);
+ }
+ {
+ TRACE("SetPassThrough")
+ mInputProcessing->SetPassThrough(mTrack->GraphImpl(),
+ mPassThrough);
+ }
+ }
+ };
+
+ // The high-pass filter is not taken into account when activating the
+ // pass through, since it's not controllable from content.
+ bool passThrough = !(prefs.mAecOn || prefs.mAgcOn || prefs.mNoiseOn);
+
+ if (track->IsDestroyed()) {
+ return;
+ }
+ track->GraphImpl()->AppendMessage(MakeUnique<Message>(
+ track, deviceID, mInputProcessing, audioProcessingConfig,
+ passThrough, prefs.mChannels));
+ }));
+}
+
+nsresult MediaEngineWebRTCMicrophoneSource::Allocate(
+ const dom::MediaTrackConstraints& aConstraints,
+ const MediaEnginePrefs& aPrefs, uint64_t aWindowID,
+ const char** aOutBadConstraint) {
+ AssertIsOnOwningThread();
+
+ mState = kAllocated;
+
+ NormalizedConstraints normalized(aConstraints);
+ MediaEnginePrefs outputPrefs;
+ nsresult rv =
+ EvaluateSettings(normalized, aPrefs, &outputPrefs, aOutBadConstraint);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ __func__, [settings = mSettings, prefs = outputPrefs] {
+ settings->mEchoCancellation.Value() = prefs.mAecOn;
+ settings->mAutoGainControl.Value() = prefs.mAgcOn;
+ settings->mNoiseSuppression.Value() = prefs.mNoiseOn;
+ settings->mChannelCount.Value() = prefs.mChannels;
+ }));
+
+ mCurrentPrefs = outputPrefs;
+
+ return rv;
+}
+
+nsresult MediaEngineWebRTCMicrophoneSource::Deallocate() {
+ AssertIsOnOwningThread();
+
+ MOZ_ASSERT(mState == kStopped || mState == kAllocated);
+
+ class EndTrackMessage : public ControlMessage {
+ const RefPtr<AudioInputProcessing> mInputProcessing;
+
+ public:
+ explicit EndTrackMessage(AudioInputProcessing* aAudioInputProcessing)
+ : ControlMessage(nullptr), mInputProcessing(aAudioInputProcessing) {}
+
+ void Run() override {
+ TRACE("mInputProcessing::End");
+ mInputProcessing->End();
+ }
+ };
+
+ if (mTrack) {
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ __func__,
+ [track = std::move(mTrack), inputProcessing = mInputProcessing] {
+ if (track->IsDestroyed()) {
+ // This track has already been destroyed on main thread by its
+ // DOMMediaStream. No cleanup left to do.
+ return;
+ }
+ track->GraphImpl()->AppendMessage(
+ MakeUnique<EndTrackMessage>(inputProcessing));
+ }));
+ }
+
+ // Reset all state. This is not strictly necessary, this instance will get
+ // destroyed soon.
+ mTrack = nullptr;
+ mPrincipal = PRINCIPAL_HANDLE_NONE;
+
+ // If empty, no callbacks to deliver data should be occuring
+ MOZ_ASSERT(mState != kReleased, "Source not allocated");
+ MOZ_ASSERT(mState != kStarted, "Source not stopped");
+
+ mState = kReleased;
+ LOG("Mic source %p Audio device %s deallocated", this,
+ NS_ConvertUTF16toUTF8(mDeviceInfo->Name()).get());
+ return NS_OK;
+}
+
+void MediaEngineWebRTCMicrophoneSource::SetTrack(
+ const RefPtr<MediaTrack>& aTrack, const PrincipalHandle& aPrincipal) {
+ AssertIsOnOwningThread();
+ MOZ_ASSERT(aTrack);
+ MOZ_ASSERT(aTrack->AsAudioProcessingTrack());
+
+ MOZ_ASSERT(!mTrack);
+ MOZ_ASSERT(mPrincipal == PRINCIPAL_HANDLE_NONE);
+ mTrack = aTrack->AsAudioProcessingTrack();
+ mPrincipal = aPrincipal;
+
+ mInputProcessing =
+ MakeAndAddRef<AudioInputProcessing>(mDeviceMaxChannelCount);
+
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ __func__, [track = mTrack, processing = mInputProcessing]() mutable {
+ track->SetInputProcessing(std::move(processing));
+ track->Resume(); // Suspended by MediaManager
+ }));
+
+ LOG("Mic source %p Track %p registered for microphone capture", this,
+ aTrack.get());
+}
+
+class StartStopMessage : public ControlMessage {
+ public:
+ enum StartStop { Start, Stop };
+
+ StartStopMessage(MediaTrack* aTrack, AudioInputProcessing* aInputProcessing,
+ StartStop aAction)
+ : ControlMessage(aTrack),
+ mInputProcessing(aInputProcessing),
+ mAction(aAction) {}
+
+ void Run() override {
+ if (mAction == StartStopMessage::Start) {
+ TRACE("InputProcessing::Start")
+ mInputProcessing->Start(mTrack->GraphImpl());
+ } else if (mAction == StartStopMessage::Stop) {
+ TRACE("InputProcessing::Stop")
+ mInputProcessing->Stop(mTrack->GraphImpl());
+ } else {
+ MOZ_CRASH("Invalid enum value");
+ }
+ }
+
+ protected:
+ const RefPtr<AudioInputProcessing> mInputProcessing;
+ const StartStop mAction;
+};
+
+nsresult MediaEngineWebRTCMicrophoneSource::Start() {
+ AssertIsOnOwningThread();
+
+ // This spans setting both the enabled state and mState.
+ if (mState == kStarted) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mState == kAllocated || mState == kStopped);
+
+ ApplySettings(mCurrentPrefs);
+
+ CubebUtils::AudioDeviceID deviceID = mDeviceInfo->DeviceID();
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ __func__, [inputProcessing = mInputProcessing, deviceID, track = mTrack,
+ principal = mPrincipal] {
+ if (track->IsDestroyed()) {
+ return;
+ }
+
+ track->GraphImpl()->AppendMessage(MakeUnique<StartStopMessage>(
+ track, inputProcessing, StartStopMessage::Start));
+ track->ConnectDeviceInput(deviceID, inputProcessing.get(), principal);
+ }));
+
+ MOZ_ASSERT(mState != kReleased);
+ mState = kStarted;
+
+ return NS_OK;
+}
+
+nsresult MediaEngineWebRTCMicrophoneSource::Stop() {
+ AssertIsOnOwningThread();
+
+ LOG("Mic source %p Stop()", this);
+ MOZ_ASSERT(mTrack, "SetTrack must have been called before ::Stop");
+
+ if (mState == kStopped) {
+ // Already stopped - this is allowed
+ return NS_OK;
+ }
+
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ __func__, [inputProcessing = mInputProcessing, deviceInfo = mDeviceInfo,
+ track = mTrack] {
+ if (track->IsDestroyed()) {
+ return;
+ }
+
+ MOZ_ASSERT(track->DeviceId().value() == deviceInfo->DeviceID());
+ track->DisconnectDeviceInput();
+ track->GraphImpl()->AppendMessage(MakeUnique<StartStopMessage>(
+ track, inputProcessing, StartStopMessage::Stop));
+ }));
+
+ MOZ_ASSERT(mState == kStarted, "Should be started when stopping");
+ mState = kStopped;
+
+ return NS_OK;
+}
+
+void MediaEngineWebRTCMicrophoneSource::GetSettings(
+ dom::MediaTrackSettings& aOutSettings) const {
+ MOZ_ASSERT(NS_IsMainThread());
+ aOutSettings = *mSettings;
+}
+
+AudioInputProcessing::AudioInputProcessing(uint32_t aMaxChannelCount)
+ : mAudioProcessing(AudioProcessingBuilder().Create().release()),
+ mRequestedInputChannelCount(aMaxChannelCount),
+ mSkipProcessing(false),
+ mInputDownmixBuffer(MAX_SAMPLING_FREQ * MAX_CHANNELS / 100),
+ mEnabled(false),
+ mEnded(false),
+ mPacketCount(0) {}
+
+void AudioInputProcessing::Disconnect(MediaTrackGraphImpl* aGraph) {
+ // This method is just for asserts.
+ MOZ_ASSERT(aGraph->OnGraphThread());
+}
+
+bool AudioInputProcessing::PassThrough(MediaTrackGraphImpl* aGraph) const {
+ MOZ_ASSERT(aGraph->OnGraphThread());
+ return mSkipProcessing;
+}
+
+void AudioInputProcessing::SetPassThrough(MediaTrackGraphImpl* aGraph,
+ bool aPassThrough) {
+ MOZ_ASSERT(aGraph->OnGraphThread());
+
+ if (aPassThrough == mSkipProcessing) {
+ return;
+ }
+ mSkipProcessing = aPassThrough;
+
+ if (!mEnabled) {
+ MOZ_ASSERT(!mPacketizerInput);
+ return;
+ }
+
+ if (aPassThrough) {
+ // Turn on pass-through
+ ResetAudioProcessing(aGraph);
+ } else {
+ // Turn off pass-through
+ MOZ_ASSERT(!mPacketizerInput);
+ EnsureAudioProcessing(aGraph, mRequestedInputChannelCount);
+ }
+}
+
+uint32_t AudioInputProcessing::GetRequestedInputChannelCount() {
+ return mRequestedInputChannelCount;
+}
+
+void AudioInputProcessing::SetRequestedInputChannelCount(
+ MediaTrackGraphImpl* aGraph, CubebUtils::AudioDeviceID aDeviceId,
+ uint32_t aRequestedInputChannelCount) {
+ mRequestedInputChannelCount = aRequestedInputChannelCount;
+
+ aGraph->ReevaluateInputDevice(aDeviceId);
+}
+
+void AudioInputProcessing::Start(MediaTrackGraphImpl* aGraph) {
+ MOZ_ASSERT(aGraph->OnGraphThread());
+
+ if (mEnabled) {
+ return;
+ }
+ mEnabled = true;
+
+ if (mSkipProcessing) {
+ return;
+ }
+
+ MOZ_ASSERT(!mPacketizerInput);
+ EnsureAudioProcessing(aGraph, mRequestedInputChannelCount);
+}
+
+void AudioInputProcessing::Stop(MediaTrackGraphImpl* aGraph) {
+ MOZ_ASSERT(aGraph->OnGraphThread());
+
+ if (!mEnabled) {
+ return;
+ }
+
+ mEnabled = false;
+
+ if (mSkipProcessing) {
+ return;
+ }
+
+ // Packetizer is active and we were just stopped. Stop the packetizer and
+ // processing.
+ ResetAudioProcessing(aGraph);
+}
+
+// The following is how how Process() works in pass-through and non-pass-through
+// mode. In both mode, Process() outputs the same amount of the frames as its
+// input data.
+//
+// I. In non-pass-through mode:
+//
+// We will use webrtc::AudioProcessing to process the input audio data in this
+// mode. The data input in webrtc::AudioProcessing needs to be a 10ms chunk,
+// while the input data passed to Process() is not necessary to have times of
+// 10ms-chunk length. To divide the input data into 10ms chunks,
+// mPacketizerInput is introduced.
+//
+// We will add one 10ms-chunk silence into the internal buffer before Process()
+// works. Those extra frames is called pre-buffering. It aims to avoid glitches
+// we may have when producing data in mPacketizerInput. Without pre-buffering,
+// when the input data length is not 10ms-times, we could end up having no
+// enough output needs since mPacketizerInput would keep some input data, which
+// is the remainder of the 10ms-chunk length. To force processing those data
+// left in mPacketizerInput, we would need to add some extra frames to make
+// mPacketizerInput produce a 10ms-chunk. For example, if the sample rate is
+// 44100 Hz, then the packet-size is 441 frames. When we only have 384 input
+// frames, we would need to put additional 57 frames to mPacketizerInput to
+// produce a packet. However, those extra 57 frames result in a glitch sound.
+//
+// By adding one 10ms-chunk silence in advance to the internal buffer, we won't
+// need to add extra frames between the input data no matter what data length it
+// is. The only drawback is the input data won't be processed and send to output
+// immediately. Process() will consume pre-buffering data for its output first.
+// The below describes how it works:
+//
+//
+// Process()
+// +-----------------------------+
+// input D(N) | +--------+ +--------+ | output D(N)
+// --------------|-->| P(N) |-->| S(N) |---|-------------->
+// | +--------+ +--------+ |
+// | packetizer mSegment |
+// +-----------------------------+
+// <------ internal buffer ------>
+//
+//
+// D(N): number of frames from the input and the output needs in the N round
+// Z: number of frames of a 10ms chunk(packet) in mPacketizerInput, Z >= 1
+// (if Z = 1, packetizer has no effect)
+// P(N): number of frames left in mPacketizerInput after the N round. Once the
+// frames in packetizer >= Z, packetizer will produce a packet to
+// mSegment, so P(N) = (P(N-1) + D(N)) % Z, 0 <= P(N) <= Z-1
+// S(N): number of frames left in mSegment after the N round. The input D(N)
+// frames will be passed to mPacketizerInput first, and then
+// mPacketizerInput may append some packets to mSegment, so
+// S(N) = S(N-1) + Z * floor((P(N-1) + D(N)) / Z) - D(N)
+//
+// At the first, we set P(0) = 0, S(0) = X, where X >= Z-1. X is the
+// pre-buffering put in the internal buffer. With this settings, P(K) + S(K) = X
+// always holds.
+//
+// Intuitively, this seems true: We put X frames in the internal buffer at
+// first. If the data won't be blocked in packetizer, after the Process(), the
+// internal buffer should still hold X frames since the number of frames coming
+// from input is the same as the output needs. The key of having enough data for
+// output needs, while the input data is piled up in packetizer, is by putting
+// at least Z-1 frames as pre-buffering, since the maximum number of frames
+// stuck in the packetizer before it can emit a packet is packet-size - 1.
+// Otherwise, we don't have enough data for output if the new input data plus
+// the data left in packetizer produces a smaller-than-10ms chunk, which will be
+// left in packetizer. Thus we must have some pre-buffering frames in the
+// mSegment to make up the length of the left chunk we need for output. This can
+// also be told by by induction:
+// (1) This holds when K = 0
+// (2) Assume this holds when K = N: so P(N) + S(N) = X
+// => P(N) + S(N) = X >= Z-1 => S(N) >= Z-1-P(N)
+// (3) When K = N+1, we have D(N+1) input frames comes
+// a. if P(N) + D(N+1) < Z, then packetizer has no enough data for one
+// packet. No data produced by packertizer, so the mSegment now has
+// S(N) >= Z-1-P(N) frames. Output needs D(N+1) < Z-P(N) frames. So it
+// needs at most Z-P(N)-1 frames, and mSegment has enough frames for
+// output, Then, P(N+1) = P(N) + D(N+1) and S(N+1) = S(N) - D(N+1)
+// => P(N+1) + S(N+1) = P(N) + S(N) = X
+// b. if P(N) + D(N+1) = Z, then packetizer will produce one packet for
+// mSegment, so mSegment now has S(N) + Z frames. Output needs D(N+1)
+// = Z-P(N) frames. S(N) has at least Z-1-P(N)+Z >= Z-P(N) frames, since
+// Z >= 1. So mSegment has enough frames for output. Then, P(N+1) = 0 and
+// S(N+1) = S(N) + Z - D(N+1) = S(N) + P(N)
+// => P(N+1) + S(N+1) = P(N) + S(N) = X
+// c. if P(N) + D(N+1) > Z, and let P(N) + D(N+1) = q * Z + r, where q >= 1
+// and 0 <= r <= Z-1, then packetizer will produce can produce q packets
+// for mSegment. Output needs D(N+1) = q * Z - P(N) + r frames and
+// mSegment has S(N) + q * z >= q * z - P(N) + Z-1 >= q*z -P(N) + r,
+// since r <= Z-1. So mSegment has enough frames for output. Then,
+// P(N+1) = r and S(N+1) = S(N) + q * Z - D(N+1)
+// => P(N+1) + S(N+1) = S(N) + (q * Z + r - D(N+1)) = S(N) + P(N) = X
+// => P(K) + S(K) = X always holds
+//
+// Since P(K) + S(K) = X and P(K) is in [0, Z-1], the S(K) is in [X-Z+1, X]
+// range. In our implementation, X is set to Z so S(K) is in [1, Z].
+// By the above workflow, we always have enough data for output and no extra
+// frames put into packetizer. It means we don't have any glitch!
+//
+// II. In pass-through mode:
+//
+// Process()
+// +--------+
+// input D(N) | | output D(N)
+// -------------->-------->--------------->
+// | |
+// +--------+
+//
+// The D(N) frames of data are just forwarded from input to output without any
+// processing
+void AudioInputProcessing::Process(MediaTrackGraphImpl* aGraph, GraphTime aFrom,
+ GraphTime aTo, AudioSegment* aInput,
+ AudioSegment* aOutput) {
+ MOZ_ASSERT(aGraph->OnGraphThread());
+ MOZ_ASSERT(aFrom <= aTo);
+ MOZ_ASSERT(!mEnded);
+
+ TrackTime need = aTo - aFrom;
+ if (need == 0) {
+ return;
+ }
+
+ if (!mEnabled) {
+ LOG_FRAME("(Graph %p, Driver %p) AudioInputProcessing %p Filling %" PRId64
+ " frames of silence to output (disabled)",
+ aGraph, aGraph->CurrentDriver(), this, need);
+ aOutput->AppendNullData(need);
+ return;
+ }
+
+ MOZ_ASSERT(aInput->GetDuration() == need,
+ "Wrong data length from input port source");
+
+ if (PassThrough(aGraph)) {
+ LOG_FRAME(
+ "(Graph %p, Driver %p) AudioInputProcessing %p Forwarding %" PRId64
+ " frames of input data to output directly (PassThrough)",
+ aGraph, aGraph->CurrentDriver(), this, aInput->GetDuration());
+ aOutput->AppendSegment(aInput);
+ return;
+ }
+
+ // SetPassThrough(false) must be called before reaching here.
+ MOZ_ASSERT(mPacketizerInput);
+ // If mRequestedInputChannelCount is updated, create a new packetizer. No
+ // need to change the pre-buffering since the rate is always the same. The
+ // frames left in the packetizer would be replaced by null data and then
+ // transferred to mSegment.
+ EnsureAudioProcessing(aGraph, mRequestedInputChannelCount);
+
+ // Preconditions of the audio-processing logic.
+ MOZ_ASSERT(static_cast<uint32_t>(mSegment.GetDuration()) +
+ mPacketizerInput->FramesAvailable() ==
+ mPacketizerInput->mPacketSize);
+ // We pre-buffer mPacketSize frames, but the maximum number of frames stuck in
+ // the packetizer before it can emit a packet is mPacketSize-1. Thus that
+ // remaining 1 frame will always be present in mSegment.
+ MOZ_ASSERT(mSegment.GetDuration() >= 1);
+ MOZ_ASSERT(mSegment.GetDuration() <= mPacketizerInput->mPacketSize);
+
+ PacketizeAndProcess(aGraph, *aInput);
+ LOG_FRAME("(Graph %p, Driver %p) AudioInputProcessing %p Buffer has %" PRId64
+ " frames of data now, after packetizing and processing",
+ aGraph, aGraph->CurrentDriver(), this, mSegment.GetDuration());
+
+ // By setting pre-buffering to the number of frames of one packet, and
+ // because the maximum number of frames stuck in the packetizer before
+ // it can emit a packet is the mPacketSize-1, we always have at least
+ // one more frame than output needs.
+ MOZ_ASSERT(mSegment.GetDuration() > need);
+ aOutput->AppendSlice(mSegment, 0, need);
+ mSegment.RemoveLeading(need);
+ LOG_FRAME("(Graph %p, Driver %p) AudioInputProcessing %p moving %" PRId64
+ " frames of data to output, leaving %" PRId64 " frames in buffer",
+ aGraph, aGraph->CurrentDriver(), this, need,
+ mSegment.GetDuration());
+
+ // Postconditions of the audio-processing logic.
+ MOZ_ASSERT(static_cast<uint32_t>(mSegment.GetDuration()) +
+ mPacketizerInput->FramesAvailable() ==
+ mPacketizerInput->mPacketSize);
+ MOZ_ASSERT(mSegment.GetDuration() >= 1);
+ MOZ_ASSERT(mSegment.GetDuration() <= mPacketizerInput->mPacketSize);
+}
+
+void AudioInputProcessing::ProcessOutputData(MediaTrackGraphImpl* aGraph,
+ AudioDataValue* aBuffer,
+ size_t aFrames, TrackRate aRate,
+ uint32_t aChannels) {
+ MOZ_ASSERT(aGraph->OnGraphThread());
+
+ if (!mEnabled || PassThrough(aGraph)) {
+ return;
+ }
+
+ if (!mPacketizerOutput ||
+ mPacketizerOutput->mPacketSize != GetPacketSize(aRate) ||
+ mPacketizerOutput->mChannels != aChannels) {
+ // It's ok to drop the audio still in the packetizer here: if this changes,
+ // we changed devices or something.
+ mPacketizerOutput = Nothing();
+ mPacketizerOutput.emplace(GetPacketSize(aRate), aChannels);
+ }
+
+ mPacketizerOutput->Input(aBuffer, aFrames);
+
+ while (mPacketizerOutput->PacketsAvailable()) {
+ uint32_t samplesPerPacket =
+ mPacketizerOutput->mPacketSize * mPacketizerOutput->mChannels;
+ if (mOutputBuffer.Length() < samplesPerPacket) {
+ mOutputBuffer.SetLength(samplesPerPacket);
+ }
+ if (mDeinterleavedBuffer.Length() < samplesPerPacket) {
+ mDeinterleavedBuffer.SetLength(samplesPerPacket);
+ }
+ float* packet = mOutputBuffer.Data();
+ mPacketizerOutput->Output(packet);
+
+ AutoTArray<float*, MAX_CHANNELS> deinterleavedPacketDataChannelPointers;
+ float* interleavedFarend = nullptr;
+ uint32_t channelCountFarend = 0;
+ uint32_t framesPerPacketFarend = 0;
+
+ // Downmix from aChannels to MAX_CHANNELS if needed. We always have
+ // floats here, the packetized performed the conversion.
+ if (aChannels > MAX_CHANNELS) {
+ AudioConverter converter(
+ AudioConfig(aChannels, 0, AudioConfig::FORMAT_FLT),
+ AudioConfig(MAX_CHANNELS, 0, AudioConfig::FORMAT_FLT));
+ framesPerPacketFarend = mPacketizerOutput->mPacketSize;
+ framesPerPacketFarend =
+ converter.Process(mInputDownmixBuffer, packet, framesPerPacketFarend);
+ interleavedFarend = mInputDownmixBuffer.Data();
+ channelCountFarend = MAX_CHANNELS;
+ deinterleavedPacketDataChannelPointers.SetLength(MAX_CHANNELS);
+ } else {
+ interleavedFarend = packet;
+ channelCountFarend = aChannels;
+ framesPerPacketFarend = mPacketizerOutput->mPacketSize;
+ deinterleavedPacketDataChannelPointers.SetLength(aChannels);
+ }
+
+ MOZ_ASSERT(interleavedFarend &&
+ (channelCountFarend == 1 || channelCountFarend == 2) &&
+ framesPerPacketFarend);
+
+ if (mInputBuffer.Length() < framesPerPacketFarend * channelCountFarend) {
+ mInputBuffer.SetLength(framesPerPacketFarend * channelCountFarend);
+ }
+
+ size_t offset = 0;
+ for (size_t i = 0; i < deinterleavedPacketDataChannelPointers.Length();
+ ++i) {
+ deinterleavedPacketDataChannelPointers[i] = mInputBuffer.Data() + offset;
+ offset += framesPerPacketFarend;
+ }
+
+ // Deinterleave, prepare a channel pointers array, with enough storage for
+ // the frames.
+ DeinterleaveAndConvertBuffer(
+ interleavedFarend, framesPerPacketFarend, channelCountFarend,
+ deinterleavedPacketDataChannelPointers.Elements());
+
+ // Having the same config for input and output means we potentially save
+ // some CPU.
+ StreamConfig inputConfig(aRate, channelCountFarend);
+ StreamConfig outputConfig = inputConfig;
+
+ // Passing the same pointers here saves a copy inside this function.
+ DebugOnly<int> err = mAudioProcessing->ProcessReverseStream(
+ deinterleavedPacketDataChannelPointers.Elements(), inputConfig,
+ outputConfig, deinterleavedPacketDataChannelPointers.Elements());
+
+ MOZ_ASSERT(!err, "Could not process the reverse stream.");
+ }
+}
+
+// Only called if we're not in passthrough mode
+void AudioInputProcessing::PacketizeAndProcess(MediaTrackGraphImpl* aGraph,
+ const AudioSegment& aSegment) {
+ MOZ_ASSERT(!PassThrough(aGraph),
+ "This should be bypassed when in PassThrough mode.");
+ MOZ_ASSERT(mEnabled);
+ MOZ_ASSERT(mPacketizerInput);
+ MOZ_ASSERT(mPacketizerInput->mPacketSize ==
+ GetPacketSize(aGraph->GraphRate()));
+
+ // Calculate number of the pending frames in mChunksInPacketizer.
+ auto pendingFrames = [&]() {
+ TrackTime frames = 0;
+ for (const auto& p : mChunksInPacketizer) {
+ frames += p.first;
+ }
+ return frames;
+ };
+
+ // Precondition of the Principal-labelling logic below.
+ MOZ_ASSERT(mPacketizerInput->FramesAvailable() ==
+ static_cast<uint32_t>(pendingFrames()));
+
+ // The WriteToInterleavedBuffer will do upmix or downmix if the channel-count
+ // in aSegment's chunks is different from mPacketizerInput->mChannels
+ // WriteToInterleavedBuffer could be avoided once Bug 1729041 is done.
+ size_t sampleCount = aSegment.WriteToInterleavedBuffer(
+ mInterleavedBuffer, mPacketizerInput->mChannels);
+ size_t frameCount =
+ sampleCount / static_cast<size_t>(mPacketizerInput->mChannels);
+
+ // Packetize our input data into 10ms chunks, deinterleave into planar channel
+ // buffers, process, and append to the right MediaStreamTrack.
+ mPacketizerInput->Input(mInterleavedBuffer.Elements(),
+ static_cast<uint32_t>(frameCount));
+
+ // Update mChunksInPacketizer and make sure the precondition for the
+ // Principal-labelling logic still holds.
+ for (AudioSegment::ConstChunkIterator iter(aSegment); !iter.IsEnded();
+ iter.Next()) {
+ MOZ_ASSERT(iter->mDuration > 0);
+ mChunksInPacketizer.emplace_back(
+ std::make_pair(iter->mDuration, iter->mPrincipalHandle));
+ }
+ MOZ_ASSERT(mPacketizerInput->FramesAvailable() ==
+ static_cast<uint32_t>(pendingFrames()));
+
+ LOG_FRAME(
+ "(Graph %p, Driver %p) AudioInputProcessing %p Packetizing %zu frames. "
+ "Packetizer has %u frames (enough for %u packets) now",
+ aGraph, aGraph->CurrentDriver(), this, frameCount,
+ mPacketizerInput->FramesAvailable(),
+ mPacketizerInput->PacketsAvailable());
+
+ size_t offset = 0;
+
+ while (mPacketizerInput->PacketsAvailable()) {
+ mPacketCount++;
+ uint32_t samplesPerPacket =
+ mPacketizerInput->mPacketSize * mPacketizerInput->mChannels;
+ if (mInputBuffer.Length() < samplesPerPacket) {
+ mInputBuffer.SetLength(samplesPerPacket);
+ }
+ if (mDeinterleavedBuffer.Length() < samplesPerPacket) {
+ mDeinterleavedBuffer.SetLength(samplesPerPacket);
+ }
+ float* packet = mInputBuffer.Data();
+ mPacketizerInput->Output(packet);
+
+ // Downmix from mPacketizerInput->mChannels to mono if needed. We always
+ // have floats here, the packetizer performed the conversion.
+ AutoTArray<float*, 8> deinterleavedPacketizedInputDataChannelPointers;
+ uint32_t channelCountInput = 0;
+ if (mPacketizerInput->mChannels > MAX_CHANNELS) {
+ channelCountInput = MONO;
+ deinterleavedPacketizedInputDataChannelPointers.SetLength(
+ channelCountInput);
+ deinterleavedPacketizedInputDataChannelPointers[0] =
+ mDeinterleavedBuffer.Data();
+ // Downmix to mono (and effectively have a planar buffer) by summing all
+ // channels in the first channel, and scaling by the number of channels to
+ // avoid clipping.
+ float gain = 1.f / mPacketizerInput->mChannels;
+ size_t readIndex = 0;
+ for (size_t i = 0; i < mPacketizerInput->mPacketSize; i++) {
+ mDeinterleavedBuffer.Data()[i] = 0.;
+ for (size_t j = 0; j < mPacketizerInput->mChannels; j++) {
+ mDeinterleavedBuffer.Data()[i] += gain * packet[readIndex++];
+ }
+ }
+ } else {
+ channelCountInput = mPacketizerInput->mChannels;
+ // Deinterleave the input data
+ // Prepare an array pointing to deinterleaved channels.
+ deinterleavedPacketizedInputDataChannelPointers.SetLength(
+ channelCountInput);
+ offset = 0;
+ for (size_t i = 0;
+ i < deinterleavedPacketizedInputDataChannelPointers.Length(); ++i) {
+ deinterleavedPacketizedInputDataChannelPointers[i] =
+ mDeinterleavedBuffer.Data() + offset;
+ offset += mPacketizerInput->mPacketSize;
+ }
+ // Deinterleave to mInputBuffer, pointed to by inputBufferChannelPointers.
+ Deinterleave(packet, mPacketizerInput->mPacketSize, channelCountInput,
+ deinterleavedPacketizedInputDataChannelPointers.Elements());
+ }
+
+ StreamConfig inputConfig(aGraph->GraphRate(), channelCountInput);
+ StreamConfig outputConfig = inputConfig;
+
+ // Bug 1404965: Get the right delay here, it saves some work down the line.
+ mAudioProcessing->set_stream_delay_ms(0);
+
+ // Bug 1414837: find a way to not allocate here.
+ CheckedInt<size_t> bufferSize(sizeof(float));
+ bufferSize *= mPacketizerInput->mPacketSize;
+ bufferSize *= channelCountInput;
+ RefPtr<SharedBuffer> buffer = SharedBuffer::Create(bufferSize);
+
+ // Prepare channel pointers to the SharedBuffer created above.
+ AutoTArray<float*, 8> processedOutputChannelPointers;
+ AutoTArray<const float*, 8> processedOutputChannelPointersConst;
+ processedOutputChannelPointers.SetLength(channelCountInput);
+ processedOutputChannelPointersConst.SetLength(channelCountInput);
+
+ offset = 0;
+ for (size_t i = 0; i < processedOutputChannelPointers.Length(); ++i) {
+ processedOutputChannelPointers[i] =
+ static_cast<float*>(buffer->Data()) + offset;
+ processedOutputChannelPointersConst[i] =
+ static_cast<float*>(buffer->Data()) + offset;
+ offset += mPacketizerInput->mPacketSize;
+ }
+
+ mAudioProcessing->ProcessStream(
+ deinterleavedPacketizedInputDataChannelPointers.Elements(), inputConfig,
+ outputConfig, processedOutputChannelPointers.Elements());
+
+ // If logging is enabled, dump the audio processing stats twice a second
+ if (MOZ_LOG_TEST(gMediaManagerLog, LogLevel::Debug) &&
+ !(mPacketCount % 50)) {
+ AudioProcessingStats stats = mAudioProcessing->GetStatistics();
+ char msg[1024];
+ size_t offset = 0;
+#define AddIfValue(format, member) \
+ if (stats.member.has_value()) { \
+ offset += SprintfBuf(msg + offset, sizeof(msg) - offset, \
+ #member ":" format ", ", stats.member.value()); \
+ }
+ AddIfValue("%d", voice_detected);
+ AddIfValue("%lf", echo_return_loss);
+ AddIfValue("%lf", echo_return_loss_enhancement);
+ AddIfValue("%lf", divergent_filter_fraction);
+ AddIfValue("%d", delay_median_ms);
+ AddIfValue("%d", delay_standard_deviation_ms);
+ AddIfValue("%lf", residual_echo_likelihood);
+ AddIfValue("%lf", residual_echo_likelihood_recent_max);
+ AddIfValue("%d", delay_ms);
+#undef AddIfValue
+ LOG("AudioProcessing statistics: %s", msg);
+ }
+
+ if (mEnded) {
+ continue;
+ }
+
+ // We already have planar audio data of the right format. Insert into the
+ // MTG.
+ MOZ_ASSERT(processedOutputChannelPointers.Length() == channelCountInput);
+
+ // Insert the processed data chunk by chunk to mSegment with the paired
+ // PrincipalHandle value. The chunks are tracked in mChunksInPacketizer.
+
+ auto getAudioChunk = [&](TrackTime aStart, TrackTime aEnd,
+ const PrincipalHandle& aPrincipalHandle) {
+ if (aStart == aEnd) {
+ return AudioChunk();
+ }
+ RefPtr<SharedBuffer> other = buffer;
+ AudioChunk c =
+ AudioChunk(other.forget(), processedOutputChannelPointersConst,
+ static_cast<TrackTime>(mPacketizerInput->mPacketSize),
+ aPrincipalHandle);
+ c.SliceTo(aStart, aEnd);
+ return c;
+ };
+
+ // The number of frames of data that needs to be labelled with Principal
+ // values.
+ TrackTime len = static_cast<TrackTime>(mPacketizerInput->mPacketSize);
+ // The start offset of the unlabelled chunk.
+ TrackTime start = 0;
+ // By mChunksInPacketizer's information, we can keep labelling the
+ // unlabelled frames chunk by chunk.
+ while (!mChunksInPacketizer.empty()) {
+ auto& [frames, principal] = mChunksInPacketizer.front();
+ const TrackTime end = start + frames;
+ if (end > len) {
+ // If the left unlabelled frames are part of this chunk, then we need to
+ // adjust the number of frames in the chunk.
+ if (len > start) {
+ mSegment.AppendAndConsumeChunk(getAudioChunk(start, len, principal));
+ frames -= len - start;
+ }
+ break;
+ }
+ // Otherwise, the number of unlabelled frames is larger than or equal to
+ // this chunk. We can label the whole chunk directly.
+ mSegment.AppendAndConsumeChunk(getAudioChunk(start, end, principal));
+ start = end;
+ mChunksInPacketizer.pop_front();
+ }
+
+ LOG_FRAME(
+ "(Graph %p, Driver %p) AudioInputProcessing %p Appending %u frames of "
+ "packetized audio, leaving %u frames in packetizer (%" PRId64
+ " frames in mChunksInPacketizer)",
+ aGraph, aGraph->CurrentDriver(), this, mPacketizerInput->mPacketSize,
+ mPacketizerInput->FramesAvailable(), pendingFrames());
+
+ // Postcondition of the Principal-labelling logic.
+ MOZ_ASSERT(mPacketizerInput->FramesAvailable() ==
+ static_cast<uint32_t>(pendingFrames()));
+ }
+}
+
+void AudioInputProcessing::DeviceChanged(MediaTrackGraphImpl* aGraph) {
+ MOZ_ASSERT(aGraph->OnGraphThread());
+
+ // Reset some processing
+ mAudioProcessing->Initialize();
+ LOG_FRAME(
+ "(Graph %p, Driver %p) AudioInputProcessing %p Reinitializing audio "
+ "processing",
+ aGraph, aGraph->CurrentDriver(), this);
+}
+
+void AudioInputProcessing::ApplyConfig(MediaTrackGraphImpl* aGraph,
+ const AudioProcessing::Config& aConfig) {
+ MOZ_ASSERT(aGraph->OnGraphThread());
+ mAudioProcessing->ApplyConfig(aConfig);
+}
+
+void AudioInputProcessing::End() {
+ mEnded = true;
+ mSegment.Clear();
+}
+
+TrackTime AudioInputProcessing::NumBufferedFrames(
+ MediaTrackGraphImpl* aGraph) const {
+ MOZ_ASSERT(aGraph->OnGraphThread());
+ return mSegment.GetDuration();
+}
+
+void AudioInputProcessing::EnsureAudioProcessing(MediaTrackGraphImpl* aGraph,
+ uint32_t aChannels) {
+ MOZ_ASSERT(aGraph->OnGraphThread());
+ MOZ_ASSERT(aChannels > 0);
+ MOZ_ASSERT(mEnabled);
+ MOZ_ASSERT(!mSkipProcessing);
+
+ if (mPacketizerInput && mPacketizerInput->mChannels == aChannels) {
+ return;
+ }
+
+ // If mPacketizerInput exists but with different channel-count, there is no
+ // need to change pre-buffering since the packet size is the same as the old
+ // one, since the rate is a constant.
+ MOZ_ASSERT_IF(mPacketizerInput, mPacketizerInput->mPacketSize ==
+ GetPacketSize(aGraph->GraphRate()));
+ bool needPreBuffering = !mPacketizerInput;
+ if (mPacketizerInput) {
+ const TrackTime numBufferedFrames =
+ static_cast<TrackTime>(mPacketizerInput->FramesAvailable());
+ mSegment.AppendNullData(numBufferedFrames);
+ mPacketizerInput = Nothing();
+ mChunksInPacketizer.clear();
+ }
+
+ mPacketizerInput.emplace(GetPacketSize(aGraph->GraphRate()), aChannels);
+
+ if (needPreBuffering) {
+ LOG_FRAME(
+ "(Graph %p, Driver %p) AudioInputProcessing %p: Adding %u frames of "
+ "silence as pre-buffering",
+ aGraph, aGraph->CurrentDriver(), this, mPacketizerInput->mPacketSize);
+
+ AudioSegment buffering;
+ buffering.AppendNullData(
+ static_cast<TrackTime>(mPacketizerInput->mPacketSize));
+ PacketizeAndProcess(aGraph, buffering);
+ }
+}
+
+void AudioInputProcessing::ResetAudioProcessing(MediaTrackGraphImpl* aGraph) {
+ MOZ_ASSERT(aGraph->OnGraphThread());
+ MOZ_ASSERT(mSkipProcessing || !mEnabled);
+ MOZ_ASSERT(mPacketizerInput);
+
+ LOG_FRAME(
+ "(Graph %p, Driver %p) AudioInputProcessing %p Resetting audio "
+ "processing",
+ aGraph, aGraph->CurrentDriver(), this);
+
+ // Reset AudioProcessing so that if we resume processing in the future it
+ // doesn't depend on old state.
+ mAudioProcessing->Initialize();
+
+ MOZ_ASSERT(static_cast<uint32_t>(mSegment.GetDuration()) +
+ mPacketizerInput->FramesAvailable() ==
+ mPacketizerInput->mPacketSize);
+
+ // It's ok to clear all the internal buffer here since we won't use mSegment
+ // in pass-through mode or when audio processing is disabled.
+ LOG_FRAME(
+ "(Graph %p, Driver %p) AudioInputProcessing %p Emptying out %" PRId64
+ " frames of data",
+ aGraph, aGraph->CurrentDriver(), this, mSegment.GetDuration());
+ mSegment.Clear();
+
+ mPacketizerInput = Nothing();
+ mChunksInPacketizer.clear();
+}
+
+void AudioProcessingTrack::Destroy() {
+ MOZ_ASSERT(NS_IsMainThread());
+ DisconnectDeviceInput();
+
+ MediaTrack::Destroy();
+}
+
+void AudioProcessingTrack::SetInputProcessing(
+ RefPtr<AudioInputProcessing> aInputProcessing) {
+ class Message : public ControlMessage {
+ const RefPtr<AudioProcessingTrack> mTrack;
+ const RefPtr<AudioInputProcessing> mProcessing;
+
+ public:
+ Message(RefPtr<AudioProcessingTrack> aTrack,
+ RefPtr<AudioInputProcessing> aProcessing)
+ : ControlMessage(aTrack),
+ mTrack(std::move(aTrack)),
+ mProcessing(std::move(aProcessing)) {}
+ void Run() override {
+ TRACE("AudioProcessingTrack::SetInputProcessingImpl");
+ mTrack->SetInputProcessingImpl(mProcessing);
+ }
+ };
+
+ if (IsDestroyed()) {
+ return;
+ }
+ GraphImpl()->AppendMessage(
+ MakeUnique<Message>(std::move(this), std::move(aInputProcessing)));
+}
+
+AudioProcessingTrack* AudioProcessingTrack::Create(MediaTrackGraph* aGraph) {
+ MOZ_ASSERT(NS_IsMainThread());
+ AudioProcessingTrack* track = new AudioProcessingTrack(aGraph->GraphRate());
+ aGraph->AddTrack(track);
+ return track;
+}
+
+void AudioProcessingTrack::DestroyImpl() {
+ ProcessedMediaTrack::DestroyImpl();
+ if (mInputProcessing) {
+ mInputProcessing->End();
+ }
+}
+
+void AudioProcessingTrack::ProcessInput(GraphTime aFrom, GraphTime aTo,
+ uint32_t aFlags) {
+ TRACE_COMMENT("AudioProcessingTrack::ProcessInput", "AudioProcessingTrack %p",
+ this);
+ MOZ_ASSERT(mInputProcessing);
+
+ LOG_FRAME(
+ "(Graph %p, Driver %p) AudioProcessingTrack %p ProcessInput from %" PRId64
+ " to %" PRId64 ", needs %" PRId64 " frames",
+ mGraph, mGraph->CurrentDriver(), this, aFrom, aTo, aTo - aFrom);
+
+ if (aFrom >= aTo) {
+ return;
+ }
+
+ if (!mInputProcessing->IsEnded()) {
+ MOZ_ASSERT(TrackTimeToGraphTime(GetEnd()) == aFrom);
+ if (mInputs.IsEmpty()) {
+ GetData<AudioSegment>()->AppendNullData(aTo - aFrom);
+ LOG_FRAME("(Graph %p, Driver %p) AudioProcessingTrack %p Filling %" PRId64
+ " frames of null data (no input source)",
+ mGraph, mGraph->CurrentDriver(), this, aTo - aFrom);
+ } else {
+ MOZ_ASSERT(mInputs.Length() == 1);
+ AudioSegment data;
+ DeviceInputConsumerTrack::GetInputSourceData(data, mInputs[0], aFrom,
+ aTo);
+ mInputProcessing->Process(GraphImpl(), aFrom, aTo, &data,
+ GetData<AudioSegment>());
+ }
+ MOZ_ASSERT(TrackTimeToGraphTime(GetEnd()) == aTo);
+
+ ApplyTrackDisabling(mSegment.get());
+ } else if (aFlags & ALLOW_END) {
+ mEnded = true;
+ }
+}
+
+void AudioProcessingTrack::NotifyOutputData(MediaTrackGraphImpl* aGraph,
+ AudioDataValue* aBuffer,
+ size_t aFrames, TrackRate aRate,
+ uint32_t aChannels) {
+ MOZ_ASSERT(mGraph == aGraph, "Cannot feed audio output to another graph");
+ MOZ_ASSERT(mGraph->OnGraphThread());
+ if (mInputProcessing) {
+ mInputProcessing->ProcessOutputData(aGraph, aBuffer, aFrames, aRate,
+ aChannels);
+ }
+}
+
+void AudioProcessingTrack::SetInputProcessingImpl(
+ RefPtr<AudioInputProcessing> aInputProcessing) {
+ MOZ_ASSERT(GraphImpl()->OnGraphThread());
+ mInputProcessing = std::move(aInputProcessing);
+}
+
+MediaEngineWebRTCAudioCaptureSource::MediaEngineWebRTCAudioCaptureSource(
+ const MediaDevice* aMediaDevice) {
+ MOZ_ASSERT(aMediaDevice->mMediaSource == MediaSourceEnum::AudioCapture);
+}
+
+/* static */
+nsString MediaEngineWebRTCAudioCaptureSource::GetUUID() {
+ nsID uuid{};
+ char uuidBuffer[NSID_LENGTH];
+ nsCString asciiString;
+ ErrorResult rv;
+
+ rv = nsID::GenerateUUIDInPlace(uuid);
+ if (rv.Failed()) {
+ return u""_ns;
+ }
+
+ uuid.ToProvidedString(uuidBuffer);
+ asciiString.AssignASCII(uuidBuffer);
+
+ // Remove {} and the null terminator
+ return NS_ConvertASCIItoUTF16(Substring(asciiString, 1, NSID_LENGTH - 3));
+}
+
+/* static */
+nsString MediaEngineWebRTCAudioCaptureSource::GetGroupId() {
+ return u"AudioCaptureGroup"_ns;
+}
+
+void MediaEngineWebRTCAudioCaptureSource::SetTrack(
+ const RefPtr<MediaTrack>& aTrack, const PrincipalHandle& aPrincipalHandle) {
+ AssertIsOnOwningThread();
+ // Nothing to do here. aTrack is a placeholder dummy and not exposed.
+}
+
+nsresult MediaEngineWebRTCAudioCaptureSource::Start() {
+ AssertIsOnOwningThread();
+ return NS_OK;
+}
+
+nsresult MediaEngineWebRTCAudioCaptureSource::Stop() {
+ AssertIsOnOwningThread();
+ return NS_OK;
+}
+
+nsresult MediaEngineWebRTCAudioCaptureSource::Reconfigure(
+ const dom::MediaTrackConstraints& aConstraints,
+ const MediaEnginePrefs& aPrefs, const char** aOutBadConstraint) {
+ return NS_OK;
+}
+
+void MediaEngineWebRTCAudioCaptureSource::GetSettings(
+ dom::MediaTrackSettings& aOutSettings) const {
+ aOutSettings.mAutoGainControl.Construct(false);
+ aOutSettings.mEchoCancellation.Construct(false);
+ aOutSettings.mNoiseSuppression.Construct(false);
+ aOutSettings.mChannelCount.Construct(1);
+}
+
+} // namespace mozilla
+
+// Don't allow our macros to leak into other cpps in our unified build unit.
+#undef MAX_CHANNELS
+#undef MONO
+#undef MAX_SAMPLING_FREQ
diff --git a/dom/media/webrtc/MediaEngineWebRTCAudio.h b/dom/media/webrtc/MediaEngineWebRTCAudio.h
new file mode 100644
index 0000000000..d9a602962d
--- /dev/null
+++ b/dom/media/webrtc/MediaEngineWebRTCAudio.h
@@ -0,0 +1,295 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MediaEngineWebRTCAudio_h
+#define MediaEngineWebRTCAudio_h
+
+#include "AudioPacketizer.h"
+#include "AudioSegment.h"
+#include "AudioDeviceInfo.h"
+#include "DeviceInputTrack.h"
+#include "MediaEngineWebRTC.h"
+#include "MediaEnginePrefs.h"
+#include "MediaTrackListener.h"
+#include "modules/audio_processing/include/audio_processing.h"
+
+namespace mozilla {
+
+class AudioInputProcessing;
+class AudioProcessingTrack;
+
+// This class is created and used exclusively on the Media Manager thread, with
+// exactly two exceptions:
+// - Pull is always called on the MTG thread. It only ever uses
+// mInputProcessing. mInputProcessing is set, then a message is sent first to
+// the main thread and then the MTG thread so that it can be used as part of
+// the graph processing. On destruction, similarly, a message is sent to the
+// graph so that it stops using it, and then it is deleted.
+// - mSettings is created on the MediaManager thread is always ever accessed on
+// the Main Thread. It is const.
+class MediaEngineWebRTCMicrophoneSource : public MediaEngineSource {
+ public:
+ explicit MediaEngineWebRTCMicrophoneSource(const MediaDevice* aMediaDevice);
+
+ nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
+ const MediaEnginePrefs& aPrefs, uint64_t aWindowID,
+ const char** aOutBadConstraint) override;
+ nsresult Deallocate() override;
+ void SetTrack(const RefPtr<MediaTrack>& aTrack,
+ const PrincipalHandle& aPrincipal) override;
+ nsresult Start() override;
+ nsresult Stop() override;
+ nsresult Reconfigure(const dom::MediaTrackConstraints& aConstraints,
+ const MediaEnginePrefs& aPrefs,
+ const char** aOutBadConstraint) override;
+
+ /**
+ * Assigns the current settings of the capture to aOutSettings.
+ * Main thread only.
+ */
+ void GetSettings(dom::MediaTrackSettings& aOutSettings) const override;
+
+ nsresult TakePhoto(MediaEnginePhotoCallback* aCallback) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ protected:
+ ~MediaEngineWebRTCMicrophoneSource() = default;
+
+ private:
+ /**
+ * From a set of constraints and about:config preferences, output the correct
+ * set of preferences that can be sent to AudioInputProcessing.
+ *
+ * This can fail if the number of channels requested is zero, negative, or
+ * more than the device supports.
+ */
+ nsresult EvaluateSettings(const NormalizedConstraints& aConstraintsUpdate,
+ const MediaEnginePrefs& aInPrefs,
+ MediaEnginePrefs* aOutPrefs,
+ const char** aOutBadConstraint);
+ /**
+ * From settings output by EvaluateSettings, send those settings to the
+ * AudioInputProcessing instance and the main thread (for use in GetSettings).
+ */
+ void ApplySettings(const MediaEnginePrefs& aPrefs);
+
+ PrincipalHandle mPrincipal = PRINCIPAL_HANDLE_NONE;
+
+ const RefPtr<AudioDeviceInfo> mDeviceInfo;
+
+ // The maximum number of channels that this device supports.
+ const uint32_t mDeviceMaxChannelCount;
+ // The current settings for the underlying device.
+ // Constructed on the MediaManager thread, and then only ever accessed on the
+ // main thread.
+ const nsMainThreadPtrHandle<media::Refcountable<dom::MediaTrackSettings>>
+ mSettings;
+
+ // Current state of the resource for this source.
+ MediaEngineSourceState mState;
+
+ // The current preferences that will be forwarded to mAudioProcessingConfig
+ // below.
+ MediaEnginePrefs mCurrentPrefs;
+
+ // The AudioProcessingTrack used to inteface with the MediaTrackGraph. Set in
+ // SetTrack as part of the initialization, and nulled in ::Deallocate.
+ RefPtr<AudioProcessingTrack> mTrack;
+
+ // See note at the top of this class.
+ RefPtr<AudioInputProcessing> mInputProcessing;
+
+ // Copy of the config currently applied to AudioProcessing through
+ // mInputProcessing.
+ webrtc::AudioProcessing::Config mAudioProcessingConfig;
+};
+
+// This class is created on the MediaManager thread, and then exclusively used
+// on the MTG thread.
+// All communication is done via message passing using MTG ControlMessages
+class AudioInputProcessing : public AudioDataListener {
+ public:
+ explicit AudioInputProcessing(uint32_t aMaxChannelCount);
+ void Process(MediaTrackGraphImpl* aGraph, GraphTime aFrom, GraphTime aTo,
+ AudioSegment* aInput, AudioSegment* aOutput);
+
+ void ProcessOutputData(MediaTrackGraphImpl* aGraph, AudioDataValue* aBuffer,
+ size_t aFrames, TrackRate aRate, uint32_t aChannels);
+ bool IsVoiceInput(MediaTrackGraphImpl* aGraph) const override {
+ // If we're passing data directly without AEC or any other process, this
+ // means that all voice-processing has been disabled intentionaly. In this
+ // case, consider that the device is not used for voice input.
+ return !PassThrough(aGraph);
+ }
+
+ void Start(MediaTrackGraphImpl* aGraph);
+ void Stop(MediaTrackGraphImpl* aGraph);
+
+ void DeviceChanged(MediaTrackGraphImpl* aGraph) override;
+
+ uint32_t RequestedInputChannelCount(MediaTrackGraphImpl*) override {
+ return GetRequestedInputChannelCount();
+ }
+
+ void Disconnect(MediaTrackGraphImpl* aGraph) override;
+
+ void PacketizeAndProcess(MediaTrackGraphImpl* aGraph,
+ const AudioSegment& aSegment);
+
+ void SetPassThrough(MediaTrackGraphImpl* aGraph, bool aPassThrough);
+ uint32_t GetRequestedInputChannelCount();
+ void SetRequestedInputChannelCount(MediaTrackGraphImpl* aGraph,
+ CubebUtils::AudioDeviceID aDeviceId,
+ uint32_t aRequestedInputChannelCount);
+ // This is true when all processing is disabled, we can skip
+ // packetization, resampling and other processing passes.
+ bool PassThrough(MediaTrackGraphImpl* aGraphImpl) const;
+
+ // This allow changing the APM options, enabling or disabling processing
+ // steps. The config gets applied the next time we're about to process input
+ // data.
+ void ApplyConfig(MediaTrackGraphImpl* aGraph,
+ const webrtc::AudioProcessing::Config& aConfig);
+
+ void End();
+
+ TrackTime NumBufferedFrames(MediaTrackGraphImpl* aGraph) const;
+
+ // The packet size contains samples in 10ms. The unit of aRate is hz.
+ constexpr static uint32_t GetPacketSize(TrackRate aRate) {
+ return static_cast<uint32_t>(aRate) / 100u;
+ }
+
+ bool IsEnded() const { return mEnded; }
+
+ private:
+ ~AudioInputProcessing() = default;
+ void EnsureAudioProcessing(MediaTrackGraphImpl* aGraph, uint32_t aChannels);
+ void ResetAudioProcessing(MediaTrackGraphImpl* aGraph);
+ PrincipalHandle GetCheckedPrincipal(const AudioSegment& aSegment);
+ // This implements the processing algoritm to apply to the input (e.g. a
+ // microphone). If all algorithms are disabled, this class in not used. This
+ // class only accepts audio chunks of 10ms. It has two inputs and one output:
+ // it is fed the speaker data and the microphone data. It outputs processed
+ // input data.
+ const UniquePtr<webrtc::AudioProcessing> mAudioProcessing;
+ // Packetizer to be able to feed 10ms packets to the input side of
+ // mAudioProcessing. Not used if the processing is bypassed.
+ Maybe<AudioPacketizer<AudioDataValue, float>> mPacketizerInput;
+ // Packetizer to be able to feed 10ms packets to the output side of
+ // mAudioProcessing. Not used if the processing is bypassed.
+ Maybe<AudioPacketizer<AudioDataValue, float>> mPacketizerOutput;
+ // The number of channels asked for by content, after clamping to the range of
+ // legal channel count for this particular device.
+ uint32_t mRequestedInputChannelCount;
+ // mSkipProcessing is true if none of the processing passes are enabled,
+ // because of prefs or constraints. This allows simply copying the audio into
+ // the MTG, skipping resampling and the whole webrtc.org code.
+ bool mSkipProcessing;
+ // Stores the mixed audio output for the reverse-stream of the AEC (the
+ // speaker data).
+ AlignedFloatBuffer mOutputBuffer;
+ // Stores the input audio, to be processed by the APM.
+ AlignedFloatBuffer mInputBuffer;
+ // Stores the deinterleaved microphone audio
+ AlignedFloatBuffer mDeinterleavedBuffer;
+ // Stores the mixed down input audio
+ AlignedFloatBuffer mInputDownmixBuffer;
+ // Stores data waiting to be pulled.
+ AudioSegment mSegment;
+ // Whether or not this MediaEngine is enabled. If it's not enabled, it
+ // operates in "pull" mode, and we append silence only, releasing the audio
+ // input track.
+ bool mEnabled;
+ // Whether or not we've ended and removed the AudioProcessingTrack.
+ bool mEnded;
+ // When processing is enabled, the number of packets received by this
+ // instance, to implement periodic logging.
+ uint64_t mPacketCount;
+ // A storage holding the interleaved audio data converted the AudioSegment.
+ // This will be used as an input parameter for PacketizeAndProcess. This
+ // should be removed once bug 1729041 is done.
+ AutoTArray<AudioDataValue,
+ SilentChannel::AUDIO_PROCESSING_FRAMES * GUESS_AUDIO_CHANNELS>
+ mInterleavedBuffer;
+ // Tracks the pending frames with paired principals piled up in packetizer.
+ std::deque<std::pair<TrackTime, PrincipalHandle>> mChunksInPacketizer;
+};
+
+// MediaTrack subclass tailored for MediaEngineWebRTCMicrophoneSource.
+class AudioProcessingTrack : public DeviceInputConsumerTrack {
+ // Only accessed on the graph thread.
+ RefPtr<AudioInputProcessing> mInputProcessing;
+
+ explicit AudioProcessingTrack(TrackRate aSampleRate)
+ : DeviceInputConsumerTrack(aSampleRate) {}
+
+ ~AudioProcessingTrack() = default;
+
+ public:
+ // Main Thread API
+ void Destroy() override;
+ void SetInputProcessing(RefPtr<AudioInputProcessing> aInputProcessing);
+ static AudioProcessingTrack* Create(MediaTrackGraph* aGraph);
+
+ // Graph Thread API
+ void DestroyImpl() override;
+ void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) override;
+ uint32_t NumberOfChannels() const override {
+ MOZ_DIAGNOSTIC_ASSERT(
+ mInputProcessing,
+ "Must set mInputProcessing before exposing to content");
+ return mInputProcessing->GetRequestedInputChannelCount();
+ }
+ // Pass the graph's mixed audio output to mInputProcessing for processing as
+ // the reverse stream.
+ void NotifyOutputData(MediaTrackGraphImpl* aGraph, AudioDataValue* aBuffer,
+ size_t aFrames, TrackRate aRate, uint32_t aChannels);
+
+ // Any thread
+ AudioProcessingTrack* AsAudioProcessingTrack() override { return this; }
+
+ private:
+ // Graph thread API
+ void SetInputProcessingImpl(RefPtr<AudioInputProcessing> aInputProcessing);
+};
+
+class MediaEngineWebRTCAudioCaptureSource : public MediaEngineSource {
+ public:
+ explicit MediaEngineWebRTCAudioCaptureSource(const MediaDevice* aMediaDevice);
+ static nsString GetUUID();
+ static nsString GetGroupId();
+ nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
+ const MediaEnginePrefs& aPrefs, uint64_t aWindowID,
+ const char** aOutBadConstraint) override {
+ // Nothing to do here, everything is managed in MediaManager.cpp
+ return NS_OK;
+ }
+ nsresult Deallocate() override {
+ // Nothing to do here, everything is managed in MediaManager.cpp
+ return NS_OK;
+ }
+ void SetTrack(const RefPtr<MediaTrack>& aTrack,
+ const PrincipalHandle& aPrincipal) override;
+ nsresult Start() override;
+ nsresult Stop() override;
+ nsresult Reconfigure(const dom::MediaTrackConstraints& aConstraints,
+ const MediaEnginePrefs& aPrefs,
+ const char** aOutBadConstraint) override;
+
+ nsresult TakePhoto(MediaEnginePhotoCallback* aCallback) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ void GetSettings(dom::MediaTrackSettings& aOutSettings) const override;
+
+ protected:
+ virtual ~MediaEngineWebRTCAudioCaptureSource() = default;
+};
+
+} // end namespace mozilla
+
+#endif // MediaEngineWebRTCAudio_h
diff --git a/dom/media/webrtc/MediaTrackConstraints.cpp b/dom/media/webrtc/MediaTrackConstraints.cpp
new file mode 100644
index 0000000000..101cd0240a
--- /dev/null
+++ b/dom/media/webrtc/MediaTrackConstraints.cpp
@@ -0,0 +1,560 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MediaTrackConstraints.h"
+
+#include <limits>
+#include <algorithm>
+#include <iterator>
+
+#include "MediaEngineSource.h"
+#include "mozilla/dom/MediaStreamTrackBinding.h"
+#include "mozilla/MediaManager.h"
+
+#ifdef MOZ_WEBRTC
+namespace mozilla {
+extern LazyLogModule gMediaManagerLog;
+}
+#else
+static mozilla::LazyLogModule gMediaManagerLog("MediaManager");
+#endif
+#define LOG(...) MOZ_LOG(gMediaManagerLog, LogLevel::Debug, (__VA_ARGS__))
+
+namespace mozilla {
+
+using dom::CallerType;
+using dom::ConstrainBooleanParameters;
+
+template <class ValueType>
+template <class ConstrainRange>
+void NormalizedConstraintSet::Range<ValueType>::SetFrom(
+ const ConstrainRange& aOther) {
+ if (aOther.mIdeal.WasPassed()) {
+ mIdeal.emplace(aOther.mIdeal.Value());
+ }
+ if (aOther.mExact.WasPassed()) {
+ mMin = aOther.mExact.Value();
+ mMax = aOther.mExact.Value();
+ } else {
+ if (aOther.mMin.WasPassed()) {
+ mMin = aOther.mMin.Value();
+ }
+ if (aOther.mMax.WasPassed()) {
+ mMax = aOther.mMax.Value();
+ }
+ }
+}
+
+// The Range code works surprisingly well for bool, except when averaging
+// ideals.
+template <>
+bool NormalizedConstraintSet::Range<bool>::Merge(const Range& aOther) {
+ if (!Intersects(aOther)) {
+ return false;
+ }
+ Intersect(aOther);
+
+ // To avoid "unsafe use of type 'bool'", we keep counter in mMergeDenominator
+ uint32_t counter = mMergeDenominator >> 16;
+ uint32_t denominator = mMergeDenominator & 0xffff;
+
+ if (aOther.mIdeal.isSome()) {
+ if (mIdeal.isNothing()) {
+ mIdeal.emplace(aOther.Get(false));
+ counter = aOther.Get(false);
+ denominator = 1;
+ } else {
+ if (!denominator) {
+ counter = Get(false);
+ denominator = 1;
+ }
+ counter += aOther.Get(false);
+ denominator++;
+ }
+ }
+ mMergeDenominator = ((counter & 0xffff) << 16) + (denominator & 0xffff);
+ return true;
+}
+
+template <>
+void NormalizedConstraintSet::Range<bool>::FinalizeMerge() {
+ if (mMergeDenominator) {
+ uint32_t counter = mMergeDenominator >> 16;
+ uint32_t denominator = mMergeDenominator & 0xffff;
+
+ *mIdeal = !!(counter / denominator);
+ mMergeDenominator = 0;
+ }
+}
+
+NormalizedConstraintSet::LongRange::LongRange(
+ LongPtrType aMemberPtr, const char* aName,
+ const dom::Optional<dom::OwningLongOrConstrainLongRange>& aOther,
+ bool advanced, nsTArray<MemberPtrType>* aList)
+ : Range<int32_t>((MemberPtrType)aMemberPtr, aName, 1 + INT32_MIN,
+ INT32_MAX, // +1 avoids Windows compiler bug
+ aList) {
+ if (!aOther.WasPassed()) {
+ return;
+ }
+ auto& other = aOther.Value();
+ if (other.IsLong()) {
+ if (advanced) {
+ mMin = mMax = other.GetAsLong();
+ } else {
+ mIdeal.emplace(other.GetAsLong());
+ }
+ } else {
+ SetFrom(other.GetAsConstrainLongRange());
+ }
+}
+
+NormalizedConstraintSet::LongLongRange::LongLongRange(
+ LongLongPtrType aMemberPtr, const char* aName, const long long& aOther,
+ nsTArray<MemberPtrType>* aList)
+ : Range<int64_t>((MemberPtrType)aMemberPtr, aName, 1 + INT64_MIN,
+ INT64_MAX, // +1 avoids Windows compiler bug
+ aList) {
+ mIdeal.emplace(aOther);
+}
+
+NormalizedConstraintSet::DoubleRange::DoubleRange(
+ DoublePtrType aMemberPtr, const char* aName,
+ const dom::Optional<dom::OwningDoubleOrConstrainDoubleRange>& aOther,
+ bool advanced, nsTArray<MemberPtrType>* aList)
+ : Range<double>((MemberPtrType)aMemberPtr, aName,
+ -std::numeric_limits<double>::infinity(),
+ std::numeric_limits<double>::infinity(), aList) {
+ if (!aOther.WasPassed()) {
+ return;
+ }
+ auto& other = aOther.Value();
+ if (other.IsDouble()) {
+ if (advanced) {
+ mMin = mMax = other.GetAsDouble();
+ } else {
+ mIdeal.emplace(other.GetAsDouble());
+ }
+ } else {
+ SetFrom(other.GetAsConstrainDoubleRange());
+ }
+}
+
+NormalizedConstraintSet::BooleanRange::BooleanRange(
+ BooleanPtrType aMemberPtr, const char* aName,
+ const dom::Optional<dom::OwningBooleanOrConstrainBooleanParameters>& aOther,
+ bool advanced, nsTArray<MemberPtrType>* aList)
+ : Range<bool>((MemberPtrType)aMemberPtr, aName, false, true, aList) {
+ if (!aOther.WasPassed()) {
+ return;
+ }
+ auto& other = aOther.Value();
+ if (other.IsBoolean()) {
+ if (advanced) {
+ mMin = mMax = other.GetAsBoolean();
+ } else {
+ mIdeal.emplace(other.GetAsBoolean());
+ }
+ } else {
+ auto& r = other.GetAsConstrainBooleanParameters();
+ if (r.mIdeal.WasPassed()) {
+ mIdeal.emplace(r.mIdeal.Value());
+ }
+ if (r.mExact.WasPassed()) {
+ mMin = r.mExact.Value();
+ mMax = r.mExact.Value();
+ }
+ }
+}
+
+NormalizedConstraintSet::StringRange::StringRange(
+ StringPtrType aMemberPtr, const char* aName,
+ const dom::Optional<
+ dom::OwningStringOrStringSequenceOrConstrainDOMStringParameters>&
+ aOther,
+ bool advanced, nsTArray<MemberPtrType>* aList)
+ : BaseRange((MemberPtrType)aMemberPtr, aName, aList) {
+ if (!aOther.WasPassed()) {
+ return;
+ }
+ auto& other = aOther.Value();
+ if (other.IsString()) {
+ if (advanced) {
+ mExact.insert(other.GetAsString());
+ } else {
+ mIdeal.insert(other.GetAsString());
+ }
+ } else if (other.IsStringSequence()) {
+ if (advanced) {
+ mExact.clear();
+ for (auto& str : other.GetAsStringSequence()) {
+ mExact.insert(str);
+ }
+ } else {
+ mIdeal.clear();
+ for (auto& str : other.GetAsStringSequence()) {
+ mIdeal.insert(str);
+ }
+ }
+ } else {
+ SetFrom(other.GetAsConstrainDOMStringParameters());
+ }
+}
+
+void NormalizedConstraintSet::StringRange::SetFrom(
+ const dom::ConstrainDOMStringParameters& aOther) {
+ if (aOther.mIdeal.WasPassed()) {
+ mIdeal.clear();
+ if (aOther.mIdeal.Value().IsString()) {
+ mIdeal.insert(aOther.mIdeal.Value().GetAsString());
+ } else {
+ for (auto& str : aOther.mIdeal.Value().GetAsStringSequence()) {
+ mIdeal.insert(str);
+ }
+ }
+ }
+ if (aOther.mExact.WasPassed()) {
+ mExact.clear();
+ if (aOther.mExact.Value().IsString()) {
+ mExact.insert(aOther.mExact.Value().GetAsString());
+ } else {
+ for (auto& str : aOther.mExact.Value().GetAsStringSequence()) {
+ mExact.insert(str);
+ }
+ }
+ }
+}
+
+auto NormalizedConstraintSet::StringRange::Clamp(const ValueType& n) const
+ -> ValueType {
+ if (mExact.empty()) {
+ return n;
+ }
+ ValueType result;
+ for (auto& entry : n) {
+ if (mExact.find(entry) != mExact.end()) {
+ result.insert(entry);
+ }
+ }
+ return result;
+}
+
+bool NormalizedConstraintSet::StringRange::Intersects(
+ const StringRange& aOther) const {
+ if (mExact.empty() || aOther.mExact.empty()) {
+ return true;
+ }
+
+ ValueType intersection;
+ set_intersection(mExact.begin(), mExact.end(), aOther.mExact.begin(),
+ aOther.mExact.end(),
+ std::inserter(intersection, intersection.begin()));
+ return !intersection.empty();
+}
+
+void NormalizedConstraintSet::StringRange::Intersect(
+ const StringRange& aOther) {
+ if (aOther.mExact.empty()) {
+ return;
+ }
+
+ ValueType intersection;
+ set_intersection(mExact.begin(), mExact.end(), aOther.mExact.begin(),
+ aOther.mExact.end(),
+ std::inserter(intersection, intersection.begin()));
+ mExact = intersection;
+}
+
+bool NormalizedConstraintSet::StringRange::Merge(const StringRange& aOther) {
+ if (!Intersects(aOther)) {
+ return false;
+ }
+ Intersect(aOther);
+
+ ValueType unioned;
+ set_union(mIdeal.begin(), mIdeal.end(), aOther.mIdeal.begin(),
+ aOther.mIdeal.end(), std::inserter(unioned, unioned.begin()));
+ mIdeal = unioned;
+ return true;
+}
+
+NormalizedConstraints::NormalizedConstraints(
+ const dom::MediaTrackConstraints& aOther, nsTArray<MemberPtrType>* aList)
+ : NormalizedConstraintSet(aOther, false, aList), mBadConstraint(nullptr) {
+ if (aOther.mAdvanced.WasPassed()) {
+ for (auto& entry : aOther.mAdvanced.Value()) {
+ mAdvanced.push_back(NormalizedConstraintSet(entry, true));
+ }
+ }
+}
+
+FlattenedConstraints::FlattenedConstraints(const NormalizedConstraints& aOther)
+ : NormalizedConstraintSet(aOther) {
+ for (auto& set : aOther.mAdvanced) {
+ // Must only apply compatible i.e. inherently non-overconstraining sets
+ // This rule is pretty much why this code is centralized here.
+ if (mWidth.Intersects(set.mWidth) && mHeight.Intersects(set.mHeight) &&
+ mFrameRate.Intersects(set.mFrameRate)) {
+ mWidth.Intersect(set.mWidth);
+ mHeight.Intersect(set.mHeight);
+ mFrameRate.Intersect(set.mFrameRate);
+ }
+ if (mEchoCancellation.Intersects(set.mEchoCancellation)) {
+ mEchoCancellation.Intersect(set.mEchoCancellation);
+ }
+ if (mNoiseSuppression.Intersects(set.mNoiseSuppression)) {
+ mNoiseSuppression.Intersect(set.mNoiseSuppression);
+ }
+ if (mAutoGainControl.Intersects(set.mAutoGainControl)) {
+ mAutoGainControl.Intersect(set.mAutoGainControl);
+ }
+ if (mChannelCount.Intersects(set.mChannelCount)) {
+ mChannelCount.Intersect(set.mChannelCount);
+ }
+ }
+}
+
+// MediaEngine helper
+//
+// The full algorithm for all devices.
+//
+// Fitness distance returned as integer math * 1000. Infinity = UINT32_MAX
+
+// First, all devices have a minimum distance based on their deviceId.
+// If you have no other constraints, use this one. Reused by all device types.
+
+/* static */
+bool MediaConstraintsHelper::SomeSettingsFit(
+ const NormalizedConstraints& aConstraints,
+ const nsTArray<RefPtr<LocalMediaDevice>>& aDevices) {
+ nsTArray<const NormalizedConstraintSet*> sets;
+ sets.AppendElement(&aConstraints);
+
+ MOZ_ASSERT(!aDevices.IsEmpty());
+ for (auto& device : aDevices) {
+ auto distance = device->GetBestFitnessDistance(sets, CallerType::NonSystem);
+ if (distance != UINT32_MAX) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// Fitness distance returned as integer math * 1000. Infinity = UINT32_MAX
+
+/* static */
+uint32_t MediaConstraintsHelper::FitnessDistance(
+ const Maybe<nsString>& aN,
+ const NormalizedConstraintSet::StringRange& aParams) {
+ if (!aParams.mExact.empty() &&
+ (aN.isNothing() || aParams.mExact.find(*aN) == aParams.mExact.end())) {
+ return UINT32_MAX;
+ }
+ if (!aParams.mIdeal.empty() &&
+ (aN.isNothing() || aParams.mIdeal.find(*aN) == aParams.mIdeal.end())) {
+ return 1000;
+ }
+ return 0;
+}
+
+/* static */ const char* MediaConstraintsHelper::SelectSettings(
+ const NormalizedConstraints& aConstraints,
+ nsTArray<RefPtr<LocalMediaDevice>>& aDevices, CallerType aCallerType) {
+ auto& c = aConstraints;
+ LogConstraints(c);
+
+ // First apply top-level constraints.
+
+ // Stack constraintSets that pass, starting with the required one, because the
+ // whole stack must be re-satisfied each time a capability-set is ruled out
+ // (this avoids storing state or pushing algorithm into the lower-level code).
+ nsTArray<RefPtr<LocalMediaDevice>> unsatisfactory;
+ nsTArray<const NormalizedConstraintSet*> aggregateConstraints;
+ aggregateConstraints.AppendElement(&c);
+
+ std::multimap<uint32_t, RefPtr<LocalMediaDevice>> ordered;
+
+ for (uint32_t i = 0; i < aDevices.Length();) {
+ uint32_t distance =
+ aDevices[i]->GetBestFitnessDistance(aggregateConstraints, aCallerType);
+ if (distance == UINT32_MAX) {
+ unsatisfactory.AppendElement(std::move(aDevices[i]));
+ aDevices.RemoveElementAt(i);
+ } else {
+ ordered.insert(std::make_pair(distance, aDevices[i]));
+ ++i;
+ }
+ }
+ if (aDevices.IsEmpty()) {
+ return FindBadConstraint(c, unsatisfactory);
+ }
+
+ // Order devices by shortest distance
+ for (auto& ordinal : ordered) {
+ aDevices.RemoveElement(ordinal.second);
+ aDevices.AppendElement(ordinal.second);
+ }
+
+ // Then apply advanced constraints.
+
+ for (const auto& advanced : c.mAdvanced) {
+ aggregateConstraints.AppendElement(&advanced);
+ nsTArray<RefPtr<LocalMediaDevice>> rejects;
+ for (uint32_t j = 0; j < aDevices.Length();) {
+ uint32_t distance = aDevices[j]->GetBestFitnessDistance(
+ aggregateConstraints, aCallerType);
+ if (distance == UINT32_MAX) {
+ rejects.AppendElement(std::move(aDevices[j]));
+ aDevices.RemoveElementAt(j);
+ } else {
+ ++j;
+ }
+ }
+ if (aDevices.IsEmpty()) {
+ aDevices.AppendElements(std::move(rejects));
+ aggregateConstraints.RemoveLastElement();
+ }
+ }
+ return nullptr;
+}
+
+/* static */ const char* MediaConstraintsHelper::FindBadConstraint(
+ const NormalizedConstraints& aConstraints,
+ const nsTArray<RefPtr<LocalMediaDevice>>& aDevices) {
+ // The spec says to report a constraint that satisfies NONE
+ // of the sources. Unfortunately, this is a bit laborious to find out, and
+ // requires updating as new constraints are added!
+ auto& c = aConstraints;
+ dom::MediaTrackConstraints empty;
+
+ if (aDevices.IsEmpty() ||
+ !SomeSettingsFit(NormalizedConstraints(empty), aDevices)) {
+ return "";
+ }
+ {
+ NormalizedConstraints fresh(empty);
+ fresh.mDeviceId = c.mDeviceId;
+ if (!SomeSettingsFit(fresh, aDevices)) {
+ return "deviceId";
+ }
+ }
+ {
+ NormalizedConstraints fresh(empty);
+ fresh.mGroupId = c.mGroupId;
+ if (!SomeSettingsFit(fresh, aDevices)) {
+ return "groupId";
+ }
+ }
+ {
+ NormalizedConstraints fresh(empty);
+ fresh.mWidth = c.mWidth;
+ if (!SomeSettingsFit(fresh, aDevices)) {
+ return "width";
+ }
+ }
+ {
+ NormalizedConstraints fresh(empty);
+ fresh.mHeight = c.mHeight;
+ if (!SomeSettingsFit(fresh, aDevices)) {
+ return "height";
+ }
+ }
+ {
+ NormalizedConstraints fresh(empty);
+ fresh.mFrameRate = c.mFrameRate;
+ if (!SomeSettingsFit(fresh, aDevices)) {
+ return "frameRate";
+ }
+ }
+ {
+ NormalizedConstraints fresh(empty);
+ fresh.mFacingMode = c.mFacingMode;
+ if (!SomeSettingsFit(fresh, aDevices)) {
+ return "facingMode";
+ }
+ }
+ return "";
+}
+
+/* static */
+const char* MediaConstraintsHelper::FindBadConstraint(
+ const NormalizedConstraints& aConstraints,
+ const MediaDevice* aMediaDevice) {
+ NormalizedConstraints c(aConstraints);
+ NormalizedConstraints empty((dom::MediaTrackConstraints()));
+ c.mDeviceId = empty.mDeviceId;
+ c.mGroupId = empty.mGroupId;
+ AutoTArray<RefPtr<LocalMediaDevice>, 1> devices;
+ devices.EmplaceBack(
+ new LocalMediaDevice(aMediaDevice, u""_ns, u""_ns, u""_ns));
+ return FindBadConstraint(c, devices);
+}
+
+static void LogConstraintStringRange(
+ const NormalizedConstraintSet::StringRange& aRange) {
+ if (aRange.mExact.size() <= 1 && aRange.mIdeal.size() <= 1) {
+ LOG(" %s: { exact: [%s], ideal: [%s] }", aRange.mName,
+ (aRange.mExact.empty()
+ ? ""
+ : NS_ConvertUTF16toUTF8(*aRange.mExact.begin()).get()),
+ (aRange.mIdeal.empty()
+ ? ""
+ : NS_ConvertUTF16toUTF8(*aRange.mIdeal.begin()).get()));
+ } else {
+ LOG(" %s: { exact: [", aRange.mName);
+ for (auto& entry : aRange.mExact) {
+ LOG(" %s,", NS_ConvertUTF16toUTF8(entry).get());
+ }
+ LOG(" ], ideal: [");
+ for (auto& entry : aRange.mIdeal) {
+ LOG(" %s,", NS_ConvertUTF16toUTF8(entry).get());
+ }
+ LOG(" ]}");
+ }
+}
+
+template <typename T>
+static void LogConstraintRange(
+ const NormalizedConstraintSet::Range<T>& aRange) {
+ if (aRange.mIdeal.isSome()) {
+ LOG(" %s: { min: %d, max: %d, ideal: %d }", aRange.mName, aRange.mMin,
+ aRange.mMax, aRange.mIdeal.valueOr(0));
+ } else {
+ LOG(" %s: { min: %d, max: %d }", aRange.mName, aRange.mMin, aRange.mMax);
+ }
+}
+
+template <>
+void LogConstraintRange(const NormalizedConstraintSet::Range<double>& aRange) {
+ if (aRange.mIdeal.isSome()) {
+ LOG(" %s: { min: %f, max: %f, ideal: %f }", aRange.mName, aRange.mMin,
+ aRange.mMax, aRange.mIdeal.valueOr(0));
+ } else {
+ LOG(" %s: { min: %f, max: %f }", aRange.mName, aRange.mMin, aRange.mMax);
+ }
+}
+
+/* static */
+void MediaConstraintsHelper::LogConstraints(
+ const NormalizedConstraintSet& aConstraints) {
+ auto& c = aConstraints;
+ LOG("Constraints: {");
+ LOG("%s", [&]() {
+ LogConstraintRange(c.mWidth);
+ LogConstraintRange(c.mHeight);
+ LogConstraintRange(c.mFrameRate);
+ LogConstraintStringRange(c.mMediaSource);
+ LogConstraintStringRange(c.mFacingMode);
+ LogConstraintStringRange(c.mDeviceId);
+ LogConstraintStringRange(c.mGroupId);
+ LogConstraintRange(c.mEchoCancellation);
+ LogConstraintRange(c.mAutoGainControl);
+ LogConstraintRange(c.mNoiseSuppression);
+ LogConstraintRange(c.mChannelCount);
+ return "}";
+ }());
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/MediaTrackConstraints.h b/dom/media/webrtc/MediaTrackConstraints.h
new file mode 100644
index 0000000000..61e0ed85ea
--- /dev/null
+++ b/dom/media/webrtc/MediaTrackConstraints.h
@@ -0,0 +1,371 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This file should not be included by other includes, as it contains code
+
+#ifndef MEDIATRACKCONSTRAINTS_H_
+#define MEDIATRACKCONSTRAINTS_H_
+
+#include <map>
+#include <set>
+#include <vector>
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/MediaStreamTrackBinding.h"
+#include "mozilla/dom/MediaTrackSupportedConstraintsBinding.h"
+
+namespace mozilla {
+
+class LocalMediaDevice;
+class MediaDevice;
+
+template <class EnumValuesStrings, class Enum>
+static Enum StringToEnum(const EnumValuesStrings& aStrings,
+ const nsAString& aValue, Enum aDefaultValue) {
+ for (size_t i = 0; aStrings[i].value; i++) {
+ if (aValue.EqualsASCII(aStrings[i].value)) {
+ return Enum(i);
+ }
+ }
+ return aDefaultValue;
+}
+
+// Helper classes for orthogonal constraints without interdependencies.
+// Instead of constraining values, constrain the constraints themselves.
+class NormalizedConstraintSet {
+ protected:
+ class BaseRange {
+ protected:
+ typedef BaseRange NormalizedConstraintSet::*MemberPtrType;
+
+ BaseRange(MemberPtrType aMemberPtr, const char* aName,
+ nsTArray<MemberPtrType>* aList)
+ : mName(aName) {
+ if (aList) {
+ aList->AppendElement(aMemberPtr);
+ }
+ }
+ virtual ~BaseRange() = default;
+
+ public:
+ virtual bool Merge(const BaseRange& aOther) = 0;
+ virtual void FinalizeMerge() = 0;
+
+ const char* mName;
+ };
+
+ typedef BaseRange NormalizedConstraintSet::*MemberPtrType;
+
+ public:
+ template <class ValueType>
+ class Range : public BaseRange {
+ public:
+ ValueType mMin, mMax;
+ Maybe<ValueType> mIdeal;
+
+ Range(MemberPtrType aMemberPtr, const char* aName, ValueType aMin,
+ ValueType aMax, nsTArray<MemberPtrType>* aList)
+ : BaseRange(aMemberPtr, aName, aList),
+ mMin(aMin),
+ mMax(aMax),
+ mMergeDenominator(0) {}
+ virtual ~Range() = default;
+
+ template <class ConstrainRange>
+ void SetFrom(const ConstrainRange& aOther);
+ ValueType Clamp(ValueType n) const {
+ return std::max(mMin, std::min(n, mMax));
+ }
+ ValueType Get(ValueType defaultValue) const {
+ return Clamp(mIdeal.valueOr(defaultValue));
+ }
+ bool Intersects(const Range& aOther) const {
+ return mMax >= aOther.mMin && mMin <= aOther.mMax;
+ }
+ void Intersect(const Range& aOther) {
+ mMin = std::max(mMin, aOther.mMin);
+ if (Intersects(aOther)) {
+ mMax = std::min(mMax, aOther.mMax);
+ } else {
+ // If there is no intersection, we will down-scale or drop frame
+ mMax = std::max(mMax, aOther.mMax);
+ }
+ }
+ bool Merge(const Range& aOther) {
+ if (strcmp(mName, "width") != 0 && strcmp(mName, "height") != 0 &&
+ strcmp(mName, "frameRate") != 0 && !Intersects(aOther)) {
+ return false;
+ }
+ Intersect(aOther);
+
+ if (aOther.mIdeal.isSome()) {
+ // Ideal values, as stored, may be outside their min max range, so use
+ // clamped values in averaging, to avoid extreme outliers skewing
+ // results.
+ if (mIdeal.isNothing()) {
+ mIdeal.emplace(aOther.Get(0));
+ mMergeDenominator = 1;
+ } else {
+ if (!mMergeDenominator) {
+ *mIdeal = Get(0);
+ mMergeDenominator = 1;
+ }
+ *mIdeal += aOther.Get(0);
+ mMergeDenominator++;
+ }
+ }
+ return true;
+ }
+ void FinalizeMerge() override {
+ if (mMergeDenominator) {
+ *mIdeal /= mMergeDenominator;
+ mMergeDenominator = 0;
+ }
+ }
+ void TakeHighestIdeal(const Range& aOther) {
+ if (aOther.mIdeal.isSome()) {
+ if (mIdeal.isNothing()) {
+ mIdeal.emplace(aOther.Get(0));
+ } else {
+ *mIdeal = std::max(Get(0), aOther.Get(0));
+ }
+ }
+ }
+
+ private:
+ bool Merge(const BaseRange& aOther) override {
+ return Merge(static_cast<const Range&>(aOther));
+ }
+
+ uint32_t mMergeDenominator;
+ };
+
+ struct LongRange : public Range<int32_t> {
+ typedef LongRange NormalizedConstraintSet::*LongPtrType;
+
+ LongRange(LongPtrType aMemberPtr, const char* aName,
+ const dom::Optional<dom::OwningLongOrConstrainLongRange>& aOther,
+ bool advanced, nsTArray<MemberPtrType>* aList);
+ };
+
+ struct LongLongRange : public Range<int64_t> {
+ typedef LongLongRange NormalizedConstraintSet::*LongLongPtrType;
+
+ LongLongRange(LongLongPtrType aMemberPtr, const char* aName,
+ const long long& aOther, nsTArray<MemberPtrType>* aList);
+ };
+
+ struct DoubleRange : public Range<double> {
+ typedef DoubleRange NormalizedConstraintSet::*DoublePtrType;
+
+ DoubleRange(
+ DoublePtrType aMemberPtr, const char* aName,
+ const dom::Optional<dom::OwningDoubleOrConstrainDoubleRange>& aOther,
+ bool advanced, nsTArray<MemberPtrType>* aList);
+ };
+
+ struct BooleanRange : public Range<bool> {
+ typedef BooleanRange NormalizedConstraintSet::*BooleanPtrType;
+
+ BooleanRange(
+ BooleanPtrType aMemberPtr, const char* aName,
+ const dom::Optional<dom::OwningBooleanOrConstrainBooleanParameters>&
+ aOther,
+ bool advanced, nsTArray<MemberPtrType>* aList);
+
+ BooleanRange(BooleanPtrType aMemberPtr, const char* aName,
+ const bool& aOther, nsTArray<MemberPtrType>* aList)
+ : Range<bool>((MemberPtrType)aMemberPtr, aName, false, true, aList) {
+ mIdeal.emplace(aOther);
+ }
+ };
+
+ struct StringRange : public BaseRange {
+ typedef std::set<nsString> ValueType;
+ ValueType mExact, mIdeal;
+
+ typedef StringRange NormalizedConstraintSet::*StringPtrType;
+
+ StringRange(
+ StringPtrType aMemberPtr, const char* aName,
+ const dom::Optional<
+ dom::OwningStringOrStringSequenceOrConstrainDOMStringParameters>&
+ aOther,
+ bool advanced, nsTArray<MemberPtrType>* aList);
+
+ StringRange(StringPtrType aMemberPtr, const char* aName,
+ const dom::Optional<nsString>& aOther,
+ nsTArray<MemberPtrType>* aList)
+ : BaseRange((MemberPtrType)aMemberPtr, aName, aList) {
+ if (aOther.WasPassed()) {
+ mIdeal.insert(aOther.Value());
+ }
+ }
+
+ ~StringRange() = default;
+
+ void SetFrom(const dom::ConstrainDOMStringParameters& aOther);
+ ValueType Clamp(const ValueType& n) const;
+ ValueType Get(const ValueType& defaultValue) const {
+ return Clamp(mIdeal.empty() ? defaultValue : mIdeal);
+ }
+ bool Intersects(const StringRange& aOther) const;
+ void Intersect(const StringRange& aOther);
+ bool Merge(const StringRange& aOther);
+ void FinalizeMerge() override {}
+
+ private:
+ bool Merge(const BaseRange& aOther) override {
+ return Merge(static_cast<const StringRange&>(aOther));
+ }
+ };
+
+ // All new constraints should be added here whether they use flattening or not
+ LongRange mWidth, mHeight;
+ DoubleRange mFrameRate;
+ StringRange mFacingMode;
+ StringRange mMediaSource;
+ LongLongRange mBrowserWindow;
+ StringRange mDeviceId;
+ StringRange mGroupId;
+ LongRange mViewportOffsetX, mViewportOffsetY, mViewportWidth, mViewportHeight;
+ BooleanRange mEchoCancellation, mNoiseSuppression, mAutoGainControl;
+ LongRange mChannelCount;
+
+ private:
+ typedef NormalizedConstraintSet T;
+
+ public:
+ NormalizedConstraintSet(const dom::MediaTrackConstraintSet& aOther,
+ bool advanced,
+ nsTArray<MemberPtrType>* aList = nullptr)
+ : mWidth(&T::mWidth, "width", aOther.mWidth, advanced, aList),
+ mHeight(&T::mHeight, "height", aOther.mHeight, advanced, aList),
+ mFrameRate(&T::mFrameRate, "frameRate", aOther.mFrameRate, advanced,
+ aList),
+ mFacingMode(&T::mFacingMode, "facingMode", aOther.mFacingMode, advanced,
+ aList),
+ mMediaSource(&T::mMediaSource, "mediaSource", aOther.mMediaSource,
+ aList),
+ mBrowserWindow(&T::mBrowserWindow, "browserWindow",
+ aOther.mBrowserWindow.WasPassed()
+ ? aOther.mBrowserWindow.Value()
+ : 0,
+ aList),
+ mDeviceId(&T::mDeviceId, "deviceId", aOther.mDeviceId, advanced, aList),
+ mGroupId(&T::mGroupId, "groupId", aOther.mGroupId, advanced, aList),
+ mViewportOffsetX(&T::mViewportOffsetX, "viewportOffsetX",
+ aOther.mViewportOffsetX, advanced, aList),
+ mViewportOffsetY(&T::mViewportOffsetY, "viewportOffsetY",
+ aOther.mViewportOffsetY, advanced, aList),
+ mViewportWidth(&T::mViewportWidth, "viewportWidth",
+ aOther.mViewportWidth, advanced, aList),
+ mViewportHeight(&T::mViewportHeight, "viewportHeight",
+ aOther.mViewportHeight, advanced, aList),
+ mEchoCancellation(&T::mEchoCancellation, "echoCancellation",
+ aOther.mEchoCancellation, advanced, aList),
+ mNoiseSuppression(&T::mNoiseSuppression, "noiseSuppression",
+ aOther.mNoiseSuppression, advanced, aList),
+ mAutoGainControl(&T::mAutoGainControl, "autoGainControl",
+ aOther.mAutoGainControl, advanced, aList),
+ mChannelCount(&T::mChannelCount, "channelCount", aOther.mChannelCount,
+ advanced, aList) {}
+};
+
+template <>
+bool NormalizedConstraintSet::Range<bool>::Merge(const Range& aOther);
+template <>
+void NormalizedConstraintSet::Range<bool>::FinalizeMerge();
+
+// Used instead of MediaTrackConstraints in lower-level code.
+struct NormalizedConstraints : public NormalizedConstraintSet {
+ explicit NormalizedConstraints(const dom::MediaTrackConstraints& aOther,
+ nsTArray<MemberPtrType>* aList = nullptr);
+
+ std::vector<NormalizedConstraintSet> mAdvanced;
+ const char* mBadConstraint;
+};
+
+// Flattened version is used in low-level code with orthogonal constraints only.
+struct FlattenedConstraints : public NormalizedConstraintSet {
+ explicit FlattenedConstraints(const NormalizedConstraints& aOther);
+
+ explicit FlattenedConstraints(const dom::MediaTrackConstraints& aOther)
+ : FlattenedConstraints(NormalizedConstraints(aOther)) {}
+};
+
+// A helper class for MediaEngineSources
+class MediaConstraintsHelper {
+ public:
+ template <class ValueType, class NormalizedRange>
+ static uint32_t FitnessDistance(ValueType aN, const NormalizedRange& aRange) {
+ if (aRange.mMin > aN || aRange.mMax < aN) {
+ return UINT32_MAX;
+ }
+ if (aN == aRange.mIdeal.valueOr(aN)) {
+ return 0;
+ }
+ return uint32_t(
+ ValueType((std::abs(aN - aRange.mIdeal.value()) * 1000) /
+ std::max(std::abs(aN), std::abs(aRange.mIdeal.value()))));
+ }
+
+ template <class ValueType, class NormalizedRange>
+ static uint32_t FeasibilityDistance(ValueType aN,
+ const NormalizedRange& aRange) {
+ if (aRange.mMin > aN) {
+ return UINT32_MAX;
+ }
+ // We prefer larger resolution because now we support downscaling
+ if (aN == aRange.mIdeal.valueOr(aN)) {
+ return 0;
+ }
+
+ if (aN > aRange.mIdeal.value()) {
+ return uint32_t(
+ ValueType((std::abs(aN - aRange.mIdeal.value()) * 1000) /
+ std::max(std::abs(aN), std::abs(aRange.mIdeal.value()))));
+ }
+
+ return 10000 +
+ uint32_t(ValueType(
+ (std::abs(aN - aRange.mIdeal.value()) * 1000) /
+ std::max(std::abs(aN), std::abs(aRange.mIdeal.value()))));
+ }
+
+ static uint32_t FitnessDistance(
+ const Maybe<nsString>& aN,
+ const NormalizedConstraintSet::StringRange& aParams);
+
+ protected:
+ static bool SomeSettingsFit(
+ const NormalizedConstraints& aConstraints,
+ const nsTArray<RefPtr<LocalMediaDevice>>& aDevices);
+
+ public:
+ static uint32_t GetMinimumFitnessDistance(
+ const NormalizedConstraintSet& aConstraints, const nsString& aDeviceId,
+ const nsString& aGroupId);
+
+ // Apply constrains to a supplied list of devices (removes items from the
+ // list)
+ static const char* SelectSettings(
+ const NormalizedConstraints& aConstraints,
+ nsTArray<RefPtr<LocalMediaDevice>>& aDevices,
+ dom::CallerType aCallerType);
+
+ static const char* FindBadConstraint(
+ const NormalizedConstraints& aConstraints,
+ const nsTArray<RefPtr<LocalMediaDevice>>& aDevices);
+
+ static const char* FindBadConstraint(
+ const NormalizedConstraints& aConstraints,
+ const MediaDevice* aMediaDevice);
+
+ static void LogConstraints(const NormalizedConstraintSet& aConstraints);
+};
+
+} // namespace mozilla
+
+#endif /* MEDIATRACKCONSTRAINTS_H_ */
diff --git a/dom/media/webrtc/MediaTransportChild.h b/dom/media/webrtc/MediaTransportChild.h
new file mode 100644
index 0000000000..b84dce70b2
--- /dev/null
+++ b/dom/media/webrtc/MediaTransportChild.h
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MTRANSPORTCHILD_H__
+#define _MTRANSPORTCHILD_H__
+
+#include "mozilla/dom/PMediaTransportChild.h"
+
+namespace mozilla {
+class MediaTransportHandlerIPC;
+
+class MediaTransportChild : public dom::PMediaTransportChild {
+ public:
+#ifdef MOZ_WEBRTC
+ explicit MediaTransportChild(MediaTransportHandlerIPC* aUser);
+ virtual ~MediaTransportChild();
+ mozilla::ipc::IPCResult RecvOnCandidate(const string& transportId,
+ const CandidateInfo& candidateInfo);
+ mozilla::ipc::IPCResult RecvOnAlpnNegotiated(const string& alpn);
+ mozilla::ipc::IPCResult RecvOnGatheringStateChange(const int& state);
+ mozilla::ipc::IPCResult RecvOnConnectionStateChange(const int& state);
+ mozilla::ipc::IPCResult RecvOnPacketReceived(const string& transportId,
+ const MediaPacket& packet);
+ mozilla::ipc::IPCResult RecvOnEncryptedSending(const string& transportId,
+ const MediaPacket& packet);
+ mozilla::ipc::IPCResult RecvOnStateChange(const string& transportId,
+ const int& state);
+ mozilla::ipc::IPCResult RecvOnRtcpStateChange(const string& transportId,
+ const int& state);
+
+ private:
+ RefPtr<MediaTransportHandlerIPC> mUser;
+#endif // MOZ_WEBRTC
+};
+
+} // namespace mozilla
+#endif
diff --git a/dom/media/webrtc/MediaTransportParent.h b/dom/media/webrtc/MediaTransportParent.h
new file mode 100644
index 0000000000..8c698d0f54
--- /dev/null
+++ b/dom/media/webrtc/MediaTransportParent.h
@@ -0,0 +1,69 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MTRANSPORTHANDLER_PARENT_H__
+#define _MTRANSPORTHANDLER_PARENT_H__
+
+#include "mozilla/dom/PMediaTransportParent.h"
+#include <memory>
+
+namespace mozilla {
+
+class MediaTransportParent : public dom::PMediaTransportParent {
+ public:
+#ifdef MOZ_WEBRTC
+ MediaTransportParent();
+ virtual ~MediaTransportParent();
+
+ mozilla::ipc::IPCResult RecvGetIceLog(const nsCString& pattern,
+ GetIceLogResolver&& aResolve);
+ mozilla::ipc::IPCResult RecvClearIceLog();
+ mozilla::ipc::IPCResult RecvEnterPrivateMode();
+ mozilla::ipc::IPCResult RecvExitPrivateMode();
+ mozilla::ipc::IPCResult RecvCreateIceCtx(const string& name);
+ mozilla::ipc::IPCResult RecvSetIceConfig(
+ nsTArray<RTCIceServer>&& iceServers,
+ const RTCIceTransportPolicy& icePolicy);
+ mozilla::ipc::IPCResult RecvSetProxyConfig(
+ const net::WebrtcProxyConfig& aProxyConfig);
+ mozilla::ipc::IPCResult RecvEnsureProvisionalTransport(
+ const string& transportId, const string& localUfrag,
+ const string& localPwd, const int& componentCount);
+ mozilla::ipc::IPCResult RecvSetTargetForDefaultLocalAddressLookup(
+ const string& targetIp, uint16_t targetPort);
+ mozilla::ipc::IPCResult RecvStartIceGathering(
+ const bool& defaultRouteOnly, const bool& obfuscateAddresses,
+ const net::NrIceStunAddrArray& stunAddrs);
+ mozilla::ipc::IPCResult RecvActivateTransport(
+ const string& transportId, const string& localUfrag,
+ const string& localPwd, const int& componentCount,
+ const string& remoteUfrag, const string& remotePwd,
+ nsTArray<uint8_t>&& keyDer, nsTArray<uint8_t>&& certDer,
+ const int& authType, const bool& dtlsClient,
+ const DtlsDigestList& digests, const bool& privacyRequested);
+ mozilla::ipc::IPCResult RecvRemoveTransportsExcept(
+ const StringVector& transportIds);
+ mozilla::ipc::IPCResult RecvStartIceChecks(const bool& isControlling,
+ const StringVector& iceOptions);
+ mozilla::ipc::IPCResult RecvSendPacket(const string& transportId,
+ MediaPacket&& packet);
+ mozilla::ipc::IPCResult RecvAddIceCandidate(const string& transportId,
+ const string& candidate,
+ const string& ufrag,
+ const string& obfuscatedAddress);
+ mozilla::ipc::IPCResult RecvUpdateNetworkState(const bool& online);
+ mozilla::ipc::IPCResult RecvGetIceStats(const string& transportId,
+ const double& now,
+ GetIceStatsResolver&& aResolve);
+
+ void ActorDestroy(ActorDestroyReason aWhy);
+
+ private:
+ // Hide the sigslot/MediaTransportHandler stuff from IPC.
+ class Impl;
+ std::unique_ptr<Impl> mImpl;
+#endif // MOZ_WEBRTC
+};
+} // namespace mozilla
+#endif //_MTRANSPORTHANDLER_PARENT_H__
diff --git a/dom/media/webrtc/PMediaTransport.ipdl b/dom/media/webrtc/PMediaTransport.ipdl
new file mode 100644
index 0000000000..b754d34ad2
--- /dev/null
+++ b/dom/media/webrtc/PMediaTransport.ipdl
@@ -0,0 +1,105 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PBackground;
+
+#ifdef MOZ_WEBRTC
+include WebrtcProxyConfig;
+
+// ParamTraits stuff for generated code and other classes we don't want to change
+include "mozilla/net/NrIceStunAddrMessageUtils.h";
+include "mozilla/media/webrtc/WebrtcIPCTraits.h";
+using mozilla::StringVector from "mozilla/media/webrtc/WebrtcIPCTraits.h";
+using mozilla::CandidateInfo from "mozilla/media/webrtc/WebrtcIPCTraits.h";
+using mozilla::DtlsDigestList from "mozilla/media/webrtc/WebrtcIPCTraits.h";
+using std::string from "string";
+using struct mozilla::dom::RTCStatsCollection from "mozilla/dom/RTCStatsReportBinding.h";
+using WebrtcGlobalLog from "mozilla/media/webrtc/WebrtcGlobal.h";
+using mozilla::dom::RTCIceServer from "mozilla/dom/RTCConfigurationBinding.h";
+using mozilla::dom::RTCIceTransportPolicy from "mozilla/dom/RTCConfigurationBinding.h";
+
+// ParamTraits stuff for our own classes
+using mozilla::MediaPacket from "transport/mediapacket.h";
+include "mozilla/net/NrIceStunAddrMessageUtils.h";
+using mozilla::net::NrIceStunAddrArray from "mozilla/net/PStunAddrsParams.h";
+#endif // MOZ_WEBRTC
+
+namespace mozilla {
+namespace dom {
+
+[ManualDealloc]
+async protocol PMediaTransport {
+ manager PBackground;
+
+parent:
+ async __delete__();
+
+#ifdef MOZ_WEBRTC
+ async GetIceLog(nsCString pattern) returns (WebrtcGlobalLog loglines);
+ async ClearIceLog();
+ async EnterPrivateMode();
+ async ExitPrivateMode();
+
+ async CreateIceCtx(string name);
+
+ async SetIceConfig(RTCIceServer[] iceServers,
+ RTCIceTransportPolicy icePolicy);
+
+ async SetProxyConfig(WebrtcProxyConfig proxyConfig);
+
+ async EnsureProvisionalTransport(string transportId,
+ string localUfrag,
+ string localPwd,
+ int componentCount);
+
+ async SetTargetForDefaultLocalAddressLookup(string targetIp,
+ uint16_t targetPort);
+
+ async StartIceGathering(bool defaultRouteOnly,
+ bool obfuscateHostAddresses,
+ NrIceStunAddrArray stunAddrs);
+
+ async ActivateTransport(string transportId,
+ string localUfrag,
+ string localPwd,
+ int componentCount,
+ string remoteUfrag,
+ string remotePwd,
+ uint8_t[] keyDer,
+ uint8_t[] certDer,
+ int authType,
+ bool dtlsClient,
+ DtlsDigestList digests,
+ bool privacyRequested);
+
+ async RemoveTransportsExcept(StringVector transportIds);
+
+ async StartIceChecks(bool isControlling,
+ StringVector iceOptions);
+
+ async SendPacket(string transportId, MediaPacket packet);
+
+ async AddIceCandidate(string transportId,
+ string candidate,
+ string ufrag,
+ string obfuscatedAddr);
+
+ async UpdateNetworkState(bool online);
+
+ async GetIceStats(string transportId, double now) returns (UniquePtr<RTCStatsCollection> stats);
+
+child:
+ async OnCandidate(string transportId, CandidateInfo candidateInfo);
+ async OnAlpnNegotiated(string alpn);
+ async OnGatheringStateChange(int state);
+ async OnConnectionStateChange(int state);
+ async OnPacketReceived(string transportId, MediaPacket packet);
+ async OnEncryptedSending(string transportId, MediaPacket packet);
+ async OnStateChange(string transportId, int state);
+ async OnRtcpStateChange(string transportId, int state);
+
+#endif // MOZ_WEBRTC
+};
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/media/webrtc/PWebrtcGlobal.ipdl b/dom/media/webrtc/PWebrtcGlobal.ipdl
new file mode 100644
index 0000000000..e274b23636
--- /dev/null
+++ b/dom/media/webrtc/PWebrtcGlobal.ipdl
@@ -0,0 +1,41 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PContent;
+
+#ifdef MOZ_WEBRTC
+include "mozilla/media/webrtc/WebrtcGlobal.h";
+
+using struct mozilla::dom::RTCStatsReportInternal from "mozilla/dom/RTCStatsReportBinding.h";
+using WebrtcGlobalLog from "mozilla/media/webrtc/WebrtcGlobal.h";
+#endif
+
+namespace mozilla {
+namespace dom {
+
+[ManualDealloc, ChildImpl=virtual, ParentImpl=virtual]
+async protocol PWebrtcGlobal {
+ manager PContent;
+
+parent: // child -> parent messages
+ async __delete__();
+#ifdef MOZ_WEBRTC
+ async PeerConnectionCreated(nsString aPcId, bool aIsLongTermStatsDisabled);
+ async PeerConnectionDestroyed(nsString aPcId);
+ async PeerConnectionFinalStats(RTCStatsReportInternal aFinalStats);
+
+child: // parent -> child messages
+ async GetStats(nsString aPcIdFilter) returns (RTCStatsReportInternal[] stats);
+ async ClearStats();
+ async GetLog() returns (WebrtcGlobalLog logs);
+ async ClearLog();
+ async SetAecLogging(bool aEnable);
+ async SetDebugMode(int aLevel);
+#endif
+};
+
+} // end namespace net
+} // end namespace mozilla
+
+
diff --git a/dom/media/webrtc/PeerIdentity.cpp b/dom/media/webrtc/PeerIdentity.cpp
new file mode 100644
index 0000000000..d36fea5ea3
--- /dev/null
+++ b/dom/media/webrtc/PeerIdentity.cpp
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 expandtab
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PeerIdentity.h"
+
+#include "mozilla/DebugOnly.h"
+#include "nsCOMPtr.h"
+#include "nsIIDNService.h"
+#include "nsNetCID.h"
+#include "nsServiceManagerUtils.h"
+
+namespace mozilla {
+
+bool PeerIdentity::Equals(const PeerIdentity& aOther) const {
+ return Equals(aOther.mPeerIdentity);
+}
+
+bool PeerIdentity::Equals(const nsAString& aOtherString) const {
+ nsString user;
+ GetUser(mPeerIdentity, user);
+ nsString otherUser;
+ GetUser(aOtherString, otherUser);
+ if (user != otherUser) {
+ return false;
+ }
+
+ nsString host;
+ GetHost(mPeerIdentity, host);
+ nsString otherHost;
+ GetHost(aOtherString, otherHost);
+
+ nsresult rv;
+ nsCOMPtr<nsIIDNService> idnService =
+ do_GetService("@mozilla.org/network/idn-service;1", &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return host == otherHost;
+ }
+
+ nsCString normHost;
+ GetNormalizedHost(idnService, host, normHost);
+ nsCString normOtherHost;
+ GetNormalizedHost(idnService, otherHost, normOtherHost);
+ return normHost == normOtherHost;
+}
+
+/* static */
+void PeerIdentity::GetUser(const nsAString& aPeerIdentity, nsAString& aUser) {
+ int32_t at = aPeerIdentity.FindChar('@');
+ if (at >= 0) {
+ aUser = Substring(aPeerIdentity, 0, at);
+ } else {
+ aUser.Truncate();
+ }
+}
+
+/* static */
+void PeerIdentity::GetHost(const nsAString& aPeerIdentity, nsAString& aHost) {
+ int32_t at = aPeerIdentity.FindChar('@');
+ if (at >= 0) {
+ aHost = Substring(aPeerIdentity, at + 1);
+ } else {
+ aHost = aPeerIdentity;
+ }
+}
+
+/* static */
+void PeerIdentity::GetNormalizedHost(const nsCOMPtr<nsIIDNService>& aIdnService,
+ const nsAString& aHost,
+ nsACString& aNormalizedHost) {
+ const nsCString chost = NS_ConvertUTF16toUTF8(aHost);
+ DebugOnly<nsresult> rv =
+ aIdnService->ConvertUTF8toACE(chost, aNormalizedHost);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "Failed to convert UTF-8 host to ASCII");
+}
+
+} /* namespace mozilla */
diff --git a/dom/media/webrtc/PeerIdentity.h b/dom/media/webrtc/PeerIdentity.h
new file mode 100644
index 0000000000..64364d3359
--- /dev/null
+++ b/dom/media/webrtc/PeerIdentity.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 sts=2 expandtab
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef PeerIdentity_h
+#define PeerIdentity_h
+
+#include "nsISupportsImpl.h"
+#include "nsString.h"
+
+template <class T>
+class nsCOMPtr;
+class nsIIDNService;
+
+namespace mozilla {
+
+/**
+ * This class implements the identifier used in WebRTC identity. Peers are
+ * identified using a string in the form [<user>@]<domain>, for instance,
+ * "user@example.com'. The (optional) user portion is a site-controlled string
+ * containing any character other than '@'. The domain portion is a valid IDN
+ * domain name and is compared accordingly.
+ *
+ * See:
+ * http://tools.ietf.org/html/draft-ietf-rtcweb-security-arch-09#section-5.6.5.3.3.1
+ */
+class PeerIdentity final {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(PeerIdentity)
+
+ explicit PeerIdentity(const nsAString& aPeerIdentity)
+ : mPeerIdentity(aPeerIdentity) {}
+
+ bool Equals(const PeerIdentity& aOther) const;
+ bool Equals(const nsAString& aOtherString) const;
+ const nsString& ToString() const { return mPeerIdentity; }
+
+ private:
+ ~PeerIdentity() = default;
+ static void GetUser(const nsAString& aPeerIdentity, nsAString& aUser);
+ static void GetHost(const nsAString& aPeerIdentity, nsAString& aHost);
+
+ static void GetNormalizedHost(const nsCOMPtr<nsIIDNService>& aIdnService,
+ const nsAString& aHost,
+ nsACString& aNormalizedHost);
+
+ nsString mPeerIdentity;
+};
+
+inline bool operator==(const PeerIdentity& aOne, const PeerIdentity& aTwo) {
+ return aOne.Equals(aTwo);
+}
+
+inline bool operator==(const PeerIdentity& aOne, const nsAString& aString) {
+ return aOne.Equals(aString);
+}
+
+inline bool operator!=(const PeerIdentity& aOne, const PeerIdentity& aTwo) {
+ return !aOne.Equals(aTwo);
+}
+
+inline bool operator!=(const PeerIdentity& aOne, const nsAString& aString) {
+ return !aOne.Equals(aString);
+}
+
+} /* namespace mozilla */
+
+#endif /* PeerIdentity_h */
diff --git a/dom/media/webrtc/RTCCertificate.cpp b/dom/media/webrtc/RTCCertificate.cpp
new file mode 100644
index 0000000000..69b6b0f563
--- /dev/null
+++ b/dom/media/webrtc/RTCCertificate.cpp
@@ -0,0 +1,438 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/RTCCertificate.h"
+
+#include <cstdio>
+#include <cstring>
+#include <memory>
+#include <new>
+#include <utility>
+#include "ErrorList.h"
+#include "MainThreadUtils.h"
+#include "cert.h"
+#include "cryptohi.h"
+#include "js/StructuredClone.h"
+#include "js/TypeDecls.h"
+#include "js/Value.h"
+#include "keyhi.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/MacroForEach.h"
+#include "mozilla/OwningNonNull.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/CryptoBuffer.h"
+#include "mozilla/dom/CryptoKey.h"
+#include "mozilla/dom/KeyAlgorithmBinding.h"
+#include "mozilla/dom/KeyAlgorithmProxy.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/RTCCertificateBinding.h"
+#include "mozilla/dom/StructuredCloneHolder.h"
+#include "mozilla/dom/SubtleCryptoBinding.h"
+#include "mozilla/dom/UnionTypes.h"
+#include "mozilla/dom/WebCryptoCommon.h"
+#include "mozilla/dom/WebCryptoTask.h"
+#include "mozilla/fallible.h"
+#include "nsDebug.h"
+#include "nsError.h"
+#include "nsLiteralString.h"
+#include "nsStringFlags.h"
+#include "nsStringFwd.h"
+#include "nsTLiteralString.h"
+#include "pk11pub.h"
+#include "plarena.h"
+#include "secasn1.h"
+#include "secasn1t.h"
+#include "seccomon.h"
+#include "secmodt.h"
+#include "secoid.h"
+#include "secoidt.h"
+#include "transport/dtlsidentity.h"
+#include "xpcpublic.h"
+
+namespace mozilla::dom {
+
+#define RTCCERTIFICATE_SC_VERSION 0x00000001
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(RTCCertificate, mGlobal)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(RTCCertificate)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(RTCCertificate)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCCertificate)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+// Note: explicit casts necessary to avoid
+// warning C4307: '*' : integral constant overflow
+#define ONE_DAY \
+ PRTime(PR_USEC_PER_SEC) * PRTime(60) /*sec*/ \
+ * PRTime(60) /*min*/ * PRTime(24) /*hours*/
+#define EXPIRATION_DEFAULT ONE_DAY* PRTime(30)
+#define EXPIRATION_SLACK ONE_DAY
+#define EXPIRATION_MAX ONE_DAY* PRTime(365) /*year*/
+
+const size_t RTCCertificateCommonNameLength = 16;
+const size_t RTCCertificateMinRsaSize = 1024;
+
+class GenerateRTCCertificateTask : public GenerateAsymmetricKeyTask {
+ public:
+ GenerateRTCCertificateTask(nsIGlobalObject* aGlobal, JSContext* aCx,
+ const ObjectOrString& aAlgorithm,
+ const Sequence<nsString>& aKeyUsages,
+ PRTime aExpires)
+ : GenerateAsymmetricKeyTask(aGlobal, aCx, aAlgorithm, true, aKeyUsages),
+ mExpires(aExpires),
+ mAuthType(ssl_kea_null),
+ mCertificate(nullptr),
+ mSignatureAlg(SEC_OID_UNKNOWN) {
+ if (NS_FAILED(mEarlyRv)) {
+ // webrtc-pc says to throw NotSupportedError if we have passed "an
+ // algorithm that the user agent cannot or will not use to generate a
+ // certificate". This catches these cases.
+ mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ }
+ }
+
+ private:
+ PRTime mExpires;
+ SSLKEAType mAuthType;
+ UniqueCERTCertificate mCertificate;
+ SECOidTag mSignatureAlg;
+
+ static CERTName* GenerateRandomName(PK11SlotInfo* aSlot) {
+ uint8_t randomName[RTCCertificateCommonNameLength];
+ SECStatus rv =
+ PK11_GenerateRandomOnSlot(aSlot, randomName, sizeof(randomName));
+ if (rv != SECSuccess) {
+ return nullptr;
+ }
+
+ char buf[sizeof(randomName) * 2 + 4];
+ strncpy(buf, "CN=", 4);
+ for (size_t i = 0; i < sizeof(randomName); ++i) {
+ snprintf(&buf[i * 2 + 3], 3, "%.2x", randomName[i]);
+ }
+ buf[sizeof(buf) - 1] = '\0';
+
+ return CERT_AsciiToName(buf);
+ }
+
+ nsresult GenerateCertificate() {
+ UniquePK11SlotInfo slot(PK11_GetInternalSlot());
+ MOZ_ASSERT(slot.get());
+
+ UniqueCERTName subjectName(GenerateRandomName(slot.get()));
+ if (!subjectName) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ UniqueSECKEYPublicKey publicKey(mKeyPair->mPublicKey->GetPublicKey());
+ UniqueCERTSubjectPublicKeyInfo spki(
+ SECKEY_CreateSubjectPublicKeyInfo(publicKey.get()));
+ if (!spki) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ UniqueCERTCertificateRequest certreq(
+ CERT_CreateCertificateRequest(subjectName.get(), spki.get(), nullptr));
+ if (!certreq) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ PRTime now = PR_Now();
+ PRTime notBefore = now - EXPIRATION_SLACK;
+ mExpires += now;
+
+ UniqueCERTValidity validity(CERT_CreateValidity(notBefore, mExpires));
+ if (!validity) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ unsigned long serial;
+ // Note: This serial in principle could collide, but it's unlikely, and we
+ // don't expect anyone to be validating certificates anyway.
+ SECStatus rv = PK11_GenerateRandomOnSlot(
+ slot.get(), reinterpret_cast<unsigned char*>(&serial), sizeof(serial));
+ if (rv != SECSuccess) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ // NB: CERTCertificates created with CERT_CreateCertificate are not safe to
+ // use with other NSS functions like CERT_DupCertificate. The strategy
+ // here is to create a tbsCertificate ("to-be-signed certificate"), encode
+ // it, and sign it, resulting in a signed DER certificate that can be
+ // decoded into a CERTCertificate.
+ UniqueCERTCertificate tbsCertificate(CERT_CreateCertificate(
+ serial, subjectName.get(), validity.get(), certreq.get()));
+ if (!tbsCertificate) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ MOZ_ASSERT(mSignatureAlg != SEC_OID_UNKNOWN);
+ PLArenaPool* arena = tbsCertificate->arena;
+
+ rv = SECOID_SetAlgorithmID(arena, &tbsCertificate->signature, mSignatureAlg,
+ nullptr);
+ if (rv != SECSuccess) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ // Set version to X509v3.
+ *(tbsCertificate->version.data) = SEC_CERTIFICATE_VERSION_3;
+ tbsCertificate->version.len = 1;
+
+ SECItem innerDER = {siBuffer, nullptr, 0};
+ if (!SEC_ASN1EncodeItem(arena, &innerDER, tbsCertificate.get(),
+ SEC_ASN1_GET(CERT_CertificateTemplate))) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ SECItem* certDer = PORT_ArenaZNew(arena, SECItem);
+ if (!certDer) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ UniqueSECKEYPrivateKey privateKey(mKeyPair->mPrivateKey->GetPrivateKey());
+ rv = SEC_DerSignData(arena, certDer, innerDER.data, innerDER.len,
+ privateKey.get(), mSignatureAlg);
+ if (rv != SECSuccess) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+
+ mCertificate.reset(CERT_NewTempCertificate(CERT_GetDefaultCertDB(), certDer,
+ nullptr, false, true));
+ if (!mCertificate) {
+ return NS_ERROR_DOM_UNKNOWN_ERR;
+ }
+ return NS_OK;
+ }
+
+ nsresult BeforeCrypto() override {
+ if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1)) {
+ // Double check that size is OK.
+ auto sz = static_cast<size_t>(mRsaParams.keySizeInBits);
+ if (sz < RTCCertificateMinRsaSize) {
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ }
+
+ KeyAlgorithmProxy& alg = mKeyPair->mPublicKey->Algorithm();
+ if (alg.mType != KeyAlgorithmProxy::RSA ||
+ !alg.mRsa.mHash.mName.EqualsLiteral(WEBCRYPTO_ALG_SHA256)) {
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ }
+
+ mSignatureAlg = SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION;
+ mAuthType = ssl_kea_rsa;
+
+ } else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA)) {
+ // We only support good curves in WebCrypto.
+ // If that ever changes, check that a good one was chosen.
+
+ mSignatureAlg = SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE;
+ mAuthType = ssl_kea_ecdh;
+ } else {
+ return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+ }
+ return NS_OK;
+ }
+
+ nsresult DoCrypto() override {
+ nsresult rv = GenerateAsymmetricKeyTask::DoCrypto();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GenerateCertificate();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+ virtual void Resolve() override {
+ // Make copies of the private key and certificate, otherwise, when this
+ // object is deleted, the structures they reference will be deleted too.
+ UniqueSECKEYPrivateKey key = mKeyPair->mPrivateKey->GetPrivateKey();
+ CERTCertificate* cert = CERT_DupCertificate(mCertificate.get());
+ RefPtr<RTCCertificate> result =
+ new RTCCertificate(mResultPromise->GetParentObject(), key.release(),
+ cert, mAuthType, mExpires);
+ mResultPromise->MaybeResolve(result);
+ }
+};
+
+static PRTime ReadExpires(JSContext* aCx, const ObjectOrString& aOptions,
+ ErrorResult& aRv) {
+ // This conversion might fail, but we don't really care; use the default.
+ // If this isn't an object, or it doesn't coerce into the right type,
+ // then we won't get the |expires| value. Either will be caught later.
+ RTCCertificateExpiration expiration;
+ if (!aOptions.IsObject()) {
+ return EXPIRATION_DEFAULT;
+ }
+ JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*aOptions.GetAsObject()));
+ if (!expiration.Init(aCx, value)) {
+ aRv.NoteJSContextException(aCx);
+ return 0;
+ }
+
+ if (!expiration.mExpires.WasPassed()) {
+ return EXPIRATION_DEFAULT;
+ }
+ static const uint64_t max =
+ static_cast<uint64_t>(EXPIRATION_MAX / PR_USEC_PER_MSEC);
+ if (expiration.mExpires.Value() > max) {
+ return EXPIRATION_MAX;
+ }
+ return static_cast<PRTime>(expiration.mExpires.Value() * PR_USEC_PER_MSEC);
+}
+
+already_AddRefed<Promise> RTCCertificate::GenerateCertificate(
+ const GlobalObject& aGlobal, const ObjectOrString& aOptions,
+ ErrorResult& aRv, JS::Compartment* aCompartment) {
+ nsIGlobalObject* global = xpc::NativeGlobal(aGlobal.Get());
+ RefPtr<Promise> p = Promise::Create(global, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ Sequence<nsString> usages;
+ if (!usages.AppendElement(u"sign"_ns, fallible)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
+ }
+
+ PRTime expires = ReadExpires(aGlobal.Context(), aOptions, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ RefPtr<WebCryptoTask> task = new GenerateRTCCertificateTask(
+ global, aGlobal.Context(), aOptions, usages, expires);
+ task->DispatchWithPromise(p);
+ return p.forget();
+}
+
+RTCCertificate::RTCCertificate(nsIGlobalObject* aGlobal)
+ : mGlobal(aGlobal),
+ mPrivateKey(nullptr),
+ mCertificate(nullptr),
+ mAuthType(ssl_kea_null),
+ mExpires(0) {}
+
+RTCCertificate::RTCCertificate(nsIGlobalObject* aGlobal,
+ SECKEYPrivateKey* aPrivateKey,
+ CERTCertificate* aCertificate,
+ SSLKEAType aAuthType, PRTime aExpires)
+ : mGlobal(aGlobal),
+ mPrivateKey(aPrivateKey),
+ mCertificate(aCertificate),
+ mAuthType(aAuthType),
+ mExpires(aExpires) {}
+
+RefPtr<DtlsIdentity> RTCCertificate::CreateDtlsIdentity() const {
+ if (!mPrivateKey || !mCertificate) {
+ return nullptr;
+ }
+ UniqueSECKEYPrivateKey key(SECKEY_CopyPrivateKey(mPrivateKey.get()));
+ UniqueCERTCertificate cert(CERT_DupCertificate(mCertificate.get()));
+ RefPtr<DtlsIdentity> id =
+ new DtlsIdentity(std::move(key), std::move(cert), mAuthType);
+ return id;
+}
+
+JSObject* RTCCertificate::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return RTCCertificate_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+bool RTCCertificate::WritePrivateKey(JSStructuredCloneWriter* aWriter) const {
+ JsonWebKey jwk;
+ nsresult rv = CryptoKey::PrivateKeyToJwk(mPrivateKey.get(), jwk);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ nsString json;
+ if (!jwk.ToJSON(json)) {
+ return false;
+ }
+ return StructuredCloneHolder::WriteString(aWriter, json);
+}
+
+bool RTCCertificate::WriteCertificate(JSStructuredCloneWriter* aWriter) const {
+ UniqueCERTCertificateList certs(CERT_CertListFromCert(mCertificate.get()));
+ if (!certs || certs->len <= 0) {
+ return false;
+ }
+ if (!JS_WriteUint32Pair(aWriter, certs->certs[0].len, 0)) {
+ return false;
+ }
+ return JS_WriteBytes(aWriter, certs->certs[0].data, certs->certs[0].len);
+}
+
+bool RTCCertificate::WriteStructuredClone(
+ JSContext* aCx, JSStructuredCloneWriter* aWriter) const {
+ if (!mPrivateKey || !mCertificate) {
+ return false;
+ }
+
+ return JS_WriteUint32Pair(aWriter, RTCCERTIFICATE_SC_VERSION, mAuthType) &&
+ JS_WriteUint32Pair(aWriter, (mExpires >> 32) & 0xffffffff,
+ mExpires & 0xffffffff) &&
+ WritePrivateKey(aWriter) && WriteCertificate(aWriter);
+}
+
+bool RTCCertificate::ReadPrivateKey(JSStructuredCloneReader* aReader) {
+ nsString json;
+ if (!StructuredCloneHolder::ReadString(aReader, json)) {
+ return false;
+ }
+ JsonWebKey jwk;
+ if (!jwk.Init(json)) {
+ return false;
+ }
+ mPrivateKey = CryptoKey::PrivateKeyFromJwk(jwk);
+ return !!mPrivateKey;
+}
+
+bool RTCCertificate::ReadCertificate(JSStructuredCloneReader* aReader) {
+ CryptoBuffer cert;
+ if (!ReadBuffer(aReader, cert) || cert.Length() == 0) {
+ return false;
+ }
+
+ SECItem der = {siBuffer, cert.Elements(),
+ static_cast<unsigned int>(cert.Length())};
+ mCertificate.reset(CERT_NewTempCertificate(CERT_GetDefaultCertDB(), &der,
+ nullptr, true, true));
+ return !!mCertificate;
+}
+
+// static
+already_AddRefed<RTCCertificate> RTCCertificate::ReadStructuredClone(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ JSStructuredCloneReader* aReader) {
+ if (!NS_IsMainThread()) {
+ // These objects are mainthread-only.
+ return nullptr;
+ }
+ uint32_t version, authType;
+ if (!JS_ReadUint32Pair(aReader, &version, &authType) ||
+ version != RTCCERTIFICATE_SC_VERSION) {
+ return nullptr;
+ }
+ RefPtr<RTCCertificate> cert = new RTCCertificate(aGlobal);
+ cert->mAuthType = static_cast<SSLKEAType>(authType);
+
+ uint32_t high, low;
+ if (!JS_ReadUint32Pair(aReader, &high, &low)) {
+ return nullptr;
+ }
+ cert->mExpires = static_cast<PRTime>(high) << 32 | low;
+
+ if (!cert->ReadPrivateKey(aReader) || !cert->ReadCertificate(aReader)) {
+ return nullptr;
+ }
+
+ return cert.forget();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webrtc/RTCCertificate.h b/dom/media/webrtc/RTCCertificate.h
new file mode 100644
index 0000000000..a5cc6bde32
--- /dev/null
+++ b/dom/media/webrtc/RTCCertificate.h
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_RTCCertificate_h
+#define mozilla_dom_RTCCertificate_h
+
+#include <cstdint>
+#include "ScopedNSSTypes.h"
+#include "certt.h"
+#include "js/RootingAPI.h"
+#include "keythi.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/RefPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIGlobalObject.h"
+#include "nsISupports.h"
+#include "nsWrapperCache.h"
+#include "prtime.h"
+#include "sslt.h"
+
+class JSObject;
+struct JSContext;
+struct JSStructuredCloneReader;
+struct JSStructuredCloneWriter;
+
+namespace JS {
+class Compartment;
+}
+
+namespace mozilla {
+class DtlsIdentity;
+class ErrorResult;
+
+namespace dom {
+
+class GlobalObject;
+class ObjectOrString;
+class Promise;
+
+class RTCCertificate final : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(RTCCertificate)
+
+ // WebIDL method that implements RTCPeerConnection.generateCertificate.
+ static already_AddRefed<Promise> GenerateCertificate(
+ const GlobalObject& aGlobal, const ObjectOrString& aOptions,
+ ErrorResult& aRv, JS::Compartment* aCompartment = nullptr);
+
+ explicit RTCCertificate(nsIGlobalObject* aGlobal);
+ RTCCertificate(nsIGlobalObject* aGlobal, SECKEYPrivateKey* aPrivateKey,
+ CERTCertificate* aCertificate, SSLKEAType aAuthType,
+ PRTime aExpires);
+
+ nsIGlobalObject* GetParentObject() const { return mGlobal; }
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // WebIDL expires attribute. Note: JS dates are milliseconds since epoch;
+ // NSPR PRTime is in microseconds since the same epoch.
+ uint64_t Expires() const { return mExpires / PR_USEC_PER_MSEC; }
+
+ // Accessors for use by PeerConnectionImpl.
+ RefPtr<DtlsIdentity> CreateDtlsIdentity() const;
+ const UniqueCERTCertificate& Certificate() const { return mCertificate; }
+
+ // Structured clone methods
+ bool WriteStructuredClone(JSContext* aCx,
+ JSStructuredCloneWriter* aWriter) const;
+ static already_AddRefed<RTCCertificate> ReadStructuredClone(
+ JSContext* aCx, nsIGlobalObject* aGlobal,
+ JSStructuredCloneReader* aReader);
+
+ private:
+ ~RTCCertificate() = default;
+ void operator=(const RTCCertificate&) = delete;
+ RTCCertificate(const RTCCertificate&) = delete;
+
+ bool ReadCertificate(JSStructuredCloneReader* aReader);
+ bool ReadPrivateKey(JSStructuredCloneReader* aReader);
+ bool WriteCertificate(JSStructuredCloneWriter* aWriter) const;
+ bool WritePrivateKey(JSStructuredCloneWriter* aWriter) const;
+
+ RefPtr<nsIGlobalObject> mGlobal;
+ UniqueSECKEYPrivateKey mPrivateKey;
+ UniqueCERTCertificate mCertificate;
+ SSLKEAType mAuthType;
+ PRTime mExpires;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_RTCCertificate_h
diff --git a/dom/media/webrtc/RTCIdentityProviderRegistrar.cpp b/dom/media/webrtc/RTCIdentityProviderRegistrar.cpp
new file mode 100644
index 0000000000..44e4c0d775
--- /dev/null
+++ b/dom/media/webrtc/RTCIdentityProviderRegistrar.cpp
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "RTCIdentityProviderRegistrar.h"
+#include "mozilla/Attributes.h"
+#include "nsCycleCollectionParticipant.h"
+
+namespace mozilla::dom {
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCIdentityProviderRegistrar)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(RTCIdentityProviderRegistrar)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(RTCIdentityProviderRegistrar)
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(RTCIdentityProviderRegistrar, mGlobal,
+ mGenerateAssertionCallback,
+ mValidateAssertionCallback)
+
+RTCIdentityProviderRegistrar::RTCIdentityProviderRegistrar(
+ nsIGlobalObject* aGlobal)
+ : mGlobal(aGlobal),
+ mGenerateAssertionCallback(nullptr),
+ mValidateAssertionCallback(nullptr) {}
+
+RTCIdentityProviderRegistrar::~RTCIdentityProviderRegistrar() = default;
+
+nsIGlobalObject* RTCIdentityProviderRegistrar::GetParentObject() const {
+ return mGlobal;
+}
+
+JSObject* RTCIdentityProviderRegistrar::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return RTCIdentityProviderRegistrar_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void RTCIdentityProviderRegistrar::Register(const RTCIdentityProvider& aIdp) {
+ mGenerateAssertionCallback = aIdp.mGenerateAssertion;
+ mValidateAssertionCallback = aIdp.mValidateAssertion;
+}
+
+bool RTCIdentityProviderRegistrar::HasIdp() const {
+ return mGenerateAssertionCallback && mValidateAssertionCallback;
+}
+
+already_AddRefed<Promise> RTCIdentityProviderRegistrar::GenerateAssertion(
+ const nsAString& aContents, const nsAString& aOrigin,
+ const RTCIdentityProviderOptions& aOptions, ErrorResult& aRv) {
+ if (!mGenerateAssertionCallback) {
+ aRv.Throw(NS_ERROR_NOT_INITIALIZED);
+ return nullptr;
+ }
+ RefPtr<GenerateAssertionCallback> callback(mGenerateAssertionCallback);
+ return callback->Call(aContents, aOrigin, aOptions, aRv);
+}
+already_AddRefed<Promise> RTCIdentityProviderRegistrar::ValidateAssertion(
+ const nsAString& aAssertion, const nsAString& aOrigin, ErrorResult& aRv) {
+ if (!mValidateAssertionCallback) {
+ aRv.Throw(NS_ERROR_NOT_INITIALIZED);
+ return nullptr;
+ }
+ RefPtr<ValidateAssertionCallback> callback(mValidateAssertionCallback);
+ return callback->Call(aAssertion, aOrigin, aRv);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webrtc/RTCIdentityProviderRegistrar.h b/dom/media/webrtc/RTCIdentityProviderRegistrar.h
new file mode 100644
index 0000000000..0510471f17
--- /dev/null
+++ b/dom/media/webrtc/RTCIdentityProviderRegistrar.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef RTCIDENTITYPROVIDER_H_
+#define RTCIDENTITYPROVIDER_H_
+
+#include "mozilla/RefPtr.h"
+#include "nsCOMPtr.h"
+#include "nsISupportsImpl.h"
+#include "nsIGlobalObject.h"
+#include "nsWrapperCache.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/RTCIdentityProviderBinding.h"
+
+namespace mozilla::dom {
+
+struct RTCIdentityProvider;
+
+class RTCIdentityProviderRegistrar final : public nsISupports,
+ public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(RTCIdentityProviderRegistrar)
+
+ explicit RTCIdentityProviderRegistrar(nsIGlobalObject* aGlobal);
+
+ // As required
+ nsIGlobalObject* GetParentObject() const;
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // setter and checker
+ void Register(const RTCIdentityProvider& aIdp);
+ bool HasIdp() const;
+
+ MOZ_CAN_RUN_SCRIPT
+ already_AddRefed<Promise> GenerateAssertion(
+ const nsAString& aContents, const nsAString& aOrigin,
+ const RTCIdentityProviderOptions& aOptions, ErrorResult& aRv);
+ MOZ_CAN_RUN_SCRIPT
+ already_AddRefed<Promise> ValidateAssertion(const nsAString& assertion,
+ const nsAString& origin,
+ ErrorResult& aRv);
+
+ private:
+ ~RTCIdentityProviderRegistrar();
+
+ nsCOMPtr<nsIGlobalObject> mGlobal;
+ RefPtr<GenerateAssertionCallback> mGenerateAssertionCallback;
+ RefPtr<ValidateAssertionCallback> mValidateAssertionCallback;
+};
+
+} // namespace mozilla::dom
+
+#endif /* RTCIDENTITYPROVIDER_H_ */
diff --git a/dom/media/webrtc/SineWaveGenerator.h b/dom/media/webrtc/SineWaveGenerator.h
new file mode 100644
index 0000000000..73bab53f19
--- /dev/null
+++ b/dom/media/webrtc/SineWaveGenerator.h
@@ -0,0 +1,58 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SINEWAVEGENERATOR_H_
+#define SINEWAVEGENERATOR_H_
+
+#include "MediaSegment.h"
+#include "prtime.h"
+
+namespace mozilla {
+
+// generate 1k sine wave per second
+template <typename Sample>
+class SineWaveGenerator {
+ static_assert(std::is_same<Sample, int16_t>::value ||
+ std::is_same<Sample, float>::value);
+
+ public:
+ static const int bytesPerSample = sizeof(Sample);
+ static const int millisecondsPerSecond = PR_MSEC_PER_SEC;
+ static constexpr float twopi = 2 * M_PI;
+
+ /* If more than 1 channel, generated samples are interleaved. */
+ SineWaveGenerator(uint32_t aSampleRate, uint32_t aFrequency)
+ : mPhase(0.), mPhaseIncrement(twopi * aFrequency / aSampleRate) {}
+
+ // NOTE: only safely called from a single thread (MTG callback)
+ void generate(Sample* aBuffer, TrackTicks aFrameCount,
+ uint32_t aChannelCount = 1) {
+ while (aFrameCount--) {
+ Sample value = sin(mPhase) * Amplitude();
+ for (uint32_t channel = 0; channel < aChannelCount; channel++) {
+ *aBuffer++ = value;
+ }
+ mPhase += mPhaseIncrement;
+ if (mPhase > twopi) {
+ mPhase -= twopi;
+ }
+ }
+ }
+
+ static float Amplitude() {
+ // Set volume to -20db.
+ if (std::is_same<Sample, int16_t>::value) {
+ return 3276.8; // 32768.0 * 10^(-20/20) = 3276.8
+ }
+ return 0.1f; // 1.0 * 10^(-20/20) = 0.1
+ }
+
+ private:
+ double mPhase;
+ const double mPhaseIncrement;
+};
+
+} // namespace mozilla
+
+#endif /* SINEWAVEGENERATOR_H_ */
diff --git a/dom/media/webrtc/WebrtcGlobal.h b/dom/media/webrtc/WebrtcGlobal.h
new file mode 100644
index 0000000000..dac51dcf6b
--- /dev/null
+++ b/dom/media/webrtc/WebrtcGlobal.h
@@ -0,0 +1,506 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _WEBRTC_GLOBAL_H_
+#define _WEBRTC_GLOBAL_H_
+
+#include "WebrtcIPCTraits.h"
+#include "ipc/EnumSerializer.h"
+#include "ipc/IPCMessageUtilsSpecializations.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/RTCDataChannelBinding.h"
+#include "mozilla/dom/RTCStatsReportBinding.h"
+#include "mozilla/UniquePtr.h"
+
+typedef mozilla::dom::RTCStatsReportInternal StatsReport;
+typedef nsTArray<mozilla::UniquePtr<StatsReport>> RTCReports;
+typedef mozilla::dom::Sequence<nsString> WebrtcGlobalLog;
+
+namespace mozilla {
+namespace dom {
+// Calls aFunction with all public members of aStats.
+// Typical usage would have aFunction take a parameter pack.
+// To avoid inconsistencies, this should be the only explicit list of the
+// public RTCStatscollection members in C++.
+template <typename Collection, typename Function>
+static auto ForAllPublicRTCStatsCollectionMembers(Collection& aStats,
+ Function aFunction) {
+ static_assert(std::is_same_v<typename std::remove_const<Collection>::type,
+ RTCStatsCollection>,
+ "aStats must be a const or non-const RTCStatsCollection");
+ return aFunction(
+ aStats.mInboundRtpStreamStats, aStats.mOutboundRtpStreamStats,
+ aStats.mRemoteInboundRtpStreamStats, aStats.mRemoteOutboundRtpStreamStats,
+ aStats.mMediaSourceStats, aStats.mPeerConnectionStats,
+ aStats.mRtpContributingSourceStats, aStats.mIceCandidatePairStats,
+ aStats.mIceCandidateStats, aStats.mTrickledIceCandidateStats,
+ aStats.mDataChannelStats, aStats.mCodecStats);
+}
+
+// Calls aFunction with all members of aStats, including internal ones.
+// Typical usage would have aFunction take a parameter pack.
+// To avoid inconsistencies, this should be the only explicit list of the
+// internal RTCStatscollection members in C++.
+template <typename Collection, typename Function>
+static auto ForAllRTCStatsCollectionMembers(Collection& aStats,
+ Function aFunction) {
+ static_assert(std::is_same_v<typename std::remove_const<Collection>::type,
+ RTCStatsCollection>,
+ "aStats must be a const or non-const RTCStatsCollection");
+ return ForAllPublicRTCStatsCollectionMembers(aStats, [&](auto&... aMember) {
+ return aFunction(aMember..., aStats.mRawLocalCandidates,
+ aStats.mRawRemoteCandidates, aStats.mVideoFrameHistories,
+ aStats.mBandwidthEstimations);
+ });
+}
+} // namespace dom
+} // namespace mozilla
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mozilla::dom::RTCStatsType>
+ : public ContiguousEnumSerializer<mozilla::dom::RTCStatsType,
+ mozilla::dom::RTCStatsType::Codec,
+ mozilla::dom::RTCStatsType::EndGuard_> {};
+
+template <>
+struct ParamTraits<mozilla::dom::RTCStatsIceCandidatePairState>
+ : public ContiguousEnumSerializer<
+ mozilla::dom::RTCStatsIceCandidatePairState,
+ mozilla::dom::RTCStatsIceCandidatePairState::Frozen,
+ mozilla::dom::RTCStatsIceCandidatePairState::EndGuard_> {};
+
+template <>
+struct ParamTraits<mozilla::dom::RTCIceCandidateType>
+ : public ContiguousEnumSerializer<
+ mozilla::dom::RTCIceCandidateType,
+ mozilla::dom::RTCIceCandidateType::Host,
+ mozilla::dom::RTCIceCandidateType::EndGuard_> {};
+
+template <>
+struct ParamTraits<mozilla::dom::RTCBundlePolicy>
+ : public ContiguousEnumSerializer<
+ mozilla::dom::RTCBundlePolicy,
+ mozilla::dom::RTCBundlePolicy::Balanced,
+ mozilla::dom::RTCBundlePolicy::EndGuard_> {};
+
+DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::dom::RTCIceServerInternal, mUrls,
+ mCredentialProvided, mUserNameProvided);
+
+DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::dom::RTCConfigurationInternal,
+ mBundlePolicy, mCertificatesProvided,
+ mIceServers, mIceTransportPolicy,
+ mPeerIdentityProvided, mSdpSemantics);
+
+DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::dom::RTCSdpParsingErrorInternal,
+ mLineNumber, mError);
+
+DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::dom::RTCSdpHistoryEntryInternal,
+ mTimestamp, mIsLocal, mSdp, mErrors);
+
+template <>
+struct ParamTraits<mozilla::dom::RTCStatsCollection> {
+ static void Write(MessageWriter* aWriter,
+ const mozilla::dom::RTCStatsCollection& aParam) {
+ mozilla::dom::ForAllRTCStatsCollectionMembers(
+ aParam,
+ [&](const auto&... aMember) { WriteParams(aWriter, aMember...); });
+ }
+
+ static bool Read(MessageReader* aReader,
+ mozilla::dom::RTCStatsCollection* aResult) {
+ return mozilla::dom::ForAllRTCStatsCollectionMembers(
+ *aResult,
+ [&](auto&... aMember) { return ReadParams(aReader, aMember...); });
+ }
+};
+
+DEFINE_IPC_SERIALIZER_WITH_SUPER_CLASS_AND_FIELDS(
+ mozilla::dom::RTCStatsReportInternal, mozilla::dom::RTCStatsCollection,
+ mClosed, mSdpHistory, mPcid, mBrowserId, mTimestamp, mCallDurationMs,
+ mIceRestarts, mIceRollbacks, mOfferer, mConfiguration);
+
+typedef mozilla::dom::RTCStats RTCStats;
+
+static void WriteRTCStats(MessageWriter* aWriter, const RTCStats& aParam) {
+ // RTCStats base class
+ WriteParam(aWriter, aParam.mId);
+ WriteParam(aWriter, aParam.mTimestamp);
+ WriteParam(aWriter, aParam.mType);
+}
+
+static bool ReadRTCStats(MessageReader* aReader, RTCStats* aResult) {
+ // RTCStats base class
+ if (!ReadParam(aReader, &(aResult->mId)) ||
+ !ReadParam(aReader, &(aResult->mTimestamp)) ||
+ !ReadParam(aReader, &(aResult->mType))) {
+ return false;
+ }
+
+ return true;
+}
+
+template <>
+struct ParamTraits<mozilla::dom::RTCIceCandidatePairStats> {
+ typedef mozilla::dom::RTCIceCandidatePairStats paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.mTransportId);
+ WriteParam(aWriter, aParam.mLocalCandidateId);
+ WriteParam(aWriter, aParam.mPriority);
+ WriteParam(aWriter, aParam.mNominated);
+ WriteParam(aWriter, aParam.mWritable);
+ WriteParam(aWriter, aParam.mReadable);
+ WriteParam(aWriter, aParam.mRemoteCandidateId);
+ WriteParam(aWriter, aParam.mSelected);
+ WriteParam(aWriter, aParam.mComponentId);
+ WriteParam(aWriter, aParam.mState);
+ WriteParam(aWriter, aParam.mBytesSent);
+ WriteParam(aWriter, aParam.mBytesReceived);
+ WriteParam(aWriter, aParam.mLastPacketSentTimestamp);
+ WriteParam(aWriter, aParam.mLastPacketReceivedTimestamp);
+ WriteRTCStats(aWriter, aParam);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ if (!ReadParam(aReader, &(aResult->mTransportId)) ||
+ !ReadParam(aReader, &(aResult->mLocalCandidateId)) ||
+ !ReadParam(aReader, &(aResult->mPriority)) ||
+ !ReadParam(aReader, &(aResult->mNominated)) ||
+ !ReadParam(aReader, &(aResult->mWritable)) ||
+ !ReadParam(aReader, &(aResult->mReadable)) ||
+ !ReadParam(aReader, &(aResult->mRemoteCandidateId)) ||
+ !ReadParam(aReader, &(aResult->mSelected)) ||
+ !ReadParam(aReader, &(aResult->mComponentId)) ||
+ !ReadParam(aReader, &(aResult->mState)) ||
+ !ReadParam(aReader, &(aResult->mBytesSent)) ||
+ !ReadParam(aReader, &(aResult->mBytesReceived)) ||
+ !ReadParam(aReader, &(aResult->mLastPacketSentTimestamp)) ||
+ !ReadParam(aReader, &(aResult->mLastPacketReceivedTimestamp)) ||
+ !ReadRTCStats(aReader, aResult)) {
+ return false;
+ }
+
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::dom::RTCIceCandidateStats> {
+ typedef mozilla::dom::RTCIceCandidateStats paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.mCandidateType);
+ WriteParam(aWriter, aParam.mPriority);
+ WriteParam(aWriter, aParam.mTransportId);
+ WriteParam(aWriter, aParam.mAddress);
+ WriteParam(aWriter, aParam.mRelayProtocol);
+ WriteParam(aWriter, aParam.mPort);
+ WriteParam(aWriter, aParam.mProtocol);
+ WriteParam(aWriter, aParam.mProxied);
+ WriteRTCStats(aWriter, aParam);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ if (!ReadParam(aReader, &(aResult->mCandidateType)) ||
+ !ReadParam(aReader, &(aResult->mPriority)) ||
+ !ReadParam(aReader, &(aResult->mTransportId)) ||
+ !ReadParam(aReader, &(aResult->mAddress)) ||
+ !ReadParam(aReader, &(aResult->mRelayProtocol)) ||
+ !ReadParam(aReader, &(aResult->mPort)) ||
+ !ReadParam(aReader, &(aResult->mProtocol)) ||
+ !ReadParam(aReader, &(aResult->mProxied)) ||
+ !ReadRTCStats(aReader, aResult)) {
+ return false;
+ }
+
+ return true;
+ }
+};
+
+static void WriteRTCRtpStreamStats(
+ MessageWriter* aWriter, const mozilla::dom::RTCRtpStreamStats& aParam) {
+ WriteParam(aWriter, aParam.mSsrc);
+ WriteParam(aWriter, aParam.mMediaType);
+ WriteParam(aWriter, aParam.mKind);
+ WriteParam(aWriter, aParam.mTransportId);
+ WriteParam(aWriter, aParam.mCodecId);
+ WriteRTCStats(aWriter, aParam);
+}
+
+static bool ReadRTCRtpStreamStats(MessageReader* aReader,
+ mozilla::dom::RTCRtpStreamStats* aResult) {
+ return ReadParam(aReader, &(aResult->mSsrc)) &&
+ ReadParam(aReader, &(aResult->mMediaType)) &&
+ ReadParam(aReader, &(aResult->mKind)) &&
+ ReadParam(aReader, &(aResult->mTransportId)) &&
+ ReadParam(aReader, &(aResult->mCodecId)) &&
+ ReadRTCStats(aReader, aResult);
+}
+
+static void WriteRTCReceivedRtpStreamStats(
+ MessageWriter* aWriter,
+ const mozilla::dom::RTCReceivedRtpStreamStats& aParam) {
+ WriteParam(aWriter, aParam.mPacketsReceived);
+ WriteParam(aWriter, aParam.mPacketsLost);
+ WriteParam(aWriter, aParam.mJitter);
+ WriteParam(aWriter, aParam.mDiscardedPackets);
+ WriteParam(aWriter, aParam.mPacketsDiscarded);
+ WriteRTCRtpStreamStats(aWriter, aParam);
+}
+
+static bool ReadRTCReceivedRtpStreamStats(
+ MessageReader* aReader, mozilla::dom::RTCReceivedRtpStreamStats* aResult) {
+ return ReadParam(aReader, &(aResult->mPacketsReceived)) &&
+ ReadParam(aReader, &(aResult->mPacketsLost)) &&
+ ReadParam(aReader, &(aResult->mJitter)) &&
+ ReadParam(aReader, &(aResult->mDiscardedPackets)) &&
+ ReadParam(aReader, &(aResult->mPacketsDiscarded)) &&
+ ReadRTCRtpStreamStats(aReader, aResult);
+}
+
+template <>
+struct ParamTraits<mozilla::dom::RTCInboundRtpStreamStats> {
+ typedef mozilla::dom::RTCInboundRtpStreamStats paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.mTrackIdentifier);
+ WriteParam(aWriter, aParam.mRemoteId);
+ WriteParam(aWriter, aParam.mFramesDecoded);
+ WriteParam(aWriter, aParam.mFramesDropped);
+ WriteParam(aWriter, aParam.mFrameWidth);
+ WriteParam(aWriter, aParam.mFrameHeight);
+ WriteParam(aWriter, aParam.mFramesPerSecond);
+ WriteParam(aWriter, aParam.mQpSum);
+ WriteParam(aWriter, aParam.mTotalDecodeTime);
+ WriteParam(aWriter, aParam.mTotalInterFrameDelay);
+ WriteParam(aWriter, aParam.mTotalSquaredInterFrameDelay);
+ WriteParam(aWriter, aParam.mLastPacketReceivedTimestamp);
+ WriteParam(aWriter, aParam.mHeaderBytesReceived);
+ WriteParam(aWriter, aParam.mFecPacketsReceived);
+ WriteParam(aWriter, aParam.mFecPacketsDiscarded);
+ WriteParam(aWriter, aParam.mBytesReceived);
+ WriteParam(aWriter, aParam.mNackCount);
+ WriteParam(aWriter, aParam.mFirCount);
+ WriteParam(aWriter, aParam.mPliCount);
+ WriteParam(aWriter, aParam.mTotalProcessingDelay);
+ // Always missing from libwebrtc stats
+ // WriteParam(aWriter, aParam.mEstimatedPlayoutTimestamp);
+ WriteParam(aWriter, aParam.mFramesReceived);
+ WriteParam(aWriter, aParam.mJitterBufferDelay);
+ WriteParam(aWriter, aParam.mJitterBufferEmittedCount);
+ WriteParam(aWriter, aParam.mTotalSamplesReceived);
+ WriteParam(aWriter, aParam.mConcealedSamples);
+ WriteParam(aWriter, aParam.mSilentConcealedSamples);
+ WriteParam(aWriter, aParam.mConcealmentEvents);
+ WriteParam(aWriter, aParam.mInsertedSamplesForDeceleration);
+ WriteParam(aWriter, aParam.mRemovedSamplesForAcceleration);
+ WriteParam(aWriter, aParam.mAudioLevel);
+ WriteParam(aWriter, aParam.mTotalAudioEnergy);
+ WriteParam(aWriter, aParam.mTotalSamplesDuration);
+ WriteRTCReceivedRtpStreamStats(aWriter, aParam);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return ReadParam(aReader, &(aResult->mTrackIdentifier)) &&
+ ReadParam(aReader, &(aResult->mRemoteId)) &&
+ ReadParam(aReader, &(aResult->mFramesDecoded)) &&
+ ReadParam(aReader, &(aResult->mFramesDropped)) &&
+ ReadParam(aReader, &(aResult->mFrameWidth)) &&
+ ReadParam(aReader, &(aResult->mFrameHeight)) &&
+ ReadParam(aReader, &(aResult->mFramesPerSecond)) &&
+ ReadParam(aReader, &(aResult->mQpSum)) &&
+ ReadParam(aReader, &(aResult->mTotalDecodeTime)) &&
+ ReadParam(aReader, &(aResult->mTotalInterFrameDelay)) &&
+ ReadParam(aReader, &(aResult->mTotalSquaredInterFrameDelay)) &&
+ ReadParam(aReader, &(aResult->mLastPacketReceivedTimestamp)) &&
+ ReadParam(aReader, &(aResult->mHeaderBytesReceived)) &&
+ ReadParam(aReader, &(aResult->mFecPacketsReceived)) &&
+ ReadParam(aReader, &(aResult->mFecPacketsDiscarded)) &&
+ ReadParam(aReader, &(aResult->mBytesReceived)) &&
+ ReadParam(aReader, &(aResult->mNackCount)) &&
+ ReadParam(aReader, &(aResult->mFirCount)) &&
+ ReadParam(aReader, &(aResult->mPliCount)) &&
+ ReadParam(aReader, &(aResult->mTotalProcessingDelay)) &&
+ // Always missing from libwebrtc
+ // ReadParam(aReader, &(aResult->mEstimatedPlayoutTimestamp)) &&
+ ReadParam(aReader, &(aResult->mFramesReceived)) &&
+ ReadParam(aReader, &(aResult->mJitterBufferDelay)) &&
+ ReadParam(aReader, &(aResult->mJitterBufferEmittedCount)) &&
+ ReadParam(aReader, &(aResult->mTotalSamplesReceived)) &&
+ ReadParam(aReader, &(aResult->mConcealedSamples)) &&
+ ReadParam(aReader, &(aResult->mSilentConcealedSamples)) &&
+ ReadParam(aReader, &(aResult->mConcealmentEvents)) &&
+ ReadParam(aReader, &(aResult->mInsertedSamplesForDeceleration)) &&
+ ReadParam(aReader, &(aResult->mRemovedSamplesForAcceleration)) &&
+ ReadParam(aReader, &(aResult->mAudioLevel)) &&
+ ReadParam(aReader, &(aResult->mTotalAudioEnergy)) &&
+ ReadParam(aReader, &(aResult->mTotalSamplesDuration)) &&
+ ReadRTCReceivedRtpStreamStats(aReader, aResult);
+ }
+};
+
+static void WriteRTCSentRtpStreamStats(
+ MessageWriter* aWriter, const mozilla::dom::RTCSentRtpStreamStats& aParam) {
+ WriteParam(aWriter, aParam.mPacketsSent);
+ WriteParam(aWriter, aParam.mBytesSent);
+ WriteRTCRtpStreamStats(aWriter, aParam);
+}
+
+static bool ReadRTCSentRtpStreamStats(
+ MessageReader* aReader, mozilla::dom::RTCSentRtpStreamStats* aResult) {
+ return ReadParam(aReader, &(aResult->mPacketsSent)) &&
+ ReadParam(aReader, &(aResult->mBytesSent)) &&
+ ReadRTCRtpStreamStats(aReader, aResult);
+}
+
+template <>
+struct ParamTraits<mozilla::dom::RTCOutboundRtpStreamStats> {
+ typedef mozilla::dom::RTCOutboundRtpStreamStats paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.mRemoteId);
+ WriteParam(aWriter, aParam.mFramesEncoded);
+ WriteParam(aWriter, aParam.mQpSum);
+ WriteParam(aWriter, aParam.mNackCount);
+ WriteParam(aWriter, aParam.mFirCount);
+ WriteParam(aWriter, aParam.mPliCount);
+ WriteParam(aWriter, aParam.mHeaderBytesSent);
+ WriteParam(aWriter, aParam.mRetransmittedPacketsSent);
+ WriteParam(aWriter, aParam.mRetransmittedBytesSent);
+ WriteParam(aWriter, aParam.mTotalEncodedBytesTarget);
+ WriteParam(aWriter, aParam.mFrameWidth);
+ WriteParam(aWriter, aParam.mFrameHeight);
+ WriteParam(aWriter, aParam.mFramesSent);
+ WriteParam(aWriter, aParam.mHugeFramesSent);
+ WriteParam(aWriter, aParam.mTotalEncodeTime);
+ WriteRTCSentRtpStreamStats(aWriter, aParam);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return ReadParam(aReader, &(aResult->mRemoteId)) &&
+ ReadParam(aReader, &(aResult->mFramesEncoded)) &&
+ ReadParam(aReader, &(aResult->mQpSum)) &&
+ ReadParam(aReader, &(aResult->mNackCount)) &&
+ ReadParam(aReader, &(aResult->mFirCount)) &&
+ ReadParam(aReader, &(aResult->mPliCount)) &&
+ ReadParam(aReader, &(aResult->mHeaderBytesSent)) &&
+ ReadParam(aReader, &(aResult->mRetransmittedPacketsSent)) &&
+ ReadParam(aReader, &(aResult->mRetransmittedBytesSent)) &&
+ ReadParam(aReader, &(aResult->mTotalEncodedBytesTarget)) &&
+ ReadParam(aReader, &(aResult->mFrameWidth)) &&
+ ReadParam(aReader, &(aResult->mFrameHeight)) &&
+ ReadParam(aReader, &(aResult->mFramesSent)) &&
+ ReadParam(aReader, &(aResult->mHugeFramesSent)) &&
+ ReadParam(aReader, &(aResult->mTotalEncodeTime)) &&
+ ReadRTCSentRtpStreamStats(aReader, aResult);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::dom::RTCRemoteInboundRtpStreamStats> {
+ typedef mozilla::dom::RTCRemoteInboundRtpStreamStats paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.mLocalId);
+ WriteParam(aWriter, aParam.mRoundTripTime);
+ WriteParam(aWriter, aParam.mTotalRoundTripTime);
+ WriteParam(aWriter, aParam.mFractionLost);
+ WriteParam(aWriter, aParam.mRoundTripTimeMeasurements);
+ WriteRTCReceivedRtpStreamStats(aWriter, aParam);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return ReadParam(aReader, &(aResult->mLocalId)) &&
+ ReadParam(aReader, &(aResult->mRoundTripTime)) &&
+ ReadParam(aReader, &(aResult->mTotalRoundTripTime)) &&
+ ReadParam(aReader, &(aResult->mFractionLost)) &&
+ ReadParam(aReader, &(aResult->mRoundTripTimeMeasurements)) &&
+ ReadRTCReceivedRtpStreamStats(aReader, aResult);
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::dom::RTCRemoteOutboundRtpStreamStats> {
+ typedef mozilla::dom::RTCRemoteOutboundRtpStreamStats paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.mLocalId);
+ WriteParam(aWriter, aParam.mRemoteTimestamp);
+ WriteRTCSentRtpStreamStats(aWriter, aParam);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ return ReadParam(aReader, &(aResult->mLocalId)) &&
+ ReadParam(aReader, &(aResult->mRemoteTimestamp)) &&
+ ReadRTCSentRtpStreamStats(aReader, aResult);
+ }
+};
+
+DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::dom::RTCMediaSourceStats, mId,
+ mTimestamp, mType, mTrackIdentifier, mKind);
+
+template <>
+struct ParamTraits<mozilla::dom::RTCRTPContributingSourceStats> {
+ typedef mozilla::dom::RTCRTPContributingSourceStats paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.mContributorSsrc);
+ WriteParam(aWriter, aParam.mInboundRtpStreamId);
+ WriteRTCStats(aWriter, aParam);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ if (!ReadParam(aReader, &(aResult->mContributorSsrc)) ||
+ !ReadParam(aReader, &(aResult->mInboundRtpStreamId)) ||
+ !ReadRTCStats(aReader, aResult)) {
+ return false;
+ }
+ return true;
+ }
+};
+
+DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::dom::RTCPeerConnectionStats, mId,
+ mTimestamp, mType, mDataChannelsOpened,
+ mDataChannelsClosed);
+
+DEFINE_IPC_SERIALIZER_WITH_FIELDS(
+ mozilla::dom::RTCVideoFrameHistoryEntryInternal, mWidth, mHeight,
+ mRotationAngle, mFirstFrameTimestamp, mLastFrameTimestamp,
+ mConsecutiveFrames, mLocalSsrc, mRemoteSsrc);
+
+DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::dom::RTCVideoFrameHistoryInternal,
+ mTrackIdentifier, mEntries);
+
+DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::dom::RTCBandwidthEstimationInternal,
+ mTrackIdentifier, mSendBandwidthBps,
+ mMaxPaddingBps, mReceiveBandwidthBps,
+ mPacerDelayMs, mRttMs);
+
+DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::dom::RTCDataChannelStats, mId,
+ mTimestamp, mType, mLabel, mProtocol,
+ mDataChannelIdentifier, mState, mMessagesSent,
+ mBytesSent, mMessagesReceived, mBytesReceived)
+
+template <>
+struct ParamTraits<mozilla::dom::RTCDataChannelState>
+ : public ContiguousEnumSerializer<
+ mozilla::dom::RTCDataChannelState,
+ mozilla::dom::RTCDataChannelState::Connecting,
+ mozilla::dom::RTCDataChannelState::EndGuard_> {};
+
+DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::dom::RTCCodecStats, mTimestamp,
+ mType, mId, mPayloadType, mCodecType,
+ mTransportId, mMimeType, mClockRate,
+ mChannels, mSdpFmtpLine)
+
+template <>
+struct ParamTraits<mozilla::dom::RTCCodecType>
+ : public ContiguousEnumSerializer<mozilla::dom::RTCCodecType,
+ mozilla::dom::RTCCodecType::Encode,
+ mozilla::dom::RTCCodecType::EndGuard_> {};
+} // namespace IPC
+
+#endif // _WEBRTC_GLOBAL_H_
diff --git a/dom/media/webrtc/WebrtcIPCTraits.h b/dom/media/webrtc/WebrtcIPCTraits.h
new file mode 100644
index 0000000000..b076745608
--- /dev/null
+++ b/dom/media/webrtc/WebrtcIPCTraits.h
@@ -0,0 +1,89 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _WEBRTC_IPC_TRAITS_H_
+#define _WEBRTC_IPC_TRAITS_H_
+
+#include "ipc/EnumSerializer.h"
+#include "ipc/IPCMessageUtils.h"
+#include "ipc/IPCMessageUtilsSpecializations.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/RTCConfigurationBinding.h"
+#include "mozilla/media/webrtc/WebrtcGlobal.h"
+#include "mozilla/dom/CandidateInfo.h"
+#include "mozilla/MacroForEach.h"
+#include "transport/dtlsidentity.h"
+#include <vector>
+
+namespace mozilla {
+typedef std::vector<std::string> StringVector;
+}
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mozilla::dom::OwningStringOrStringSequence> {
+ typedef mozilla::dom::OwningStringOrStringSequence paramType;
+
+ // Ugh. OwningStringOrStringSequence already has this enum, but it is
+ // private generated code. So we have to re-create it.
+ enum Type { kUninitialized, kString, kStringSequence };
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ if (aParam.IsString()) {
+ aWriter->WriteInt16(kString);
+ WriteParam(aWriter, aParam.GetAsString());
+ } else if (aParam.IsStringSequence()) {
+ aWriter->WriteInt16(kStringSequence);
+ WriteParam(aWriter, aParam.GetAsStringSequence());
+ } else {
+ aWriter->WriteInt16(kUninitialized);
+ }
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ int16_t type;
+ if (!aReader->ReadInt16(&type)) {
+ return false;
+ }
+
+ switch (type) {
+ case kUninitialized:
+ aResult->Uninit();
+ return true;
+ case kString:
+ return ReadParam(aReader, &aResult->SetAsString());
+ case kStringSequence:
+ return ReadParam(aReader, &aResult->SetAsStringSequence());
+ }
+
+ return false;
+ }
+};
+
+template <typename T>
+struct WebidlEnumSerializer
+ : public ContiguousEnumSerializer<T, T(0), T::EndGuard_> {};
+
+template <>
+struct ParamTraits<mozilla::dom::RTCIceCredentialType>
+ : public WebidlEnumSerializer<mozilla::dom::RTCIceCredentialType> {};
+
+template <>
+struct ParamTraits<mozilla::dom::RTCIceTransportPolicy>
+ : public WebidlEnumSerializer<mozilla::dom::RTCIceTransportPolicy> {};
+
+DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::dom::RTCIceServer, mCredential,
+ mCredentialType, mUrl, mUrls, mUsername)
+
+DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::CandidateInfo, mCandidate, mUfrag,
+ mDefaultHostRtp, mDefaultPortRtp,
+ mDefaultHostRtcp, mDefaultPortRtcp,
+ mMDNSAddress, mActualAddress)
+
+DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::DtlsDigest, algorithm_, value_)
+
+} // namespace IPC
+
+#endif // _WEBRTC_IPC_TRAITS_H_
diff --git a/dom/media/webrtc/common/CandidateInfo.h b/dom/media/webrtc/common/CandidateInfo.h
new file mode 100644
index 0000000000..eb5b2ea299
--- /dev/null
+++ b/dom/media/webrtc/common/CandidateInfo.h
@@ -0,0 +1,27 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _CANDIDATE_INFO_H__
+#define _CANDIDATE_INFO_H__
+
+#include <string>
+#include <cstdint>
+
+namespace mozilla {
+
+// This is used both by IPDL code, and by signaling code.
+struct CandidateInfo {
+ std::string mCandidate;
+ std::string mMDNSAddress;
+ std::string mActualAddress;
+ std::string mUfrag;
+ std::string mDefaultHostRtp;
+ uint16_t mDefaultPortRtp = 0;
+ std::string mDefaultHostRtcp;
+ uint16_t mDefaultPortRtcp = 0;
+};
+
+} // namespace mozilla
+
+#endif //_CANDIDATE_INFO_H__
diff --git a/dom/media/webrtc/common/CommonTypes.h b/dom/media/webrtc/common/CommonTypes.h
new file mode 100644
index 0000000000..e9c9ffdefc
--- /dev/null
+++ b/dom/media/webrtc/common/CommonTypes.h
@@ -0,0 +1,52 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#pragma once
+
+#include <string>
+
+namespace csf {
+
+namespace ProviderStateEnum {
+enum ProviderState {
+ Ready,
+ Registering,
+ AwaitingIpAddress,
+ FetchingDeviceConfig,
+ Idle,
+ RecoveryPending,
+ Connected
+};
+const std::string toString(ProviderState);
+} // namespace ProviderStateEnum
+namespace LoginErrorStatusEnum {
+enum LoginErrorStatus {
+ Ok, // No Error
+ Unknown, // Unknown Error
+ NoCallManagerConfigured, // No Primary or Backup Call Manager
+ NoDevicesFound, // No devices
+ NoCsfDevicesFound, // Devices but none of type CSF
+ PhoneConfigGenError, // Could not generate phone config
+ SipProfileGenError, // Could not build SIP profile
+ ConfigNotSet, // Config not set before calling login()
+ CreateConfigProviderFailed, // Could not create ConfigProvider
+ CreateSoftPhoneProviderFailed, // Could not create SoftPhoneProvider
+ MissingUsername, // Username argument missing,
+ ManualLogout, // logout() has been called
+ LoggedInElseWhere, // Another process has the mutex indicating it is logged
+ // in
+ AuthenticationFailure, // Authentication failure (probably bad password, but
+ // best not to say for sure)
+ CtiCouldNotConnect, // Could not connect to CTI service
+ InvalidServerSearchList
+};
+const std::string toString(LoginErrorStatus);
+} // namespace LoginErrorStatusEnum
+
+namespace ErrorCodeEnum {
+enum ErrorCode { Ok, Unknown, InvalidState, InvalidArgument };
+const std::string toString(ErrorCode);
+} // namespace ErrorCodeEnum
+
+} // namespace csf
diff --git a/dom/media/webrtc/common/EncodingConstraints.h b/dom/media/webrtc/common/EncodingConstraints.h
new file mode 100644
index 0000000000..0bfa7f9f75
--- /dev/null
+++ b/dom/media/webrtc/common/EncodingConstraints.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _ENCODING_CONSTRAINTS_H_
+#define _ENCODING_CONSTRAINTS_H_
+
+#include <algorithm>
+#include "mozilla/Maybe.h"
+
+namespace mozilla {
+class EncodingConstraints {
+ public:
+ EncodingConstraints()
+ : maxWidth(0),
+ maxHeight(0),
+ maxFs(0),
+ maxBr(0),
+ maxPps(0),
+ maxMbps(0),
+ maxCpb(0),
+ maxDpb(0),
+ scaleDownBy(1.0) {}
+
+ bool operator==(const EncodingConstraints& constraints) const {
+ return maxWidth == constraints.maxWidth &&
+ maxHeight == constraints.maxHeight && maxFps == constraints.maxFps &&
+ maxFs == constraints.maxFs && maxBr == constraints.maxBr &&
+ maxPps == constraints.maxPps && maxMbps == constraints.maxMbps &&
+ maxCpb == constraints.maxCpb && maxDpb == constraints.maxDpb &&
+ scaleDownBy == constraints.scaleDownBy;
+ }
+
+ /**
+ * This returns true if the constraints affecting resolution are equal.
+ */
+ bool ResolutionEquals(const EncodingConstraints& constraints) const {
+ return maxWidth == constraints.maxWidth &&
+ maxHeight == constraints.maxHeight && maxFs == constraints.maxFs &&
+ scaleDownBy == constraints.scaleDownBy;
+ }
+
+ uint32_t maxWidth;
+ uint32_t maxHeight;
+ Maybe<double> maxFps;
+ uint32_t maxFs;
+ uint32_t maxBr;
+ uint32_t maxPps;
+ uint32_t maxMbps; // macroblocks per second
+ uint32_t maxCpb; // coded picture buffer size
+ uint32_t maxDpb; // decoded picture buffer size
+ double scaleDownBy; // To preserve resolution
+};
+} // namespace mozilla
+
+#endif // _ENCODING_CONSTRAINTS_H_
diff --git a/dom/media/webrtc/common/MediaEngineWrapper.h b/dom/media/webrtc/common/MediaEngineWrapper.h
new file mode 100644
index 0000000000..ba48ad57d8
--- /dev/null
+++ b/dom/media/webrtc/common/MediaEngineWrapper.h
@@ -0,0 +1,32 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MEDIA_ENGINE_WRAPPER_H_
+#define MEDIA_ENGINE_WRAPPER_H_
+
+#include <mozilla/Scoped.h>
+
+namespace mozilla {
+/**
+ * A Custom scoped template to release a resoure of Type T
+ * with a function of Type F
+ * ScopedCustomReleasePtr<webrtc::VoENetwork> ptr =
+ * webrtc::VoENetwork->GetInterface(voiceEngine);
+ *
+ */
+template <typename T>
+struct ScopedCustomReleaseTraits0 {
+ typedef T* type;
+ static T* empty() { return nullptr; }
+ static void release(T* ptr) {
+ if (ptr) {
+ (ptr)->Release();
+ }
+ }
+};
+
+SCOPED_TEMPLATE(ScopedCustomReleasePtr, ScopedCustomReleaseTraits0)
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/common/NullDeleter.h b/dom/media/webrtc/common/NullDeleter.h
new file mode 100644
index 0000000000..76326c197e
--- /dev/null
+++ b/dom/media/webrtc/common/NullDeleter.h
@@ -0,0 +1,14 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#pragma once
+
+/*
+ * Helper class to allow smart pointers to stack objects to be constructed for
+ * ease of unit testing. Recycled here to help expose a shared_ptr interface to
+ * objects which are really raw pointers.
+ */
+struct null_deleter {
+ void operator()(void const*) const {}
+};
diff --git a/dom/media/webrtc/common/NullTransport.h b/dom/media/webrtc/common/NullTransport.h
new file mode 100644
index 0000000000..a9b270c6d6
--- /dev/null
+++ b/dom/media/webrtc/common/NullTransport.h
@@ -0,0 +1,56 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NULL_TRANSPORT_H_
+#define NULL_TRANSPORT_H_
+
+#include "mozilla/Attributes.h"
+
+#include "api/call/transport.h"
+
+namespace mozilla {
+
+/**
+ * NullTransport is registered as ExternalTransport to throw away data
+ */
+class NullTransport : public webrtc::Transport {
+ public:
+ virtual bool SendRtp(const uint8_t* packet, size_t length,
+ const webrtc::PacketOptions& options) override {
+ (void)packet;
+ (void)length;
+ (void)options;
+ return true;
+ }
+
+ virtual bool SendRtcp(const uint8_t* packet, size_t length) override {
+ (void)packet;
+ (void)length;
+ return true;
+ }
+#if 0
+ virtual int SendPacket(int channel, const void *data, size_t len)
+ {
+ (void) channel; (void) data;
+ return len;
+ }
+
+ virtual int SendRTCPPacket(int channel, const void *data, size_t len)
+ {
+ (void) channel; (void) data;
+ return len;
+ }
+#endif
+ NullTransport() {}
+
+ virtual ~NullTransport() {}
+
+ private:
+ NullTransport(const NullTransport& other) = delete;
+ void operator=(const NullTransport& other) = delete;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/common/Wrapper.h b/dom/media/webrtc/common/Wrapper.h
new file mode 100644
index 0000000000..69c7406b07
--- /dev/null
+++ b/dom/media/webrtc/common/Wrapper.h
@@ -0,0 +1,157 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#pragma once
+
+/*
+ * Wrapper - Helper class for wrapper objects.
+ *
+ * This helps to construct a shared_ptr object which wraps access to an
+ * underlying handle. (The handle could be a pointer to some low-level type, a
+ * conventional C handle, an int ID, a GUID, etc.)
+ *
+ * Usage:
+ * To obtain a FooPtr from a foo_handle_t, call
+ * FooPtr Foo::wrap(foo_handle_t);
+ *
+ * To implement Foo using Wrapper, Foo needs to include this macro in its class
+ * definition:
+ * CSF_DECLARE_WRAP(Foo, foo_handle_t);
+ * It also needs to include this in the cpp file, to provide the wrap()
+ * implementation and define the static Wrapper.
+ * CSF_IMPLEMENT_WRAP(Foo, foo_handle_t);
+ * These are all declared in common/Wrapper.h - Foo.h needs to include this
+ * too.
+ * The client needs to declare Foo(foo_handle_t) as private, and provide a
+ * suitable implementation, as well as implementing wrappers for any other
+ * functions to be exposed.
+ * The client needs to implement ~Foo() to perform any cleanup as usual.
+ *
+ * wrap() will always return the same FooPtr for a given foo_handle_t, it will
+ * not construct additional objects if a suitable one already exists.
+ * changeHandle() is used in rare cases where the underlying handle is changed,
+ * but the wrapper object is intended to remain. This is the
+ * case for the "fake" CC_DPCall generated on
+ * CC_DPLine::CreateCall(), where the correct IDPCall* is
+ * provided later.
+ * reset() is a cleanup step to wipe the handle map and allow memory to be
+ * reclaimed.
+ *
+ * Future enhancements:
+ * - For now, objects remain in the map forever. Better would be to add a
+ * releaseHandle() function which would allow the map to be emptied as
+ * underlying handles expired. While we can't force the client to give up
+ * its shared_ptr<Foo> objects, we can remove our own copy, for instance on a
+ * call ended event.
+ */
+
+#include <map>
+#include "prlock.h"
+#include "mozilla/Assertions.h"
+
+/*
+ * Wrapper has its own autolock class because the instances are declared
+ * statically and mozilla::Mutex will not work properly when instantiated
+ * in a static constructor.
+ */
+
+class LockNSPR {
+ public:
+ LockNSPR() : lock_(nullptr) {
+ lock_ = PR_NewLock();
+ MOZ_ASSERT(lock_);
+ }
+ ~LockNSPR() { PR_DestroyLock(lock_); }
+
+ void Acquire() { PR_Lock(lock_); }
+
+ void Release() { PR_Unlock(lock_); }
+
+ private:
+ PRLock* lock_;
+};
+
+class AutoLockNSPR {
+ public:
+ explicit AutoLockNSPR(LockNSPR& lock) : lock_(lock) { lock_.Acquire(); }
+ ~AutoLockNSPR() { lock_.Release(); }
+
+ private:
+ LockNSPR& lock_;
+};
+
+template <class T>
+class Wrapper {
+ private:
+ typedef std::map<typename T::Handle, typename T::Ptr> HandleMapType;
+ HandleMapType handleMap;
+ LockNSPR handleMapMutex;
+
+ public:
+ Wrapper() {}
+
+ typename T::Ptr wrap(typename T::Handle handle) {
+ AutoLockNSPR lock(handleMapMutex);
+ typename HandleMapType::iterator it = handleMap.find(handle);
+ if (it != handleMap.end()) {
+ return it->second;
+ } else {
+ typename T::Ptr p(new T(handle));
+ handleMap[handle] = p;
+ return p;
+ }
+ }
+
+ bool changeHandle(typename T::Handle oldHandle,
+ typename T::Handle newHandle) {
+ AutoLockNSPR lock(handleMapMutex);
+ typename HandleMapType::iterator it = handleMap.find(oldHandle);
+ if (it != handleMap.end()) {
+ typename T::Ptr p = it->second;
+ handleMap.erase(it);
+ handleMap[newHandle] = p;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ bool release(typename T::Handle handle) {
+ AutoLockNSPR lock(handleMapMutex);
+ typename HandleMapType::iterator it = handleMap.find(handle);
+ if (it != handleMap.end()) {
+ handleMap.erase(it);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ void reset() {
+ AutoLockNSPR lock(handleMapMutex);
+ handleMap.clear();
+ }
+};
+
+#define CSF_DECLARE_WRAP(classname, handletype) \
+ public: \
+ static classname##Ptr wrap(handletype handle); \
+ static void reset(); \
+ static void release(handletype handle); \
+ \
+ private: \
+ friend class Wrapper<classname>; \
+ typedef classname##Ptr Ptr; \
+ typedef handletype Handle; \
+ static Wrapper<classname>& getWrapper() { \
+ static Wrapper<classname> wrapper; \
+ return wrapper; \
+ }
+
+#define CSF_IMPLEMENT_WRAP(classname, handletype) \
+ classname##Ptr classname::wrap(handletype handle) { \
+ return getWrapper().wrap(handle); \
+ } \
+ void classname::reset() { getWrapper().reset(); } \
+ void classname::release(handletype handle) { getWrapper().release(handle); }
diff --git a/dom/media/webrtc/common/YuvStamper.cpp b/dom/media/webrtc/common/YuvStamper.cpp
new file mode 100644
index 0000000000..b0c8426983
--- /dev/null
+++ b/dom/media/webrtc/common/YuvStamper.cpp
@@ -0,0 +1,394 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#elif defined XP_WIN
+# include <winsock2.h>
+#endif
+#include <string.h>
+
+#include "nspr.h"
+#include "YuvStamper.h"
+#include "mozilla/Sprintf.h"
+
+typedef uint32_t UINT4; // Needed for r_crc32() call
+extern "C" {
+#include "r_crc32.h"
+}
+
+namespace mozilla {
+
+#define ON_5 0x20
+#define ON_4 0x10
+#define ON_3 0x08
+#define ON_2 0x04
+#define ON_1 0x02
+#define ON_0 0x01
+
+/*
+ 0, 0, 1, 1, 0, 0,
+ 0, 1, 0, 0, 1, 0,
+ 1, 0, 0, 0, 0, 1,
+ 1, 0, 0, 0, 0, 1,
+ 1, 0, 0, 0, 0, 1,
+ 0, 1, 0, 0, 1, 0,
+ 0, 0, 1, 1, 0, 0
+*/
+static unsigned char DIGIT_0[] = {ON_3 | ON_2, ON_4 | ON_1, ON_5 | ON_0,
+ ON_5 | ON_0, ON_5 | ON_0, ON_4 | ON_1,
+ ON_3 | ON_2};
+
+/*
+ 0, 0, 0, 1, 0, 0,
+ 0, 0, 0, 1, 0, 0,
+ 0, 0, 0, 1, 0, 0,
+ 0, 0, 0, 1, 0, 0,
+ 0, 0, 0, 1, 0, 0,
+ 0, 0, 0, 1, 0, 0,
+ 0, 0, 0, 1, 0, 0,
+*/
+static unsigned char DIGIT_1[] = {ON_2, ON_2, ON_2, ON_2, ON_2, ON_2, ON_2};
+
+/*
+ 1, 1, 1, 1, 1, 0,
+ 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 0, 0, 1,
+ 0, 1, 1, 1, 1, 0,
+ 1, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 0, 0,
+ 0, 1, 1, 1, 1, 1,
+*/
+static unsigned char DIGIT_2[] = {
+ ON_5 | ON_4 | ON_3 | ON_2 | ON_1, ON_0, ON_0,
+ ON_4 | ON_3 | ON_2 | ON_1, ON_5, ON_5,
+ ON_4 | ON_3 | ON_2 | ON_1 | ON_0,
+};
+
+/*
+ 1, 1, 1, 1, 1, 0,
+ 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 0, 0, 1,
+ 0, 1, 1, 1, 1, 1,
+ 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 0, 0, 1,
+ 1, 1, 1, 1, 1, 0,
+*/
+static unsigned char DIGIT_3[] = {
+ ON_5 | ON_4 | ON_3 | ON_2 | ON_1, ON_0, ON_0,
+ ON_4 | ON_3 | ON_2 | ON_1 | ON_0, ON_0, ON_0,
+ ON_5 | ON_4 | ON_3 | ON_2 | ON_1,
+};
+
+/*
+ 0, 1, 0, 0, 0, 1,
+ 0, 1, 0, 0, 0, 1,
+ 0, 1, 0, 0, 0, 1,
+ 0, 1, 1, 1, 1, 1,
+ 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 0, 0, 1
+*/
+static unsigned char DIGIT_4[] = {
+ ON_4 | ON_0, ON_4 | ON_0, ON_4 | ON_0, ON_4 | ON_3 | ON_2 | ON_1 | ON_0,
+ ON_0, ON_0, ON_0,
+};
+
+/*
+ 0, 1, 1, 1, 1, 1,
+ 1, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 0, 0,
+ 0, 1, 1, 1, 1, 0,
+ 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 0, 0, 1,
+ 1, 1, 1, 1, 1, 0,
+*/
+static unsigned char DIGIT_5[] = {
+ ON_4 | ON_3 | ON_2 | ON_1 | ON_0, ON_5, ON_5,
+ ON_4 | ON_3 | ON_2 | ON_1, ON_0, ON_0,
+ ON_5 | ON_4 | ON_3 | ON_2 | ON_1,
+};
+
+/*
+ 0, 1, 1, 1, 1, 1,
+ 1, 0, 0, 0, 0, 0,
+ 1, 0, 0, 0, 0, 0,
+ 1, 1, 1, 1, 1, 0,
+ 1, 0, 0, 0, 0, 1,
+ 1, 0, 0, 0, 0, 1,
+ 0, 1, 1, 1, 1, 0,
+*/
+static unsigned char DIGIT_6[] = {
+ ON_4 | ON_3 | ON_2 | ON_1 | ON_0, ON_5, ON_5,
+ ON_4 | ON_3 | ON_2 | ON_1, ON_5 | ON_0, ON_5 | ON_0,
+ ON_4 | ON_3 | ON_2 | ON_1,
+};
+
+/*
+ 1, 1, 1, 1, 1, 1,
+ 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 0, 1, 0,
+ 0, 0, 0, 1, 0, 0,
+ 0, 0, 1, 0, 0, 0,
+ 0, 1, 0, 0, 0, 0,
+ 1, 0, 0, 0, 0, 0
+*/
+static unsigned char DIGIT_7[] = {ON_5 | ON_4 | ON_3 | ON_2 | ON_1 | ON_0,
+ ON_0,
+ ON_1,
+ ON_2,
+ ON_3,
+ ON_4,
+ ON_5};
+
+/*
+ 0, 1, 1, 1, 1, 1,
+ 1, 0, 0, 0, 0, 1,
+ 1, 0, 0, 0, 0, 1,
+ 0, 1, 1, 1, 1, 0,
+ 1, 0, 0, 0, 0, 1,
+ 1, 0, 0, 0, 0, 1,
+ 0, 1, 1, 1, 1, 0
+*/
+static unsigned char DIGIT_8[] = {
+ ON_4 | ON_3 | ON_2 | ON_1, ON_5 | ON_0, ON_5 | ON_0,
+ ON_4 | ON_3 | ON_2 | ON_1, ON_5 | ON_0, ON_5 | ON_0,
+ ON_4 | ON_3 | ON_2 | ON_1,
+};
+
+/*
+ 0, 1, 1, 1, 1, 1,
+ 1, 0, 0, 0, 0, 1,
+ 1, 0, 0, 0, 0, 1,
+ 0, 1, 1, 1, 1, 1,
+ 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 0, 0, 1,
+ 0, 1, 1, 1, 1, 0
+*/
+static unsigned char DIGIT_9[] = {
+ ON_4 | ON_3 | ON_2 | ON_1 | ON_0, ON_5 | ON_0, ON_5 | ON_0,
+ ON_4 | ON_3 | ON_2 | ON_1 | ON_0, ON_0, ON_0,
+ ON_4 | ON_3 | ON_2 | ON_1,
+};
+
+static unsigned char* DIGITS[] = {DIGIT_0, DIGIT_1, DIGIT_2, DIGIT_3, DIGIT_4,
+ DIGIT_5, DIGIT_6, DIGIT_7, DIGIT_8, DIGIT_9};
+
+YuvStamper::YuvStamper(unsigned char* pYData, uint32_t width, uint32_t height,
+ uint32_t stride, uint32_t x, uint32_t y,
+ unsigned char symbol_width, unsigned char symbol_height)
+ : pYData(pYData),
+ mStride(stride),
+ mWidth(width),
+ mHeight(height),
+ mSymbolWidth(symbol_width),
+ mSymbolHeight(symbol_height),
+ mCursor(x, y) {}
+
+bool YuvStamper::Encode(uint32_t width, uint32_t height, uint32_t stride,
+ unsigned char* pYData, unsigned char* pMsg,
+ size_t msg_len, uint32_t x, uint32_t y) {
+ YuvStamper stamper(pYData, width, height, stride, x, y, sBitSize, sBitSize);
+
+ // Reserve space for a checksum.
+ if (stamper.Capacity() < 8 * (msg_len + sizeof(uint32_t))) {
+ return false;
+ }
+
+ bool ok = false;
+ uint32_t crc;
+ unsigned char* pCrc = reinterpret_cast<unsigned char*>(&crc);
+ r_crc32(reinterpret_cast<char*>(pMsg), (int)msg_len, &crc);
+ crc = htonl(crc);
+
+ while (msg_len-- > 0) {
+ if (!stamper.Write8(*pMsg++)) {
+ return false;
+ }
+ }
+
+ // Add checksum after the message.
+ ok = stamper.Write8(*pCrc++) && stamper.Write8(*pCrc++) &&
+ stamper.Write8(*pCrc++) && stamper.Write8(*pCrc++);
+
+ return ok;
+}
+
+bool YuvStamper::Decode(uint32_t width, uint32_t height, uint32_t stride,
+ unsigned char* pYData, unsigned char* pMsg,
+ size_t msg_len, uint32_t x, uint32_t y) {
+ YuvStamper stamper(pYData, width, height, stride, x, y, sBitSize, sBitSize);
+
+ unsigned char* ptr = pMsg;
+ size_t len = msg_len;
+ uint32_t crc, msg_crc;
+ unsigned char* pCrc = reinterpret_cast<unsigned char*>(&crc);
+
+ // Account for space reserved for the checksum
+ if (stamper.Capacity() < 8 * (len + sizeof(uint32_t))) {
+ return false;
+ }
+
+ while (len-- > 0) {
+ if (!stamper.Read8(*ptr++)) {
+ return false;
+ }
+ }
+
+ if (!(stamper.Read8(*pCrc++) && stamper.Read8(*pCrc++) &&
+ stamper.Read8(*pCrc++) && stamper.Read8(*pCrc++))) {
+ return false;
+ }
+
+ r_crc32(reinterpret_cast<char*>(pMsg), (int)msg_len, &msg_crc);
+ return crc == htonl(msg_crc);
+}
+
+inline uint32_t YuvStamper::Capacity() {
+ // Enforce at least a symbol width and height offset from outer edges.
+ if (mCursor.y + mSymbolHeight > mHeight) {
+ return 0;
+ }
+
+ if (mCursor.x + mSymbolWidth > mWidth && !AdvanceCursor()) {
+ return 0;
+ }
+
+ // Normalize frame integral to mSymbolWidth x mSymbolHeight
+ uint32_t width = mWidth / mSymbolWidth;
+ uint32_t height = mHeight / mSymbolHeight;
+ uint32_t x = mCursor.x / mSymbolWidth;
+ uint32_t y = mCursor.y / mSymbolHeight;
+
+ return (width * height - width * y) - x;
+}
+
+bool YuvStamper::Write8(unsigned char value) {
+ // Encode MSB to LSB.
+ unsigned char mask = 0x80;
+ while (mask) {
+ if (!WriteBit(!!(value & mask))) {
+ return false;
+ }
+ mask >>= 1;
+ }
+ return true;
+}
+
+bool YuvStamper::WriteBit(bool one) {
+ // A bit is mapped to a mSymbolWidth x mSymbolHeight square of luma data
+ // points. Don't use ternary op.:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1001708
+ unsigned char value;
+ if (one)
+ value = sYOn;
+ else
+ value = sYOff;
+
+ for (uint32_t y = 0; y < mSymbolHeight; y++) {
+ for (uint32_t x = 0; x < mSymbolWidth; x++) {
+ *(pYData + (mCursor.x + x) + ((mCursor.y + y) * mStride)) = value;
+ }
+ }
+
+ return AdvanceCursor();
+}
+
+bool YuvStamper::AdvanceCursor() {
+ mCursor.x += mSymbolWidth;
+ if (mCursor.x + mSymbolWidth > mWidth) {
+ // move to the start of the next row if possible.
+ mCursor.y += mSymbolHeight;
+ if (mCursor.y + mSymbolHeight > mHeight) {
+ // end of frame, do not advance
+ mCursor.y -= mSymbolHeight;
+ mCursor.x -= mSymbolWidth;
+ return false;
+ } else {
+ mCursor.x = 0;
+ }
+ }
+
+ return true;
+}
+
+bool YuvStamper::Read8(unsigned char& value) {
+ unsigned char octet = 0;
+ unsigned char bit = 0;
+
+ for (int i = 8; i > 0; --i) {
+ if (!ReadBit(bit)) {
+ return false;
+ }
+ octet <<= 1;
+ octet |= bit;
+ }
+
+ value = octet;
+ return true;
+}
+
+bool YuvStamper::ReadBit(unsigned char& bit) {
+ uint32_t sum = 0;
+ for (uint32_t y = 0; y < mSymbolHeight; y++) {
+ for (uint32_t x = 0; x < mSymbolWidth; x++) {
+ sum += *(pYData + mStride * (mCursor.y + y) + mCursor.x + x);
+ }
+ }
+
+ // apply threshold to collected bit square
+ bit = (sum > (sBitThreshold * mSymbolWidth * mSymbolHeight)) ? 1 : 0;
+ return AdvanceCursor();
+}
+
+bool YuvStamper::WriteDigits(uint32_t value) {
+ char buf[20];
+ SprintfLiteral(buf, "%.5u", value);
+ size_t size = strlen(buf);
+
+ if (Capacity() < size) {
+ return false;
+ }
+
+ for (size_t i = 0; i < size; ++i) {
+ if (!WriteDigit(buf[i] - '0')) return false;
+ if (!AdvanceCursor()) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool YuvStamper::WriteDigit(unsigned char digit) {
+ if (digit > sizeof(DIGITS) / sizeof(DIGITS[0])) return false;
+
+ unsigned char* dig = DIGITS[digit];
+ for (uint32_t row = 0; row < sDigitHeight; ++row) {
+ unsigned char mask = 0x01 << (sDigitWidth - 1);
+ for (uint32_t col = 0; col < sDigitWidth; ++col, mask >>= 1) {
+ if (dig[row] & mask) {
+ for (uint32_t xx = 0; xx < sPixelSize; ++xx) {
+ for (uint32_t yy = 0; yy < sPixelSize; ++yy) {
+ WritePixel(pYData, mCursor.x + (col * sPixelSize) + xx,
+ mCursor.y + (row * sPixelSize) + yy);
+ }
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+void YuvStamper::WritePixel(unsigned char* data, uint32_t x, uint32_t y) {
+ unsigned char* ptr = &data[y * mStride + x];
+ // Don't use ternary op.: https://bugzilla.mozilla.org/show_bug.cgi?id=1001708
+ if (*ptr > sLumaThreshold)
+ *ptr = sLumaMin;
+ else
+ *ptr = sLumaMax;
+}
+
+} // namespace mozilla.
diff --git a/dom/media/webrtc/common/YuvStamper.h b/dom/media/webrtc/common/YuvStamper.h
new file mode 100644
index 0000000000..f355055cd9
--- /dev/null
+++ b/dom/media/webrtc/common/YuvStamper.h
@@ -0,0 +1,77 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef YUV_STAMPER_H_
+#define YUV_STAMPER_H_
+
+#include <cstdint>
+
+namespace mozilla {
+
+class YuvStamper {
+ public:
+ bool WriteDigits(uint32_t value);
+
+ template <typename T>
+ static bool Write(uint32_t width, uint32_t height, uint32_t stride,
+ unsigned char* pYData, const T& value, uint32_t x = 0,
+ uint32_t y = 0) {
+ YuvStamper stamper(pYData, width, height, stride, x, y,
+ (sDigitWidth + sInterDigit) * sPixelSize,
+ (sDigitHeight + sInterLine) * sPixelSize);
+ return stamper.WriteDigits(value);
+ }
+
+ static bool Encode(uint32_t width, uint32_t height, uint32_t stride,
+ unsigned char* pYData, unsigned char* pMsg, size_t msg_len,
+ uint32_t x = 0, uint32_t y = 0);
+
+ static bool Decode(uint32_t width, uint32_t height, uint32_t stride,
+ unsigned char* pYData, unsigned char* pMsg, size_t msg_len,
+ uint32_t x = 0, uint32_t y = 0);
+
+ private:
+ YuvStamper(unsigned char* pYData, uint32_t width, uint32_t height,
+ uint32_t stride, uint32_t x, uint32_t y,
+ unsigned char symbol_width, unsigned char symbol_height);
+
+ bool WriteDigit(unsigned char digit);
+ void WritePixel(unsigned char* data, uint32_t x, uint32_t y);
+ uint32_t Capacity();
+ bool AdvanceCursor();
+ bool WriteBit(bool one);
+ bool Write8(unsigned char value);
+ bool ReadBit(unsigned char& value);
+ bool Read8(unsigned char& bit);
+
+ const static unsigned char sPixelSize = 3;
+ const static unsigned char sDigitWidth = 6;
+ const static unsigned char sDigitHeight = 7;
+ const static unsigned char sInterDigit = 1;
+ const static unsigned char sInterLine = 1;
+ const static uint32_t sBitSize = 4;
+ const static uint32_t sBitThreshold = 60;
+ const static unsigned char sYOn = 0x80;
+ const static unsigned char sYOff = 0;
+ const static unsigned char sLumaThreshold = 96;
+ const static unsigned char sLumaMin = 16;
+ const static unsigned char sLumaMax = 235;
+
+ unsigned char* pYData;
+ uint32_t mStride;
+ uint32_t mWidth;
+ uint32_t mHeight;
+ unsigned char mSymbolWidth;
+ unsigned char mSymbolHeight;
+
+ struct Cursor {
+ Cursor(uint32_t x, uint32_t y) : x(x), y(y) {}
+ uint32_t x;
+ uint32_t y;
+ } mCursor;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/common/browser_logging/CSFLog.cpp b/dom/media/webrtc/common/browser_logging/CSFLog.cpp
new file mode 100644
index 0000000000..d58f5726a5
--- /dev/null
+++ b/dom/media/webrtc/common/browser_logging/CSFLog.cpp
@@ -0,0 +1,85 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include "CSFLog.h"
+
+#include <map>
+#include "prrwlock.h"
+#include "prthread.h"
+#include "nsThreadUtils.h"
+
+#include "mozilla/Logging.h"
+#include "mozilla/Sprintf.h"
+
+mozilla::LazyLogModule gSignalingLog("signaling");
+
+void CSFLogV(CSFLogLevel priority, const char* sourceFile, int sourceLine,
+ const char* tag, const char* format, va_list args) {
+#ifdef STDOUT_LOGGING
+ printf("%s\n:", tag);
+ vprintf(format, args);
+#else
+
+ mozilla::LogLevel level =
+ static_cast<mozilla::LogLevel>(static_cast<unsigned int>(priority));
+
+ // Skip doing any of this work if we're not logging the indicated level...
+ if (!MOZ_LOG_TEST(gSignalingLog, level)) {
+ return;
+ }
+
+ // Trim the path component from the filename
+ const char* lastSlash = sourceFile;
+ while (*sourceFile) {
+ if (*sourceFile == '/' || *sourceFile == '\\') {
+ lastSlash = sourceFile;
+ }
+ sourceFile++;
+ }
+ sourceFile = lastSlash;
+ if (*sourceFile == '/' || *sourceFile == '\\') {
+ sourceFile++;
+ }
+
+# define MAX_MESSAGE_LENGTH 1024
+ char message[MAX_MESSAGE_LENGTH];
+
+ const char* threadName = NULL;
+
+ // Check if we're the main thread...
+ if (NS_IsMainThread()) {
+ threadName = "main";
+ } else {
+ threadName = PR_GetThreadName(PR_GetCurrentThread());
+ }
+
+ // If we can't find it anywhere, use a blank string
+ if (!threadName) {
+ threadName = "";
+ }
+
+ VsprintfLiteral(message, format, args);
+ MOZ_LOG(
+ gSignalingLog, level,
+ ("[%s|%s] %s:%d: %s", threadName, tag, sourceFile, sourceLine, message));
+#endif
+}
+
+void CSFLog(CSFLogLevel priority, const char* sourceFile, int sourceLine,
+ const char* tag, const char* format, ...) {
+ va_list ap;
+ va_start(ap, format);
+
+ CSFLogV(priority, sourceFile, sourceLine, tag, format, ap);
+ va_end(ap);
+}
+
+int CSFLogTestLevel(CSFLogLevel priority) {
+ return MOZ_LOG_TEST(gSignalingLog, static_cast<mozilla::LogLevel>(
+ static_cast<unsigned int>(priority)));
+}
diff --git a/dom/media/webrtc/common/browser_logging/CSFLog.h b/dom/media/webrtc/common/browser_logging/CSFLog.h
new file mode 100644
index 0000000000..eb46b37cc3
--- /dev/null
+++ b/dom/media/webrtc/common/browser_logging/CSFLog.h
@@ -0,0 +1,58 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CSFLOG_H
+#define CSFLOG_H
+
+#include <stdarg.h>
+
+typedef enum {
+ CSF_LOG_ERROR = 1,
+ CSF_LOG_WARNING,
+ CSF_LOG_INFO,
+ CSF_LOG_DEBUG,
+ CSF_LOG_VERBOSE,
+} CSFLogLevel;
+
+#define CSFLogError(tag, format, ...) \
+ CSFLog(CSF_LOG_ERROR, __FILE__, __LINE__, tag, format, ##__VA_ARGS__)
+#define CSFLogErrorV(tag, format, va_list_arg) \
+ CSFLogV(CSF_LOG_ERROR, __FILE__, __LINE__, tag, format, va_list_arg)
+#define CSFLogWarn(tag, format, ...) \
+ CSFLog(CSF_LOG_WARNING, __FILE__, __LINE__, tag, format, ##__VA_ARGS__)
+#define CSFLogWarnV(tag, format, va_list_arg) \
+ CSFLogV(CSF_LOG_WARNING, __FILE__, __LINE__, tag, format, va_list_arg)
+#define CSFLogInfo(tag, format, ...) \
+ CSFLog(CSF_LOG_INFO, __FILE__, __LINE__, tag, format, ##__VA_ARGS__)
+#define CSFLogInfoV(tag, format, va_list_arg) \
+ CSFLogV(CSF_LOG_INFO, __FILE__, __LINE__, tag, format, va_list_arg)
+#define CSFLogDebug(tag, format, ...) \
+ CSFLog(CSF_LOG_DEBUG, __FILE__, __LINE__, tag, format, ##__VA_ARGS__)
+#define CSFLogDebugV(tag, format, va_list_arg) \
+ CSFLogV(CSF_LOG_DEBUG, __FILE__, __LINE__, tag, format, va_list_arg)
+#define CSFLogVerbose(tag, format, ...) \
+ CSFLog(CSF_LOG_VERBOSE, __FILE__, __LINE__, tag, format, ##__VA_ARGS__)
+#define CSFLogVerboseV(tag, format, va_list_arg) \
+ CSFLogV(CSF_LOG_VERBOSE, __FILE__, __LINE__, tag, format, va_list_arg)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+void CSFLog(CSFLogLevel priority, const char* sourceFile, int sourceLine,
+ const char* tag, const char* format, ...)
+#ifdef __GNUC__
+ __attribute__((format(printf, 5, 6)))
+#endif
+ ;
+
+void CSFLogV(CSFLogLevel priority, const char* sourceFile, int sourceLine,
+ const char* tag, const char* format, va_list args);
+
+int CSFLogTestLevel(CSFLogLevel priority);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/dom/media/webrtc/common/browser_logging/WebRtcLog.cpp b/dom/media/webrtc/common/browser_logging/WebRtcLog.cpp
new file mode 100644
index 0000000000..0f3769604e
--- /dev/null
+++ b/dom/media/webrtc/common/browser_logging/WebRtcLog.cpp
@@ -0,0 +1,162 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebRtcLog.h"
+
+#include "mozilla/Logging.h"
+#include "mozilla/StaticPtr.h"
+#include "prenv.h"
+#include "rtc_base/logging.h"
+
+#include "nscore.h"
+#include "nsString.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/Preferences.h"
+
+#include "nsIFile.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsNativeCharsetUtils.h"
+
+using mozilla::LogLevel;
+
+static mozilla::LazyLogModule sWebRtcLog("webrtc_trace");
+static mozilla::LazyLogModule sLogAEC("AEC");
+
+class LogSinkImpl : public rtc::LogSink {
+ public:
+ LogSinkImpl() {}
+
+ private:
+ void OnLogMessage(const std::string& message) override {
+ MOZ_LOG(sWebRtcLog, LogLevel::Debug, ("%s", message.data()));
+ }
+};
+
+// For RTC_LOG()
+static mozilla::StaticAutoPtr<LogSinkImpl> sSink;
+
+void GetWebRtcLogPrefs() {
+ rtc::LogMessage::set_aec_debug_size(
+ mozilla::Preferences::GetUint("media.webrtc.debug.aec_dump_max_size"));
+}
+
+mozilla::LogLevel CheckOverrides() {
+ mozilla::LogModule* log_info = sWebRtcLog;
+ mozilla::LogLevel log_level = log_info->Level();
+
+ log_info = sLogAEC;
+ if (sLogAEC && (log_info->Level() != mozilla::LogLevel::Disabled)) {
+ rtc::LogMessage::set_aec_debug(true);
+ }
+
+ return log_level;
+}
+
+void ConfigWebRtcLog(mozilla::LogLevel level) {
+ rtc::LoggingSeverity log_level;
+ switch (level) {
+ case mozilla::LogLevel::Verbose:
+ log_level = rtc::LoggingSeverity::LS_VERBOSE;
+ break;
+ case mozilla::LogLevel::Debug:
+ case mozilla::LogLevel::Info:
+ log_level = rtc::LoggingSeverity::LS_INFO;
+ break;
+ case mozilla::LogLevel::Warning:
+ log_level = rtc::LoggingSeverity::LS_WARNING;
+ break;
+ case mozilla::LogLevel::Error:
+ log_level = rtc::LoggingSeverity::LS_ERROR;
+ break;
+ case mozilla::LogLevel::Disabled:
+ log_level = rtc::LoggingSeverity::LS_NONE;
+ break;
+ default:
+ MOZ_ASSERT(false);
+ break;
+ }
+ rtc::LogMessage::LogToDebug(log_level);
+ if (level != mozilla::LogLevel::Disabled) {
+ // always capture LOG(...) << ... logging in webrtc.org code to nspr logs
+ if (!sSink) {
+ sSink = new LogSinkImpl();
+ rtc::LogMessage::AddLogToStream(sSink, log_level);
+ // it's ok if this leaks to program end
+ }
+ } else if (sSink) {
+ rtc::LogMessage::RemoveLogToStream(sSink);
+ sSink = nullptr;
+ }
+}
+
+void StartWebRtcLog(mozilla::LogLevel log_level) {
+ if (log_level == mozilla::LogLevel::Disabled) {
+ return;
+ }
+
+ GetWebRtcLogPrefs();
+ mozilla::LogLevel level = CheckOverrides();
+
+ ConfigWebRtcLog(level);
+}
+
+void EnableWebRtcLog() {
+ GetWebRtcLogPrefs();
+ mozilla::LogLevel level = CheckOverrides();
+ ConfigWebRtcLog(level);
+}
+
+// Called when we destroy the singletons from PeerConnectionCtx or if the
+// user changes logging in about:webrtc
+void StopWebRtcLog() {
+ if (sSink) {
+ rtc::LogMessage::RemoveLogToStream(sSink);
+ sSink = nullptr;
+ }
+}
+
+nsCString ConfigAecLog() {
+ nsCString aecLogDir;
+ if (rtc::LogMessage::aec_debug()) {
+ return ""_ns;
+ }
+#if defined(ANDROID)
+ const char* default_tmp_dir = "/dev/null";
+ aecLogDir.Assign(default_tmp_dir);
+#else
+ nsCOMPtr<nsIFile> tempDir;
+ nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tempDir));
+ if (NS_SUCCEEDED(rv)) {
+# ifdef XP_WIN
+ // WebRTC wants a path encoded in the native charset, not UTF-8.
+ nsAutoString temp;
+ tempDir->GetPath(temp);
+ NS_CopyUnicodeToNative(temp, aecLogDir);
+# else
+ tempDir->GetNativePath(aecLogDir);
+# endif
+ }
+#endif
+ rtc::LogMessage::set_aec_debug_filename(aecLogDir.get());
+
+ return aecLogDir;
+}
+
+nsCString StartAecLog() {
+ nsCString aecLogDir;
+ if (rtc::LogMessage::aec_debug()) {
+ return ""_ns;
+ }
+
+ GetWebRtcLogPrefs();
+ CheckOverrides();
+ aecLogDir = ConfigAecLog();
+
+ rtc::LogMessage::set_aec_debug(true);
+
+ return aecLogDir;
+}
+
+void StopAecLog() { rtc::LogMessage::set_aec_debug(false); }
diff --git a/dom/media/webrtc/common/browser_logging/WebRtcLog.h b/dom/media/webrtc/common/browser_logging/WebRtcLog.h
new file mode 100644
index 0000000000..b933ff43a5
--- /dev/null
+++ b/dom/media/webrtc/common/browser_logging/WebRtcLog.h
@@ -0,0 +1,17 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WEBRTCLOG_H_
+#define WEBRTCLOG_H_
+
+#include "mozilla/Logging.h"
+#include "nsStringFwd.h"
+
+nsCString StartAecLog();
+void StopAecLog();
+void EnableWebRtcLog();
+void StartWebRtcLog(mozilla::LogLevel level);
+void StopWebRtcLog();
+
+#endif
diff --git a/dom/media/webrtc/common/csf_common.h b/dom/media/webrtc/common/csf_common.h
new file mode 100644
index 0000000000..8b01047553
--- /dev/null
+++ b/dom/media/webrtc/common/csf_common.h
@@ -0,0 +1,90 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _CSF_COMMON_E58E5677_950A_424c_B6C2_CA180092E6A2_H
+#define _CSF_COMMON_E58E5677_950A_424c_B6C2_CA180092E6A2_H
+
+#include <assert.h>
+#include <memory>
+#include <vector>
+#include <stdlib.h>
+
+/*
+
+This header file defines:
+
+csf_countof
+csf_sprintf
+csf_vsprintf
+
+*/
+
+/*
+ General security tip: Ensure that "format" is never a user-defined string.
+ Format should ALWAYS be something that's built into your code, not user
+ supplied. For example: never write:
+
+ csf_sprintf(buffer, csf_countof(buffer), pUserSuppliedString);
+
+ Instead write:
+
+ csf_sprintf(buffer, csf_countof(buffer), "%s", pUserSuppliedString);
+
+*/
+
+#ifdef WIN32
+# if !defined(_countof)
+# if !defined(__cplusplus)
+# define _countof(_Array) (sizeof(_Array) / sizeof(_Array[0]))
+# else
+extern "C++" {
+template <typename _CountofType, size_t _SizeOfArray>
+char (*_csf_countof_helper(_CountofType (&_Array)[_SizeOfArray]))[_SizeOfArray];
+# define _countof(_Array) sizeof(*_csf_countof_helper(_Array))
+}
+# endif
+# endif
+#else
+# define _countof(_Array) (sizeof(_Array) / sizeof(_Array[0]))
+#endif
+// csf_countof
+
+#define csf_countof(anArray) _countof(anArray)
+
+// csf_sprintf
+
+#ifdef _WIN32
+// Unlike snprintf, sprintf_s guarantees that the buffer will be null-terminated
+// (unless the buffer size is zero).
+# define csf_sprintf(/* char* */ buffer, \
+ /* size_t */ sizeOfBufferInCharsInclNullTerm, \
+ /* const char * */ format, ...) \
+ _snprintf_s(buffer, sizeOfBufferInCharsInclNullTerm, _TRUNCATE, format, \
+ __VA_ARGS__)
+#else
+# define csf_sprintf(/* char */ buffer, \
+ /* size_t */ sizeOfBufferInCharsInclNullTerm, \
+ /* const char * */ format, ...) \
+ snprintf(buffer, sizeOfBufferInCharsInclNullTerm, format, __VA_ARGS__); \
+ buffer[sizeOfBufferInCharsInclNullTerm - 1] = '\0'
+#endif
+
+// csf_vsprintf
+
+#ifdef _WIN32
+# define csf_vsprintf(/* char* */ buffer, \
+ /* size_t */ sizeOfBufferInCharsInclNullTerm, \
+ /* const char * */ format, /* va_list */ vaList) \
+ vsnprintf_s(buffer, sizeOfBufferInCharsInclNullTerm, _TRUNCATE, format, \
+ vaList); \
+ buffer[sizeOfBufferInCharsInclNullTerm - 1] = '\0'
+#else
+# define csf_vsprintf(/* char */ buffer, \
+ /* size_t */ sizeOfBufferInCharsInclNullTerm, \
+ /* const char * */ format, /* va_list */ vaList) \
+ vsprintf(buffer, format, vaList); \
+ buffer[sizeOfBufferInCharsInclNullTerm - 1] = '\0'
+#endif
+
+#endif
diff --git a/dom/media/webrtc/common/moz.build b/dom/media/webrtc/common/moz.build
new file mode 100644
index 0000000000..7605d27835
--- /dev/null
+++ b/dom/media/webrtc/common/moz.build
@@ -0,0 +1,23 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+include("/dom/media/webrtc/third_party_build/webrtc.mozbuild")
+
+EXPORTS.mozilla.dom += ["CandidateInfo.h"]
+
+LOCAL_INCLUDES += [
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr",
+ "/third_party/libwebrtc",
+ "/third_party/libwebrtc/third_party/abseil-cpp",
+]
+
+UNIFIED_SOURCES += [
+ "browser_logging/CSFLog.cpp",
+ "browser_logging/WebRtcLog.cpp",
+ "time_profiling/timecard.c",
+ "YuvStamper.cpp",
+]
+
+FINAL_LIBRARY = "xul"
diff --git a/dom/media/webrtc/common/time_profiling/timecard.c b/dom/media/webrtc/common/time_profiling/timecard.c
new file mode 100644
index 0000000000..c0218cb92d
--- /dev/null
+++ b/dom/media/webrtc/common/time_profiling/timecard.c
@@ -0,0 +1,112 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <stdio.h>
+#include <string.h>
+#include "timecard.h"
+#include "mozilla/mozalloc.h"
+
+Timecard* create_timecard() {
+ Timecard* tc = moz_xcalloc(1, sizeof(Timecard));
+ tc->entries_allocated = TIMECARD_INITIAL_TABLE_SIZE;
+ tc->entries = moz_xcalloc(tc->entries_allocated, sizeof(TimecardEntry));
+ tc->start_time = PR_Now();
+ return tc;
+}
+
+void destroy_timecard(Timecard* tc) {
+ free(tc->entries);
+ free(tc);
+}
+
+void stamp_timecard(Timecard* tc, const char* event, const char* file,
+ unsigned int line, const char* function) {
+ TimecardEntry* entry = NULL;
+
+ /* Trim the path component from the filename */
+ const char* last_slash = file;
+ while (*file) {
+ if (*file == '/' || *file == '\\') {
+ last_slash = file;
+ }
+ file++;
+ }
+ file = last_slash;
+ if (*file == '/' || *file == '\\') {
+ file++;
+ }
+
+ /* Ensure there is enough space left in the entries list */
+ if (tc->curr_entry == tc->entries_allocated) {
+ tc->entries_allocated *= 2;
+ tc->entries = moz_xrealloc(tc->entries,
+ tc->entries_allocated * sizeof(TimecardEntry));
+ }
+
+ /* Record the data into the timecard entry */
+ entry = &tc->entries[tc->curr_entry];
+ entry->timestamp = PR_Now();
+ entry->event = event;
+ entry->file = file;
+ entry->line = line;
+ entry->function = function;
+ tc->curr_entry++;
+}
+
+void print_timecard(Timecard* tc) {
+ size_t i;
+ TimecardEntry* entry;
+ size_t event_width = 5;
+ size_t file_width = 4;
+ size_t function_width = 8;
+ size_t line_width;
+ PRTime offset, delta;
+
+ for (i = 0; i < tc->curr_entry; i++) {
+ entry = &tc->entries[i];
+ if (strlen(entry->event) > event_width) {
+ event_width = strlen(entry->event);
+ }
+ if (strlen(entry->file) > file_width) {
+ file_width = strlen(entry->file);
+ }
+ if (strlen(entry->function) > function_width) {
+ function_width = strlen(entry->function);
+ }
+ }
+
+ printf("\nTimecard created %4ld.%6.6ld\n\n",
+ (long)(tc->start_time / PR_USEC_PER_SEC),
+ (long)(tc->start_time % PR_USEC_PER_SEC));
+
+ line_width =
+ 1 + 11 + 11 + event_width + file_width + 6 + function_width + (4 * 3);
+
+ printf(" %-11s | %-11s | %-*s | %-*s | %-*s\n", "Timestamp", "Delta",
+ (int)event_width, "Event", (int)file_width + 6, "File",
+ (int)function_width, "Function");
+
+ for (i = 0; i <= line_width; i++) {
+ printf("=");
+ }
+ printf("\n");
+
+ for (i = 0; i < tc->curr_entry; i++) {
+ entry = &tc->entries[i];
+ offset = entry->timestamp - tc->start_time;
+ if (i > 0) {
+ delta = entry->timestamp - tc->entries[i - 1].timestamp;
+ } else {
+ delta = entry->timestamp - tc->start_time;
+ }
+ printf(" %4ld.%6.6ld | %4ld.%6.6ld | %-*s | %*s:%-5d | %-*s\n",
+ (long)(offset / PR_USEC_PER_SEC), (long)(offset % PR_USEC_PER_SEC),
+ (long)(delta / PR_USEC_PER_SEC), (long)(delta % PR_USEC_PER_SEC),
+ (int)event_width, entry->event, (int)file_width, entry->file,
+ entry->line, (int)function_width, entry->function);
+ }
+ printf("\n");
+}
diff --git a/dom/media/webrtc/common/time_profiling/timecard.h b/dom/media/webrtc/common/time_profiling/timecard.h
new file mode 100644
index 0000000000..38d4a8d1ec
--- /dev/null
+++ b/dom/media/webrtc/common/time_profiling/timecard.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef timecard_h__
+#define timecard_h__
+
+#include <stdlib.h>
+#include "prtime.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define STAMP_TIMECARD(card, event) \
+ do { \
+ if (card) { \
+ stamp_timecard((card), (event), __FILE__, __LINE__, __FUNCTION__); \
+ } \
+ } while (0)
+
+#define TIMECARD_INITIAL_TABLE_SIZE 16
+
+/*
+ * The "const char *" members of this structure point to static strings.
+ * We do not own them, and should not attempt to deallocate them.
+ */
+
+typedef struct {
+ PRTime timestamp;
+ const char* event;
+ const char* file;
+ unsigned int line;
+ const char* function;
+} TimecardEntry;
+
+typedef struct Timecard {
+ size_t curr_entry;
+ size_t entries_allocated;
+ TimecardEntry* entries;
+ PRTime start_time;
+} Timecard;
+
+/**
+ * Creates a new Timecard structure for tracking events.
+ */
+Timecard* create_timecard();
+
+/**
+ * Frees the memory associated with a timecard. After returning, the
+ * timecard pointed to by tc is no longer valid.
+ */
+void destroy_timecard(Timecard* tc);
+
+/**
+ * Records a new event in the indicated timecard. This should not be
+ * called directly; code should instead use the STAMP_TIMECARD macro,
+ * above.
+ */
+void stamp_timecard(Timecard* tc, const char* event, const char* file,
+ unsigned int line, const char* function);
+
+/**
+ * Formats and outputs the contents of a timecard onto stdout.
+ */
+void print_timecard(Timecard* tc);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/dom/media/webrtc/jsapi/MediaTransportHandler.cpp b/dom/media/webrtc/jsapi/MediaTransportHandler.cpp
new file mode 100644
index 0000000000..3ee95f36f6
--- /dev/null
+++ b/dom/media/webrtc/jsapi/MediaTransportHandler.cpp
@@ -0,0 +1,1727 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MediaTransportHandler.h"
+#include "MediaTransportHandlerIPC.h"
+#include "transport/nricemediastream.h"
+#include "transport/nriceresolver.h"
+#include "transport/transportflow.h"
+#include "transport/transportlayerice.h"
+#include "transport/transportlayerdtls.h"
+#include "transport/transportlayersrtp.h"
+
+// Config stuff
+#include "mozilla/dom/RTCConfigurationBinding.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_network.h"
+
+// Parsing STUN/TURN URIs
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsURLHelper.h"
+#include "nsIURLParser.h"
+
+// Logging stuff
+#include "common/browser_logging/CSFLog.h"
+
+// For fetching ICE logging
+#include "transport/rlogconnector.h"
+
+// DTLS
+#include "sdp/SdpAttribute.h"
+
+#include "transport/runnable_utils.h"
+
+#include "mozilla/Algorithm.h"
+#include "mozilla/Telemetry.h"
+
+#include "mozilla/dom/RTCStatsReportBinding.h"
+
+#include "nss.h" // For NSS_NoDB_Init
+#include "mozilla/PublicSSL.h" // For psm::InitializeCipherSuite
+
+#include "nsISocketTransportService.h"
+#include "nsDNSService2.h"
+
+#include <string>
+#include <vector>
+#include <map>
+
+#ifdef MOZ_GECKO_PROFILER
+# include "mozilla/ProfilerMarkers.h"
+
+# define MEDIA_TRANSPORT_HANDLER_PACKET_RECEIVED(aPacket) \
+ PROFILER_MARKER_TEXT("WebRTC Packet Received", MEDIA_RT, {}, \
+ ProfilerString8View::WrapNullTerminatedString( \
+ PacketTypeToString((aPacket).type())));
+#else
+# define MEDIA_TRANSPORT_HANDLER_PACKET_RECEIVED(aPacket)
+#endif
+
+namespace mozilla {
+
+static const char* mthLogTag = "MediaTransportHandler";
+#ifdef LOGTAG
+# undef LOGTAG
+#endif
+#define LOGTAG mthLogTag
+
+class MediaTransportHandlerSTS : public MediaTransportHandler,
+ public sigslot::has_slots<> {
+ public:
+ explicit MediaTransportHandlerSTS(nsISerialEventTarget* aCallbackThread);
+
+ RefPtr<IceLogPromise> GetIceLog(const nsCString& aPattern) override;
+ void ClearIceLog() override;
+ void EnterPrivateMode() override;
+ void ExitPrivateMode() override;
+
+ void CreateIceCtx(const std::string& aName) override;
+
+ nsresult SetIceConfig(const nsTArray<dom::RTCIceServer>& aIceServers,
+ dom::RTCIceTransportPolicy aIcePolicy) override;
+
+ // We will probably be able to move the proxy lookup stuff into
+ // this class once we move mtransport to its own process.
+ void SetProxyConfig(NrSocketProxyConfig&& aProxyConfig) override;
+
+ void EnsureProvisionalTransport(const std::string& aTransportId,
+ const std::string& aUfrag,
+ const std::string& aPwd,
+ int aComponentCount) override;
+
+ void SetTargetForDefaultLocalAddressLookup(const std::string& aTargetIp,
+ uint16_t aTargetPort) override;
+
+ // We set default-route-only as late as possible because it depends on what
+ // capture permissions have been granted on the window, which could easily
+ // change between Init (ie; when the PC is created) and StartIceGathering
+ // (ie; when we set the local description).
+ void StartIceGathering(bool aDefaultRouteOnly, bool aObfuscateHostAddresses,
+ // This will go away once mtransport moves to its
+ // own process, because we won't need to get this
+ // via IPC anymore
+ const nsTArray<NrIceStunAddr>& aStunAddrs) override;
+
+ void ActivateTransport(
+ const std::string& aTransportId, const std::string& aLocalUfrag,
+ const std::string& aLocalPwd, size_t aComponentCount,
+ const std::string& aUfrag, const std::string& aPassword,
+ const nsTArray<uint8_t>& aKeyDer, const nsTArray<uint8_t>& aCertDer,
+ SSLKEAType aAuthType, bool aDtlsClient, const DtlsDigestList& aDigests,
+ bool aPrivacyRequested) override;
+
+ void RemoveTransportsExcept(
+ const std::set<std::string>& aTransportIds) override;
+
+ void StartIceChecks(bool aIsControlling,
+ const std::vector<std::string>& aIceOptions) override;
+
+ void AddIceCandidate(const std::string& aTransportId,
+ const std::string& aCandidate, const std::string& aUfrag,
+ const std::string& aObfuscatedAddress) override;
+
+ void UpdateNetworkState(bool aOnline) override;
+
+ void SendPacket(const std::string& aTransportId,
+ MediaPacket&& aPacket) override;
+
+ RefPtr<dom::RTCStatsPromise> GetIceStats(const std::string& aTransportId,
+ DOMHighResTimeStamp aNow) override;
+
+ void Shutdown();
+
+ private:
+ void Destroy() override;
+ void Destroy_s();
+ void DestroyFinal();
+ void Shutdown_s();
+ RefPtr<TransportFlow> CreateTransportFlow(
+ const std::string& aTransportId, bool aIsRtcp,
+ const RefPtr<DtlsIdentity>& aDtlsIdentity, bool aDtlsClient,
+ const DtlsDigestList& aDigests, bool aPrivacyRequested);
+
+ struct Transport {
+ RefPtr<TransportFlow> mFlow;
+ RefPtr<TransportFlow> mRtcpFlow;
+ };
+
+ using MediaTransportHandler::OnAlpnNegotiated;
+ using MediaTransportHandler::OnCandidate;
+ using MediaTransportHandler::OnConnectionStateChange;
+ using MediaTransportHandler::OnEncryptedSending;
+ using MediaTransportHandler::OnGatheringStateChange;
+ using MediaTransportHandler::OnPacketReceived;
+ using MediaTransportHandler::OnRtcpStateChange;
+ using MediaTransportHandler::OnStateChange;
+
+ void OnGatheringStateChange(NrIceCtx* aIceCtx,
+ NrIceCtx::GatheringState aState);
+ void OnConnectionStateChange(NrIceCtx* aIceCtx,
+ NrIceCtx::ConnectionState aState);
+ void OnCandidateFound(NrIceMediaStream* aStream,
+ const std::string& aCandidate,
+ const std::string& aUfrag, const std::string& aMDNSAddr,
+ const std::string& aActualAddr);
+ void OnStateChange(TransportLayer* aLayer, TransportLayer::State);
+ void OnRtcpStateChange(TransportLayer* aLayer, TransportLayer::State);
+ void PacketReceived(TransportLayer* aLayer, MediaPacket& aPacket);
+ void EncryptedPacketSending(TransportLayer* aLayer, MediaPacket& aPacket);
+ RefPtr<TransportFlow> GetTransportFlow(const std::string& aTransportId,
+ bool aIsRtcp) const;
+ void GetIceStats(const NrIceMediaStream& aStream, DOMHighResTimeStamp aNow,
+ dom::RTCStatsCollection* aStats) const;
+
+ virtual ~MediaTransportHandlerSTS() = default;
+ nsCOMPtr<nsISerialEventTarget> mStsThread;
+ RefPtr<NrIceCtx> mIceCtx;
+ RefPtr<NrIceResolver> mDNSResolver;
+ std::map<std::string, Transport> mTransports;
+ bool mObfuscateHostAddresses = false;
+ bool mTurnDisabled = false;
+ uint32_t mMinDtlsVersion = 0;
+ uint32_t mMaxDtlsVersion = 0;
+ bool mForceNoHost = false;
+ Maybe<NrIceCtx::NatSimulatorConfig> mNatConfig;
+
+ std::set<std::string> mSignaledAddresses;
+
+ // Init can only be done on main, but we want this to be usable on any thread
+ using InitPromise = MozPromise<bool, std::string, false>;
+ RefPtr<InitPromise> mInitPromise;
+};
+
+/* static */
+already_AddRefed<MediaTransportHandler> MediaTransportHandler::Create(
+ nsISerialEventTarget* aCallbackThread) {
+ RefPtr<MediaTransportHandler> result;
+ if (XRE_IsContentProcess() &&
+ Preferences::GetBool("media.peerconnection.mtransport_process") &&
+ StaticPrefs::network_process_enabled()) {
+ result = new MediaTransportHandlerIPC(aCallbackThread);
+ } else {
+ result = new MediaTransportHandlerSTS(aCallbackThread);
+ }
+ result->Initialize();
+ return result.forget();
+}
+
+class STSShutdownHandler : public nsISTSShutdownObserver {
+ public:
+ NS_DECL_ISUPPORTS
+
+ // Lazy singleton
+ static RefPtr<STSShutdownHandler>& Instance() {
+ MOZ_ASSERT(NS_IsMainThread());
+ static RefPtr<STSShutdownHandler> sHandler(new STSShutdownHandler);
+ return sHandler;
+ }
+
+ void Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+ for (const auto& handler : mHandlers) {
+ handler->Shutdown();
+ }
+ mHandlers.clear();
+ }
+
+ STSShutdownHandler() {
+ CSFLogDebug(LOGTAG, "%s", __func__);
+ nsresult res;
+ nsCOMPtr<nsISocketTransportService> sts =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &res);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(res));
+ MOZ_RELEASE_ASSERT(sts);
+ sts->AddShutdownObserver(this);
+ }
+
+ NS_IMETHOD Observe() override {
+ CSFLogDebug(LOGTAG, "%s", __func__);
+ Shutdown();
+ nsresult res;
+ nsCOMPtr<nsISocketTransportService> sts =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &res);
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(res));
+ MOZ_RELEASE_ASSERT(sts);
+ sts->RemoveShutdownObserver(this);
+ Instance() = nullptr;
+ return NS_OK;
+ }
+
+ void Register(MediaTransportHandlerSTS* aHandler) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mHandlers.insert(aHandler);
+ }
+
+ void Deregister(MediaTransportHandlerSTS* aHandler) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mHandlers.erase(aHandler);
+ }
+
+ private:
+ virtual ~STSShutdownHandler() = default;
+
+ // Raw ptrs, registered on init, deregistered on destruction, all on main
+ std::set<MediaTransportHandlerSTS*> mHandlers;
+};
+
+NS_IMPL_ISUPPORTS(STSShutdownHandler, nsISTSShutdownObserver);
+
+MediaTransportHandlerSTS::MediaTransportHandlerSTS(
+ nsISerialEventTarget* aCallbackThread)
+ : MediaTransportHandler(aCallbackThread) {
+ nsresult rv;
+ mStsThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (!mStsThread) {
+ MOZ_CRASH();
+ }
+
+ RLogConnector::CreateInstance();
+
+ CSFLogDebug(LOGTAG, "%s done %p", __func__, this);
+
+ // We do not set up mDNSService here, because we are not running on main (we
+ // use PBackground), and the DNS service asserts.
+}
+
+static NrIceCtx::Policy toNrIcePolicy(dom::RTCIceTransportPolicy aPolicy) {
+ switch (aPolicy) {
+ case dom::RTCIceTransportPolicy::Relay:
+ return NrIceCtx::ICE_POLICY_RELAY;
+ case dom::RTCIceTransportPolicy::All:
+ return NrIceCtx::ICE_POLICY_ALL;
+ default:
+ MOZ_CRASH();
+ }
+ return NrIceCtx::ICE_POLICY_ALL;
+}
+
+// list of known acceptable ports for webrtc
+int16_t gGoodWebrtcPortList[] = {
+ 53, // Some deplyoments use DNS port to punch through overzealous NATs
+ 3478, // stun or turn
+ 5349, // stuns or turns
+ 0, // Sentinel value: This MUST be zero
+};
+
+static nsresult addNrIceServer(const nsString& aIceUrl,
+ const dom::RTCIceServer& aIceServer,
+ std::vector<NrIceStunServer>* aStunServersOut,
+ std::vector<NrIceTurnServer>* aTurnServersOut) {
+ // Without STUN/TURN handlers, NS_NewURI returns nsSimpleURI rather than
+ // nsStandardURL. To parse STUN/TURN URI's to spec
+ // http://tools.ietf.org/html/draft-nandakumar-rtcweb-stun-uri-02#section-3
+ // http://tools.ietf.org/html/draft-petithuguenin-behave-turn-uri-03#section-3
+ // we parse out the query-string, and use ParseAuthority() on the rest
+ RefPtr<nsIURI> url;
+ nsresult rv = NS_NewURI(getter_AddRefs(url), aIceUrl);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool isStun = url->SchemeIs("stun");
+ bool isStuns = url->SchemeIs("stuns");
+ bool isTurn = url->SchemeIs("turn");
+ bool isTurns = url->SchemeIs("turns");
+ if (!(isStun || isStuns || isTurn || isTurns)) {
+ return NS_ERROR_FAILURE;
+ }
+ if (isStuns) {
+ return NS_OK; // TODO: Support STUNS (Bug 1056934)
+ }
+
+ nsAutoCString spec;
+ rv = url->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // TODO(jib@mozilla.com): Revisit once nsURI supports STUN/TURN (Bug 833509)
+ int32_t port;
+ nsAutoCString host;
+ nsAutoCString transport;
+ {
+ uint32_t hostPos;
+ int32_t hostLen;
+ nsAutoCString path;
+ rv = url->GetPathQueryRef(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Tolerate query-string + parse 'transport=[udp|tcp]' by hand.
+ int32_t questionmark = path.FindChar('?');
+ if (questionmark >= 0) {
+ const nsCString match = "transport="_ns;
+
+ for (int32_t i = questionmark, endPos; i >= 0; i = endPos) {
+ endPos = path.FindCharInSet("&", i + 1);
+ const nsDependentCSubstring fieldvaluepair =
+ Substring(path, i + 1, endPos);
+ if (StringBeginsWith(fieldvaluepair, match)) {
+ transport = Substring(fieldvaluepair, match.Length());
+ ToLowerCase(transport);
+ }
+ }
+ path.SetLength(questionmark);
+ }
+
+ rv = net_GetAuthURLParser()->ParseAuthority(
+ path.get(), static_cast<int>(path.Length()), nullptr, nullptr, nullptr,
+ nullptr, &hostPos, &hostLen, &port);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!hostLen) {
+ return NS_ERROR_FAILURE;
+ }
+ if (hostPos > 1) {
+ /* The username was removed */
+ return NS_ERROR_FAILURE;
+ }
+ path.Mid(host, hostPos, hostLen);
+ // Strip off brackets around IPv6 literals
+ host.Trim("[]");
+ }
+ if (port == -1) port = (isStuns || isTurns) ? 5349 : 3478;
+
+ // First check the known good ports for webrtc
+ bool goodPort = false;
+ for (int i = 0; !goodPort && gGoodWebrtcPortList[i]; i++) {
+ if (port == gGoodWebrtcPortList[i]) {
+ goodPort = true;
+ }
+ }
+
+ // if not in the list of known good ports for webrtc, check
+ // the generic block list using NS_CheckPortSafety.
+ if (!goodPort) {
+ rv = NS_CheckPortSafety(port, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (isStuns || isTurns) {
+ // Should we barf if transport is set to udp or something?
+ transport = kNrIceTransportTls;
+ }
+
+ if (transport.IsEmpty()) {
+ transport = kNrIceTransportUdp;
+ }
+
+ if (isTurn || isTurns) {
+ std::string pwd(
+ NS_ConvertUTF16toUTF8(aIceServer.mCredential.Value()).get());
+ std::string username(
+ NS_ConvertUTF16toUTF8(aIceServer.mUsername.Value()).get());
+
+ std::vector<unsigned char> password(pwd.begin(), pwd.end());
+
+ UniquePtr<NrIceTurnServer> server(NrIceTurnServer::Create(
+ host.get(), port, username, password, transport.get()));
+ if (!server) {
+ return NS_ERROR_FAILURE;
+ }
+ if (server->HasFqdn()) {
+ // Add an IPv4 entry, then an IPv6 entry
+ aTurnServersOut->push_back(*server);
+ server->SetUseIPv6IfFqdn();
+ }
+ aTurnServersOut->emplace_back(std::move(*server));
+ } else {
+ UniquePtr<NrIceStunServer> server(
+ NrIceStunServer::Create(host.get(), port, transport.get()));
+ if (!server) {
+ return NS_ERROR_FAILURE;
+ }
+ if (server->HasFqdn()) {
+ // Add an IPv4 entry, then an IPv6 entry
+ aStunServersOut->push_back(*server);
+ server->SetUseIPv6IfFqdn();
+ }
+ aStunServersOut->emplace_back(std::move(*server));
+ }
+ return NS_OK;
+}
+
+/* static */
+nsresult MediaTransportHandler::ConvertIceServers(
+ const nsTArray<dom::RTCIceServer>& aIceServers,
+ std::vector<NrIceStunServer>* aStunServers,
+ std::vector<NrIceTurnServer>* aTurnServers) {
+ for (const auto& iceServer : aIceServers) {
+ NS_ENSURE_STATE(iceServer.mUrls.WasPassed());
+ NS_ENSURE_STATE(iceServer.mUrls.Value().IsStringSequence());
+ for (const auto& iceUrl : iceServer.mUrls.Value().GetAsStringSequence()) {
+ nsresult rv =
+ addNrIceServer(iceUrl, iceServer, aStunServers, aTurnServers);
+ if (NS_FAILED(rv)) {
+ CSFLogError(LOGTAG, "%s: invalid STUN/TURN server: %s", __FUNCTION__,
+ NS_ConvertUTF16toUTF8(iceUrl).get());
+ return rv;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+static NrIceCtx::GlobalConfig GetGlobalConfig() {
+ NrIceCtx::GlobalConfig config;
+ config.mAllowLinkLocal =
+ Preferences::GetBool("media.peerconnection.ice.link_local", false);
+ config.mAllowLoopback =
+ Preferences::GetBool("media.peerconnection.ice.loopback", false);
+ config.mTcpEnabled =
+ Preferences::GetBool("media.peerconnection.ice.tcp", false);
+ config.mStunClientMaxTransmits = Preferences::GetInt(
+ "media.peerconnection.ice.stun_client_maximum_transmits",
+ config.mStunClientMaxTransmits);
+ config.mTrickleIceGracePeriod =
+ Preferences::GetInt("media.peerconnection.ice.trickle_grace_period",
+ config.mTrickleIceGracePeriod);
+ config.mIceTcpSoSockCount = Preferences::GetInt(
+ "media.peerconnection.ice.tcp_so_sock_count", config.mIceTcpSoSockCount);
+ config.mIceTcpListenBacklog =
+ Preferences::GetInt("media.peerconnection.ice.tcp_listen_backlog",
+ config.mIceTcpListenBacklog);
+ (void)Preferences::GetCString("media.peerconnection.ice.force_interface",
+ config.mForceNetInterface);
+ return config;
+}
+
+static Maybe<NrIceCtx::NatSimulatorConfig> GetNatConfig() {
+ bool block_tcp = Preferences::GetBool(
+ "media.peerconnection.nat_simulator.block_tcp", false);
+ bool block_udp = Preferences::GetBool(
+ "media.peerconnection.nat_simulator.block_udp", false);
+ bool block_tls = Preferences::GetBool(
+ "media.peerconnection.nat_simulator.block_tls", false);
+ int error_code_for_drop = Preferences::GetInt(
+ "media.peerconnection.nat_simulator.error_code_for_drop", 0);
+ nsAutoCString mapping_type;
+ (void)Preferences::GetCString(
+ "media.peerconnection.nat_simulator.mapping_type", mapping_type);
+ nsAutoCString filtering_type;
+ (void)Preferences::GetCString(
+ "media.peerconnection.nat_simulator.filtering_type", filtering_type);
+ nsAutoCString redirect_address;
+ (void)Preferences::GetCString(
+ "media.peerconnection.nat_simulator.redirect_address", redirect_address);
+ nsAutoCString redirect_targets;
+ (void)Preferences::GetCString(
+ "media.peerconnection.nat_simulator.redirect_targets", redirect_targets);
+
+ if (block_udp || block_tcp || block_tls || !mapping_type.IsEmpty() ||
+ !filtering_type.IsEmpty() || !redirect_address.IsEmpty()) {
+ CSFLogDebug(LOGTAG, "NAT filtering type: %s", filtering_type.get());
+ CSFLogDebug(LOGTAG, "NAT mapping type: %s", mapping_type.get());
+ NrIceCtx::NatSimulatorConfig natConfig;
+ natConfig.mBlockUdp = block_udp;
+ natConfig.mBlockTcp = block_tcp;
+ natConfig.mBlockTls = block_tls;
+ natConfig.mErrorCodeForDrop = error_code_for_drop;
+ natConfig.mFilteringType = filtering_type;
+ natConfig.mMappingType = mapping_type;
+ if (redirect_address.Length()) {
+ CSFLogDebug(LOGTAG, "Redirect address: %s", redirect_address.get());
+ CSFLogDebug(LOGTAG, "Redirect targets: %s", redirect_targets.get());
+ natConfig.mRedirectAddress = redirect_address;
+ std::stringstream str(redirect_targets.Data());
+ std::string target;
+ while (getline(str, target, ',')) {
+ CSFLogDebug(LOGTAG, "Adding target: %s", target.c_str());
+ natConfig.mRedirectTargets.AppendElement(target);
+ }
+ }
+ return Some(natConfig);
+ }
+ return Nothing();
+}
+
+void MediaTransportHandlerSTS::CreateIceCtx(const std::string& aName) {
+ mInitPromise = InvokeAsync(
+ GetMainThreadSerialEventTarget(), __func__,
+ [=, self = RefPtr<MediaTransportHandlerSTS>(this)]() {
+ CSFLogDebug(LOGTAG, "%s starting", __func__);
+ if (!NSS_IsInitialized()) {
+ if (NSS_NoDB_Init(nullptr) != SECSuccess) {
+ MOZ_CRASH();
+ return InitPromise::CreateAndReject("NSS_NoDB_Init failed",
+ __func__);
+ }
+
+ if (NS_FAILED(mozilla::psm::InitializeCipherSuite())) {
+ MOZ_CRASH();
+ return InitPromise::CreateAndReject("InitializeCipherSuite failed",
+ __func__);
+ }
+
+ mozilla::psm::DisableMD5();
+ }
+
+ static bool globalInitDone = false;
+ if (!globalInitDone) {
+ // Ensure the DNS service is initted for the first time on main
+ DebugOnly<RefPtr<nsIDNSService>> dnsService =
+ RefPtr<nsIDNSService>(nsDNSService::GetXPCOMSingleton());
+ MOZ_ASSERT(dnsService.value);
+ mStsThread->Dispatch(
+ WrapRunnableNM(&NrIceCtx::InitializeGlobals, GetGlobalConfig()),
+ NS_DISPATCH_NORMAL);
+ globalInitDone = true;
+ }
+
+ // Give us a way to globally turn off TURN support
+ mTurnDisabled =
+ Preferences::GetBool("media.peerconnection.turn.disable", false);
+ // We are reading these here, because when we setup the DTLS transport
+ // we are on the wrong thread to read prefs
+ mMinDtlsVersion =
+ Preferences::GetUint("media.peerconnection.dtls.version.min");
+ mMaxDtlsVersion =
+ Preferences::GetUint("media.peerconnection.dtls.version.max");
+ mForceNoHost =
+ Preferences::GetBool("media.peerconnection.ice.no_host", false);
+ mNatConfig = GetNatConfig();
+
+ MOZ_RELEASE_ASSERT(STSShutdownHandler::Instance());
+ STSShutdownHandler::Instance()->Register(this);
+
+ return InvokeAsync(
+ mStsThread, __func__,
+ [=, self = RefPtr<MediaTransportHandlerSTS>(this)]() {
+ mIceCtx = NrIceCtx::Create(aName);
+ if (!mIceCtx) {
+ return InitPromise::CreateAndReject("NrIceCtx::Create failed",
+ __func__);
+ }
+
+ mIceCtx->SignalGatheringStateChange.connect(
+ this, &MediaTransportHandlerSTS::OnGatheringStateChange);
+ mIceCtx->SignalConnectionStateChange.connect(
+ this, &MediaTransportHandlerSTS::OnConnectionStateChange);
+
+ mDNSResolver = new NrIceResolver;
+ nsresult rv;
+ if (NS_FAILED(rv = mDNSResolver->Init())) {
+ CSFLogError(LOGTAG, "%s: Failed to initialize dns resolver",
+ __FUNCTION__);
+ return InitPromise::CreateAndReject(
+ "Failed to initialize dns resolver", __func__);
+ }
+ if (NS_FAILED(rv = mIceCtx->SetResolver(
+ mDNSResolver->AllocateResolver()))) {
+ CSFLogError(LOGTAG, "%s: Failed to get dns resolver",
+ __FUNCTION__);
+ return InitPromise::CreateAndReject(
+ "Failed to get dns resolver", __func__);
+ }
+
+ CSFLogDebug(LOGTAG, "%s done", __func__);
+ return InitPromise::CreateAndResolve(true, __func__);
+ });
+ });
+}
+
+nsresult MediaTransportHandlerSTS::SetIceConfig(
+ const nsTArray<dom::RTCIceServer>& aIceServers,
+ dom::RTCIceTransportPolicy aIcePolicy) {
+ // We rely on getting an error when this happens, so do it up front.
+ std::vector<NrIceStunServer> stunServers;
+ std::vector<NrIceTurnServer> turnServers;
+ nsresult rv = ConvertIceServers(aIceServers, &stunServers, &turnServers);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ MOZ_RELEASE_ASSERT(mInitPromise);
+
+ mInitPromise->Then(
+ mStsThread, __func__,
+ [=, self = RefPtr<MediaTransportHandlerSTS>(this)]() {
+ if (!mIceCtx) {
+ CSFLogError(LOGTAG, "%s: mIceCtx is null", __FUNCTION__);
+ return;
+ }
+ NrIceCtx::Config config;
+ config.mPolicy = toNrIcePolicy(aIcePolicy);
+ if (config.mPolicy == NrIceCtx::ICE_POLICY_ALL && mForceNoHost) {
+ config.mPolicy = NrIceCtx::ICE_POLICY_NO_HOST;
+ }
+ config.mNatSimulatorConfig = mNatConfig;
+
+ nsresult rv;
+
+ if (NS_FAILED(rv = mIceCtx->SetStunServers(stunServers))) {
+ CSFLogError(LOGTAG, "%s: Failed to set stun servers", __FUNCTION__);
+ return;
+ }
+ if (!mTurnDisabled) {
+ if (NS_FAILED(rv = mIceCtx->SetTurnServers(turnServers))) {
+ CSFLogError(LOGTAG, "%s: Failed to set turn servers", __FUNCTION__);
+ return;
+ }
+ } else if (!turnServers.empty()) {
+ CSFLogError(LOGTAG, "%s: Setting turn servers disabled",
+ __FUNCTION__);
+ }
+ if (NS_FAILED(rv = mIceCtx->SetIceConfig(config))) {
+ CSFLogError(LOGTAG, "%s: Failed to set config", __FUNCTION__);
+ }
+ });
+
+ return NS_OK;
+}
+
+void MediaTransportHandlerSTS::Shutdown() {
+ CSFLogDebug(LOGTAG, "%s", __func__);
+ MOZ_ASSERT(NS_IsMainThread());
+ mStsThread->Dispatch(NewNonOwningRunnableMethod(
+ __func__, this, &MediaTransportHandlerSTS::Shutdown_s));
+}
+
+void MediaTransportHandlerSTS::Shutdown_s() {
+ CSFLogDebug(LOGTAG, "%s", __func__);
+ disconnect_all();
+ // Clear the transports before destroying the ice ctx so that
+ // the close_notify alerts have a chance to be sent as the
+ // TransportFlow destructors execute.
+ mTransports.clear();
+ if (mIceCtx) {
+ NrIceStats stats = mIceCtx->Destroy();
+ CSFLogDebug(LOGTAG,
+ "Ice Telemetry: stun (retransmits: %d)"
+ " turn (401s: %d 403s: %d 438s: %d)",
+ stats.stun_retransmits, stats.turn_401s, stats.turn_403s,
+ stats.turn_438s);
+ }
+ mIceCtx = nullptr;
+ mDNSResolver = nullptr;
+}
+
+void MediaTransportHandlerSTS::Destroy() {
+ CSFLogDebug(LOGTAG, "%s %p", __func__, this);
+ // Our "destruction tour" starts on main, because we need to deregister.
+ if (!NS_IsMainThread()) {
+ GetMainThreadSerialEventTarget()->Dispatch(
+ NewNonOwningRunnableMethod("MediaTransportHandlerSTS::Destroy", this,
+ &MediaTransportHandlerSTS::Destroy));
+ return;
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+ if (STSShutdownHandler::Instance()) {
+ STSShutdownHandler::Instance()->Deregister(this);
+ Shutdown();
+ }
+
+ // mIceCtx still has a reference to us via sigslot! We must dispach to STS,
+ // and clean up there. However, by the time _that_ happens, we may have
+ // dispatched a signal callback to mCallbackThread, so we have to dispatch
+ // the final destruction to mCallbackThread.
+ nsresult rv = mStsThread->Dispatch(
+ NewNonOwningRunnableMethod("MediaTransportHandlerSTS::Destroy_s", this,
+ &MediaTransportHandlerSTS::Destroy_s));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ CSFLogError(LOGTAG,
+ "Unable to dispatch to STS: why has the XPCOM shutdown handler "
+ "not been invoked?");
+ delete this;
+ }
+}
+
+void MediaTransportHandlerSTS::Destroy_s() {
+ if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) {
+ nsresult rv = mCallbackThread->Dispatch(NewNonOwningRunnableMethod(
+ __func__, this, &MediaTransportHandlerSTS::DestroyFinal));
+ if (NS_SUCCEEDED(rv)) {
+ return;
+ }
+ }
+
+ DestroyFinal();
+}
+
+void MediaTransportHandlerSTS::DestroyFinal() { delete this; }
+
+void MediaTransportHandlerSTS::SetProxyConfig(
+ NrSocketProxyConfig&& aProxyConfig) {
+ MOZ_RELEASE_ASSERT(mInitPromise);
+
+ mInitPromise->Then(
+ mStsThread, __func__,
+ [this, self = RefPtr<MediaTransportHandlerSTS>(this),
+ aProxyConfig = std::move(aProxyConfig)]() mutable {
+ if (!mIceCtx) {
+ return; // Probably due to XPCOM shutdown
+ }
+
+ mIceCtx->SetProxyConfig(std::move(aProxyConfig));
+ },
+ [](const std::string& aError) {});
+}
+
+void MediaTransportHandlerSTS::EnsureProvisionalTransport(
+ const std::string& aTransportId, const std::string& aUfrag,
+ const std::string& aPwd, int aComponentCount) {
+ MOZ_RELEASE_ASSERT(mInitPromise);
+
+ mInitPromise->Then(
+ mStsThread, __func__,
+ [=, self = RefPtr<MediaTransportHandlerSTS>(this)]() {
+ if (!mIceCtx) {
+ return; // Probably due to XPCOM shutdown
+ }
+
+ RefPtr<NrIceMediaStream> stream(mIceCtx->GetStream(aTransportId));
+ if (!stream) {
+ CSFLogDebug(LOGTAG, "%s: Creating ICE media stream=%s components=%d",
+ mIceCtx->name().c_str(), aTransportId.c_str(),
+ aComponentCount);
+
+ std::ostringstream os;
+ os << mIceCtx->name() << " transport-id=" << aTransportId;
+ stream =
+ mIceCtx->CreateStream(aTransportId, os.str(), aComponentCount);
+
+ if (!stream) {
+ CSFLogError(LOGTAG, "Failed to create ICE stream.");
+ return;
+ }
+
+ stream->SignalCandidate.connect(
+ this, &MediaTransportHandlerSTS::OnCandidateFound);
+ }
+
+ // Begins an ICE restart if this stream has a different ufrag/pwd
+ stream->SetIceCredentials(aUfrag, aPwd);
+
+ // Make sure there's an entry in mTransports
+ mTransports[aTransportId];
+ },
+ [](const std::string& aError) {});
+}
+
+void MediaTransportHandlerSTS::ActivateTransport(
+ const std::string& aTransportId, const std::string& aLocalUfrag,
+ const std::string& aLocalPwd, size_t aComponentCount,
+ const std::string& aUfrag, const std::string& aPassword,
+ const nsTArray<uint8_t>& aKeyDer, const nsTArray<uint8_t>& aCertDer,
+ SSLKEAType aAuthType, bool aDtlsClient, const DtlsDigestList& aDigests,
+ bool aPrivacyRequested) {
+ MOZ_RELEASE_ASSERT(mInitPromise);
+
+ mInitPromise->Then(
+ mStsThread, __func__,
+ [=, keyDer = aKeyDer.Clone(), certDer = aCertDer.Clone(),
+ self = RefPtr<MediaTransportHandlerSTS>(this)]() {
+ if (!mIceCtx) {
+ return; // Probably due to XPCOM shutdown
+ }
+
+ MOZ_ASSERT(aComponentCount);
+ RefPtr<DtlsIdentity> dtlsIdentity(
+ DtlsIdentity::Deserialize(keyDer, certDer, aAuthType));
+ if (!dtlsIdentity) {
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ RefPtr<NrIceMediaStream> stream(mIceCtx->GetStream(aTransportId));
+ if (!stream) {
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ CSFLogDebug(LOGTAG, "%s: Activating ICE media stream=%s components=%u",
+ mIceCtx->name().c_str(), aTransportId.c_str(),
+ static_cast<unsigned>(aComponentCount));
+
+ std::vector<std::string> attrs;
+ attrs.reserve(2 /* ufrag + pwd */);
+ attrs.push_back("ice-ufrag:" + aUfrag);
+ attrs.push_back("ice-pwd:" + aPassword);
+
+ // If we started an ICE restart in EnsureProvisionalTransport, this is
+ // where we decide whether to commit or rollback.
+ nsresult rv = stream->ConnectToPeer(aLocalUfrag, aLocalPwd, attrs);
+ if (NS_FAILED(rv)) {
+ CSFLogError(LOGTAG, "Couldn't parse ICE attributes, rv=%u",
+ static_cast<unsigned>(rv));
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ Transport transport = mTransports[aTransportId];
+ if (!transport.mFlow) {
+ transport.mFlow =
+ CreateTransportFlow(aTransportId, false, dtlsIdentity,
+ aDtlsClient, aDigests, aPrivacyRequested);
+ if (!transport.mFlow) {
+ return;
+ }
+ TransportLayer* dtls =
+ transport.mFlow->GetLayer(TransportLayerDtls::ID());
+ dtls->SignalStateChange.connect(
+ this, &MediaTransportHandlerSTS::OnStateChange);
+ if (aComponentCount < 2) {
+ dtls->SignalStateChange.connect(
+ this, &MediaTransportHandlerSTS::OnRtcpStateChange);
+ }
+ }
+
+ if (aComponentCount == 2) {
+ if (!transport.mRtcpFlow) {
+ transport.mRtcpFlow =
+ CreateTransportFlow(aTransportId, true, dtlsIdentity,
+ aDtlsClient, aDigests, aPrivacyRequested);
+ if (!transport.mRtcpFlow) {
+ return;
+ }
+ TransportLayer* dtls =
+ transport.mRtcpFlow->GetLayer(TransportLayerDtls::ID());
+ dtls->SignalStateChange.connect(
+ this, &MediaTransportHandlerSTS::OnRtcpStateChange);
+ }
+ } else {
+ transport.mRtcpFlow = nullptr;
+ // components are 1-indexed
+ stream->DisableComponent(2);
+ }
+
+ mTransports[aTransportId] = transport;
+ },
+ [](const std::string& aError) {});
+}
+
+void MediaTransportHandlerSTS::SetTargetForDefaultLocalAddressLookup(
+ const std::string& aTargetIp, uint16_t aTargetPort) {
+ MOZ_RELEASE_ASSERT(mInitPromise);
+
+ mInitPromise->Then(
+ mStsThread, __func__,
+ [=, self = RefPtr<MediaTransportHandlerSTS>(this)]() {
+ if (!mIceCtx) {
+ return; // Probably due to XPCOM shutdown
+ }
+
+ mIceCtx->SetTargetForDefaultLocalAddressLookup(aTargetIp, aTargetPort);
+ },
+ [](const std::string& aError) {});
+}
+
+void MediaTransportHandlerSTS::StartIceGathering(
+ bool aDefaultRouteOnly, bool aObfuscateHostAddresses,
+ const nsTArray<NrIceStunAddr>& aStunAddrs) {
+ MOZ_RELEASE_ASSERT(mInitPromise);
+
+ mInitPromise->Then(
+ mStsThread, __func__,
+ [=, stunAddrs = aStunAddrs.Clone(),
+ self = RefPtr<MediaTransportHandlerSTS>(this)]() {
+ if (!mIceCtx) {
+ return; // Probably due to XPCOM shutdown
+ }
+
+ mObfuscateHostAddresses = aObfuscateHostAddresses;
+
+ // Belt and suspenders - in e10s mode, the call below to SetStunAddrs
+ // needs to have the proper flags set on ice ctx. For non-e10s,
+ // setting those flags happens in StartGathering. We could probably
+ // just set them here, and only do it here.
+ mIceCtx->SetCtxFlags(aDefaultRouteOnly);
+
+ if (stunAddrs.Length()) {
+ mIceCtx->SetStunAddrs(stunAddrs);
+ }
+
+ // Start gathering, but only if there are streams
+ if (!mIceCtx->GetStreams().empty()) {
+ mIceCtx->StartGathering(aDefaultRouteOnly, aObfuscateHostAddresses);
+ }
+ },
+ [](const std::string& aError) {});
+}
+
+void MediaTransportHandlerSTS::StartIceChecks(
+ bool aIsControlling, const std::vector<std::string>& aIceOptions) {
+ MOZ_RELEASE_ASSERT(mInitPromise);
+
+ mInitPromise->Then(
+ mStsThread, __func__,
+ [=, self = RefPtr<MediaTransportHandlerSTS>(this)]() {
+ if (!mIceCtx) {
+ return; // Probably due to XPCOM shutdown
+ }
+
+ nsresult rv = mIceCtx->ParseGlobalAttributes(aIceOptions);
+ if (NS_FAILED(rv)) {
+ CSFLogError(LOGTAG, "%s: couldn't parse global parameters",
+ __FUNCTION__);
+ return;
+ }
+
+ rv = mIceCtx->SetControlling(aIsControlling ? NrIceCtx::ICE_CONTROLLING
+ : NrIceCtx::ICE_CONTROLLED);
+ if (NS_FAILED(rv)) {
+ CSFLogError(LOGTAG, "%s: couldn't set controlling to %d",
+ __FUNCTION__, aIsControlling);
+ return;
+ }
+
+ rv = mIceCtx->StartChecks();
+ if (NS_FAILED(rv)) {
+ CSFLogError(LOGTAG, "%s: couldn't start checks", __FUNCTION__);
+ return;
+ }
+ },
+ [](const std::string& aError) {});
+}
+
+void TokenizeCandidate(const std::string& aCandidate,
+ std::vector<std::string>& aTokens) {
+ aTokens.clear();
+
+ std::istringstream iss(aCandidate);
+ std::string token;
+ while (std::getline(iss, token, ' ')) {
+ aTokens.push_back(token);
+ }
+}
+
+void MediaTransportHandlerSTS::AddIceCandidate(
+ const std::string& aTransportId, const std::string& aCandidate,
+ const std::string& aUfrag, const std::string& aObfuscatedAddress) {
+ MOZ_RELEASE_ASSERT(mInitPromise);
+
+ mInitPromise->Then(
+ mStsThread, __func__,
+ [=, self = RefPtr<MediaTransportHandlerSTS>(this)]() {
+ if (!mIceCtx) {
+ return; // Probably due to XPCOM shutdown
+ }
+
+ std::vector<std::string> tokens;
+ TokenizeCandidate(aCandidate, tokens);
+
+ RefPtr<NrIceMediaStream> stream(mIceCtx->GetStream(aTransportId));
+ if (!stream) {
+ CSFLogError(LOGTAG,
+ "No ICE stream for candidate with transport id %s: %s",
+ aTransportId.c_str(), aCandidate.c_str());
+ return;
+ }
+
+ nsresult rv = stream->ParseTrickleCandidate(aCandidate, aUfrag,
+ aObfuscatedAddress);
+ if (NS_SUCCEEDED(rv)) {
+ // If the address is not obfuscated, we want to track it as
+ // explicitly signaled so that we know it is fine to reveal
+ // the address later on.
+ if (mObfuscateHostAddresses && tokens.size() > 4 &&
+ aObfuscatedAddress.empty()) {
+ mSignaledAddresses.insert(tokens[4]);
+ }
+ } else {
+ CSFLogError(LOGTAG,
+ "Couldn't process ICE candidate with transport id %s: "
+ "%s",
+ aTransportId.c_str(), aCandidate.c_str());
+ }
+ },
+ [](const std::string& aError) {});
+}
+
+void MediaTransportHandlerSTS::UpdateNetworkState(bool aOnline) {
+ MOZ_RELEASE_ASSERT(mInitPromise);
+
+ mInitPromise->Then(
+ mStsThread, __func__,
+ [=, self = RefPtr<MediaTransportHandlerSTS>(this)]() {
+ if (!mIceCtx) {
+ return; // Probably due to XPCOM shutdown
+ }
+
+ mIceCtx->UpdateNetworkState(aOnline);
+ },
+ [](const std::string& aError) {});
+}
+
+void MediaTransportHandlerSTS::RemoveTransportsExcept(
+ const std::set<std::string>& aTransportIds) {
+ MOZ_RELEASE_ASSERT(mInitPromise);
+
+ mInitPromise->Then(
+ mStsThread, __func__,
+ [=, self = RefPtr<MediaTransportHandlerSTS>(this)]() {
+ if (!mIceCtx) {
+ return; // Probably due to XPCOM shutdown
+ }
+
+ for (auto it = mTransports.begin(); it != mTransports.end();) {
+ const std::string transportId(it->first);
+ if (!aTransportIds.count(transportId)) {
+ if (it->second.mFlow) {
+ OnStateChange(transportId, TransportLayer::TS_NONE);
+ OnRtcpStateChange(transportId, TransportLayer::TS_NONE);
+ }
+ // Erase the transport before destroying the ice stream so that
+ // the close_notify alerts have a chance to be sent as the
+ // TransportFlow destructors execute.
+ it = mTransports.erase(it);
+ // We're already on the STS thread, but the TransportFlow
+ // destructor executed when mTransports.erase(it) is called
+ // above dispatches the call to DestroyFinal to the STS thread. If
+ // we don't also dispatch the call to destroy the NrIceMediaStream
+ // to the STS thread, it will tear down the NrIceMediaStream
+ // before the TransportFlow is destroyed. Without a valid
+ // NrIceMediaStream the close_notify alert cannot be sent.
+ mStsThread->Dispatch(NS_NewRunnableFunction(
+ __func__, [iceCtx = RefPtr<NrIceCtx>(mIceCtx), transportId] {
+ iceCtx->DestroyStream(transportId);
+ }));
+ } else {
+ MOZ_ASSERT(it->second.mFlow);
+ ++it;
+ }
+ }
+ },
+ [](const std::string& aError) {});
+}
+
+void MediaTransportHandlerSTS::SendPacket(const std::string& aTransportId,
+ MediaPacket&& aPacket) {
+ MOZ_RELEASE_ASSERT(mInitPromise);
+
+ mInitPromise->Then(
+ mStsThread, __func__,
+ [this, self = RefPtr<MediaTransportHandlerSTS>(this), aTransportId,
+ aPacket = std::move(aPacket)]() mutable {
+ if (!mIceCtx) {
+ return; // Probably due to XPCOM shutdown
+ }
+
+ MOZ_ASSERT(aPacket.type() != MediaPacket::UNCLASSIFIED);
+ RefPtr<TransportFlow> flow =
+ GetTransportFlow(aTransportId, aPacket.type() == MediaPacket::RTCP);
+
+ if (!flow) {
+ CSFLogError(LOGTAG,
+ "%s: No such transport flow (%s) for outgoing packet",
+ mIceCtx->name().c_str(), aTransportId.c_str());
+ return;
+ }
+
+ TransportLayer* layer = nullptr;
+ switch (aPacket.type()) {
+ case MediaPacket::SCTP:
+ layer = flow->GetLayer(TransportLayerDtls::ID());
+ break;
+ case MediaPacket::RTP:
+ case MediaPacket::RTCP:
+ layer = flow->GetLayer(TransportLayerSrtp::ID());
+ break;
+ default:
+ // Maybe it would be useful to allow the injection of other packet
+ // types for testing?
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ MOZ_ASSERT(layer);
+
+ if (layer->SendPacket(aPacket) < 0) {
+ CSFLogError(LOGTAG, "%s: Transport flow (%s) failed to send packet",
+ mIceCtx->name().c_str(), aTransportId.c_str());
+ }
+ },
+ [](const std::string& aError) {});
+}
+
+TransportLayer::State MediaTransportHandler::GetState(
+ const std::string& aTransportId, bool aRtcp) const {
+ // TODO Bug 1520692: we should allow Datachannel to connect without
+ // DTLS SRTP keys
+ if (mCallbackThread) {
+ MOZ_ASSERT(mCallbackThread->IsOnCurrentThread());
+ }
+
+ const std::map<std::string, TransportLayer::State>* cache = nullptr;
+ if (aRtcp) {
+ cache = &mRtcpStateCache;
+ } else {
+ cache = &mStateCache;
+ }
+
+ auto it = cache->find(aTransportId);
+ if (it != cache->end()) {
+ return it->second;
+ }
+ return TransportLayer::TS_NONE;
+}
+
+void MediaTransportHandler::OnCandidate(const std::string& aTransportId,
+ const CandidateInfo& aCandidateInfo) {
+ if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) {
+ mCallbackThread->Dispatch(
+ // This is being called from sigslot, which does not hold a strong ref.
+ WrapRunnable(this, &MediaTransportHandler::OnCandidate, aTransportId,
+ aCandidateInfo),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ SignalCandidate(aTransportId, aCandidateInfo);
+}
+
+void MediaTransportHandler::OnAlpnNegotiated(const std::string& aAlpn) {
+ if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) {
+ mCallbackThread->Dispatch(
+ // This is being called from sigslot, which does not hold a strong ref.
+ WrapRunnable(this, &MediaTransportHandler::OnAlpnNegotiated, aAlpn),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ const bool privacyRequested = aAlpn == "c-webrtc";
+ SignalAlpnNegotiated(aAlpn, privacyRequested);
+}
+
+void MediaTransportHandler::OnGatheringStateChange(
+ dom::RTCIceGatheringState aState) {
+ if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) {
+ mCallbackThread->Dispatch(
+ // This is being called from sigslot, which does not hold a strong ref.
+ WrapRunnable(this, &MediaTransportHandler::OnGatheringStateChange,
+ aState),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ SignalGatheringStateChange(aState);
+}
+
+void MediaTransportHandler::OnConnectionStateChange(
+ dom::RTCIceConnectionState aState) {
+ if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) {
+ mCallbackThread->Dispatch(
+ // This is being called from sigslot, which does not hold a strong ref.
+ WrapRunnable(this, &MediaTransportHandler::OnConnectionStateChange,
+ aState),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ SignalConnectionStateChange(aState);
+}
+
+void MediaTransportHandler::OnPacketReceived(const std::string& aTransportId,
+ const MediaPacket& aPacket) {
+ if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) {
+ mCallbackThread->Dispatch(
+ // This is being called from sigslot, which does not hold a strong ref.
+ WrapRunnable(this, &MediaTransportHandler::OnPacketReceived,
+ aTransportId, aPacket.Clone()),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ SignalPacketReceived(aTransportId, aPacket);
+}
+
+void MediaTransportHandler::OnEncryptedSending(const std::string& aTransportId,
+ const MediaPacket& aPacket) {
+ if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) {
+ mCallbackThread->Dispatch(
+ // This is being called from sigslot, which does not hold a strong ref.
+ WrapRunnable(this, &MediaTransportHandler::OnEncryptedSending,
+ aTransportId, aPacket.Clone()),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ SignalEncryptedSending(aTransportId, aPacket);
+}
+
+void MediaTransportHandler::OnStateChange(const std::string& aTransportId,
+ TransportLayer::State aState) {
+ if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) {
+ mCallbackThread->Dispatch(
+ // This is being called from sigslot, which does not hold a strong ref.
+ WrapRunnable(this, &MediaTransportHandler::OnStateChange, aTransportId,
+ aState),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ if (aState == TransportLayer::TS_NONE) {
+ mStateCache.erase(aTransportId);
+ } else {
+ mStateCache[aTransportId] = aState;
+ }
+ SignalStateChange(aTransportId, aState);
+}
+
+void MediaTransportHandler::OnRtcpStateChange(const std::string& aTransportId,
+ TransportLayer::State aState) {
+ if (mCallbackThread && !mCallbackThread->IsOnCurrentThread()) {
+ mCallbackThread->Dispatch(
+ // This is being called from sigslot, which does not hold a strong ref.
+ WrapRunnable(this, &MediaTransportHandler::OnRtcpStateChange,
+ aTransportId, aState),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ if (aState == TransportLayer::TS_NONE) {
+ mRtcpStateCache.erase(aTransportId);
+ } else {
+ mRtcpStateCache[aTransportId] = aState;
+ }
+ SignalRtcpStateChange(aTransportId, aState);
+}
+
+RefPtr<dom::RTCStatsPromise> MediaTransportHandlerSTS::GetIceStats(
+ const std::string& aTransportId, DOMHighResTimeStamp aNow) {
+ MOZ_RELEASE_ASSERT(mInitPromise);
+
+ return mInitPromise->Then(mStsThread, __func__, [=, self = RefPtr(this)]() {
+ UniquePtr<dom::RTCStatsCollection> stats(new dom::RTCStatsCollection);
+ if (mIceCtx) {
+ for (const auto& stream : mIceCtx->GetStreams()) {
+ if (aTransportId.empty() || aTransportId == stream->GetId()) {
+ GetIceStats(*stream, aNow, stats.get());
+ }
+ }
+ }
+ return dom::RTCStatsPromise::CreateAndResolve(std::move(stats), __func__);
+ });
+}
+
+RefPtr<MediaTransportHandler::IceLogPromise>
+MediaTransportHandlerSTS::GetIceLog(const nsCString& aPattern) {
+ return InvokeAsync(
+ mStsThread, __func__, [=, self = RefPtr<MediaTransportHandlerSTS>(this)] {
+ dom::Sequence<nsString> converted;
+ RLogConnector* logs = RLogConnector::GetInstance();
+ std::deque<std::string> result;
+ // Might not exist yet.
+ if (logs) {
+ logs->Filter(aPattern.get(), 0, &result);
+ }
+ /// XXX(Bug 1631386) Check if we should reject the promise instead of
+ /// crashing in an OOM situation.
+ if (!converted.SetCapacity(result.size(), fallible)) {
+ mozalloc_handle_oom(sizeof(nsString) * result.size());
+ }
+ for (auto& line : result) {
+ // Cannot fail, SetCapacity was called before.
+ (void)converted.AppendElement(NS_ConvertUTF8toUTF16(line.c_str()),
+ fallible);
+ }
+ return IceLogPromise::CreateAndResolve(std::move(converted), __func__);
+ });
+}
+
+void MediaTransportHandlerSTS::ClearIceLog() {
+ if (!mStsThread->IsOnCurrentThread()) {
+ mStsThread->Dispatch(WrapRunnable(RefPtr<MediaTransportHandlerSTS>(this),
+ &MediaTransportHandlerSTS::ClearIceLog),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ RLogConnector* logs = RLogConnector::GetInstance();
+ if (logs) {
+ logs->Clear();
+ }
+}
+
+void MediaTransportHandlerSTS::EnterPrivateMode() {
+ if (!mStsThread->IsOnCurrentThread()) {
+ mStsThread->Dispatch(
+ WrapRunnable(RefPtr<MediaTransportHandlerSTS>(this),
+ &MediaTransportHandlerSTS::EnterPrivateMode),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ RLogConnector::GetInstance()->EnterPrivateMode();
+}
+
+void MediaTransportHandlerSTS::ExitPrivateMode() {
+ if (!mStsThread->IsOnCurrentThread()) {
+ mStsThread->Dispatch(
+ WrapRunnable(RefPtr<MediaTransportHandlerSTS>(this),
+ &MediaTransportHandlerSTS::ExitPrivateMode),
+ NS_DISPATCH_NORMAL);
+ return;
+ }
+
+ auto* log = RLogConnector::GetInstance();
+ MOZ_ASSERT(log);
+ if (log) {
+ log->ExitPrivateMode();
+ }
+}
+
+static void ToRTCIceCandidateStats(
+ const std::vector<NrIceCandidate>& candidates,
+ dom::RTCStatsType candidateType, const nsString& transportId,
+ DOMHighResTimeStamp now, dom::RTCStatsCollection* stats,
+ bool obfuscateHostAddresses,
+ const std::set<std::string>& signaledAddresses) {
+ MOZ_ASSERT(stats);
+ for (const auto& candidate : candidates) {
+ dom::RTCIceCandidateStats cand;
+ cand.mType.Construct(candidateType);
+ NS_ConvertASCIItoUTF16 codeword(candidate.codeword.c_str());
+ cand.mTransportId.Construct(transportId);
+ cand.mId.Construct(codeword);
+ cand.mTimestamp.Construct(now);
+ cand.mCandidateType.Construct(dom::RTCIceCandidateType(candidate.type));
+ cand.mPriority.Construct(candidate.priority);
+ // https://tools.ietf.org/html/draft-ietf-rtcweb-mdns-ice-candidates-03#section-3.3.1
+ // This obfuscates the address with the mDNS address if one exists
+ if (!candidate.mdns_addr.empty()) {
+ cand.mAddress.Construct(
+ NS_ConvertASCIItoUTF16(candidate.mdns_addr.c_str()));
+ } else if (obfuscateHostAddresses &&
+ candidate.type == NrIceCandidate::ICE_PEER_REFLEXIVE &&
+ signaledAddresses.find(candidate.cand_addr.host) ==
+ signaledAddresses.end()) {
+ cand.mAddress.Construct(NS_ConvertASCIItoUTF16("(redacted)"));
+ } else {
+ cand.mAddress.Construct(
+ NS_ConvertASCIItoUTF16(candidate.cand_addr.host.c_str()));
+ }
+ cand.mPort.Construct(candidate.cand_addr.port);
+ cand.mProtocol.Construct(
+ NS_ConvertASCIItoUTF16(candidate.cand_addr.transport.c_str()));
+ if (candidateType == dom::RTCStatsType::Local_candidate &&
+ dom::RTCIceCandidateType(candidate.type) ==
+ dom::RTCIceCandidateType::Relay) {
+ cand.mRelayProtocol.Construct(
+ NS_ConvertASCIItoUTF16(candidate.local_addr.transport.c_str()));
+ }
+ cand.mProxied.Construct(NS_ConvertASCIItoUTF16(
+ candidate.is_proxied ? "proxied" : "non-proxied"));
+ if (!stats->mIceCandidateStats.AppendElement(cand, fallible)) {
+ // XXX(Bug 1632090) Instead of extending the array 1-by-1 (which might
+ // involve multiple reallocations) and potentially crashing here,
+ // SetCapacity could be called outside the loop once.
+ mozalloc_handle_oom(0);
+ }
+ if (candidate.trickled) {
+ if (!stats->mTrickledIceCandidateStats.AppendElement(cand, fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ }
+ }
+}
+
+void MediaTransportHandlerSTS::GetIceStats(
+ const NrIceMediaStream& aStream, DOMHighResTimeStamp aNow,
+ dom::RTCStatsCollection* aStats) const {
+ MOZ_ASSERT(mStsThread->IsOnCurrentThread());
+
+ NS_ConvertASCIItoUTF16 transportId(aStream.GetId().c_str());
+
+ std::vector<NrIceCandidatePair> candPairs;
+ nsresult res = aStream.GetCandidatePairs(&candPairs);
+ if (NS_FAILED(res)) {
+ CSFLogError(LOGTAG,
+ "%s: Error getting candidate pairs for transport id \"%s\"",
+ __FUNCTION__, aStream.GetId().c_str());
+ return;
+ }
+
+ for (auto& candPair : candPairs) {
+ NS_ConvertASCIItoUTF16 codeword(candPair.codeword.c_str());
+ NS_ConvertASCIItoUTF16 localCodeword(candPair.local.codeword.c_str());
+ NS_ConvertASCIItoUTF16 remoteCodeword(candPair.remote.codeword.c_str());
+ // Only expose candidate-pair statistics to chrome, until we've thought
+ // through the implications of exposing it to content.
+
+ dom::RTCIceCandidatePairStats s;
+ s.mId.Construct(codeword);
+ s.mTransportId.Construct(transportId);
+ s.mTimestamp.Construct(aNow);
+ s.mType.Construct(dom::RTCStatsType::Candidate_pair);
+ s.mLocalCandidateId.Construct(localCodeword);
+ s.mRemoteCandidateId.Construct(remoteCodeword);
+ s.mNominated.Construct(candPair.nominated);
+ s.mWritable.Construct(candPair.writable);
+ s.mReadable.Construct(candPair.readable);
+ s.mPriority.Construct(candPair.priority);
+ s.mSelected.Construct(candPair.selected);
+ s.mBytesSent.Construct(candPair.bytes_sent);
+ s.mBytesReceived.Construct(candPair.bytes_recvd);
+ s.mLastPacketSentTimestamp.Construct(candPair.ms_since_last_send);
+ s.mLastPacketReceivedTimestamp.Construct(candPair.ms_since_last_recv);
+ s.mState.Construct(dom::RTCStatsIceCandidatePairState(candPair.state));
+ s.mComponentId.Construct(candPair.component_id);
+ if (!aStats->mIceCandidatePairStats.AppendElement(s, fallible)) {
+ // XXX(Bug 1632090) Instead of extending the array 1-by-1 (which might
+ // involve multiple reallocations) and potentially crashing here,
+ // SetCapacity could be called outside the loop once.
+ mozalloc_handle_oom(0);
+ }
+ }
+
+ std::vector<NrIceCandidate> candidates;
+ if (NS_SUCCEEDED(aStream.GetLocalCandidates(&candidates))) {
+ ToRTCIceCandidateStats(candidates, dom::RTCStatsType::Local_candidate,
+ transportId, aNow, aStats, mObfuscateHostAddresses,
+ mSignaledAddresses);
+ // add the local candidates unparsed string to a sequence
+ for (const auto& candidate : candidates) {
+ if (!aStats->mRawLocalCandidates.AppendElement(
+ NS_ConvertASCIItoUTF16(candidate.label.c_str()), fallible)) {
+ // XXX(Bug 1632090) Instead of extending the array 1-by-1 (which might
+ // involve multiple reallocations) and potentially crashing here,
+ // SetCapacity could be called outside the loop once.
+ mozalloc_handle_oom(0);
+ }
+ }
+ }
+ candidates.clear();
+
+ if (NS_SUCCEEDED(aStream.GetRemoteCandidates(&candidates))) {
+ ToRTCIceCandidateStats(candidates, dom::RTCStatsType::Remote_candidate,
+ transportId, aNow, aStats, mObfuscateHostAddresses,
+ mSignaledAddresses);
+ // add the remote candidates unparsed string to a sequence
+ for (const auto& candidate : candidates) {
+ if (!aStats->mRawRemoteCandidates.AppendElement(
+ NS_ConvertASCIItoUTF16(candidate.label.c_str()), fallible)) {
+ // XXX(Bug 1632090) Instead of extending the array 1-by-1 (which might
+ // involve multiple reallocations) and potentially crashing here,
+ // SetCapacity could be called outside the loop once.
+ mozalloc_handle_oom(0);
+ }
+ }
+ }
+}
+
+RefPtr<TransportFlow> MediaTransportHandlerSTS::GetTransportFlow(
+ const std::string& aTransportId, bool aIsRtcp) const {
+ auto it = mTransports.find(aTransportId);
+ if (it == mTransports.end()) {
+ return nullptr;
+ }
+
+ if (aIsRtcp) {
+ return it->second.mRtcpFlow ? it->second.mRtcpFlow : it->second.mFlow;
+ ;
+ }
+
+ return it->second.mFlow;
+}
+
+RefPtr<TransportFlow> MediaTransportHandlerSTS::CreateTransportFlow(
+ const std::string& aTransportId, bool aIsRtcp,
+ const RefPtr<DtlsIdentity>& aDtlsIdentity, bool aDtlsClient,
+ const DtlsDigestList& aDigests, bool aPrivacyRequested) {
+ nsresult rv;
+ RefPtr<TransportFlow> flow = new TransportFlow(aTransportId);
+
+ // The media streams are made on STS so we need to defer setup.
+ auto ice = MakeUnique<TransportLayerIce>();
+ auto dtls = MakeUnique<TransportLayerDtls>();
+ auto srtp = MakeUnique<TransportLayerSrtp>(*dtls);
+ dtls->SetRole(aDtlsClient ? TransportLayerDtls::CLIENT
+ : TransportLayerDtls::SERVER);
+
+ dtls->SetIdentity(aDtlsIdentity);
+
+ dtls->SetMinMaxVersion(
+ static_cast<TransportLayerDtls::Version>(mMinDtlsVersion),
+ static_cast<TransportLayerDtls::Version>(mMaxDtlsVersion));
+
+ for (const auto& digest : aDigests) {
+ rv = dtls->SetVerificationDigest(digest);
+ if (NS_FAILED(rv)) {
+ CSFLogError(LOGTAG, "Could not set fingerprint");
+ return nullptr;
+ }
+ }
+
+ std::vector<uint16_t> srtpCiphers =
+ TransportLayerDtls::GetDefaultSrtpCiphers();
+
+ rv = dtls->SetSrtpCiphers(srtpCiphers);
+ if (NS_FAILED(rv)) {
+ CSFLogError(LOGTAG, "Couldn't set SRTP ciphers");
+ return nullptr;
+ }
+
+ // Always permits negotiation of the confidential mode.
+ // Only allow non-confidential (which is an allowed default),
+ // if we aren't confidential.
+ std::set<std::string> alpn = {"c-webrtc"};
+ std::string alpnDefault;
+ if (!aPrivacyRequested) {
+ alpnDefault = "webrtc";
+ alpn.insert(alpnDefault);
+ }
+ rv = dtls->SetAlpn(alpn, alpnDefault);
+ if (NS_FAILED(rv)) {
+ CSFLogError(LOGTAG, "Couldn't set ALPN");
+ return nullptr;
+ }
+
+ ice->SetParameters(mIceCtx->GetStream(aTransportId), aIsRtcp ? 2 : 1);
+ NS_ENSURE_SUCCESS(ice->Init(), nullptr);
+ NS_ENSURE_SUCCESS(dtls->Init(), nullptr);
+ NS_ENSURE_SUCCESS(srtp->Init(), nullptr);
+ dtls->Chain(ice.get());
+ srtp->Chain(ice.get());
+
+ dtls->SignalPacketReceived.connect(this,
+ &MediaTransportHandlerSTS::PacketReceived);
+ srtp->SignalPacketReceived.connect(this,
+ &MediaTransportHandlerSTS::PacketReceived);
+ ice->SignalPacketSending.connect(
+ this, &MediaTransportHandlerSTS::EncryptedPacketSending);
+ flow->PushLayer(ice.release());
+ flow->PushLayer(dtls.release());
+ flow->PushLayer(srtp.release());
+ return flow;
+}
+
+static mozilla::dom::RTCIceGatheringState toDomIceGatheringState(
+ NrIceCtx::GatheringState aState) {
+ switch (aState) {
+ case NrIceCtx::ICE_CTX_GATHER_INIT:
+ return dom::RTCIceGatheringState::New;
+ case NrIceCtx::ICE_CTX_GATHER_STARTED:
+ return dom::RTCIceGatheringState::Gathering;
+ case NrIceCtx::ICE_CTX_GATHER_COMPLETE:
+ return dom::RTCIceGatheringState::Complete;
+ }
+ MOZ_CRASH();
+}
+
+void MediaTransportHandlerSTS::OnGatheringStateChange(
+ NrIceCtx* aIceCtx, NrIceCtx::GatheringState aState) {
+ OnGatheringStateChange(toDomIceGatheringState(aState));
+}
+
+static mozilla::dom::RTCIceConnectionState toDomIceConnectionState(
+ NrIceCtx::ConnectionState aState) {
+ switch (aState) {
+ case NrIceCtx::ICE_CTX_INIT:
+ return dom::RTCIceConnectionState::New;
+ case NrIceCtx::ICE_CTX_CHECKING:
+ return dom::RTCIceConnectionState::Checking;
+ case NrIceCtx::ICE_CTX_CONNECTED:
+ return dom::RTCIceConnectionState::Connected;
+ case NrIceCtx::ICE_CTX_COMPLETED:
+ return dom::RTCIceConnectionState::Completed;
+ case NrIceCtx::ICE_CTX_FAILED:
+ return dom::RTCIceConnectionState::Failed;
+ case NrIceCtx::ICE_CTX_DISCONNECTED:
+ return dom::RTCIceConnectionState::Disconnected;
+ case NrIceCtx::ICE_CTX_CLOSED:
+ return dom::RTCIceConnectionState::Closed;
+ }
+ MOZ_CRASH();
+}
+
+void MediaTransportHandlerSTS::OnConnectionStateChange(
+ NrIceCtx* aIceCtx, NrIceCtx::ConnectionState aState) {
+ OnConnectionStateChange(toDomIceConnectionState(aState));
+}
+
+// The stuff below here will eventually go into the MediaTransportChild class
+void MediaTransportHandlerSTS::OnCandidateFound(
+ NrIceMediaStream* aStream, const std::string& aCandidate,
+ const std::string& aUfrag, const std::string& aMDNSAddr,
+ const std::string& aActualAddr) {
+ CandidateInfo info;
+ info.mCandidate = aCandidate;
+ MOZ_ASSERT(!aUfrag.empty());
+ info.mUfrag = aUfrag;
+ NrIceCandidate defaultRtpCandidate;
+ NrIceCandidate defaultRtcpCandidate;
+ nsresult rv = aStream->GetDefaultCandidate(1, &defaultRtpCandidate);
+ if (NS_SUCCEEDED(rv)) {
+ if (!defaultRtpCandidate.mdns_addr.empty()) {
+ info.mDefaultHostRtp = "0.0.0.0";
+ info.mDefaultPortRtp = 9;
+ } else {
+ info.mDefaultHostRtp = defaultRtpCandidate.cand_addr.host;
+ info.mDefaultPortRtp = defaultRtpCandidate.cand_addr.port;
+ }
+ } else {
+ CSFLogError(LOGTAG,
+ "%s: GetDefaultCandidates failed for transport id %s, "
+ "res=%u",
+ __FUNCTION__, aStream->GetId().c_str(),
+ static_cast<unsigned>(rv));
+ }
+
+ // Optional; component won't exist if doing rtcp-mux
+ if (NS_SUCCEEDED(aStream->GetDefaultCandidate(2, &defaultRtcpCandidate))) {
+ if (!defaultRtcpCandidate.mdns_addr.empty()) {
+ info.mDefaultHostRtcp = defaultRtcpCandidate.mdns_addr;
+ } else {
+ info.mDefaultHostRtcp = defaultRtcpCandidate.cand_addr.host;
+ }
+ info.mDefaultPortRtcp = defaultRtcpCandidate.cand_addr.port;
+ }
+
+ info.mMDNSAddress = aMDNSAddr;
+ info.mActualAddress = aActualAddr;
+
+ OnCandidate(aStream->GetId(), info);
+}
+
+void MediaTransportHandlerSTS::OnStateChange(TransportLayer* aLayer,
+ TransportLayer::State aState) {
+ if (aState == TransportLayer::TS_OPEN) {
+ MOZ_ASSERT(aLayer->id() == TransportLayerDtls::ID());
+ TransportLayerDtls* dtlsLayer = static_cast<TransportLayerDtls*>(aLayer);
+ OnAlpnNegotiated(dtlsLayer->GetNegotiatedAlpn());
+ }
+
+ // DTLS state indicates the readiness of the transport as a whole, because
+ // SRTP uses the keys from the DTLS handshake.
+ MediaTransportHandler::OnStateChange(aLayer->flow_id(), aState);
+}
+
+void MediaTransportHandlerSTS::OnRtcpStateChange(TransportLayer* aLayer,
+ TransportLayer::State aState) {
+ MediaTransportHandler::OnRtcpStateChange(aLayer->flow_id(), aState);
+}
+
+constexpr static const char* PacketTypeToString(MediaPacket::Type type) {
+ switch (type) {
+ case MediaPacket::Type::UNCLASSIFIED:
+ return "UNCLASSIFIED";
+ case MediaPacket::Type::SRTP:
+ return "SRTP";
+ case MediaPacket::Type::SRTCP:
+ return "SRTCP";
+ case MediaPacket::Type::DTLS:
+ return "DTLS";
+ case MediaPacket::Type::RTP:
+ return "RTP";
+ case MediaPacket::Type::RTCP:
+ return "RTCP";
+ case MediaPacket::Type::SCTP:
+ return "SCTP";
+ default:
+ MOZ_ASSERT(false, "unreached");
+ return "";
+ }
+}
+
+void MediaTransportHandlerSTS::PacketReceived(TransportLayer* aLayer,
+ MediaPacket& aPacket) {
+ MEDIA_TRANSPORT_HANDLER_PACKET_RECEIVED(aPacket);
+ OnPacketReceived(aLayer->flow_id(), aPacket);
+}
+
+void MediaTransportHandlerSTS::EncryptedPacketSending(TransportLayer* aLayer,
+ MediaPacket& aPacket) {
+ OnEncryptedSending(aLayer->flow_id(), aPacket);
+}
+
+} // namespace mozilla
+
+#undef MEDIA_TRANSPORT_HANDLER_PACKET_RECEIVED
diff --git a/dom/media/webrtc/jsapi/MediaTransportHandler.h b/dom/media/webrtc/jsapi/MediaTransportHandler.h
new file mode 100644
index 0000000000..a776cb6fd7
--- /dev/null
+++ b/dom/media/webrtc/jsapi/MediaTransportHandler.h
@@ -0,0 +1,167 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MTRANSPORTHANDLER_H__
+#define _MTRANSPORTHANDLER_H__
+
+#include "mozilla/RefPtr.h"
+#include "nsISupportsImpl.h"
+#include "transport/sigslot.h"
+#include "transport/transportlayer.h" // Need the State enum
+#include "transport/dtlsidentity.h" // For DtlsDigest
+#include "mozilla/dom/RTCPeerConnectionBinding.h"
+#include "mozilla/dom/RTCConfigurationBinding.h"
+#include "transport/nricectx.h" // Need some enums
+#include "common/CandidateInfo.h"
+#include "transport/nr_socket_proxy_config.h"
+#include "RTCStatsReport.h"
+
+#include "nsString.h"
+
+#include <string>
+#include <set>
+#include <vector>
+
+namespace mozilla {
+class DtlsIdentity;
+class NrIceCtx;
+class NrIceMediaStream;
+class NrIceResolver;
+class TransportFlow;
+class RTCStatsQuery;
+
+namespace dom {
+struct RTCStatsReportInternal;
+}
+
+class MediaTransportHandler {
+ public:
+ // Creates either a MediaTransportHandlerSTS or a MediaTransportHandlerIPC,
+ // as appropriate. If you want signals to fire on a specific thread, pass
+ // the event target here, otherwise they will fire on whatever is convenient.
+ // Note: This also determines what thread the state cache is updated on!
+ // Don't call GetState on any other thread!
+ static already_AddRefed<MediaTransportHandler> Create(
+ nsISerialEventTarget* aCallbackThread);
+
+ explicit MediaTransportHandler(nsISerialEventTarget* aCallbackThread)
+ : mCallbackThread(aCallbackThread) {}
+
+ // Exposed so we can synchronously validate ICE servers from PeerConnection
+ static nsresult ConvertIceServers(
+ const nsTArray<dom::RTCIceServer>& aIceServers,
+ std::vector<NrIceStunServer>* aStunServers,
+ std::vector<NrIceTurnServer>* aTurnServers);
+
+ typedef MozPromise<dom::Sequence<nsString>, nsresult, true> IceLogPromise;
+
+ virtual void Initialize() {}
+
+ // There's a wrinkle here; the ICE logging is not separated out by
+ // MediaTransportHandler. These are a little more like static methods, but
+ // to avoid needing yet another IPC interface, we bolt them on here.
+ virtual RefPtr<IceLogPromise> GetIceLog(const nsCString& aPattern) = 0;
+ virtual void ClearIceLog() = 0;
+ virtual void EnterPrivateMode() = 0;
+ virtual void ExitPrivateMode() = 0;
+
+ virtual void CreateIceCtx(const std::string& aName) = 0;
+
+ virtual nsresult SetIceConfig(const nsTArray<dom::RTCIceServer>& aIceServers,
+ dom::RTCIceTransportPolicy aIcePolicy) = 0;
+
+ // We will probably be able to move the proxy lookup stuff into
+ // this class once we move mtransport to its own process.
+ virtual void SetProxyConfig(NrSocketProxyConfig&& aProxyConfig) = 0;
+
+ virtual void EnsureProvisionalTransport(const std::string& aTransportId,
+ const std::string& aLocalUfrag,
+ const std::string& aLocalPwd,
+ int aComponentCount) = 0;
+
+ virtual void SetTargetForDefaultLocalAddressLookup(
+ const std::string& aTargetIp, uint16_t aTargetPort) = 0;
+
+ // We set default-route-only as late as possible because it depends on what
+ // capture permissions have been granted on the window, which could easily
+ // change between Init (ie; when the PC is created) and StartIceGathering
+ // (ie; when we set the local description).
+ virtual void StartIceGathering(bool aDefaultRouteOnly,
+ bool aObfuscateHostAddresses,
+ // TODO: It probably makes sense to look
+ // this up internally
+ const nsTArray<NrIceStunAddr>& aStunAddrs) = 0;
+
+ virtual void ActivateTransport(
+ const std::string& aTransportId, const std::string& aLocalUfrag,
+ const std::string& aLocalPwd, size_t aComponentCount,
+ const std::string& aUfrag, const std::string& aPassword,
+ const nsTArray<uint8_t>& aKeyDer, const nsTArray<uint8_t>& aCertDer,
+ SSLKEAType aAuthType, bool aDtlsClient, const DtlsDigestList& aDigests,
+ bool aPrivacyRequested) = 0;
+
+ virtual void RemoveTransportsExcept(
+ const std::set<std::string>& aTransportIds) = 0;
+
+ virtual void StartIceChecks(bool aIsControlling,
+ const std::vector<std::string>& aIceOptions) = 0;
+
+ virtual void SendPacket(const std::string& aTransportId,
+ MediaPacket&& aPacket) = 0;
+
+ virtual void AddIceCandidate(const std::string& aTransportId,
+ const std::string& aCandidate,
+ const std::string& aUFrag,
+ const std::string& aObfuscatedAddress) = 0;
+
+ virtual void UpdateNetworkState(bool aOnline) = 0;
+
+ virtual RefPtr<dom::RTCStatsPromise> GetIceStats(
+ const std::string& aTransportId, DOMHighResTimeStamp aNow) = 0;
+
+ sigslot::signal2<const std::string&, const CandidateInfo&> SignalCandidate;
+ sigslot::signal2<const std::string&, bool> SignalAlpnNegotiated;
+ sigslot::signal1<dom::RTCIceGatheringState> SignalGatheringStateChange;
+ sigslot::signal1<dom::RTCIceConnectionState> SignalConnectionStateChange;
+
+ sigslot::signal2<const std::string&, const MediaPacket&> SignalPacketReceived;
+ sigslot::signal2<const std::string&, const MediaPacket&>
+ SignalEncryptedSending;
+ sigslot::signal2<const std::string&, TransportLayer::State> SignalStateChange;
+ sigslot::signal2<const std::string&, TransportLayer::State>
+ SignalRtcpStateChange;
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DESTROY(MediaTransportHandler,
+ Destroy())
+
+ TransportLayer::State GetState(const std::string& aTransportId,
+ bool aRtcp) const;
+
+ protected:
+ void OnCandidate(const std::string& aTransportId,
+ const CandidateInfo& aCandidateInfo);
+ void OnAlpnNegotiated(const std::string& aAlpn);
+ void OnGatheringStateChange(dom::RTCIceGatheringState aState);
+ void OnConnectionStateChange(dom::RTCIceConnectionState aState);
+ void OnPacketReceived(const std::string& aTransportId,
+ const MediaPacket& aPacket);
+ void OnEncryptedSending(const std::string& aTransportId,
+ const MediaPacket& aPacket);
+ void OnStateChange(const std::string& aTransportId,
+ TransportLayer::State aState);
+ void OnRtcpStateChange(const std::string& aTransportId,
+ TransportLayer::State aState);
+ virtual void Destroy() = 0;
+ virtual ~MediaTransportHandler() = default;
+ std::map<std::string, TransportLayer::State> mStateCache;
+ std::map<std::string, TransportLayer::State> mRtcpStateCache;
+ RefPtr<nsISerialEventTarget> mCallbackThread;
+};
+
+void TokenizeCandidate(const std::string& aCandidate,
+ std::vector<std::string>& aTokens);
+
+} // namespace mozilla
+
+#endif //_MTRANSPORTHANDLER_H__
diff --git a/dom/media/webrtc/jsapi/MediaTransportHandlerIPC.cpp b/dom/media/webrtc/jsapi/MediaTransportHandlerIPC.cpp
new file mode 100644
index 0000000000..6369d229ee
--- /dev/null
+++ b/dom/media/webrtc/jsapi/MediaTransportHandlerIPC.cpp
@@ -0,0 +1,414 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MediaTransportHandlerIPC.h"
+#include "mozilla/dom/MediaTransportChild.h"
+#include "nsThreadUtils.h"
+#include "mozilla/net/SocketProcessBridgeChild.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "common/browser_logging/CSFLog.h"
+
+namespace mozilla {
+
+static const char* mthipcLogTag = "MediaTransportHandler";
+#ifdef LOGTAG
+# undef LOGTAG
+#endif
+#define LOGTAG mthipcLogTag
+
+MediaTransportHandlerIPC::MediaTransportHandlerIPC(
+ nsISerialEventTarget* aCallbackThread)
+ : MediaTransportHandler(aCallbackThread) {}
+
+void MediaTransportHandlerIPC::Initialize() {
+ mInitPromise = net::SocketProcessBridgeChild::GetSocketProcessBridge()->Then(
+ mCallbackThread, __func__,
+ [this, self = RefPtr<MediaTransportHandlerIPC>(this)](
+ const RefPtr<net::SocketProcessBridgeChild>& aBridge) {
+ ipc::PBackgroundChild* actor =
+ ipc::BackgroundChild::GetOrCreateSocketActorForCurrentThread();
+ // An actor that can't send is possible if the socket process has
+ // crashed but hasn't been reconnected properly. See
+ // SocketProcessBridgeChild::ActorDestroy for more info.
+ if (!actor || !actor->CanSend()) {
+ NS_WARNING(
+ "MediaTransportHandlerIPC async init failed! Webrtc networking "
+ "will not work!");
+ return InitPromise::CreateAndReject(
+ nsCString("GetOrCreateSocketActorForCurrentThread failed!"),
+ __func__);
+ }
+ MediaTransportChild* child = new MediaTransportChild(this);
+ // PBackgroungChild owns mChild! When it is done with it,
+ // mChild will let us know it it going away.
+ mChild = actor->SendPMediaTransportConstructor(child);
+ CSFLogDebug(LOGTAG, "%s Init done", __func__);
+ return InitPromise::CreateAndResolve(true, __func__);
+ },
+ [=](const nsCString& aError) {
+ CSFLogError(LOGTAG,
+ "MediaTransportHandlerIPC async init failed! Webrtc "
+ "networking will not work! Error was %s",
+ aError.get());
+ NS_WARNING(
+ "MediaTransportHandlerIPC async init failed! Webrtc networking "
+ "will not work!");
+ return InitPromise::CreateAndReject(aError, __func__);
+ });
+}
+
+RefPtr<MediaTransportHandler::IceLogPromise>
+MediaTransportHandlerIPC::GetIceLog(const nsCString& aPattern) {
+ return mInitPromise->Then(
+ mCallbackThread, __func__,
+ [=, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /* dummy */) {
+ if (!mChild) {
+ return IceLogPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+ // Compiler has trouble deducing the return type here for some reason,
+ // so we use a temp variable as a hint.
+ // SendGetIceLog _almost_ returns an IceLogPromise; the reject value
+ // differs (ipc::ResponseRejectReason vs nsresult) so we need to
+ // convert.
+ RefPtr<IceLogPromise> promise = mChild->SendGetIceLog(aPattern)->Then(
+ mCallbackThread, __func__,
+ [](WebrtcGlobalLog&& aLogLines) {
+ return IceLogPromise::CreateAndResolve(std::move(aLogLines),
+ __func__);
+ },
+ [](ipc::ResponseRejectReason aReason) {
+ return IceLogPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ });
+ return promise;
+ },
+ [](const nsCString& aError) {
+ return IceLogPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ });
+}
+
+void MediaTransportHandlerIPC::ClearIceLog() {
+ mInitPromise->Then(
+ mCallbackThread, __func__,
+ [=, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
+ if (mChild) {
+ mChild->SendClearIceLog();
+ }
+ },
+ [](const nsCString& aError) {});
+}
+
+void MediaTransportHandlerIPC::EnterPrivateMode() {
+ mInitPromise->Then(
+ mCallbackThread, __func__,
+ [=, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
+ if (mChild) {
+ mChild->SendEnterPrivateMode();
+ }
+ },
+ [](const nsCString& aError) {});
+}
+
+void MediaTransportHandlerIPC::ExitPrivateMode() {
+ mInitPromise->Then(
+ mCallbackThread, __func__,
+ [=, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
+ if (mChild) {
+ mChild->SendExitPrivateMode();
+ }
+ },
+ [](const nsCString& aError) {});
+}
+
+void MediaTransportHandlerIPC::CreateIceCtx(const std::string& aName) {
+ CSFLogDebug(LOGTAG, "MediaTransportHandlerIPC::CreateIceCtx start");
+
+ mInitPromise->Then(
+ mCallbackThread, __func__,
+ [=, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
+ if (mChild) {
+ CSFLogDebug(LOGTAG, "%s starting", __func__);
+ if (NS_WARN_IF(!mChild->SendCreateIceCtx(aName))) {
+ CSFLogError(LOGTAG, "%s failed!", __func__);
+ }
+ }
+ },
+ [](const nsCString& aError) {});
+}
+
+nsresult MediaTransportHandlerIPC::SetIceConfig(
+ const nsTArray<dom::RTCIceServer>& aIceServers,
+ dom::RTCIceTransportPolicy aIcePolicy) {
+ // Run some validation on this side of the IPC boundary so we can return
+ // errors synchronously. We don't actually use the results. It might make
+ // sense to move this check to PeerConnection and have this API take the
+ // converted form, but we would need to write IPC serialization code for
+ // the NrIce*Server types.
+ std::vector<NrIceStunServer> stunServers;
+ std::vector<NrIceTurnServer> turnServers;
+ nsresult rv = ConvertIceServers(aIceServers, &stunServers, &turnServers);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mInitPromise->Then(
+ mCallbackThread, __func__,
+ [=, iceServers = aIceServers.Clone(),
+ self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
+ if (mChild) {
+ if (NS_WARN_IF(!mChild->SendSetIceConfig(std::move(iceServers),
+ aIcePolicy))) {
+ CSFLogError(LOGTAG, "%s failed!", __func__);
+ }
+ }
+ },
+ [](const nsCString& aError) {});
+
+ return NS_OK;
+}
+
+void MediaTransportHandlerIPC::Destroy() {
+ if (mChild) {
+ MediaTransportChild::Send__delete__(mChild);
+ mChild = nullptr;
+ }
+ delete this;
+}
+
+// We will probably be able to move the proxy lookup stuff into
+// this class once we move mtransport to its own process.
+void MediaTransportHandlerIPC::SetProxyConfig(
+ NrSocketProxyConfig&& aProxyConfig) {
+ mInitPromise->Then(
+ mCallbackThread, __func__,
+ [aProxyConfig = std::move(aProxyConfig), this,
+ self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) mutable {
+ if (mChild) {
+ mChild->SendSetProxyConfig(aProxyConfig.GetConfig());
+ }
+ },
+ [](const nsCString& aError) {});
+}
+
+void MediaTransportHandlerIPC::EnsureProvisionalTransport(
+ const std::string& aTransportId, const std::string& aLocalUfrag,
+ const std::string& aLocalPwd, int aComponentCount) {
+ mInitPromise->Then(
+ mCallbackThread, __func__,
+ [=, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
+ if (mChild) {
+ mChild->SendEnsureProvisionalTransport(aTransportId, aLocalUfrag,
+ aLocalPwd, aComponentCount);
+ }
+ },
+ [](const nsCString& aError) {});
+}
+
+void MediaTransportHandlerIPC::SetTargetForDefaultLocalAddressLookup(
+ const std::string& aTargetIp, uint16_t aTargetPort) {
+ mInitPromise->Then(
+ mCallbackThread, __func__,
+ [=, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
+ if (mChild) {
+ mChild->SendSetTargetForDefaultLocalAddressLookup(aTargetIp,
+ aTargetPort);
+ }
+ },
+ [](const nsCString& aError) {});
+}
+
+// We set default-route-only as late as possible because it depends on what
+// capture permissions have been granted on the window, which could easily
+// change between Init (ie; when the PC is created) and StartIceGathering
+// (ie; when we set the local description).
+void MediaTransportHandlerIPC::StartIceGathering(
+ bool aDefaultRouteOnly, bool aObfuscateHostAddresses,
+ // TODO(bug 1522205): It probably makes sense to look this up internally
+ const nsTArray<NrIceStunAddr>& aStunAddrs) {
+ mInitPromise->Then(
+ mCallbackThread, __func__,
+ [=, stunAddrs = aStunAddrs.Clone(),
+ self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
+ if (mChild) {
+ mChild->SendStartIceGathering(aDefaultRouteOnly,
+ aObfuscateHostAddresses, stunAddrs);
+ }
+ },
+ [](const nsCString& aError) {});
+}
+
+void MediaTransportHandlerIPC::ActivateTransport(
+ const std::string& aTransportId, const std::string& aLocalUfrag,
+ const std::string& aLocalPwd, size_t aComponentCount,
+ const std::string& aUfrag, const std::string& aPassword,
+ const nsTArray<uint8_t>& aKeyDer, const nsTArray<uint8_t>& aCertDer,
+ SSLKEAType aAuthType, bool aDtlsClient, const DtlsDigestList& aDigests,
+ bool aPrivacyRequested) {
+ mInitPromise->Then(
+ mCallbackThread, __func__,
+ [=, keyDer = aKeyDer.Clone(), certDer = aCertDer.Clone(),
+ self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
+ if (mChild) {
+ mChild->SendActivateTransport(aTransportId, aLocalUfrag, aLocalPwd,
+ aComponentCount, aUfrag, aPassword,
+ keyDer, certDer, aAuthType, aDtlsClient,
+ aDigests, aPrivacyRequested);
+ }
+ },
+ [](const nsCString& aError) {});
+}
+
+void MediaTransportHandlerIPC::RemoveTransportsExcept(
+ const std::set<std::string>& aTransportIds) {
+ std::vector<std::string> transportIds(aTransportIds.begin(),
+ aTransportIds.end());
+ mInitPromise->Then(
+ mCallbackThread, __func__,
+ [=, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
+ if (mChild) {
+ mChild->SendRemoveTransportsExcept(transportIds);
+ }
+ },
+ [](const nsCString& aError) {});
+}
+
+void MediaTransportHandlerIPC::StartIceChecks(
+ bool aIsControlling, const std::vector<std::string>& aIceOptions) {
+ mInitPromise->Then(
+ mCallbackThread, __func__,
+ [=, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
+ if (mChild) {
+ mChild->SendStartIceChecks(aIsControlling, aIceOptions);
+ }
+ },
+ [](const nsCString& aError) {});
+}
+
+void MediaTransportHandlerIPC::SendPacket(const std::string& aTransportId,
+ MediaPacket&& aPacket) {
+ mInitPromise->Then(
+ mCallbackThread, __func__,
+ [this, self = RefPtr<MediaTransportHandlerIPC>(this), aTransportId,
+ aPacket = std::move(aPacket)](bool /*dummy*/) mutable {
+ if (mChild) {
+ mChild->SendSendPacket(aTransportId, aPacket);
+ }
+ },
+ [](const nsCString& aError) {});
+}
+
+void MediaTransportHandlerIPC::AddIceCandidate(
+ const std::string& aTransportId, const std::string& aCandidate,
+ const std::string& aUfrag, const std::string& aObfuscatedAddress) {
+ mInitPromise->Then(
+ mCallbackThread, __func__,
+ [=, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
+ if (mChild) {
+ mChild->SendAddIceCandidate(aTransportId, aCandidate, aUfrag,
+ aObfuscatedAddress);
+ }
+ },
+ [](const nsCString& aError) {});
+}
+
+void MediaTransportHandlerIPC::UpdateNetworkState(bool aOnline) {
+ mInitPromise->Then(
+ mCallbackThread, __func__,
+ [=, self = RefPtr<MediaTransportHandlerIPC>(this)](bool /*dummy*/) {
+ if (mChild) {
+ mChild->SendUpdateNetworkState(aOnline);
+ }
+ },
+ [](const nsCString& aError) {});
+}
+
+RefPtr<dom::RTCStatsPromise> MediaTransportHandlerIPC::GetIceStats(
+ const std::string& aTransportId, DOMHighResTimeStamp aNow) {
+ using IPCPromise = dom::PMediaTransportChild::GetIceStatsPromise;
+ return mInitPromise
+ ->Then(mCallbackThread, __func__,
+ [aTransportId, aNow, this, self = RefPtr(this)](
+ const InitPromise::ResolveOrRejectValue& aValue) {
+ if (aValue.IsReject()) {
+ return IPCPromise::CreateAndResolve(
+ MakeUnique<dom::RTCStatsCollection>(),
+ "MediaTransportHandlerIPC::GetIceStats_1");
+ }
+ if (!mChild) {
+ return IPCPromise::CreateAndResolve(
+ MakeUnique<dom::RTCStatsCollection>(),
+ "MediaTransportHandlerIPC::GetIceStats_1");
+ }
+ return mChild->SendGetIceStats(aTransportId, aNow);
+ })
+ ->Then(mCallbackThread, __func__,
+ [](IPCPromise::ResolveOrRejectValue&& aValue) {
+ if (aValue.IsReject()) {
+ return dom::RTCStatsPromise::CreateAndResolve(
+ MakeUnique<dom::RTCStatsCollection>(),
+ "MediaTransportHandlerIPC::GetIceStats_2");
+ }
+ return dom::RTCStatsPromise::CreateAndResolve(
+ std::move(aValue.ResolveValue()),
+ "MediaTransportHandlerIPC::GetIceStats_2");
+ });
+}
+
+MediaTransportChild::MediaTransportChild(MediaTransportHandlerIPC* aUser)
+ : mUser(aUser) {}
+
+MediaTransportChild::~MediaTransportChild() { mUser->mChild = nullptr; }
+
+mozilla::ipc::IPCResult MediaTransportChild::RecvOnCandidate(
+ const string& transportId, const CandidateInfo& candidateInfo) {
+ mUser->OnCandidate(transportId, candidateInfo);
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportChild::RecvOnAlpnNegotiated(
+ const string& alpn) {
+ mUser->OnAlpnNegotiated(alpn);
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportChild::RecvOnGatheringStateChange(
+ const int& state) {
+ mUser->OnGatheringStateChange(static_cast<dom::RTCIceGatheringState>(state));
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportChild::RecvOnConnectionStateChange(
+ const int& state) {
+ mUser->OnConnectionStateChange(
+ static_cast<dom::RTCIceConnectionState>(state));
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportChild::RecvOnPacketReceived(
+ const string& transportId, const MediaPacket& packet) {
+ mUser->OnPacketReceived(transportId, packet);
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportChild::RecvOnEncryptedSending(
+ const string& transportId, const MediaPacket& packet) {
+ mUser->OnEncryptedSending(transportId, packet);
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportChild::RecvOnStateChange(
+ const string& transportId, const int& state) {
+ mUser->OnStateChange(transportId, static_cast<TransportLayer::State>(state));
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportChild::RecvOnRtcpStateChange(
+ const string& transportId, const int& state) {
+ mUser->OnRtcpStateChange(transportId,
+ static_cast<TransportLayer::State>(state));
+ return ipc::IPCResult::Ok();
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/jsapi/MediaTransportHandlerIPC.h b/dom/media/webrtc/jsapi/MediaTransportHandlerIPC.h
new file mode 100644
index 0000000000..6b285dee2a
--- /dev/null
+++ b/dom/media/webrtc/jsapi/MediaTransportHandlerIPC.h
@@ -0,0 +1,96 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MTRANSPORTHANDLER_IPC_H__
+#define _MTRANSPORTHANDLER_IPC_H__
+
+#include "jsapi/MediaTransportHandler.h"
+#include "mozilla/dom/PMediaTransportChild.h"
+
+namespace mozilla {
+
+class MediaTransportChild;
+
+// Implementation of MediaTransportHandler that uses IPC (PMediaTransport) to
+// talk to mtransport on another process.
+class MediaTransportHandlerIPC final : public MediaTransportHandler {
+ public:
+ explicit MediaTransportHandlerIPC(nsISerialEventTarget* aCallbackThread);
+ void Initialize() override;
+ RefPtr<IceLogPromise> GetIceLog(const nsCString& aPattern) override;
+ void ClearIceLog() override;
+ void EnterPrivateMode() override;
+ void ExitPrivateMode() override;
+
+ void CreateIceCtx(const std::string& aName) override;
+
+ nsresult SetIceConfig(const nsTArray<dom::RTCIceServer>& aIceServers,
+ dom::RTCIceTransportPolicy aIcePolicy) override;
+
+ // We will probably be able to move the proxy lookup stuff into
+ // this class once we move mtransport to its own process.
+ void SetProxyConfig(NrSocketProxyConfig&& aProxyConfig) override;
+
+ void EnsureProvisionalTransport(const std::string& aTransportId,
+ const std::string& aLocalUfrag,
+ const std::string& aLocalPwd,
+ int aComponentCount) override;
+
+ void SetTargetForDefaultLocalAddressLookup(const std::string& aTargetIp,
+ uint16_t aTargetPort) override;
+
+ // We set default-route-only as late as possible because it depends on what
+ // capture permissions have been granted on the window, which could easily
+ // change between Init (ie; when the PC is created) and StartIceGathering
+ // (ie; when we set the local description).
+ void StartIceGathering(bool aDefaultRouteOnly, bool aObfuscateHostAddresses,
+ // TODO: It probably makes sense to look
+ // this up internally
+ const nsTArray<NrIceStunAddr>& aStunAddrs) override;
+
+ void ActivateTransport(
+ const std::string& aTransportId, const std::string& aLocalUfrag,
+ const std::string& aLocalPwd, size_t aComponentCount,
+ const std::string& aUfrag, const std::string& aPassword,
+ const nsTArray<uint8_t>& aKeyDer, const nsTArray<uint8_t>& aCertDer,
+ SSLKEAType aAuthType, bool aDtlsClient, const DtlsDigestList& aDigests,
+ bool aPrivacyRequested) override;
+
+ void RemoveTransportsExcept(
+ const std::set<std::string>& aTransportIds) override;
+
+ void StartIceChecks(bool aIsControlling,
+ const std::vector<std::string>& aIceOptions) override;
+
+ void SendPacket(const std::string& aTransportId,
+ MediaPacket&& aPacket) override;
+
+ void AddIceCandidate(const std::string& aTransportId,
+ const std::string& aCandidate, const std::string& aUfrag,
+ const std::string& aObfuscatedAddress) override;
+
+ void UpdateNetworkState(bool aOnline) override;
+
+ RefPtr<dom::RTCStatsPromise> GetIceStats(const std::string& aTransportId,
+ DOMHighResTimeStamp aNow) override;
+
+ private:
+ friend class MediaTransportChild;
+ void Destroy() override;
+
+ // We do not own this; it will tell us when it is going away.
+ dom::PMediaTransportChild* mChild = nullptr;
+
+ // |mChild| can only be initted asynchronously, |mInitPromise| resolves
+ // when that happens. The |Then| calls make it convenient to dispatch API
+ // calls to main, which is a bonus.
+ // Init promise is not exclusive; this lets us call |Then| on it for every
+ // API call we get, instead of creating another promise each time.
+ typedef MozPromise<bool, nsCString, false> InitPromise;
+ RefPtr<InitPromise> mInitPromise;
+};
+
+} // namespace mozilla
+
+#endif //_MTRANSPORTHANDLER_IPC_H__
diff --git a/dom/media/webrtc/jsapi/MediaTransportParent.cpp b/dom/media/webrtc/jsapi/MediaTransportParent.cpp
new file mode 100644
index 0000000000..d0f90df8c5
--- /dev/null
+++ b/dom/media/webrtc/jsapi/MediaTransportParent.cpp
@@ -0,0 +1,240 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/MediaTransportParent.h"
+#include "jsapi/MediaTransportHandler.h"
+
+#include "transport/sigslot.h"
+#include "common/browser_logging/CSFLog.h"
+
+namespace mozilla {
+
+// Deals with the MediaTransportHandler interface, so MediaTransportParent
+// doesn't have to..
+class MediaTransportParent::Impl : public sigslot::has_slots<> {
+ public:
+ explicit Impl(MediaTransportParent* aParent)
+ : mHandler(MediaTransportHandler::Create(GetCurrentSerialEventTarget())),
+ mParent(aParent) {
+ mHandler->SignalCandidate.connect(this,
+ &MediaTransportParent::Impl::OnCandidate);
+ mHandler->SignalAlpnNegotiated.connect(
+ this, &MediaTransportParent::Impl::OnAlpnNegotiated);
+ mHandler->SignalGatheringStateChange.connect(
+ this, &MediaTransportParent::Impl::OnGatheringStateChange);
+ mHandler->SignalConnectionStateChange.connect(
+ this, &MediaTransportParent::Impl::OnConnectionStateChange);
+ mHandler->SignalPacketReceived.connect(
+ this, &MediaTransportParent::Impl::OnPacketReceived);
+ mHandler->SignalEncryptedSending.connect(
+ this, &MediaTransportParent::Impl::OnEncryptedSending);
+ mHandler->SignalStateChange.connect(
+ this, &MediaTransportParent::Impl::OnStateChange);
+ mHandler->SignalRtcpStateChange.connect(
+ this, &MediaTransportParent::Impl::OnRtcpStateChange);
+ }
+
+ virtual ~Impl() {
+ disconnect_all();
+ mHandler = nullptr;
+ }
+
+ void OnCandidate(const std::string& aTransportId,
+ const CandidateInfo& aCandidateInfo) {
+ NS_ENSURE_TRUE_VOID(mParent->SendOnCandidate(aTransportId, aCandidateInfo));
+ }
+
+ void OnAlpnNegotiated(const std::string& aAlpn, bool aPrivacyRequested) {
+ NS_ENSURE_TRUE_VOID(mParent->SendOnAlpnNegotiated(aAlpn));
+ }
+
+ void OnGatheringStateChange(dom::RTCIceGatheringState aState) {
+ NS_ENSURE_TRUE_VOID(
+ mParent->SendOnGatheringStateChange(static_cast<int>(aState)));
+ }
+
+ void OnConnectionStateChange(dom::RTCIceConnectionState aState) {
+ NS_ENSURE_TRUE_VOID(
+ mParent->SendOnConnectionStateChange(static_cast<int>(aState)));
+ }
+
+ void OnPacketReceived(const std::string& aTransportId,
+ const MediaPacket& aPacket) {
+ NS_ENSURE_TRUE_VOID(mParent->SendOnPacketReceived(aTransportId, aPacket));
+ }
+
+ void OnEncryptedSending(const std::string& aTransportId,
+ const MediaPacket& aPacket) {
+ NS_ENSURE_TRUE_VOID(mParent->SendOnEncryptedSending(aTransportId, aPacket));
+ }
+
+ void OnStateChange(const std::string& aTransportId,
+ TransportLayer::State aState) {
+ NS_ENSURE_TRUE_VOID(mParent->SendOnStateChange(aTransportId, aState));
+ }
+
+ void OnRtcpStateChange(const std::string& aTransportId,
+ TransportLayer::State aState) {
+ NS_ENSURE_TRUE_VOID(mParent->SendOnRtcpStateChange(aTransportId, aState));
+ }
+
+ RefPtr<MediaTransportHandler> mHandler;
+
+ private:
+ MediaTransportParent* mParent;
+};
+
+MediaTransportParent::MediaTransportParent() : mImpl(new Impl(this)) {}
+
+MediaTransportParent::~MediaTransportParent() {}
+
+mozilla::ipc::IPCResult MediaTransportParent::RecvGetIceLog(
+ const nsCString& pattern, GetIceLogResolver&& aResolve) {
+ mImpl->mHandler->GetIceLog(pattern)->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ // IPDL doesn't give us a reject function, so we cannot reject async, so
+ // we are forced to resolve with an empty result. Laaaaaaame.
+ [aResolve = std::move(aResolve)](
+ MediaTransportHandler::IceLogPromise::ResolveOrRejectValue&&
+ aResult) mutable {
+ WebrtcGlobalLog logLines;
+ if (aResult.IsResolve()) {
+ logLines = std::move(aResult.ResolveValue());
+ }
+ aResolve(logLines);
+ });
+
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportParent::RecvClearIceLog() {
+ mImpl->mHandler->ClearIceLog();
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportParent::RecvEnterPrivateMode() {
+ mImpl->mHandler->EnterPrivateMode();
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportParent::RecvExitPrivateMode() {
+ mImpl->mHandler->ExitPrivateMode();
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportParent::RecvCreateIceCtx(
+ const string& name) {
+ mImpl->mHandler->CreateIceCtx(name);
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportParent::RecvSetIceConfig(
+ nsTArray<RTCIceServer>&& iceServers,
+ const RTCIceTransportPolicy& icePolicy) {
+ nsresult rv = mImpl->mHandler->SetIceConfig(iceServers, icePolicy);
+ if (NS_FAILED(rv)) {
+ return ipc::IPCResult::Fail(WrapNotNull(this), __func__,
+ "MediaTransportHandler::SetIceConfig failed");
+ }
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportParent::RecvSetProxyConfig(
+ const net::WebrtcProxyConfig& aProxyConfig) {
+ mImpl->mHandler->SetProxyConfig(NrSocketProxyConfig(aProxyConfig));
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportParent::RecvEnsureProvisionalTransport(
+ const string& transportId, const string& localUfrag, const string& localPwd,
+ const int& componentCount) {
+ mImpl->mHandler->EnsureProvisionalTransport(transportId, localUfrag, localPwd,
+ componentCount);
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult
+MediaTransportParent::RecvSetTargetForDefaultLocalAddressLookup(
+ const std::string& targetIp, uint16_t targetPort) {
+ mImpl->mHandler->SetTargetForDefaultLocalAddressLookup(targetIp, targetPort);
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportParent::RecvStartIceGathering(
+ const bool& defaultRouteOnly, const bool& obfuscateHostAddresses,
+ const net::NrIceStunAddrArray& stunAddrs) {
+ mImpl->mHandler->StartIceGathering(defaultRouteOnly, obfuscateHostAddresses,
+ stunAddrs);
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportParent::RecvActivateTransport(
+ const string& transportId, const string& localUfrag, const string& localPwd,
+ const int& componentCount, const string& remoteUfrag,
+ const string& remotePwd, nsTArray<uint8_t>&& keyDer,
+ nsTArray<uint8_t>&& certDer, const int& authType, const bool& dtlsClient,
+ const DtlsDigestList& digests, const bool& privacyRequested) {
+ mImpl->mHandler->ActivateTransport(
+ transportId, localUfrag, localPwd, componentCount, remoteUfrag, remotePwd,
+ keyDer, certDer, static_cast<SSLKEAType>(authType), dtlsClient, digests,
+ privacyRequested);
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportParent::RecvRemoveTransportsExcept(
+ const StringVector& transportIds) {
+ std::set<std::string> ids(transportIds.begin(), transportIds.end());
+ mImpl->mHandler->RemoveTransportsExcept(ids);
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportParent::RecvStartIceChecks(
+ const bool& isControlling, const StringVector& iceOptions) {
+ mImpl->mHandler->StartIceChecks(isControlling, iceOptions);
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportParent::RecvSendPacket(
+ const string& transportId, MediaPacket&& packet) {
+ mImpl->mHandler->SendPacket(transportId, std::move(packet));
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportParent::RecvAddIceCandidate(
+ const string& transportId, const string& candidate, const string& ufrag,
+ const string& obfuscatedAddr) {
+ mImpl->mHandler->AddIceCandidate(transportId, candidate, ufrag,
+ obfuscatedAddr);
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportParent::RecvUpdateNetworkState(
+ const bool& online) {
+ mImpl->mHandler->UpdateNetworkState(online);
+ return ipc::IPCResult::Ok();
+}
+
+mozilla::ipc::IPCResult MediaTransportParent::RecvGetIceStats(
+ const string& transportId, const double& now,
+ GetIceStatsResolver&& aResolve) {
+ mImpl->mHandler->GetIceStats(transportId, now)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ // IPDL doesn't give us a reject function, so we cannot reject async,
+ // so we are forced to resolve with an unmodified result. Laaaaaaame.
+ [aResolve = std::move(aResolve)](
+ dom::RTCStatsPromise::ResolveOrRejectValue&& aResult) {
+ if (aResult.IsResolve()) {
+ aResolve(aResult.ResolveValue());
+ } else {
+ aResolve(MakeUnique<dom::RTCStatsCollection>());
+ }
+ });
+
+ return ipc::IPCResult::Ok();
+}
+
+void MediaTransportParent::ActorDestroy(ActorDestroyReason aWhy) {}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/jsapi/PacketDumper.cpp b/dom/media/webrtc/jsapi/PacketDumper.cpp
new file mode 100644
index 0000000000..2b62e0806e
--- /dev/null
+++ b/dom/media/webrtc/jsapi/PacketDumper.cpp
@@ -0,0 +1,124 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "jsapi/PacketDumper.h"
+#include "jsapi/PeerConnectionImpl.h"
+#include "mozilla/media/MediaUtils.h" // NewRunnableFrom
+#include "nsThreadUtils.h" // NS_DispatchToMainThread
+
+namespace mozilla {
+
+/* static */
+RefPtr<PacketDumper> PacketDumper::GetPacketDumper(
+ const std::string& aPcHandle) {
+ MOZ_ASSERT(NS_IsMainThread());
+ PeerConnectionWrapper pcw(aPcHandle);
+ if (pcw.impl()) {
+ return pcw.impl()->GetPacketDumper();
+ }
+
+ return new PacketDumper("");
+}
+
+PacketDumper::PacketDumper(const std::string& aPcHandle)
+ : mPcHandle(aPcHandle),
+ mPacketDumpEnabled(false),
+ mPacketDumpFlagsMutex("Packet dump flags mutex") {}
+
+void PacketDumper::Dump(size_t aLevel, dom::mozPacketDumpType aType,
+ bool aSending, const void* aData, size_t aSize) {
+ // Optimization; avoids making a copy of the buffer, but we need to lock a
+ // mutex and check the flags. Could be optimized further, if we really want to
+ if (!ShouldDumpPacket(aLevel, aType, aSending)) {
+ return;
+ }
+
+ UniquePtr<uint8_t[]> ownedPacket = MakeUnique<uint8_t[]>(aSize);
+ memcpy(ownedPacket.get(), aData, aSize);
+
+ RefPtr<Runnable> dumpRunnable = media::NewRunnableFrom(std::bind(
+ [this, self = RefPtr<PacketDumper>(this), aLevel, aType, aSending,
+ aSize](UniquePtr<uint8_t[]>& aPacket) -> nsresult {
+ // Check again; packet dump might have been disabled since the dispatch
+ if (ShouldDumpPacket(aLevel, aType, aSending)) {
+ PeerConnectionWrapper pcw(mPcHandle);
+ RefPtr<PeerConnectionImpl> pc = pcw.impl();
+ if (pc) {
+ pc->DumpPacket_m(aLevel, aType, aSending, aPacket, aSize);
+ }
+ }
+ return NS_OK;
+ },
+ std::move(ownedPacket)));
+
+ NS_DispatchToMainThread(dumpRunnable);
+}
+
+nsresult PacketDumper::EnablePacketDump(unsigned long aLevel,
+ dom::mozPacketDumpType aType,
+ bool aSending) {
+ mPacketDumpEnabled = true;
+ std::vector<unsigned>* packetDumpFlags;
+ if (aSending) {
+ packetDumpFlags = &mSendPacketDumpFlags;
+ } else {
+ packetDumpFlags = &mRecvPacketDumpFlags;
+ }
+
+ unsigned flag = 1 << (unsigned)aType;
+
+ MutexAutoLock lock(mPacketDumpFlagsMutex);
+ if (aLevel >= packetDumpFlags->size()) {
+ packetDumpFlags->resize(aLevel + 1);
+ }
+
+ (*packetDumpFlags)[aLevel] |= flag;
+ return NS_OK;
+}
+
+nsresult PacketDumper::DisablePacketDump(unsigned long aLevel,
+ dom::mozPacketDumpType aType,
+ bool aSending) {
+ std::vector<unsigned>* packetDumpFlags;
+ if (aSending) {
+ packetDumpFlags = &mSendPacketDumpFlags;
+ } else {
+ packetDumpFlags = &mRecvPacketDumpFlags;
+ }
+
+ unsigned flag = 1 << (unsigned)aType;
+
+ MutexAutoLock lock(mPacketDumpFlagsMutex);
+ if (aLevel < packetDumpFlags->size()) {
+ (*packetDumpFlags)[aLevel] &= ~flag;
+ }
+
+ return NS_OK;
+}
+
+bool PacketDumper::ShouldDumpPacket(size_t aLevel, dom::mozPacketDumpType aType,
+ bool aSending) const {
+ if (!mPacketDumpEnabled) {
+ return false;
+ }
+
+ MutexAutoLock lock(mPacketDumpFlagsMutex);
+
+ const std::vector<unsigned>* packetDumpFlags;
+
+ if (aSending) {
+ packetDumpFlags = &mSendPacketDumpFlags;
+ } else {
+ packetDumpFlags = &mRecvPacketDumpFlags;
+ }
+
+ if (aLevel < packetDumpFlags->size()) {
+ unsigned flag = 1 << (unsigned)aType;
+ return flag & packetDumpFlags->at(aLevel);
+ }
+
+ return false;
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/jsapi/PacketDumper.h b/dom/media/webrtc/jsapi/PacketDumper.h
new file mode 100644
index 0000000000..e998b3871f
--- /dev/null
+++ b/dom/media/webrtc/jsapi/PacketDumper.h
@@ -0,0 +1,52 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _PACKET_DUMPER_H_
+#define _PACKET_DUMPER_H_
+
+#include "nsISupportsImpl.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/RTCPeerConnectionBinding.h"
+
+#include <vector>
+
+namespace mozilla {
+class PeerConnectionImpl;
+
+class PacketDumper {
+ public:
+ static RefPtr<PacketDumper> GetPacketDumper(const std::string& aPcHandle);
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PacketDumper)
+
+ PacketDumper(const PacketDumper&) = delete;
+ PacketDumper& operator=(const PacketDumper&) = delete;
+
+ void Dump(size_t aLevel, dom::mozPacketDumpType aType, bool aSending,
+ const void* aData, size_t aSize);
+
+ nsresult EnablePacketDump(unsigned long aLevel, dom::mozPacketDumpType aType,
+ bool aSending);
+
+ nsresult DisablePacketDump(unsigned long aLevel, dom::mozPacketDumpType aType,
+ bool aSending);
+
+ private:
+ friend class PeerConnectionImpl;
+ explicit PacketDumper(const std::string& aPcHandle);
+ ~PacketDumper() = default;
+ bool ShouldDumpPacket(size_t aLevel, dom::mozPacketDumpType aType,
+ bool aSending) const;
+
+ // This class is not cycle-collected, so it cannot hold onto a strong ref
+ const std::string mPcHandle;
+ std::vector<unsigned> mSendPacketDumpFlags;
+ std::vector<unsigned> mRecvPacketDumpFlags;
+ Atomic<bool> mPacketDumpEnabled;
+ mutable Mutex mPacketDumpFlagsMutex;
+};
+
+} // namespace mozilla
+
+#endif // _PACKET_DUMPER_H_
diff --git a/dom/media/webrtc/jsapi/PeerConnectionCtx.cpp b/dom/media/webrtc/jsapi/PeerConnectionCtx.cpp
new file mode 100644
index 0000000000..9a8f27fb59
--- /dev/null
+++ b/dom/media/webrtc/jsapi/PeerConnectionCtx.cpp
@@ -0,0 +1,650 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PeerConnectionCtx.h"
+
+#include "WebrtcGlobalStatsHistory.h"
+#include "api/audio/audio_mixer.h"
+#include "api/audio_codecs/builtin_audio_decoder_factory.h"
+#include "modules/rtp_rtcp/source/rtp_header_extensions.h"
+#include "call/audio_state.h"
+#include "common/browser_logging/CSFLog.h"
+#include "common/browser_logging/WebRtcLog.h"
+#include "gmp-video-decode.h" // GMP_API_VIDEO_DECODER
+#include "gmp-video-encode.h" // GMP_API_VIDEO_ENCODER
+#include "libwebrtcglue/CallWorkerThread.h"
+#include "modules/audio_device/include/fake_audio_device.h"
+#include "modules/audio_processing/include/audio_processing.h"
+#include "modules/audio_processing/include/aec_dump.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/RTCPeerConnectionBinding.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Types.h"
+#include "mozilla/dom/RTCStatsReportBinding.h"
+#include "nsCRTGlue.h"
+#include "nsIIOService.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsNetCID.h" // NS_SOCKETTRANSPORTSERVICE_CONTRACTID
+#include "nsServiceManagerUtils.h" // do_GetService
+#include "PeerConnectionImpl.h"
+#include "prcvar.h"
+#include "transport/runnable_utils.h"
+#include "WebrtcGlobalChild.h"
+#include "WebrtcGlobalInformation.h"
+
+static const char* pccLogTag = "PeerConnectionCtx";
+#ifdef LOGTAG
+# undef LOGTAG
+#endif
+#define LOGTAG pccLogTag
+
+using namespace webrtc;
+
+namespace {
+class DummyAudioMixer : public AudioMixer {
+ public:
+ bool AddSource(Source*) override { return true; }
+ void RemoveSource(Source*) override {}
+ void Mix(size_t, AudioFrame*) override { MOZ_CRASH("Unexpected call"); }
+};
+
+class DummyAudioProcessing : public AudioProcessing {
+ public:
+ int Initialize() override {
+ MOZ_CRASH("Unexpected call");
+ return kNoError;
+ }
+ int Initialize(const ProcessingConfig&) override { return Initialize(); }
+ void ApplyConfig(const Config&) override { MOZ_CRASH("Unexpected call"); }
+ int proc_sample_rate_hz() const override {
+ MOZ_CRASH("Unexpected call");
+ return 0;
+ }
+ int proc_split_sample_rate_hz() const override {
+ MOZ_CRASH("Unexpected call");
+ return 0;
+ }
+ size_t num_input_channels() const override {
+ MOZ_CRASH("Unexpected call");
+ return 0;
+ }
+ size_t num_proc_channels() const override {
+ MOZ_CRASH("Unexpected call");
+ return 0;
+ }
+ size_t num_output_channels() const override {
+ MOZ_CRASH("Unexpected call");
+ return 0;
+ }
+ size_t num_reverse_channels() const override {
+ MOZ_CRASH("Unexpected call");
+ return 0;
+ }
+ void set_output_will_be_muted(bool) override { MOZ_CRASH("Unexpected call"); }
+ void SetRuntimeSetting(RuntimeSetting) override {
+ MOZ_CRASH("Unexpected call");
+ }
+ bool PostRuntimeSetting(RuntimeSetting setting) override { return false; }
+ int ProcessStream(const int16_t* const, const StreamConfig&,
+ const StreamConfig&, int16_t* const) override {
+ MOZ_CRASH("Unexpected call");
+ return kNoError;
+ }
+ int ProcessStream(const float* const*, const StreamConfig&,
+ const StreamConfig&, float* const*) override {
+ MOZ_CRASH("Unexpected call");
+ return kNoError;
+ }
+ int ProcessReverseStream(const int16_t* const, const StreamConfig&,
+ const StreamConfig&, int16_t* const) override {
+ MOZ_CRASH("Unexpected call");
+ return kNoError;
+ }
+ int ProcessReverseStream(const float* const*, const StreamConfig&,
+ const StreamConfig&, float* const*) override {
+ MOZ_CRASH("Unexpected call");
+ return kNoError;
+ }
+ int AnalyzeReverseStream(const float* const*, const StreamConfig&) override {
+ MOZ_CRASH("Unexpected call");
+ return kNoError;
+ }
+ bool GetLinearAecOutput(
+ rtc::ArrayView<std::array<float, 160>>) const override {
+ MOZ_CRASH("Unexpected call");
+ return false;
+ }
+ void set_stream_analog_level(int) override { MOZ_CRASH("Unexpected call"); }
+ int recommended_stream_analog_level() const override {
+ MOZ_CRASH("Unexpected call");
+ return -1;
+ }
+ int set_stream_delay_ms(int) override {
+ MOZ_CRASH("Unexpected call");
+ return kNoError;
+ }
+ int stream_delay_ms() const override {
+ MOZ_CRASH("Unexpected call");
+ return 0;
+ }
+ void set_stream_key_pressed(bool) override { MOZ_CRASH("Unexpected call"); }
+ bool CreateAndAttachAecDump(absl::string_view, int64_t,
+ rtc::TaskQueue*) override {
+ MOZ_CRASH("Unexpected call");
+ return false;
+ }
+ bool CreateAndAttachAecDump(FILE*, int64_t, rtc::TaskQueue*) override {
+ MOZ_CRASH("Unexpected call");
+ return false;
+ }
+ void AttachAecDump(std::unique_ptr<AecDump>) override {
+ MOZ_CRASH("Unexpected call");
+ }
+ void DetachAecDump() override { MOZ_CRASH("Unexpected call"); }
+ AudioProcessingStats GetStatistics() override {
+ return AudioProcessingStats();
+ }
+ AudioProcessingStats GetStatistics(bool) override { return GetStatistics(); }
+ AudioProcessing::Config GetConfig() const override {
+ MOZ_CRASH("Unexpected call");
+ return Config();
+ }
+};
+} // namespace
+
+namespace mozilla {
+
+using namespace dom;
+
+SharedWebrtcState::SharedWebrtcState(
+ RefPtr<AbstractThread> aCallWorkerThread,
+ webrtc::AudioState::Config&& aAudioStateConfig,
+ RefPtr<webrtc::AudioDecoderFactory> aAudioDecoderFactory,
+ UniquePtr<webrtc::WebRtcKeyValueConfig> aTrials)
+ : mCallWorkerThread(std::move(aCallWorkerThread)),
+ mAudioStateConfig(std::move(aAudioStateConfig)),
+ mAudioDecoderFactory(std::move(aAudioDecoderFactory)),
+ mTrials(std::move(aTrials)) {}
+
+SharedWebrtcState::~SharedWebrtcState() = default;
+
+class PeerConnectionCtxObserver : public nsIObserver {
+ public:
+ NS_DECL_ISUPPORTS
+
+ PeerConnectionCtxObserver() {}
+
+ void Init() {
+ nsCOMPtr<nsIObserverService> observerService =
+ services::GetObserverService();
+ if (!observerService) return;
+
+ nsresult rv = NS_OK;
+
+ rv = observerService->AddObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID,
+ false);
+ MOZ_ALWAYS_SUCCEEDS(rv);
+ rv = observerService->AddObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC,
+ false);
+ MOZ_ALWAYS_SUCCEEDS(rv);
+ (void)rv;
+ }
+
+ NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) override {
+ if (strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID) == 0) {
+ CSFLogDebug(LOGTAG, "Shutting down PeerConnectionCtx");
+ PeerConnectionCtx::Destroy();
+
+ nsCOMPtr<nsIObserverService> observerService =
+ services::GetObserverService();
+ if (!observerService) return NS_ERROR_FAILURE;
+
+ nsresult rv = observerService->RemoveObserver(
+ this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC);
+ MOZ_ALWAYS_SUCCEEDS(rv);
+ rv = observerService->RemoveObserver(this,
+ NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID);
+ MOZ_ALWAYS_SUCCEEDS(rv);
+
+ // Make sure we're not deleted while still inside ::Observe()
+ RefPtr<PeerConnectionCtxObserver> kungFuDeathGrip(this);
+ PeerConnectionCtx::gPeerConnectionCtxObserver = nullptr;
+ }
+ if (strcmp(aTopic, NS_IOSERVICE_OFFLINE_STATUS_TOPIC) == 0) {
+ if (NS_strcmp(aData, u"" NS_IOSERVICE_OFFLINE) == 0) {
+ CSFLogDebug(LOGTAG, "Updating network state to offline");
+ PeerConnectionCtx::UpdateNetworkState(false);
+ } else if (NS_strcmp(aData, u"" NS_IOSERVICE_ONLINE) == 0) {
+ CSFLogDebug(LOGTAG, "Updating network state to online");
+ PeerConnectionCtx::UpdateNetworkState(true);
+ } else {
+ CSFLogDebug(LOGTAG, "Received unsupported network state event");
+ MOZ_CRASH();
+ }
+ }
+ return NS_OK;
+ }
+
+ private:
+ virtual ~PeerConnectionCtxObserver() {
+ nsCOMPtr<nsIObserverService> observerService =
+ services::GetObserverService();
+ if (observerService) {
+ observerService->RemoveObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC);
+ observerService->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID);
+ }
+ }
+};
+
+NS_IMPL_ISUPPORTS(PeerConnectionCtxObserver, nsIObserver);
+
+PeerConnectionCtx* PeerConnectionCtx::gInstance;
+StaticRefPtr<PeerConnectionCtxObserver>
+ PeerConnectionCtx::gPeerConnectionCtxObserver;
+
+nsresult PeerConnectionCtx::InitializeGlobal() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult res;
+
+ if (!gInstance) {
+ CSFLogDebug(LOGTAG, "Creating PeerConnectionCtx");
+ PeerConnectionCtx* ctx = new PeerConnectionCtx();
+
+ res = ctx->Initialize();
+ PR_ASSERT(NS_SUCCEEDED(res));
+ if (!NS_SUCCEEDED(res)) return res;
+
+ gInstance = ctx;
+
+ if (!PeerConnectionCtx::gPeerConnectionCtxObserver) {
+ PeerConnectionCtx::gPeerConnectionCtxObserver =
+ new PeerConnectionCtxObserver();
+ PeerConnectionCtx::gPeerConnectionCtxObserver->Init();
+ }
+ }
+
+ EnableWebRtcLog();
+ return NS_OK;
+}
+
+PeerConnectionCtx* PeerConnectionCtx::GetInstance() {
+ MOZ_ASSERT(gInstance);
+ return gInstance;
+}
+
+bool PeerConnectionCtx::isActive() { return gInstance; }
+
+void PeerConnectionCtx::Destroy() {
+ CSFLogDebug(LOGTAG, "%s", __FUNCTION__);
+
+ if (gInstance) {
+ // Null out gInstance first, so PeerConnectionImpl doesn't try to use it
+ // in Cleanup.
+ auto* instance = gInstance;
+ gInstance = nullptr;
+ instance->Cleanup();
+ delete instance;
+ }
+
+ StopWebRtcLog();
+}
+
+template <typename T>
+static void RecordCommonRtpTelemetry(const T& list, const T& lastList,
+ const bool isRemote) {
+ using namespace Telemetry;
+ for (const auto& s : list) {
+ const bool isAudio = s.mKind.Find(u"audio") != -1;
+ if (s.mPacketsLost.WasPassed() && s.mPacketsReceived.WasPassed()) {
+ if (const uint64_t total =
+ s.mPacketsLost.Value() + s.mPacketsReceived.Value()) {
+ HistogramID id =
+ isRemote ? (isAudio ? WEBRTC_AUDIO_QUALITY_OUTBOUND_PACKETLOSS_RATE
+ : WEBRTC_VIDEO_QUALITY_OUTBOUND_PACKETLOSS_RATE)
+ : (isAudio ? WEBRTC_AUDIO_QUALITY_INBOUND_PACKETLOSS_RATE
+ : WEBRTC_VIDEO_QUALITY_INBOUND_PACKETLOSS_RATE);
+ Accumulate(id, (s.mPacketsLost.Value() * 1000) / total);
+ }
+ }
+ if (s.mJitter.WasPassed()) {
+ HistogramID id = isRemote
+ ? (isAudio ? WEBRTC_AUDIO_QUALITY_OUTBOUND_JITTER
+ : WEBRTC_VIDEO_QUALITY_OUTBOUND_JITTER)
+ : (isAudio ? WEBRTC_AUDIO_QUALITY_INBOUND_JITTER
+ : WEBRTC_VIDEO_QUALITY_INBOUND_JITTER);
+ Accumulate(id, s.mJitter.Value() * 1000);
+ }
+ }
+}
+
+// Telemetry reporting every second after start of first call.
+// The threading model around the media pipelines is weird:
+// - The pipelines are containers,
+// - containers that are only safe on main thread, with members only safe on
+// STS,
+// - hence the there and back again approach.
+
+void PeerConnectionCtx::DeliverStats(
+ UniquePtr<dom::RTCStatsReportInternal>&& aReport) {
+ using namespace Telemetry;
+
+ // First, get reports from a second ago, if any, for calculations below
+ UniquePtr<dom::RTCStatsReportInternal> lastReport;
+ {
+ auto i = mLastReports.find(aReport->mPcid);
+ if (i != mLastReports.end()) {
+ lastReport = std::move(i->second);
+ } else {
+ lastReport = MakeUnique<dom::RTCStatsReportInternal>();
+ }
+ }
+ // Record Telemetery
+ RecordCommonRtpTelemetry(aReport->mInboundRtpStreamStats,
+ lastReport->mInboundRtpStreamStats, false);
+ // Record bandwidth telemetry
+ for (const auto& s : aReport->mInboundRtpStreamStats) {
+ if (s.mBytesReceived.WasPassed()) {
+ const bool isAudio = s.mKind.Find(u"audio") != -1;
+ for (const auto& lastS : lastReport->mInboundRtpStreamStats) {
+ if (lastS.mId == s.mId) {
+ int32_t deltaMs = s.mTimestamp.Value() - lastS.mTimestamp.Value();
+ // In theory we're called every second, so delta *should* be in that
+ // range. Small deltas could cause errors due to division
+ if (deltaMs < 500 || deltaMs > 60000 ||
+ !lastS.mBytesReceived.WasPassed()) {
+ break;
+ }
+ HistogramID id = isAudio
+ ? WEBRTC_AUDIO_QUALITY_INBOUND_BANDWIDTH_KBITS
+ : WEBRTC_VIDEO_QUALITY_INBOUND_BANDWIDTH_KBITS;
+ // We could accumulate values until enough time has passed
+ // and then Accumulate() but this isn't that important
+ Accumulate(
+ id,
+ ((s.mBytesReceived.Value() - lastS.mBytesReceived.Value()) * 8) /
+ deltaMs);
+ break;
+ }
+ }
+ }
+ }
+ RecordCommonRtpTelemetry(aReport->mRemoteInboundRtpStreamStats,
+ lastReport->mRemoteInboundRtpStreamStats, true);
+ for (const auto& s : aReport->mRemoteInboundRtpStreamStats) {
+ if (s.mRoundTripTime.WasPassed()) {
+ const bool isAudio = s.mKind.Find(u"audio") != -1;
+ HistogramID id = isAudio ? WEBRTC_AUDIO_QUALITY_OUTBOUND_RTT
+ : WEBRTC_VIDEO_QUALITY_OUTBOUND_RTT;
+ Accumulate(id, s.mRoundTripTime.Value() * 1000);
+ }
+ }
+
+ mLastReports[aReport->mPcid] = std::move(aReport);
+}
+
+void PeerConnectionCtx::EverySecondTelemetryCallback_m(nsITimer* timer,
+ void* closure) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(PeerConnectionCtx::isActive());
+
+ for (auto& idAndPc : GetInstance()->mPeerConnections) {
+ if (!idAndPc.second->IsClosed()) {
+ idAndPc.second->GetStats(nullptr, true)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [=](UniquePtr<dom::RTCStatsReportInternal>&& aReport) {
+ if (PeerConnectionCtx::isActive()) {
+ PeerConnectionCtx::GetInstance()->DeliverStats(
+ std::move(aReport));
+ }
+ },
+ [=](nsresult aError) {});
+ idAndPc.second->CollectConduitTelemetryData();
+ }
+ }
+}
+
+void PeerConnectionCtx::UpdateNetworkState(bool online) {
+ auto ctx = GetInstance();
+ if (ctx->mPeerConnections.empty()) {
+ return;
+ }
+ for (auto pc : ctx->mPeerConnections) {
+ pc.second->UpdateNetworkState(online);
+ }
+}
+
+SharedWebrtcState* PeerConnectionCtx::GetSharedWebrtcState() const {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mSharedWebrtcState;
+}
+
+void PeerConnectionCtx::RemovePeerConnection(const std::string& aKey) {
+ MOZ_ASSERT(NS_IsMainThread());
+ auto it = mPeerConnections.find(aKey);
+ if (it != mPeerConnections.end()) {
+ if (it->second->GetFinalStats() && !it->second->LongTermStatsIsDisabled()) {
+ WebrtcGlobalInformation::StashStats(*(it->second->GetFinalStats()));
+ }
+ nsAutoString pcId = NS_ConvertASCIItoUTF16(it->second->GetName().c_str());
+ if (XRE_IsContentProcess()) {
+ if (auto* child = WebrtcGlobalChild::Get(); child) {
+ auto pcId = NS_ConvertASCIItoUTF16(it->second->GetName().c_str());
+ child->SendPeerConnectionFinalStats(*(it->second->GetFinalStats()));
+ child->SendPeerConnectionDestroyed(pcId);
+ }
+ } else {
+ using Update = WebrtcGlobalInformation::PcTrackingUpdate;
+ auto update = Update::Remove(pcId);
+ auto finalStats =
+ MakeUnique<RTCStatsReportInternal>(*(it->second->GetFinalStats()));
+ WebrtcGlobalStatsHistory::Record(std::move(finalStats));
+ WebrtcGlobalInformation::PeerConnectionTracking(update);
+ }
+
+ mPeerConnections.erase(it);
+ if (mPeerConnections.empty()) {
+ mSharedWebrtcState = nullptr;
+ StopTelemetryTimer();
+ }
+ }
+}
+
+void PeerConnectionCtx::AddPeerConnection(const std::string& aKey,
+ PeerConnectionImpl* aPeerConnection) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mPeerConnections.count(aKey) == 0,
+ "PeerConnection with this key should not already exist");
+ if (mPeerConnections.empty()) {
+ AudioState::Config audioStateConfig;
+ audioStateConfig.audio_mixer = new rtc::RefCountedObject<DummyAudioMixer>();
+ AudioProcessingBuilder audio_processing_builder;
+ audioStateConfig.audio_processing =
+ new rtc::RefCountedObject<DummyAudioProcessing>();
+ audioStateConfig.audio_device_module =
+ new rtc::RefCountedObject<FakeAudioDeviceModule>();
+
+ SharedThreadPoolWebRtcTaskQueueFactory taskQueueFactory;
+ constexpr bool supportTailDispatch = true;
+ // Note the NonBlocking DeletionPolicy!
+ // This task queue is passed into libwebrtc as a raw pointer.
+ // WebrtcCallWrapper guarantees that it outlives its webrtc::Call instance.
+ // Outside of libwebrtc we must use ref-counting to either the
+ // WebrtcCallWrapper or to the CallWorkerThread to keep it alive.
+ auto callWorkerThread =
+ WrapUnique(taskQueueFactory
+ .CreateTaskQueueWrapper<DeletionPolicy::NonBlocking>(
+ "CallWorker", supportTailDispatch,
+ webrtc::TaskQueueFactory::Priority::NORMAL,
+ MediaThreadType::WEBRTC_CALL_THREAD)
+ .release());
+
+ UniquePtr<webrtc::WebRtcKeyValueConfig> trials =
+ WrapUnique(new NoTrialsConfig());
+
+ mSharedWebrtcState = MakeAndAddRef<SharedWebrtcState>(
+ new CallWorkerThread(std::move(callWorkerThread)),
+ std::move(audioStateConfig),
+ already_AddRefed(CreateBuiltinAudioDecoderFactory().release()),
+ std::move(trials));
+ StartTelemetryTimer();
+ }
+ auto pcId = NS_ConvertASCIItoUTF16(aPeerConnection->GetName().c_str());
+ if (XRE_IsContentProcess()) {
+ if (auto* child = WebrtcGlobalChild::Get(); child) {
+ child->SendPeerConnectionCreated(
+ pcId, aPeerConnection->LongTermStatsIsDisabled());
+ }
+ } else {
+ using Update = WebrtcGlobalInformation::PcTrackingUpdate;
+ auto update = Update::Add(pcId, aPeerConnection->LongTermStatsIsDisabled());
+ WebrtcGlobalInformation::PeerConnectionTracking(update);
+ }
+ mPeerConnections[aKey] = aPeerConnection;
+}
+
+PeerConnectionImpl* PeerConnectionCtx::GetPeerConnection(
+ const std::string& aKey) const {
+ MOZ_ASSERT(NS_IsMainThread());
+ auto iterator = mPeerConnections.find(aKey);
+ if (iterator == mPeerConnections.end()) {
+ return nullptr;
+ }
+ return iterator->second;
+}
+
+void PeerConnectionCtx::ClearClosedStats() {
+ for (auto& [id, pc] : mPeerConnections) {
+ Unused << id;
+ if (pc->IsClosed()) {
+ // Rare case
+ pc->DisableLongTermStats();
+ }
+ }
+}
+
+nsresult PeerConnectionCtx::Initialize() {
+ MOZ_ASSERT(NS_IsMainThread());
+ initGMP();
+ SdpRidAttributeList::kMaxRidLength =
+ webrtc::BaseRtpStringExtension::kMaxValueSizeBytes;
+
+ if (XRE_IsContentProcess()) {
+ WebrtcGlobalChild::Get();
+ }
+
+ return NS_OK;
+}
+
+nsresult PeerConnectionCtx::StartTelemetryTimer() {
+ return NS_NewTimerWithFuncCallback(getter_AddRefs(mTelemetryTimer),
+ EverySecondTelemetryCallback_m, this, 1000,
+ nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP,
+ "EverySecondTelemetryCallback_m");
+}
+
+void PeerConnectionCtx::StopTelemetryTimer() {
+ if (mTelemetryTimer) {
+ mTelemetryTimer->Cancel();
+ mTelemetryTimer = nullptr;
+ }
+}
+
+static void GMPReady_m() {
+ if (PeerConnectionCtx::isActive()) {
+ PeerConnectionCtx::GetInstance()->onGMPReady();
+ }
+};
+
+static void GMPReady() {
+ GetMainThreadSerialEventTarget()->Dispatch(WrapRunnableNM(&GMPReady_m),
+ NS_DISPATCH_NORMAL);
+};
+
+void PeerConnectionCtx::initGMP() {
+ mGMPService = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+
+ if (!mGMPService) {
+ CSFLogError(LOGTAG, "%s failed to get the gecko-media-plugin-service",
+ __FUNCTION__);
+ return;
+ }
+
+ nsCOMPtr<nsIThread> thread;
+ nsresult rv = mGMPService->GetThread(getter_AddRefs(thread));
+
+ if (NS_FAILED(rv)) {
+ mGMPService = nullptr;
+ CSFLogError(LOGTAG,
+ "%s failed to get the gecko-media-plugin thread, err=%u",
+ __FUNCTION__, static_cast<unsigned>(rv));
+ return;
+ }
+
+ // presumes that all GMP dir scans have been queued for the GMPThread
+ thread->Dispatch(WrapRunnableNM(&GMPReady), NS_DISPATCH_NORMAL);
+}
+
+nsresult PeerConnectionCtx::Cleanup() {
+ CSFLogDebug(LOGTAG, "%s", __FUNCTION__);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mQueuedJSEPOperations.Clear();
+ mGMPService = nullptr;
+ mTransportHandler = nullptr;
+ for (auto& [id, pc] : mPeerConnections) {
+ (void)id;
+ pc->Close();
+ }
+ mPeerConnections.clear();
+ mSharedWebrtcState = nullptr;
+ return NS_OK;
+}
+
+void PeerConnectionCtx::queueJSEPOperation(nsIRunnable* aOperation) {
+ mQueuedJSEPOperations.AppendElement(aOperation);
+}
+
+void PeerConnectionCtx::onGMPReady() {
+ mGMPReady = true;
+ for (size_t i = 0; i < mQueuedJSEPOperations.Length(); ++i) {
+ mQueuedJSEPOperations[i]->Run();
+ }
+ mQueuedJSEPOperations.Clear();
+}
+
+bool PeerConnectionCtx::gmpHasH264() {
+ if (!mGMPService) {
+ return false;
+ }
+
+ // XXX I'd prefer if this was all known ahead of time...
+
+ AutoTArray<nsCString, 1> tags;
+ tags.AppendElement("h264"_ns);
+
+ bool has_gmp;
+ nsresult rv;
+ rv = mGMPService->HasPluginForAPI(nsLiteralCString(GMP_API_VIDEO_ENCODER),
+ tags, &has_gmp);
+ if (NS_FAILED(rv) || !has_gmp) {
+ return false;
+ }
+
+ rv = mGMPService->HasPluginForAPI(nsLiteralCString(GMP_API_VIDEO_DECODER),
+ tags, &has_gmp);
+ if (NS_FAILED(rv) || !has_gmp) {
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/jsapi/PeerConnectionCtx.h b/dom/media/webrtc/jsapi/PeerConnectionCtx.h
new file mode 100644
index 0000000000..fdd81f6406
--- /dev/null
+++ b/dom/media/webrtc/jsapi/PeerConnectionCtx.h
@@ -0,0 +1,194 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef peerconnectionctx_h___h__
+#define peerconnectionctx_h___h__
+
+#include <map>
+#include <string>
+
+#include "WebrtcGlobalChild.h"
+#include "api/field_trials_view.h"
+#include "api/scoped_refptr.h"
+#include "call/audio_state.h"
+#include "MediaTransportHandler.h" // Mostly for IceLogPromise
+#include "mozIGeckoMediaPluginService.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/StaticPtr.h"
+#include "nsIRunnable.h"
+#include "PeerConnectionImpl.h"
+
+namespace webrtc {
+class AudioDecoderFactory;
+
+// Used for testing in mediapipeline_unittest.cpp, MockCall.h
+class NoTrialsConfig : public FieldTrialsView {
+ public:
+ NoTrialsConfig() = default;
+ std::string Lookup(absl::string_view key) const override {
+ // Upstream added a new default field trial string for
+ // CongestionWindow, that we don't want. In
+ // third_party/libwebrtc/rtc_base/experiments/rate_control_settings.cc
+ // they set kCongestionWindowDefaultFieldTrialString to
+ // "QueueSize:350,MinBitrate:30000,DropFrame:true". With QueueSize
+ // set, GoogCcNetworkController::UpdateCongestionWindowSize is
+ // called. Because negative values are calculated in
+ // feedback_rtt, an assert fires when calculating data_window in
+ // GoogCcNetworkController::UpdateCongestionWindowSize. We probably
+ // need to figure out why we're calculating negative feedback_rtt.
+ // See Bug 1780620.
+ if ("WebRTC-CongestionWindow" == key) {
+ return std::string("MinBitrate:30000,DropFrame:true");
+ }
+ return std::string();
+ }
+};
+} // namespace webrtc
+
+namespace mozilla {
+class PeerConnectionCtxObserver;
+
+namespace dom {
+class WebrtcGlobalInformation;
+}
+
+/**
+ * Refcounted class containing state shared across all PeerConnections and all
+ * Call instances. Managed by PeerConnectionCtx, and kept around while there are
+ * registered peer connections.
+ */
+class SharedWebrtcState {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedWebrtcState)
+
+ SharedWebrtcState(RefPtr<AbstractThread> aCallWorkerThread,
+ webrtc::AudioState::Config&& aAudioStateConfig,
+ RefPtr<webrtc::AudioDecoderFactory> aAudioDecoderFactory,
+ UniquePtr<webrtc::FieldTrialsView> aTrials);
+
+ // A global Call worker thread shared between all Call instances. Implements
+ // AbstractThread for running tasks that call into a Call instance through its
+ // webrtc::TaskQueue member, and for using AbstractThread-specific higher
+ // order constructs like StateMirroring.
+ const RefPtr<AbstractThread> mCallWorkerThread;
+
+ // AudioState config containing dummy implementations of the audio stack,
+ // since we use our own audio stack instead. Shared across all Call instances.
+ const webrtc::AudioState::Config mAudioStateConfig;
+
+ // AudioDecoderFactory instance shared between calls, to limit the number of
+ // instances in large calls.
+ const RefPtr<webrtc::AudioDecoderFactory> mAudioDecoderFactory;
+
+ // Trials instance shared between calls, to limit the number of instances in
+ // large calls.
+ const UniquePtr<webrtc::FieldTrialsView> mTrials;
+
+ private:
+ virtual ~SharedWebrtcState();
+};
+
+// A class to hold some of the singleton objects we need:
+// * The global PeerConnectionImpl table and its associated lock.
+// * Stats report objects for PCs that are gone
+// * GMP related state
+// * Upstream webrtc state shared across all Calls (processing thread)
+class PeerConnectionCtx {
+ public:
+ static nsresult InitializeGlobal();
+ static PeerConnectionCtx* GetInstance();
+ static bool isActive();
+ static void Destroy();
+
+ bool isReady() {
+ // If mGMPService is not set, we aren't using GMP.
+ if (mGMPService) {
+ return mGMPReady;
+ }
+ return true;
+ }
+
+ void queueJSEPOperation(nsIRunnable* aJSEPOperation);
+ void onGMPReady();
+
+ bool gmpHasH264();
+
+ static void UpdateNetworkState(bool online);
+
+ RefPtr<MediaTransportHandler> GetTransportHandler() const {
+ return mTransportHandler;
+ }
+
+ SharedWebrtcState* GetSharedWebrtcState() const;
+
+ void RemovePeerConnection(const std::string& aKey);
+ void AddPeerConnection(const std::string& aKey,
+ PeerConnectionImpl* aPeerConnection);
+ PeerConnectionImpl* GetPeerConnection(const std::string& aKey) const;
+ template <typename Function>
+ void ForEachPeerConnection(Function&& aFunction) const {
+ MOZ_ASSERT(NS_IsMainThread());
+ for (const auto& pair : mPeerConnections) {
+ aFunction(pair.second);
+ }
+ }
+
+ void ClearClosedStats();
+
+ private:
+ std::map<const std::string, PeerConnectionImpl*> mPeerConnections;
+
+ PeerConnectionCtx()
+ : mGMPReady(false),
+ mTransportHandler(
+ MediaTransportHandler::Create(GetMainThreadSerialEventTarget())) {}
+
+ // This is a singleton, so don't copy construct it, etc.
+ PeerConnectionCtx(const PeerConnectionCtx& other) = delete;
+ void operator=(const PeerConnectionCtx& other) = delete;
+ virtual ~PeerConnectionCtx() = default;
+
+ nsresult Initialize();
+ nsresult StartTelemetryTimer();
+ void StopTelemetryTimer();
+ nsresult Cleanup();
+
+ void initGMP();
+
+ static void EverySecondTelemetryCallback_m(nsITimer* timer, void*);
+
+ nsCOMPtr<nsITimer> mTelemetryTimer;
+
+ private:
+ void DeliverStats(UniquePtr<dom::RTCStatsReportInternal>&& aReport);
+
+ std::map<nsString, UniquePtr<dom::RTCStatsReportInternal>> mLastReports;
+ // We cannot form offers/answers properly until the Gecko Media Plugin stuff
+ // has been initted, which is a complicated mess of thread dispatches,
+ // including sync dispatches to main. So, we need to be able to queue up
+ // offer creation (or SetRemote, when we're the answerer) until all of this is
+ // ready to go, since blocking on this init is just begging for deadlock.
+ nsCOMPtr<mozIGeckoMediaPluginService> mGMPService;
+ bool mGMPReady;
+ nsTArray<nsCOMPtr<nsIRunnable>> mQueuedJSEPOperations;
+
+ // Not initted, just for ICE logging stuff
+ RefPtr<MediaTransportHandler> mTransportHandler;
+
+ // State used by libwebrtc that needs to be shared across all PeerConnections
+ // and all Call instances. Set while there is at least one peer connection
+ // registered. CallWrappers can hold a ref to this object to be sure members
+ // are alive long enough.
+ RefPtr<SharedWebrtcState> mSharedWebrtcState;
+
+ static PeerConnectionCtx* gInstance;
+
+ public:
+ static mozilla::StaticRefPtr<mozilla::PeerConnectionCtxObserver>
+ gPeerConnectionCtxObserver;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/jsapi/PeerConnectionImpl.cpp b/dom/media/webrtc/jsapi/PeerConnectionImpl.cpp
new file mode 100644
index 0000000000..567b682b2a
--- /dev/null
+++ b/dom/media/webrtc/jsapi/PeerConnectionImpl.cpp
@@ -0,0 +1,4640 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <cstdlib>
+#include <cerrno>
+#include <deque>
+#include <set>
+#include <sstream>
+#include <vector>
+
+#include "common/browser_logging/CSFLog.h"
+#include "base/histogram.h"
+#include "common/time_profiling/timecard.h"
+
+#include "jsapi.h"
+#include "nspr.h"
+#include "nss.h"
+#include "pk11pub.h"
+
+#include "nsNetCID.h"
+#include "nsIIDNService.h"
+#include "nsILoadContext.h"
+#include "nsEffectiveTLDService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsProxyRelease.h"
+#include "prtime.h"
+
+#include "libwebrtcglue/AudioConduit.h"
+#include "libwebrtcglue/VideoConduit.h"
+#include "libwebrtcglue/WebrtcCallWrapper.h"
+#include "MediaTrackGraph.h"
+#include "transport/runnable_utils.h"
+#include "IPeerConnection.h"
+#include "PeerConnectionCtx.h"
+#include "PeerConnectionImpl.h"
+#include "RemoteTrackSource.h"
+#include "nsDOMDataChannelDeclarations.h"
+#include "transport/dtlsidentity.h"
+#include "sdp/SdpAttribute.h"
+
+#include "jsep/JsepTrack.h"
+#include "jsep/JsepSession.h"
+#include "jsep/JsepSessionImpl.h"
+
+#include "transportbridge/MediaPipeline.h"
+#include "jsapi/RTCRtpReceiver.h"
+#include "jsapi/RTCRtpSender.h"
+
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Sprintf.h"
+
+#ifdef XP_WIN
+// We need to undef the MS macro for Document::CreateEvent
+# ifdef CreateEvent
+# undef CreateEvent
+# endif
+#endif // XP_WIN
+
+#include "mozilla/dom/Document.h"
+#include "nsGlobalWindow.h"
+#include "nsDOMDataChannel.h"
+#include "mozilla/dom/Location.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PublicSSL.h"
+#include "nsXULAppAPI.h"
+#include "nsContentUtils.h"
+#include "nsDOMJSUtils.h"
+#include "nsPrintfCString.h"
+#include "nsURLHelper.h"
+#include "nsNetUtil.h"
+#include "js/ArrayBuffer.h" // JS::NewArrayBufferWithContents
+#include "js/GCAnnotations.h" // JS_HAZ_ROOTED
+#include "js/RootingAPI.h" // JS::{{,Mutable}Handle,Rooted}
+#include "mozilla/PeerIdentity.h"
+#include "mozilla/dom/RTCCertificate.h"
+#include "mozilla/dom/RTCSctpTransportBinding.h" // RTCSctpTransportState
+#include "mozilla/dom/RTCDtlsTransportBinding.h" // RTCDtlsTransportState
+#include "mozilla/dom/RTCRtpReceiverBinding.h"
+#include "mozilla/dom/RTCRtpSenderBinding.h"
+#include "mozilla/dom/RTCStatsReportBinding.h"
+#include "mozilla/dom/RTCPeerConnectionBinding.h"
+#include "mozilla/dom/PeerConnectionImplBinding.h"
+#include "mozilla/dom/RTCDataChannelBinding.h"
+#include "mozilla/dom/PluginCrashedEvent.h"
+#include "MediaStreamTrack.h"
+#include "AudioStreamTrack.h"
+#include "VideoStreamTrack.h"
+#include "nsIScriptGlobalObject.h"
+#include "DOMMediaStream.h"
+#include "WebrtcGlobalInformation.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/net/DataChannelProtocol.h"
+#include "MediaManager.h"
+
+#include "transport/nr_socket_proxy_config.h"
+#include "RTCSctpTransport.h"
+#include "RTCDtlsTransport.h"
+#include "jsep/JsepTransport.h"
+
+#include "nsILoadInfo.h"
+#include "nsIPrincipal.h"
+#include "mozilla/LoadInfo.h"
+#include "nsIProxiedChannel.h"
+
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/net/WebrtcProxyConfig.h"
+
+#ifdef XP_WIN
+// We need to undef the MS macro again in case the windows include file
+// got imported after we included mozilla/dom/Document.h
+# ifdef CreateEvent
+# undef CreateEvent
+# endif
+#endif // XP_WIN
+
+#include "MediaSegment.h"
+
+#ifdef USE_FAKE_PCOBSERVER
+# include "FakePCObserver.h"
+#else
+# include "mozilla/dom/PeerConnectionObserverBinding.h"
+#endif
+#include "mozilla/dom/PeerConnectionObserverEnumsBinding.h"
+
+#define ICE_PARSING \
+ "In RTCConfiguration passed to RTCPeerConnection constructor"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+typedef PCObserverString ObString;
+
+static const char* pciLogTag = "PeerConnectionImpl";
+#ifdef LOGTAG
+# undef LOGTAG
+#endif
+#define LOGTAG pciLogTag
+
+static mozilla::LazyLogModule logModuleInfo("signaling");
+
+// Getting exceptions back down from PCObserver is generally not harmful.
+namespace {
+// This is a terrible hack. The problem is that SuppressException is not
+// inline, and we link this file without libxul in some cases (e.g. for our test
+// setup). So we can't use ErrorResult or IgnoredErrorResult because those call
+// SuppressException... And we can't use FastErrorResult because we can't
+// include BindingUtils.h, because our linking is completely broken. Use
+// BaseErrorResult directly. Please do not let me see _anyone_ doing this
+// without really careful review from someone who knows what they are doing.
+class JSErrorResult : public binding_danger::TErrorResult<
+ binding_danger::JustAssertCleanupPolicy> {
+ public:
+ ~JSErrorResult() { SuppressException(); }
+} JS_HAZ_ROOTED;
+
+// The WrapRunnable() macros copy passed-in args and passes them to the function
+// later on the other thread. ErrorResult cannot be passed like this because it
+// disallows copy-semantics.
+//
+// This WrappableJSErrorResult hack solves this by not actually copying the
+// ErrorResult, but creating a new one instead, which works because we don't
+// care about the result.
+//
+// Since this is for JS-calls, these can only be dispatched to the main thread.
+
+class WrappableJSErrorResult {
+ public:
+ WrappableJSErrorResult() : mRv(MakeUnique<JSErrorResult>()), isCopy(false) {}
+ WrappableJSErrorResult(const WrappableJSErrorResult& other)
+ : mRv(MakeUnique<JSErrorResult>()), isCopy(true) {}
+ ~WrappableJSErrorResult() {
+ if (isCopy) {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+ }
+ operator ErrorResult&() { return *mRv; }
+
+ private:
+ mozilla::UniquePtr<JSErrorResult> mRv;
+ bool isCopy;
+} JS_HAZ_ROOTED;
+
+} // namespace
+
+static nsresult InitNSSInContent() {
+ NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_SAME_THREAD);
+
+ if (!XRE_IsContentProcess()) {
+ MOZ_ASSERT_UNREACHABLE("Must be called in content process");
+ return NS_ERROR_FAILURE;
+ }
+
+ static bool nssStarted = false;
+ if (nssStarted) {
+ return NS_OK;
+ }
+
+ if (NSS_NoDB_Init(nullptr) != SECSuccess) {
+ CSFLogError(LOGTAG, "NSS_NoDB_Init failed.");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (NS_FAILED(mozilla::psm::InitializeCipherSuite())) {
+ CSFLogError(LOGTAG, "Fail to set up nss cipher suite.");
+ return NS_ERROR_FAILURE;
+ }
+
+ mozilla::psm::DisableMD5();
+
+ nssStarted = true;
+
+ return NS_OK;
+}
+
+namespace mozilla {
+class DataChannel;
+}
+
+namespace mozilla {
+
+void PeerConnectionAutoTimer::RegisterConnection() { mRefCnt++; }
+
+void PeerConnectionAutoTimer::UnregisterConnection(bool aContainedAV) {
+ MOZ_ASSERT(mRefCnt);
+ mRefCnt--;
+ mUsedAV |= aContainedAV;
+ if (mRefCnt == 0) {
+ if (mUsedAV) {
+ Telemetry::Accumulate(
+ Telemetry::WEBRTC_AV_CALL_DURATION,
+ static_cast<uint32_t>((TimeStamp::Now() - mStart).ToSeconds()));
+ }
+ Telemetry::Accumulate(
+ Telemetry::WEBRTC_CALL_DURATION,
+ static_cast<uint32_t>((TimeStamp::Now() - mStart).ToSeconds()));
+ }
+}
+
+bool PeerConnectionAutoTimer::IsStopped() { return mRefCnt == 0; }
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(PeerConnectionImpl)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PeerConnectionImpl)
+ tmp->Close();
+ tmp->BreakCycles();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPCObserver, mWindow, mCertificate,
+ mSTSThread, mReceiveStreams, mOperations,
+ mSctpTransport, mKungFuDeathGrip)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(PeerConnectionImpl)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(
+ mPCObserver, mWindow, mCertificate, mSTSThread, mReceiveStreams,
+ mOperations, mTransceivers, mSctpTransport, mKungFuDeathGrip)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(PeerConnectionImpl)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(PeerConnectionImpl)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PeerConnectionImpl)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+already_AddRefed<PeerConnectionImpl> PeerConnectionImpl::Constructor(
+ const dom::GlobalObject& aGlobal) {
+ RefPtr<PeerConnectionImpl> pc = new PeerConnectionImpl(&aGlobal);
+
+ CSFLogDebug(LOGTAG, "Created PeerConnection: %p", pc.get());
+
+ return pc.forget();
+}
+
+JSObject* PeerConnectionImpl::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return PeerConnectionImpl_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsPIDOMWindowInner* PeerConnectionImpl::GetParentObject() const {
+ return mWindow;
+}
+
+bool PCUuidGenerator::Generate(std::string* idp) {
+ nsresult rv;
+
+ if (!mGenerator) {
+ mGenerator = do_GetService("@mozilla.org/uuid-generator;1", &rv);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ if (!mGenerator) {
+ return false;
+ }
+ }
+
+ nsID id;
+ rv = mGenerator->GenerateUUIDInPlace(&id);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ char buffer[NSID_LENGTH];
+ id.ToProvidedString(buffer);
+ idp->assign(buffer);
+
+ return true;
+}
+
+bool IsPrivateBrowsing(nsPIDOMWindowInner* aWindow) {
+ if (!aWindow) {
+ return false;
+ }
+
+ Document* doc = aWindow->GetExtantDoc();
+ if (!doc) {
+ return false;
+ }
+
+ nsILoadContext* loadContext = doc->GetLoadContext();
+ return loadContext && loadContext->UsePrivateBrowsing();
+}
+
+PeerConnectionImpl::PeerConnectionImpl(const GlobalObject* aGlobal)
+ : mTimeCard(MOZ_LOG_TEST(logModuleInfo, LogLevel::Error) ? create_timecard()
+ : nullptr),
+ mJsConfiguration(),
+ mSignalingState(RTCSignalingState::Stable),
+ mIceConnectionState(RTCIceConnectionState::New),
+ mIceGatheringState(RTCIceGatheringState::New),
+ mConnectionState(RTCPeerConnectionState::New),
+ mWindow(do_QueryInterface(aGlobal ? aGlobal->GetAsSupports() : nullptr)),
+ mCertificate(nullptr),
+ mSTSThread(nullptr),
+ mForceIceTcp(false),
+ mTransportHandler(nullptr),
+ mUuidGen(MakeUnique<PCUuidGenerator>()),
+ mIceRestartCount(0),
+ mIceRollbackCount(0),
+ mHaveConfiguredCodecs(false),
+ mTrickle(true) // TODO(ekr@rtfm.com): Use pref
+ ,
+ mPrivateWindow(false),
+ mActiveOnWindow(false),
+ mTimestampMaker(dom::RTCStatsTimestampMaker::Create(mWindow)),
+ mIdGenerator(new RTCStatsIdGenerator()),
+ listenPort(0),
+ connectPort(0),
+ connectStr(nullptr) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT_IF(aGlobal, mWindow);
+ mKungFuDeathGrip = this;
+ if (aGlobal) {
+ if (IsPrivateBrowsing(mWindow)) {
+ mPrivateWindow = true;
+ mDisableLongTermStats = true;
+ }
+ mWindow->AddPeerConnection();
+ mActiveOnWindow = true;
+
+ if (mWindow->GetDocumentURI()) {
+ mWindow->GetDocumentURI()->GetAsciiHost(mHostname);
+ nsresult rv;
+ nsCOMPtr<nsIEffectiveTLDService> eTLDService(
+ do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv));
+ if (eTLDService) {
+ Unused << eTLDService->GetBaseDomain(mWindow->GetDocumentURI(), 0,
+ mEffectiveTLDPlus1);
+ }
+
+ mRtxIsAllowed = !HostnameInPref(
+ "media.peerconnection.video.use_rtx.blocklist", mHostname);
+ }
+ }
+
+ if (!mUuidGen->Generate(&mHandle)) {
+ MOZ_CRASH();
+ }
+
+ CSFLogInfo(LOGTAG, "%s: PeerConnectionImpl constructor for %s", __FUNCTION__,
+ mHandle.c_str());
+ STAMP_TIMECARD(mTimeCard, "Constructor Completed");
+ mForceIceTcp =
+ Preferences::GetBool("media.peerconnection.ice.force_ice_tcp", false);
+ memset(mMaxReceiving, 0, sizeof(mMaxReceiving));
+ memset(mMaxSending, 0, sizeof(mMaxSending));
+ mJsConfiguration.mCertificatesProvided = false;
+ mJsConfiguration.mPeerIdentityProvided = false;
+}
+
+PeerConnectionImpl::~PeerConnectionImpl() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ MOZ_ASSERT(!mTransportHandler,
+ "PeerConnection should either be closed, or not initted in the "
+ "first place.");
+
+ if (mTimeCard) {
+ STAMP_TIMECARD(mTimeCard, "Destructor Invoked");
+ STAMP_TIMECARD(mTimeCard, mHandle.c_str());
+ print_timecard(mTimeCard);
+ destroy_timecard(mTimeCard);
+ mTimeCard = nullptr;
+ }
+
+ CSFLogInfo(LOGTAG, "%s: PeerConnectionImpl destructor invoked for %s",
+ __FUNCTION__, mHandle.c_str());
+}
+
+nsresult PeerConnectionImpl::Initialize(PeerConnectionObserver& aObserver,
+ nsGlobalWindowInner* aWindow) {
+ nsresult res;
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mPCObserver = &aObserver;
+
+ // Find the STS thread
+
+ mSTSThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &res);
+ MOZ_ASSERT(mSTSThread);
+
+ // We do callback handling on STS instead of main to avoid media jank.
+ // Someday, we may have a dedicated thread for this.
+ mTransportHandler = MediaTransportHandler::Create(mSTSThread);
+ if (mPrivateWindow) {
+ mTransportHandler->EnterPrivateMode();
+ }
+
+ // Initialize NSS if we are in content process. For chrome process, NSS should
+ // already been initialized.
+ if (XRE_IsParentProcess()) {
+ // This code interferes with the C++ unit test startup code.
+ nsCOMPtr<nsISupports> nssDummy = do_GetService("@mozilla.org/psm;1", &res);
+ NS_ENSURE_SUCCESS(res, res);
+ } else {
+ NS_ENSURE_SUCCESS(res = InitNSSInContent(), res);
+ }
+
+ // Currently no standalone unit tests for DataChannel,
+ // which is the user of mWindow
+ MOZ_ASSERT(aWindow);
+ mWindow = aWindow;
+ NS_ENSURE_STATE(mWindow);
+
+ PRTime timestamp = PR_Now();
+ // Ok if we truncate this, but we want it to be large enough to reliably
+ // contain the location on the tests we run in CI.
+ char temp[256];
+
+ nsAutoCString locationCStr;
+
+ RefPtr<Location> location = mWindow->Location();
+ nsAutoString locationAStr;
+ res = location->ToString(locationAStr);
+ NS_ENSURE_SUCCESS(res, res);
+
+ CopyUTF16toUTF8(locationAStr, locationCStr);
+
+ SprintfLiteral(temp, "%s %" PRIu64 " (id=%" PRIu64 " url=%s)",
+ mHandle.c_str(), static_cast<uint64_t>(timestamp),
+ static_cast<uint64_t>(mWindow ? mWindow->WindowID() : 0),
+ locationCStr.get() ? locationCStr.get() : "NULL");
+
+ mName = temp;
+
+ STAMP_TIMECARD(mTimeCard, "Initializing PC Ctx");
+ res = PeerConnectionCtx::InitializeGlobal();
+ NS_ENSURE_SUCCESS(res, res);
+
+ mTransportHandler->CreateIceCtx("PC:" + GetName());
+
+ mJsepSession =
+ MakeUnique<JsepSessionImpl>(mName, MakeUnique<PCUuidGenerator>());
+ mJsepSession->SetRtxIsAllowed(mRtxIsAllowed);
+
+ res = mJsepSession->Init();
+ if (NS_FAILED(res)) {
+ CSFLogError(LOGTAG, "%s: Couldn't init JSEP Session, res=%u", __FUNCTION__,
+ static_cast<unsigned>(res));
+ return res;
+ }
+
+ std::vector<UniquePtr<JsepCodecDescription>> preferredCodecs;
+ SetupPreferredCodecs(preferredCodecs);
+ mJsepSession->SetDefaultCodecs(preferredCodecs);
+
+ std::vector<RtpExtensionHeader> preferredHeaders;
+ SetupPreferredRtpExtensions(preferredHeaders);
+
+ for (const auto& header : preferredHeaders) {
+ mJsepSession->AddRtpExtension(header.mMediaType, header.extensionname,
+ header.direction);
+ }
+
+ if (XRE_IsContentProcess()) {
+ mStunAddrsRequest =
+ new net::StunAddrsRequestChild(new StunAddrsHandler(this));
+ }
+
+ // Initialize the media object.
+ mForceProxy = ShouldForceProxy();
+
+ // We put this here, in case we later want to set this based on a non-standard
+ // param in RTCConfiguration.
+ mAllowOldSetParameters = Preferences::GetBool(
+ "media.peerconnection.allow_old_setParameters", false);
+
+ // setup the stun local addresses IPC async call
+ InitLocalAddrs();
+
+ mSignalHandler = MakeUnique<SignalHandler>(this, mTransportHandler.get());
+
+ PeerConnectionCtx::GetInstance()->AddPeerConnection(mHandle, this);
+
+ return NS_OK;
+}
+
+void PeerConnectionImpl::Initialize(PeerConnectionObserver& aObserver,
+ nsGlobalWindowInner& aWindow,
+ ErrorResult& rv) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult res = Initialize(aObserver, &aWindow);
+ if (NS_FAILED(res)) {
+ rv.Throw(res);
+ return;
+ }
+}
+
+void PeerConnectionImpl::SetCertificate(
+ mozilla::dom::RTCCertificate& aCertificate) {
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ MOZ_ASSERT(!mCertificate, "This can only be called once");
+ mCertificate = &aCertificate;
+
+ std::vector<uint8_t> fingerprint;
+ nsresult rv =
+ CalculateFingerprint(DtlsIdentity::DEFAULT_HASH_ALGORITHM, &fingerprint);
+ if (NS_FAILED(rv)) {
+ CSFLogError(LOGTAG, "%s: Couldn't calculate fingerprint, rv=%u",
+ __FUNCTION__, static_cast<unsigned>(rv));
+ mCertificate = nullptr;
+ return;
+ }
+ rv = mJsepSession->AddDtlsFingerprint(DtlsIdentity::DEFAULT_HASH_ALGORITHM,
+ fingerprint);
+ if (NS_FAILED(rv)) {
+ CSFLogError(LOGTAG, "%s: Couldn't set DTLS credentials, rv=%u",
+ __FUNCTION__, static_cast<unsigned>(rv));
+ mCertificate = nullptr;
+ }
+
+ if (mUncommittedJsepSession) {
+ Unused << mUncommittedJsepSession->AddDtlsFingerprint(
+ DtlsIdentity::DEFAULT_HASH_ALGORITHM, fingerprint);
+ }
+}
+
+const RefPtr<mozilla::dom::RTCCertificate>& PeerConnectionImpl::Certificate()
+ const {
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ return mCertificate;
+}
+
+RefPtr<DtlsIdentity> PeerConnectionImpl::Identity() const {
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ MOZ_ASSERT(mCertificate);
+ return mCertificate->CreateDtlsIdentity();
+}
+
+class CompareCodecPriority {
+ public:
+ void SetPreferredCodec(int32_t preferredCodec) {
+ // This pref really ought to be a string, preferably something like
+ // "H264" or "VP8" instead of a payload type.
+ // Bug 1101259.
+ std::ostringstream os;
+ os << preferredCodec;
+ mPreferredCodec = os.str();
+ }
+
+ bool operator()(const UniquePtr<JsepCodecDescription>& lhs,
+ const UniquePtr<JsepCodecDescription>& rhs) const {
+ if (!mPreferredCodec.empty() && lhs->mDefaultPt == mPreferredCodec &&
+ rhs->mDefaultPt != mPreferredCodec) {
+ return true;
+ }
+
+ if (lhs->mStronglyPreferred && !rhs->mStronglyPreferred) {
+ return true;
+ }
+
+ return false;
+ }
+
+ private:
+ std::string mPreferredCodec;
+};
+
+class ConfigureCodec {
+ public:
+ explicit ConfigureCodec(nsCOMPtr<nsIPrefBranch>& branch)
+ : mHardwareH264Enabled(false),
+ mSoftwareH264Enabled(false),
+ mH264Enabled(false),
+ mVP9Enabled(true),
+ mVP9Preferred(false),
+ mH264Level(13), // minimum suggested for WebRTC spec
+ mH264MaxBr(0), // Unlimited
+ mH264MaxMbps(0), // Unlimited
+ mVP8MaxFs(0),
+ mVP8MaxFr(0),
+ mUseTmmbr(false),
+ mUseRemb(false),
+ mUseTransportCC(false),
+ mUseAudioFec(false),
+ mRedUlpfecEnabled(false),
+ mDtmfEnabled(false) {
+ mSoftwareH264Enabled = PeerConnectionCtx::GetInstance()->gmpHasH264();
+
+ if (WebrtcVideoConduit::HasH264Hardware()) {
+ Telemetry::Accumulate(Telemetry::WEBRTC_HAS_H264_HARDWARE, true);
+ branch->GetBoolPref("media.webrtc.hw.h264.enabled",
+ &mHardwareH264Enabled);
+ }
+
+ mH264Enabled = mHardwareH264Enabled || mSoftwareH264Enabled;
+ Telemetry::Accumulate(Telemetry::WEBRTC_SOFTWARE_H264_ENABLED,
+ mSoftwareH264Enabled);
+ Telemetry::Accumulate(Telemetry::WEBRTC_HARDWARE_H264_ENABLED,
+ mHardwareH264Enabled);
+ Telemetry::Accumulate(Telemetry::WEBRTC_H264_ENABLED, mH264Enabled);
+
+ branch->GetIntPref("media.navigator.video.h264.level", &mH264Level);
+ mH264Level &= 0xFF;
+
+ branch->GetIntPref("media.navigator.video.h264.max_br", &mH264MaxBr);
+
+ branch->GetIntPref("media.navigator.video.h264.max_mbps", &mH264MaxMbps);
+
+ branch->GetBoolPref("media.peerconnection.video.vp9_enabled", &mVP9Enabled);
+
+ branch->GetBoolPref("media.peerconnection.video.vp9_preferred",
+ &mVP9Preferred);
+
+ branch->GetIntPref("media.navigator.video.max_fs", &mVP8MaxFs);
+ if (mVP8MaxFs <= 0) {
+ mVP8MaxFs = 12288; // We must specify something other than 0
+ }
+
+ branch->GetIntPref("media.navigator.video.max_fr", &mVP8MaxFr);
+ if (mVP8MaxFr <= 0) {
+ mVP8MaxFr = 60; // We must specify something other than 0
+ }
+
+ // TMMBR is enabled from a pref in about:config
+ branch->GetBoolPref("media.navigator.video.use_tmmbr", &mUseTmmbr);
+
+ // REMB is enabled by default, but can be disabled from about:config
+ branch->GetBoolPref("media.navigator.video.use_remb", &mUseRemb);
+
+ branch->GetBoolPref("media.navigator.video.use_transport_cc",
+ &mUseTransportCC);
+
+ branch->GetBoolPref("media.navigator.audio.use_fec", &mUseAudioFec);
+
+ branch->GetBoolPref("media.navigator.video.red_ulpfec_enabled",
+ &mRedUlpfecEnabled);
+
+ // media.peerconnection.dtmf.enabled controls both sdp generation for
+ // DTMF support as well as DTMF exposure to DOM
+ branch->GetBoolPref("media.peerconnection.dtmf.enabled", &mDtmfEnabled);
+ }
+
+ void operator()(UniquePtr<JsepCodecDescription>& codec) const {
+ switch (codec->Type()) {
+ case SdpMediaSection::kAudio: {
+ JsepAudioCodecDescription& audioCodec =
+ static_cast<JsepAudioCodecDescription&>(*codec);
+ if (audioCodec.mName == "opus") {
+ audioCodec.mFECEnabled = mUseAudioFec;
+ } else if (audioCodec.mName == "telephone-event") {
+ audioCodec.mEnabled = mDtmfEnabled;
+ }
+ } break;
+ case SdpMediaSection::kVideo: {
+ JsepVideoCodecDescription& videoCodec =
+ static_cast<JsepVideoCodecDescription&>(*codec);
+
+ if (videoCodec.mName == "H264") {
+ // Override level
+ videoCodec.mProfileLevelId &= 0xFFFF00;
+ videoCodec.mProfileLevelId |= mH264Level;
+
+ videoCodec.mConstraints.maxBr = mH264MaxBr;
+
+ videoCodec.mConstraints.maxMbps = mH264MaxMbps;
+
+ // Might disable it, but we set up other params anyway
+ videoCodec.mEnabled = mH264Enabled;
+
+ if (videoCodec.mPacketizationMode == 0 && !mSoftwareH264Enabled) {
+ // We're assuming packetization mode 0 is unsupported by
+ // hardware.
+ videoCodec.mEnabled = false;
+ }
+
+ if (mHardwareH264Enabled) {
+ videoCodec.mStronglyPreferred = true;
+ }
+ } else if (videoCodec.mName == "red") {
+ videoCodec.mEnabled = mRedUlpfecEnabled;
+ } else if (videoCodec.mName == "ulpfec") {
+ videoCodec.mEnabled = mRedUlpfecEnabled;
+ } else if (videoCodec.mName == "VP8" || videoCodec.mName == "VP9") {
+ if (videoCodec.mName == "VP9") {
+ if (!mVP9Enabled) {
+ videoCodec.mEnabled = false;
+ break;
+ }
+ if (mVP9Preferred) {
+ videoCodec.mStronglyPreferred = true;
+ }
+ }
+ videoCodec.mConstraints.maxFs = mVP8MaxFs;
+ videoCodec.mConstraints.maxFps = Some(mVP8MaxFr);
+ }
+
+ if (mUseTmmbr) {
+ videoCodec.EnableTmmbr();
+ }
+ if (mUseRemb) {
+ videoCodec.EnableRemb();
+ }
+ if (mUseTransportCC) {
+ videoCodec.EnableTransportCC();
+ }
+ } break;
+ case SdpMediaSection::kText:
+ case SdpMediaSection::kApplication:
+ case SdpMediaSection::kMessage: {
+ } // Nothing to configure for these.
+ }
+ }
+
+ private:
+ bool mHardwareH264Enabled;
+ bool mSoftwareH264Enabled;
+ bool mH264Enabled;
+ bool mVP9Enabled;
+ bool mVP9Preferred;
+ int32_t mH264Level;
+ int32_t mH264MaxBr;
+ int32_t mH264MaxMbps;
+ int32_t mVP8MaxFs;
+ int32_t mVP8MaxFr;
+ bool mUseTmmbr;
+ bool mUseRemb;
+ bool mUseTransportCC;
+ bool mUseAudioFec;
+ bool mRedUlpfecEnabled;
+ bool mDtmfEnabled;
+};
+
+class ConfigureRedCodec {
+ public:
+ explicit ConfigureRedCodec(nsCOMPtr<nsIPrefBranch>& branch,
+ std::vector<uint8_t>* redundantEncodings)
+ : mRedundantEncodings(redundantEncodings) {
+ // if we wanted to override or modify which encodings are considered
+ // for redundant encodings, we'd probably want to handle it here by
+ // checking prefs modifying the operator() code below
+ }
+
+ void operator()(UniquePtr<JsepCodecDescription>& codec) const {
+ if (codec->Type() == SdpMediaSection::kVideo && !codec->mEnabled) {
+ uint8_t pt = (uint8_t)strtoul(codec->mDefaultPt.c_str(), nullptr, 10);
+ // don't search for the codec payload type unless we have a valid
+ // conversion (non-zero)
+ if (pt != 0) {
+ std::vector<uint8_t>::iterator it = std::find(
+ mRedundantEncodings->begin(), mRedundantEncodings->end(), pt);
+ if (it != mRedundantEncodings->end()) {
+ mRedundantEncodings->erase(it);
+ }
+ }
+ }
+ }
+
+ private:
+ std::vector<uint8_t>* mRedundantEncodings;
+};
+
+nsresult PeerConnectionImpl::ConfigureJsepSessionCodecs() {
+ nsresult res;
+ nsCOMPtr<nsIPrefService> prefs =
+ do_GetService("@mozilla.org/preferences-service;1", &res);
+
+ if (NS_FAILED(res)) {
+ CSFLogError(LOGTAG, "%s: Couldn't get prefs service, res=%u", __FUNCTION__,
+ static_cast<unsigned>(res));
+ return res;
+ }
+
+ nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(prefs);
+ if (!branch) {
+ CSFLogError(LOGTAG, "%s: Couldn't get prefs branch", __FUNCTION__);
+ return NS_ERROR_FAILURE;
+ }
+
+ ConfigureCodec configurer(branch);
+ mJsepSession->ForEachCodec(configurer);
+
+ // if red codec is enabled, configure it for the other enabled codecs
+ for (auto& codec : mJsepSession->Codecs()) {
+ if (codec->mName == "red" && codec->mEnabled) {
+ JsepVideoCodecDescription* redCodec =
+ static_cast<JsepVideoCodecDescription*>(codec.get());
+ ConfigureRedCodec configureRed(branch, &(redCodec->mRedundantEncodings));
+ mJsepSession->ForEachCodec(configureRed);
+ break;
+ }
+ }
+
+ // We use this to sort the list of codecs once everything is configured
+ CompareCodecPriority comparator;
+
+ // Sort by priority
+ int32_t preferredCodec = 0;
+ branch->GetIntPref("media.navigator.video.preferred_codec", &preferredCodec);
+
+ if (preferredCodec) {
+ comparator.SetPreferredCodec(preferredCodec);
+ }
+
+ mJsepSession->SortCodecs(comparator);
+ return NS_OK;
+}
+
+// Data channels won't work without a window, so in order for the C++ unit
+// tests to work (it doesn't have a window available) we ifdef the following
+// two implementations.
+//
+// Note: 'media.peerconnection.sctp.force_maximum_message_size' changes
+// behaviour triggered by these parameters.
+NS_IMETHODIMP
+PeerConnectionImpl::EnsureDataConnection(uint16_t aLocalPort,
+ uint16_t aNumstreams,
+ uint32_t aMaxMessageSize,
+ bool aMMSSet) {
+ PC_AUTO_ENTER_API_CALL(false);
+
+ if (mDataConnection) {
+ CSFLogDebug(LOGTAG, "%s DataConnection already connected", __FUNCTION__);
+ mDataConnection->SetMaxMessageSize(aMMSSet, aMaxMessageSize);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsISerialEventTarget> target =
+ mWindow ? mWindow->EventTargetFor(TaskCategory::Other) : nullptr;
+ Maybe<uint64_t> mms = aMMSSet ? Some(aMaxMessageSize) : Nothing();
+ if (auto res = DataChannelConnection::Create(this, target, mTransportHandler,
+ aLocalPort, aNumstreams, mms)) {
+ mDataConnection = res.value();
+ CSFLogDebug(LOGTAG, "%s DataChannelConnection %p attached to %s",
+ __FUNCTION__, (void*)mDataConnection.get(), mHandle.c_str());
+ return NS_OK;
+ }
+ CSFLogError(LOGTAG, "%s DataConnection Create Failed", __FUNCTION__);
+ return NS_ERROR_FAILURE;
+}
+
+nsresult PeerConnectionImpl::GetDatachannelParameters(
+ uint32_t* channels, uint16_t* localport, uint16_t* remoteport,
+ uint32_t* remotemaxmessagesize, bool* mmsset, std::string* transportId,
+ bool* client) const {
+ // Clear, just in case we fail.
+ *channels = 0;
+ *localport = 0;
+ *remoteport = 0;
+ *remotemaxmessagesize = 0;
+ *mmsset = false;
+ transportId->clear();
+
+ Maybe<const JsepTransceiver> datachannelTransceiver =
+ mJsepSession->FindTransceiver([](const JsepTransceiver& aTransceiver) {
+ return aTransceiver.GetMediaType() == SdpMediaSection::kApplication;
+ });
+
+ if (!datachannelTransceiver ||
+ !datachannelTransceiver->mTransport.mComponents ||
+ !datachannelTransceiver->mSendTrack.GetNegotiatedDetails()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // This will release assert if there is no such index, and that's ok
+ const JsepTrackEncoding& encoding =
+ datachannelTransceiver->mSendTrack.GetNegotiatedDetails()->GetEncoding(0);
+
+ if (NS_WARN_IF(encoding.GetCodecs().empty())) {
+ CSFLogError(LOGTAG,
+ "%s: Negotiated m=application with no codec. "
+ "This is likely to be broken.",
+ __FUNCTION__);
+ return NS_ERROR_FAILURE;
+ }
+
+ for (const auto& codec : encoding.GetCodecs()) {
+ if (codec->Type() != SdpMediaSection::kApplication) {
+ CSFLogError(LOGTAG,
+ "%s: Codec type for m=application was %u, this "
+ "is a bug.",
+ __FUNCTION__, static_cast<unsigned>(codec->Type()));
+ MOZ_ASSERT(false, "Codec for m=application was not \"application\"");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (codec->mName != "webrtc-datachannel") {
+ CSFLogWarn(LOGTAG,
+ "%s: Codec for m=application was not "
+ "webrtc-datachannel (was instead %s). ",
+ __FUNCTION__, codec->mName.c_str());
+ continue;
+ }
+
+ if (codec->mChannels) {
+ *channels = codec->mChannels;
+ } else {
+ *channels = WEBRTC_DATACHANNEL_STREAMS_DEFAULT;
+ }
+ const JsepApplicationCodecDescription* appCodec =
+ static_cast<const JsepApplicationCodecDescription*>(codec.get());
+ *localport = appCodec->mLocalPort;
+ *remoteport = appCodec->mRemotePort;
+ *remotemaxmessagesize = appCodec->mRemoteMaxMessageSize;
+ *mmsset = appCodec->mRemoteMMSSet;
+ MOZ_ASSERT(!datachannelTransceiver->mTransport.mTransportId.empty());
+ *transportId = datachannelTransceiver->mTransport.mTransportId;
+ *client = datachannelTransceiver->mTransport.mDtls->GetRole() ==
+ JsepDtlsTransport::kJsepDtlsClient;
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+nsresult PeerConnectionImpl::AddRtpTransceiverToJsepSession(
+ JsepTransceiver& transceiver) {
+ nsresult res = ConfigureJsepSessionCodecs();
+ if (NS_FAILED(res)) {
+ CSFLogError(LOGTAG, "Failed to configure codecs");
+ return res;
+ }
+
+ mJsepSession->AddTransceiver(transceiver);
+ return NS_OK;
+}
+
+static Maybe<SdpMediaSection::MediaType> ToSdpMediaType(
+ const nsAString& aKind) {
+ if (aKind.EqualsASCII("audio")) {
+ return Some(SdpMediaSection::MediaType::kAudio);
+ } else if (aKind.EqualsASCII("video")) {
+ return Some(SdpMediaSection::MediaType::kVideo);
+ }
+ return Nothing();
+}
+
+already_AddRefed<RTCRtpTransceiver> PeerConnectionImpl::AddTransceiver(
+ const dom::RTCRtpTransceiverInit& aInit, const nsAString& aKind,
+ dom::MediaStreamTrack* aSendTrack, bool aAddTrackMagic, ErrorResult& aRv) {
+ // Copy, because we might need to modify
+ RTCRtpTransceiverInit init(aInit);
+
+ Maybe<SdpMediaSection::MediaType> type = ToSdpMediaType(aKind);
+ if (NS_WARN_IF(!type.isSome())) {
+ MOZ_ASSERT(false, "Invalid media kind");
+ aRv = NS_ERROR_INVALID_ARG;
+ return nullptr;
+ }
+
+ JsepTransceiver jsepTransceiver(*type, *mUuidGen);
+ jsepTransceiver.SetRtxIsAllowed(mRtxIsAllowed);
+
+ // Do this last, since it is not possible to roll back.
+ nsresult rv = AddRtpTransceiverToJsepSession(jsepTransceiver);
+ if (NS_FAILED(rv)) {
+ CSFLogError(LOGTAG, "%s: AddRtpTransceiverToJsepSession failed, res=%u",
+ __FUNCTION__, static_cast<unsigned>(rv));
+ aRv = rv;
+ return nullptr;
+ }
+
+ auto& sendEncodings = init.mSendEncodings;
+
+ // CheckAndRectifyEncodings covers these six:
+ // If any encoding contains a rid member whose value does not conform to the
+ // grammar requirements specified in Section 10 of [RFC8851], throw a
+ // TypeError.
+
+ // If some but not all encodings contain a rid member, throw a TypeError.
+
+ // If any encoding contains a rid member whose value is the same as that of a
+ // rid contained in another encoding in sendEncodings, throw a TypeError.
+
+ // If kind is "audio", remove the scaleResolutionDownBy member from all
+ // encodings that contain one.
+
+ // If any encoding contains a scaleResolutionDownBy member whose value is
+ // less than 1.0, throw a RangeError.
+
+ // Verify that the value of each maxFramerate member in sendEncodings that is
+ // defined is greater than 0.0. If one of the maxFramerate values does not
+ // meet this requirement, throw a RangeError.
+ RTCRtpSender::CheckAndRectifyEncodings(sendEncodings,
+ *type == SdpMediaSection::kVideo, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ // If any encoding contains a read-only parameter other than rid, throw an
+ // InvalidAccessError.
+ // NOTE: We don't support any additional read-only params right now. Also,
+ // spec shoehorns this in between checks that setParameters also performs
+ // (between the rid checks and the scaleResolutionDownBy checks).
+
+ // If any encoding contains a scaleResolutionDownBy member, then for each
+ // encoding without one, add a scaleResolutionDownBy member with the value
+ // 1.0.
+ for (const auto& constEncoding : sendEncodings) {
+ if (constEncoding.mScaleResolutionDownBy.WasPassed()) {
+ for (auto& encoding : sendEncodings) {
+ if (!encoding.mScaleResolutionDownBy.WasPassed()) {
+ encoding.mScaleResolutionDownBy.Construct(1.0f);
+ }
+ }
+ break;
+ }
+ }
+
+ // Let maxN be the maximum number of total simultaneous encodings the user
+ // agent may support for this kind, at minimum 1.This should be an optimistic
+ // number since the codec to be used is not known yet.
+ size_t maxN =
+ (*type == SdpMediaSection::kVideo) ? webrtc::kMaxSimulcastStreams : 1;
+
+ // If the number of encodings stored in sendEncodings exceeds maxN, then trim
+ // sendEncodings from the tail until its length is maxN.
+ // NOTE: Spec has this after all validation steps; even if there are elements
+ // that we will trim off, we still validate them.
+ if (sendEncodings.Length() > maxN) {
+ sendEncodings.TruncateLength(maxN);
+ }
+
+ // If kind is "video" and none of the encodings contain a
+ // scaleResolutionDownBy member, then for each encoding, add a
+ // scaleResolutionDownBy member with the value 2^(length of sendEncodings -
+ // encoding index - 1). This results in smaller-to-larger resolutions where
+ // the last encoding has no scaling applied to it, e.g. 4:2:1 if the length
+ // is 3.
+ // NOTE: The code above ensures that these are all set, or all unset, so we
+ // can just check the first one.
+ if (sendEncodings.Length() && *type == SdpMediaSection::kVideo &&
+ !sendEncodings[0].mScaleResolutionDownBy.WasPassed()) {
+ double scale = 1.0f;
+ for (auto it = sendEncodings.rbegin(); it != sendEncodings.rend(); ++it) {
+ it->mScaleResolutionDownBy.Construct(scale);
+ scale *= 2;
+ }
+ }
+
+ // If the number of encodings now stored in sendEncodings is 1, then remove
+ // any rid member from the lone entry.
+ if (sendEncodings.Length() == 1) {
+ sendEncodings[0].mRid.Reset();
+ }
+
+ RefPtr<RTCRtpTransceiver> transceiver = CreateTransceiver(
+ jsepTransceiver.GetUuid(),
+ jsepTransceiver.GetMediaType() == SdpMediaSection::kVideo, init,
+ aSendTrack, aAddTrackMagic, aRv);
+
+ if (aRv.Failed()) {
+ // Would be nice if we could peek at the rv without stealing it, so we
+ // could log...
+ CSFLogError(LOGTAG, "%s: failed", __FUNCTION__);
+ return nullptr;
+ }
+
+ mTransceivers.AppendElement(transceiver);
+ return transceiver.forget();
+}
+
+bool PeerConnectionImpl::CheckNegotiationNeeded() {
+ MOZ_ASSERT(mSignalingState == RTCSignalingState::Stable);
+ SyncToJsep();
+ return !mLocalIceCredentialsToReplace.empty() ||
+ mJsepSession->CheckNegotiationNeeded();
+}
+
+bool PeerConnectionImpl::CreatedSender(const dom::RTCRtpSender& aSender) const {
+ return aSender.IsMyPc(this);
+}
+
+nsresult PeerConnectionImpl::InitializeDataChannel() {
+ PC_AUTO_ENTER_API_CALL(false);
+ CSFLogDebug(LOGTAG, "%s", __FUNCTION__);
+
+ uint32_t channels = 0;
+ uint16_t localport = 0;
+ uint16_t remoteport = 0;
+ uint32_t remotemaxmessagesize = 0;
+ bool mmsset = false;
+ std::string transportId;
+ bool client = false;
+ nsresult rv = GetDatachannelParameters(&channels, &localport, &remoteport,
+ &remotemaxmessagesize, &mmsset,
+ &transportId, &client);
+
+ if (NS_FAILED(rv)) {
+ CSFLogDebug(LOGTAG, "%s: We did not negotiate datachannel", __FUNCTION__);
+ return NS_OK;
+ }
+
+ if (channels > MAX_NUM_STREAMS) {
+ channels = MAX_NUM_STREAMS;
+ }
+
+ rv = EnsureDataConnection(localport, channels, remotemaxmessagesize, mmsset);
+ if (NS_SUCCEEDED(rv)) {
+ if (mDataConnection->ConnectToTransport(transportId, client, localport,
+ remoteport)) {
+ return NS_OK;
+ }
+ // If we inited the DataConnection, call Destroy() before releasing it
+ mDataConnection->Destroy();
+ }
+ mDataConnection = nullptr;
+ return NS_ERROR_FAILURE;
+}
+
+already_AddRefed<nsDOMDataChannel> PeerConnectionImpl::CreateDataChannel(
+ const nsAString& aLabel, const nsAString& aProtocol, uint16_t aType,
+ bool ordered, uint16_t aMaxTime, uint16_t aMaxNum, bool aExternalNegotiated,
+ uint16_t aStream, ErrorResult& rv) {
+ RefPtr<nsDOMDataChannel> result;
+ rv = CreateDataChannel(aLabel, aProtocol, aType, ordered, aMaxTime, aMaxNum,
+ aExternalNegotiated, aStream, getter_AddRefs(result));
+ return result.forget();
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::CreateDataChannel(
+ const nsAString& aLabel, const nsAString& aProtocol, uint16_t aType,
+ bool ordered, uint16_t aMaxTime, uint16_t aMaxNum, bool aExternalNegotiated,
+ uint16_t aStream, nsDOMDataChannel** aRetval) {
+ PC_AUTO_ENTER_API_CALL(false);
+ MOZ_ASSERT(aRetval);
+
+ RefPtr<DataChannel> dataChannel;
+ DataChannelConnection::Type theType =
+ static_cast<DataChannelConnection::Type>(aType);
+
+ nsresult rv = EnsureDataConnection(
+ WEBRTC_DATACHANNEL_PORT_DEFAULT, WEBRTC_DATACHANNEL_STREAMS_DEFAULT,
+ WEBRTC_DATACHANNEL_MAX_MESSAGE_SIZE_REMOTE_DEFAULT, false);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ dataChannel = mDataConnection->Open(
+ NS_ConvertUTF16toUTF8(aLabel), NS_ConvertUTF16toUTF8(aProtocol), theType,
+ ordered,
+ aType == DataChannelConnection::PARTIAL_RELIABLE_REXMIT
+ ? aMaxNum
+ : (aType == DataChannelConnection::PARTIAL_RELIABLE_TIMED ? aMaxTime
+ : 0),
+ nullptr, nullptr, aExternalNegotiated, aStream);
+ NS_ENSURE_TRUE(dataChannel, NS_ERROR_NOT_AVAILABLE);
+
+ CSFLogDebug(LOGTAG, "%s: making DOMDataChannel", __FUNCTION__);
+
+ Maybe<JsepTransceiver> dcTransceiver =
+ mJsepSession->FindTransceiver([](const JsepTransceiver& aTransceiver) {
+ return aTransceiver.GetMediaType() == SdpMediaSection::kApplication;
+ });
+
+ if (dcTransceiver) {
+ dcTransceiver->RestartDatachannelTransceiver();
+ mJsepSession->SetTransceiver(*dcTransceiver);
+ } else {
+ mJsepSession->AddTransceiver(
+ JsepTransceiver(SdpMediaSection::MediaType::kApplication, *mUuidGen));
+ }
+
+ RefPtr<nsDOMDataChannel> retval;
+ rv = NS_NewDOMDataChannel(dataChannel.forget(), mWindow,
+ getter_AddRefs(retval));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ retval.forget(aRetval);
+ return NS_OK;
+}
+
+NS_IMPL_CYCLE_COLLECTION(PeerConnectionImpl::Operation, mPromise, mPc)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PeerConnectionImpl::Operation)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+NS_IMPL_CYCLE_COLLECTING_ADDREF(PeerConnectionImpl::Operation)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(PeerConnectionImpl::Operation)
+
+PeerConnectionImpl::Operation::Operation(PeerConnectionImpl* aPc,
+ ErrorResult& aError)
+ : mPromise(aPc->MakePromise(aError)), mPc(aPc) {}
+
+PeerConnectionImpl::Operation::~Operation() = default;
+
+void PeerConnectionImpl::Operation::Call(ErrorResult& aError) {
+ RefPtr<dom::Promise> opPromise = CallImpl(aError);
+ if (aError.Failed()) {
+ return;
+ }
+ // Upon fulfillment or rejection of the promise returned by the operation,
+ // run the following steps:
+ // (NOTE: mPromise is p from https://w3c.github.io/webrtc-pc/#dfn-chain,
+ // and CallImpl() is what returns the promise for the operation itself)
+ opPromise->AppendNativeHandler(this);
+}
+
+void PeerConnectionImpl::Operation::ResolvedCallback(
+ JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv) {
+ // If connection.[[IsClosed]] is true, abort these steps.
+ // (the spec wants p to never settle in this event)
+ if (!mPc->IsClosed()) {
+ // If the promise returned by operation was fulfilled with a
+ // value, fulfill p with that value.
+ mPromise->MaybeResolveWithClone(aCx, aValue);
+ // Upon fulfillment or rejection of p, execute the following
+ // steps:
+ // (Static analysis forces us to use a temporary)
+ RefPtr<PeerConnectionImpl> pc = mPc;
+ pc->RunNextOperation(aRv);
+ }
+}
+
+void PeerConnectionImpl::Operation::RejectedCallback(
+ JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv) {
+ // If connection.[[IsClosed]] is true, abort these steps.
+ // (the spec wants p to never settle in this event)
+ if (!mPc->IsClosed()) {
+ // If the promise returned by operation was rejected with a
+ // value, reject p with that value.
+ mPromise->MaybeRejectWithClone(aCx, aValue);
+ // Upon fulfillment or rejection of p, execute the following
+ // steps:
+ // (Static analysis forces us to use a temporary)
+ RefPtr<PeerConnectionImpl> pc = mPc;
+ pc->RunNextOperation(aRv);
+ }
+}
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(PeerConnectionImpl::JSOperation,
+ PeerConnectionImpl::Operation, mOperation)
+
+NS_IMPL_ADDREF_INHERITED(PeerConnectionImpl::JSOperation,
+ PeerConnectionImpl::Operation)
+NS_IMPL_RELEASE_INHERITED(PeerConnectionImpl::JSOperation,
+ PeerConnectionImpl::Operation)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PeerConnectionImpl::JSOperation)
+NS_INTERFACE_MAP_END_INHERITING(PeerConnectionImpl::Operation)
+
+PeerConnectionImpl::JSOperation::JSOperation(PeerConnectionImpl* aPc,
+ dom::ChainedOperation& aOp,
+ ErrorResult& aError)
+ : Operation(aPc, aError), mOperation(&aOp) {}
+
+RefPtr<dom::Promise> PeerConnectionImpl::JSOperation::CallImpl(
+ ErrorResult& aError) {
+ // Static analysis will not let us call this without a temporary :(
+ RefPtr<dom::ChainedOperation> op = mOperation;
+ return op->Call(aError);
+}
+
+already_AddRefed<dom::Promise> PeerConnectionImpl::Chain(
+ dom::ChainedOperation& aOperation, ErrorResult& aError) {
+ MOZ_RELEASE_ASSERT(!mChainingOperation);
+ mChainingOperation = true;
+ RefPtr<Operation> operation = new JSOperation(this, aOperation, aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+ RefPtr<Promise> promise = Chain(operation, aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+ mChainingOperation = false;
+ return promise.forget();
+}
+
+// This is kinda complicated, but it is what the spec requires us to do. The
+// core of what makes this complicated is the requirement that |aOperation| be
+// run _immediately_ (without any Promise.Then!) if the operations chain is
+// empty.
+already_AddRefed<dom::Promise> PeerConnectionImpl::Chain(
+ const RefPtr<Operation>& aOperation, ErrorResult& aError) {
+ // If connection.[[IsClosed]] is true, return a promise rejected with a newly
+ // created InvalidStateError.
+ if (IsClosed()) {
+ CSFLogDebug(LOGTAG, "%s:%d: Peer connection is closed", __FILE__, __LINE__);
+ RefPtr<dom::Promise> error = MakePromise(aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+ error->MaybeRejectWithInvalidStateError("Peer connection is closed");
+ return error.forget();
+ }
+
+ // Append operation to [[Operations]].
+ mOperations.AppendElement(aOperation);
+
+ // If the length of [[Operations]] is exactly 1, execute operation.
+ if (mOperations.Length() == 1) {
+ aOperation->Call(aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+ }
+
+ // This is the promise p from https://w3c.github.io/webrtc-pc/#dfn-chain
+ return do_AddRef(aOperation->GetPromise());
+}
+
+void PeerConnectionImpl::RunNextOperation(ErrorResult& aError) {
+ // If connection.[[IsClosed]] is true, abort these steps.
+ if (IsClosed()) {
+ return;
+ }
+
+ // Remove the first element of [[Operations]].
+ mOperations.RemoveElementAt(0);
+
+ // If [[Operations]] is non-empty, execute the operation represented by the
+ // first element of [[Operations]], and abort these steps.
+ if (mOperations.Length()) {
+ // Cannot call without a temporary :(
+ RefPtr<Operation> op = mOperations[0];
+ op->Call(aError);
+ return;
+ }
+
+ // If connection.[[UpdateNegotiationNeededFlagOnEmptyChain]] is false, abort
+ // these steps.
+ if (!mUpdateNegotiationNeededFlagOnEmptyChain) {
+ return;
+ }
+
+ // Set connection.[[UpdateNegotiationNeededFlagOnEmptyChain]] to false.
+ mUpdateNegotiationNeededFlagOnEmptyChain = false;
+ // Update the negotiation-needed flag for connection.
+ UpdateNegotiationNeeded();
+}
+
+void PeerConnectionImpl::SyncToJsep() {
+ for (const auto& transceiver : mTransceivers) {
+ transceiver->SyncToJsep(*mJsepSession);
+ }
+}
+
+void PeerConnectionImpl::SyncFromJsep() {
+ CSFLogDebug(LOGTAG, "%s", __FUNCTION__);
+ mJsepSession->ForEachTransceiver(
+ [this, self = RefPtr<PeerConnectionImpl>(this)](
+ const JsepTransceiver& jsepTransceiver) {
+ if (jsepTransceiver.GetMediaType() ==
+ SdpMediaSection::MediaType::kApplication) {
+ return;
+ }
+
+ CSFLogDebug(LOGTAG, "%s: Looking for match", __FUNCTION__);
+ RefPtr<RTCRtpTransceiver> transceiver;
+ for (auto& temp : mTransceivers) {
+ if (temp->GetJsepTransceiverId() == jsepTransceiver.GetUuid()) {
+ CSFLogDebug(LOGTAG, "%s: Found match", __FUNCTION__);
+ transceiver = temp;
+ break;
+ }
+ }
+
+ if (!transceiver) {
+ CSFLogDebug(LOGTAG, "%s: No match, making new", __FUNCTION__);
+ dom::RTCRtpTransceiverInit init;
+ init.mDirection = RTCRtpTransceiverDirection::Recvonly;
+ IgnoredErrorResult rv;
+ transceiver = CreateTransceiver(
+ jsepTransceiver.GetUuid(),
+ jsepTransceiver.GetMediaType() == SdpMediaSection::kVideo, init,
+ nullptr, false, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ MOZ_ASSERT(false);
+ return;
+ }
+ mTransceivers.AppendElement(transceiver);
+ }
+
+ CSFLogDebug(LOGTAG, "%s: Syncing transceiver", __FUNCTION__);
+ transceiver->SyncFromJsep(*mJsepSession);
+ });
+}
+
+already_AddRefed<dom::Promise> PeerConnectionImpl::MakePromise(
+ ErrorResult& aError) const {
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);
+ return dom::Promise::Create(global, aError);
+}
+
+void PeerConnectionImpl::UpdateNegotiationNeeded() {
+ // If the length of connection.[[Operations]] is not 0, then set
+ // connection.[[UpdateNegotiationNeededFlagOnEmptyChain]] to true, and abort
+ // these steps.
+ if (mOperations.Length() != 0) {
+ mUpdateNegotiationNeededFlagOnEmptyChain = true;
+ return;
+ }
+
+ // Queue a task to run the following steps:
+ GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
+ __func__, [this, self = RefPtr<PeerConnectionImpl>(this)] {
+ // If connection.[[IsClosed]] is true, abort these steps.
+ if (IsClosed()) {
+ return;
+ }
+ // If the length of connection.[[Operations]] is not 0, then set
+ // connection.[[UpdateNegotiationNeededFlagOnEmptyChain]] to true, and
+ // abort these steps.
+ if (mOperations.Length()) {
+ mUpdateNegotiationNeededFlagOnEmptyChain = true;
+ return;
+ }
+ // If connection's signaling state is not "stable", abort these steps.
+ if (mSignalingState != RTCSignalingState::Stable) {
+ return;
+ }
+ // If the result of checking if negotiation is needed is false, clear
+ // the negotiation-needed flag by setting
+ // connection.[[NegotiationNeeded]] to false, and abort these steps.
+ if (!CheckNegotiationNeeded()) {
+ mNegotiationNeeded = false;
+ return;
+ }
+
+ // If connection.[[NegotiationNeeded]] is already true, abort these
+ // steps.
+ if (mNegotiationNeeded) {
+ return;
+ }
+
+ // Set connection.[[NegotiationNeeded]] to true.
+ mNegotiationNeeded = true;
+
+ // Fire an event named negotiationneeded at connection.
+ ErrorResult rv;
+ mPCObserver->FireNegotiationNeededEvent(rv);
+ }));
+}
+
+void PeerConnectionImpl::NotifyDataChannel(
+ already_AddRefed<DataChannel> aChannel) {
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+
+ RefPtr<DataChannel> channel(aChannel);
+ MOZ_ASSERT(channel);
+ CSFLogDebug(LOGTAG, "%s: channel: %p", __FUNCTION__, channel.get());
+
+ RefPtr<nsDOMDataChannel> domchannel;
+ nsresult rv = NS_NewDOMDataChannel(channel.forget(), mWindow,
+ getter_AddRefs(domchannel));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ JSErrorResult jrv;
+ mPCObserver->NotifyDataChannel(*domchannel, jrv);
+}
+
+void PeerConnectionImpl::NotifyDataChannelOpen(DataChannel*) {
+ mDataChannelsOpened++;
+}
+
+void PeerConnectionImpl::NotifyDataChannelClosed(DataChannel*) {
+ mDataChannelsClosed++;
+}
+
+void PeerConnectionImpl::NotifySctpConnected() {
+ if (!mSctpTransport) {
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ mSctpTransport->UpdateState(RTCSctpTransportState::Connected);
+}
+
+void PeerConnectionImpl::NotifySctpClosed() {
+ if (!mSctpTransport) {
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ mSctpTransport->UpdateState(RTCSctpTransportState::Closed);
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::CreateOffer(const RTCOfferOptions& aOptions) {
+ JsepOfferOptions options;
+ // convert the RTCOfferOptions to JsepOfferOptions
+ if (aOptions.mOfferToReceiveAudio.WasPassed()) {
+ options.mOfferToReceiveAudio =
+ mozilla::Some(size_t(aOptions.mOfferToReceiveAudio.Value()));
+ }
+
+ if (aOptions.mOfferToReceiveVideo.WasPassed()) {
+ options.mOfferToReceiveVideo =
+ mozilla::Some(size_t(aOptions.mOfferToReceiveVideo.Value()));
+ }
+
+ options.mIceRestart = mozilla::Some(aOptions.mIceRestart ||
+ !mLocalIceCredentialsToReplace.empty());
+
+ return CreateOffer(options);
+}
+
+static void DeferredCreateOffer(const std::string& aPcHandle,
+ const JsepOfferOptions& aOptions) {
+ PeerConnectionWrapper wrapper(aPcHandle);
+
+ if (wrapper.impl()) {
+ if (!PeerConnectionCtx::GetInstance()->isReady()) {
+ MOZ_CRASH(
+ "Why is DeferredCreateOffer being executed when the "
+ "PeerConnectionCtx isn't ready?");
+ }
+ wrapper.impl()->CreateOffer(aOptions);
+ }
+}
+
+// Have to use unique_ptr because webidl enums are generated without a
+// copy c'tor.
+static std::unique_ptr<dom::PCErrorData> buildJSErrorData(
+ const JsepSession::Result& aResult, const std::string& aMessage) {
+ std::unique_ptr<dom::PCErrorData> result(new dom::PCErrorData);
+ result->mName = *aResult.mError;
+ result->mMessage = NS_ConvertASCIItoUTF16(aMessage.c_str());
+ return result;
+}
+
+// Used by unit tests and the IDL CreateOffer.
+NS_IMETHODIMP
+PeerConnectionImpl::CreateOffer(const JsepOfferOptions& aOptions) {
+ PC_AUTO_ENTER_API_CALL(true);
+
+ if (!PeerConnectionCtx::GetInstance()->isReady()) {
+ // Uh oh. We're not ready yet. Enqueue this operation.
+ PeerConnectionCtx::GetInstance()->queueJSEPOperation(
+ WrapRunnableNM(DeferredCreateOffer, mHandle, aOptions));
+ STAMP_TIMECARD(mTimeCard, "Deferring CreateOffer (not ready)");
+ return NS_OK;
+ }
+
+ CSFLogDebug(LOGTAG, "CreateOffer()");
+
+ nsresult nrv = ConfigureJsepSessionCodecs();
+ if (NS_FAILED(nrv)) {
+ CSFLogError(LOGTAG, "Failed to configure codecs");
+ return nrv;
+ }
+
+ STAMP_TIMECARD(mTimeCard, "Create Offer");
+
+ GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
+ __func__, [this, self = RefPtr<PeerConnectionImpl>(this), aOptions] {
+ std::string offer;
+
+ SyncToJsep();
+ UniquePtr<JsepSession> uncommittedJsepSession(mJsepSession->Clone());
+ JsepSession::Result result =
+ uncommittedJsepSession->CreateOffer(aOptions, &offer);
+ JSErrorResult rv;
+ if (result.mError.isSome()) {
+ std::string errorString = uncommittedJsepSession->GetLastError();
+
+ CSFLogError(LOGTAG, "%s: pc = %s, error = %s", __FUNCTION__,
+ mHandle.c_str(), errorString.c_str());
+
+ mPCObserver->OnCreateOfferError(
+ *buildJSErrorData(result, errorString), rv);
+ } else {
+ mJsepSession = std::move(uncommittedJsepSession);
+ mPCObserver->OnCreateOfferSuccess(ObString(offer.c_str()), rv);
+ }
+ }));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::CreateAnswer() {
+ PC_AUTO_ENTER_API_CALL(true);
+
+ CSFLogDebug(LOGTAG, "CreateAnswer()");
+
+ STAMP_TIMECARD(mTimeCard, "Create Answer");
+ // TODO(bug 1098015): Once RTCAnswerOptions is standardized, we'll need to
+ // add it as a param to CreateAnswer, and convert it here.
+ JsepAnswerOptions options;
+
+ GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
+ __func__, [this, self = RefPtr<PeerConnectionImpl>(this), options] {
+ std::string answer;
+ SyncToJsep();
+ UniquePtr<JsepSession> uncommittedJsepSession(mJsepSession->Clone());
+ JsepSession::Result result =
+ uncommittedJsepSession->CreateAnswer(options, &answer);
+ JSErrorResult rv;
+ if (result.mError.isSome()) {
+ std::string errorString = uncommittedJsepSession->GetLastError();
+
+ CSFLogError(LOGTAG, "%s: pc = %s, error = %s", __FUNCTION__,
+ mHandle.c_str(), errorString.c_str());
+
+ mPCObserver->OnCreateAnswerError(
+ *buildJSErrorData(result, errorString), rv);
+ } else {
+ mJsepSession = std::move(uncommittedJsepSession);
+ mPCObserver->OnCreateAnswerSuccess(ObString(answer.c_str()), rv);
+ }
+ }));
+
+ return NS_OK;
+}
+
+dom::RTCSdpType ToDomSdpType(JsepSdpType aType) {
+ switch (aType) {
+ case kJsepSdpOffer:
+ return dom::RTCSdpType::Offer;
+ case kJsepSdpAnswer:
+ return dom::RTCSdpType::Answer;
+ case kJsepSdpPranswer:
+ return dom::RTCSdpType::Pranswer;
+ case kJsepSdpRollback:
+ return dom::RTCSdpType::Rollback;
+ }
+
+ MOZ_CRASH("Nonexistent JsepSdpType");
+}
+
+JsepSdpType ToJsepSdpType(dom::RTCSdpType aType) {
+ switch (aType) {
+ case dom::RTCSdpType::Offer:
+ return kJsepSdpOffer;
+ case dom::RTCSdpType::Pranswer:
+ return kJsepSdpPranswer;
+ case dom::RTCSdpType::Answer:
+ return kJsepSdpAnswer;
+ case dom::RTCSdpType::Rollback:
+ return kJsepSdpRollback;
+ case dom::RTCSdpType::EndGuard_:;
+ }
+
+ MOZ_CRASH("Nonexistent dom::RTCSdpType");
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::SetLocalDescription(int32_t aAction, const char* aSDP) {
+ PC_AUTO_ENTER_API_CALL(true);
+
+ if (!aSDP) {
+ CSFLogError(LOGTAG, "%s - aSDP is NULL", __FUNCTION__);
+ return NS_ERROR_FAILURE;
+ }
+
+ STAMP_TIMECARD(mTimeCard, "Set Local Description");
+
+ if (AnyLocalTrackHasPeerIdentity()) {
+ mRequestedPrivacy = Some(PrincipalPrivacy::Private);
+ }
+
+ mozilla::dom::RTCSdpHistoryEntryInternal sdpEntry;
+ sdpEntry.mIsLocal = true;
+ sdpEntry.mTimestamp = mTimestampMaker.GetNow().ToDom();
+ sdpEntry.mSdp = NS_ConvertASCIItoUTF16(aSDP);
+ auto appendHistory = [&]() {
+ if (!mSdpHistory.AppendElement(sdpEntry, fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ };
+
+ mLocalRequestedSDP = aSDP;
+
+ SyncToJsep();
+
+ bool wasRestartingIce = mJsepSession->IsIceRestarting();
+ JsepSdpType sdpType;
+ switch (aAction) {
+ case IPeerConnection::kActionOffer:
+ sdpType = mozilla::kJsepSdpOffer;
+ break;
+ case IPeerConnection::kActionAnswer:
+ sdpType = mozilla::kJsepSdpAnswer;
+ break;
+ case IPeerConnection::kActionPRAnswer:
+ sdpType = mozilla::kJsepSdpPranswer;
+ break;
+ case IPeerConnection::kActionRollback:
+ sdpType = mozilla::kJsepSdpRollback;
+ break;
+ default:
+ MOZ_ASSERT(false);
+ appendHistory();
+ return NS_ERROR_FAILURE;
+ }
+ MOZ_ASSERT(!mUncommittedJsepSession);
+ mUncommittedJsepSession.reset(mJsepSession->Clone());
+ JsepSession::Result result =
+ mUncommittedJsepSession->SetLocalDescription(sdpType, mLocalRequestedSDP);
+ JSErrorResult rv;
+ if (result.mError.isSome()) {
+ std::string errorString = mUncommittedJsepSession->GetLastError();
+ mUncommittedJsepSession = nullptr;
+ CSFLogError(LOGTAG, "%s: pc = %s, error = %s", __FUNCTION__,
+ mHandle.c_str(), errorString.c_str());
+ mPCObserver->OnSetDescriptionError(*buildJSErrorData(result, errorString),
+ rv);
+ sdpEntry.mErrors = GetLastSdpParsingErrors();
+ } else {
+ if (wasRestartingIce) {
+ RecordIceRestartStatistics(sdpType);
+ }
+
+ mPCObserver->OnSetDescriptionSuccess(rv);
+ }
+
+ appendHistory();
+
+ if (rv.Failed()) {
+ return rv.StealNSResult();
+ }
+
+ return NS_OK;
+}
+
+static void DeferredSetRemote(const std::string& aPcHandle, int32_t aAction,
+ const std::string& aSdp) {
+ PeerConnectionWrapper wrapper(aPcHandle);
+
+ if (wrapper.impl()) {
+ if (!PeerConnectionCtx::GetInstance()->isReady()) {
+ MOZ_CRASH(
+ "Why is DeferredSetRemote being executed when the "
+ "PeerConnectionCtx isn't ready?");
+ }
+ wrapper.impl()->SetRemoteDescription(aAction, aSdp.c_str());
+ }
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::SetRemoteDescription(int32_t action, const char* aSDP) {
+ PC_AUTO_ENTER_API_CALL(true);
+
+ if (!aSDP) {
+ CSFLogError(LOGTAG, "%s - aSDP is NULL", __FUNCTION__);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (action == IPeerConnection::kActionOffer) {
+ if (!PeerConnectionCtx::GetInstance()->isReady()) {
+ // Uh oh. We're not ready yet. Enqueue this operation. (This must be a
+ // remote offer, or else we would not have gotten this far)
+ PeerConnectionCtx::GetInstance()->queueJSEPOperation(WrapRunnableNM(
+ DeferredSetRemote, mHandle, action, std::string(aSDP)));
+ STAMP_TIMECARD(mTimeCard, "Deferring SetRemote (not ready)");
+ return NS_OK;
+ }
+
+ nsresult nrv = ConfigureJsepSessionCodecs();
+ if (NS_FAILED(nrv)) {
+ CSFLogError(LOGTAG, "Failed to configure codecs");
+ return nrv;
+ }
+ }
+
+ STAMP_TIMECARD(mTimeCard, "Set Remote Description");
+
+ mozilla::dom::RTCSdpHistoryEntryInternal sdpEntry;
+ sdpEntry.mIsLocal = false;
+ sdpEntry.mTimestamp = mTimestampMaker.GetNow().ToDom();
+ sdpEntry.mSdp = NS_ConvertASCIItoUTF16(aSDP);
+ auto appendHistory = [&]() {
+ if (!mSdpHistory.AppendElement(sdpEntry, fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ };
+
+ SyncToJsep();
+
+ mRemoteRequestedSDP = aSDP;
+ bool wasRestartingIce = mJsepSession->IsIceRestarting();
+ JsepSdpType sdpType;
+ switch (action) {
+ case IPeerConnection::kActionOffer:
+ sdpType = mozilla::kJsepSdpOffer;
+ break;
+ case IPeerConnection::kActionAnswer:
+ sdpType = mozilla::kJsepSdpAnswer;
+ break;
+ case IPeerConnection::kActionPRAnswer:
+ sdpType = mozilla::kJsepSdpPranswer;
+ break;
+ case IPeerConnection::kActionRollback:
+ sdpType = mozilla::kJsepSdpRollback;
+ break;
+ default:
+ MOZ_ASSERT(false);
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(!mUncommittedJsepSession);
+ mUncommittedJsepSession.reset(mJsepSession->Clone());
+ JsepSession::Result result = mUncommittedJsepSession->SetRemoteDescription(
+ sdpType, mRemoteRequestedSDP);
+ JSErrorResult jrv;
+ if (result.mError.isSome()) {
+ std::string errorString = mUncommittedJsepSession->GetLastError();
+ mUncommittedJsepSession = nullptr;
+ sdpEntry.mErrors = GetLastSdpParsingErrors();
+ CSFLogError(LOGTAG, "%s: pc = %s, error = %s", __FUNCTION__,
+ mHandle.c_str(), errorString.c_str());
+ mPCObserver->OnSetDescriptionError(*buildJSErrorData(result, errorString),
+ jrv);
+ } else {
+ if (wasRestartingIce) {
+ RecordIceRestartStatistics(sdpType);
+ }
+
+ mPCObserver->OnSetDescriptionSuccess(jrv);
+ }
+
+ appendHistory();
+
+ if (jrv.Failed()) {
+ return jrv.StealNSResult();
+ }
+
+ return NS_OK;
+}
+
+already_AddRefed<dom::Promise> PeerConnectionImpl::GetStats(
+ MediaStreamTrack* aSelector) {
+ if (!mWindow) {
+ MOZ_CRASH("Cannot create a promise without a window!");
+ }
+
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);
+ ErrorResult rv;
+ RefPtr<Promise> promise = Promise::Create(global, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ MOZ_CRASH("Failed to create a promise!");
+ }
+
+ if (!IsClosed()) {
+ GetStats(aSelector, false)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [promise, window = mWindow](
+ UniquePtr<dom::RTCStatsReportInternal>&& aReport) {
+ RefPtr<RTCStatsReport> report(new RTCStatsReport(window));
+ report->Incorporate(*aReport);
+ promise->MaybeResolve(std::move(report));
+ },
+ [promise, window = mWindow](nsresult aError) {
+ RefPtr<RTCStatsReport> report(new RTCStatsReport(window));
+ promise->MaybeResolve(std::move(report));
+ });
+ } else {
+ promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+ }
+
+ return promise.forget();
+}
+
+void PeerConnectionImpl::GetRemoteStreams(
+ nsTArray<RefPtr<DOMMediaStream>>& aStreamsOut) const {
+ aStreamsOut = mReceiveStreams.Clone();
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::AddIceCandidate(
+ const char* aCandidate, const char* aMid, const char* aUfrag,
+ const dom::Nullable<unsigned short>& aLevel) {
+ PC_AUTO_ENTER_API_CALL(true);
+
+ if (mForceIceTcp &&
+ std::string::npos != std::string(aCandidate).find(" UDP ")) {
+ CSFLogError(LOGTAG, "Blocking remote UDP candidate: %s", aCandidate);
+ return NS_OK;
+ }
+
+ STAMP_TIMECARD(mTimeCard, "Add Ice Candidate");
+
+ CSFLogDebug(LOGTAG, "AddIceCandidate: %s %s", aCandidate, aUfrag);
+
+ std::string transportId;
+ Maybe<unsigned short> level;
+ if (!aLevel.IsNull()) {
+ level = Some(aLevel.Value());
+ }
+ MOZ_DIAGNOSTIC_ASSERT(
+ !mUncommittedJsepSession,
+ "AddIceCandidate is chained, which means it should never "
+ "run while an sRD/sLD is in progress");
+ JsepSession::Result result = mJsepSession->AddRemoteIceCandidate(
+ aCandidate, aMid, level, aUfrag, &transportId);
+
+ if (!result.mError.isSome()) {
+ // We do not bother the MediaTransportHandler about this before
+ // offer/answer concludes. Once offer/answer concludes, we will extract
+ // these candidates from the remote SDP.
+ if (mSignalingState == RTCSignalingState::Stable && !transportId.empty()) {
+ AddIceCandidate(aCandidate, transportId, aUfrag);
+ mRawTrickledCandidates.push_back(aCandidate);
+ }
+ // Spec says we queue a task for these updates
+ GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
+ __func__, [this, self = RefPtr<PeerConnectionImpl>(this)] {
+ if (IsClosed()) {
+ return;
+ }
+ mPendingRemoteDescription =
+ mJsepSession->GetRemoteDescription(kJsepDescriptionPending);
+ mCurrentRemoteDescription =
+ mJsepSession->GetRemoteDescription(kJsepDescriptionCurrent);
+ JSErrorResult rv;
+ mPCObserver->OnAddIceCandidateSuccess(rv);
+ }));
+ } else {
+ std::string errorString = mJsepSession->GetLastError();
+
+ CSFLogError(LOGTAG,
+ "Failed to incorporate remote candidate into SDP:"
+ " res = %u, candidate = %s, level = %i, error = %s",
+ static_cast<unsigned>(*result.mError), aCandidate,
+ level.valueOr(-1), errorString.c_str());
+
+ GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
+ __func__,
+ [this, self = RefPtr<PeerConnectionImpl>(this), errorString, result] {
+ if (IsClosed()) {
+ return;
+ }
+ JSErrorResult rv;
+ mPCObserver->OnAddIceCandidateError(
+ *buildJSErrorData(result, errorString), rv);
+ }));
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::CloseStreams() {
+ PC_AUTO_ENTER_API_CALL(false);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::SetPeerIdentity(const nsAString& aPeerIdentity) {
+ PC_AUTO_ENTER_API_CALL(true);
+ MOZ_ASSERT(!aPeerIdentity.IsEmpty());
+
+ // once set, this can't be changed
+ if (mPeerIdentity) {
+ if (!mPeerIdentity->Equals(aPeerIdentity)) {
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ mPeerIdentity = new PeerIdentity(aPeerIdentity);
+ Document* doc = mWindow->GetExtantDoc();
+ if (!doc) {
+ CSFLogInfo(LOGTAG, "Can't update principal on streams; document gone");
+ return NS_ERROR_FAILURE;
+ }
+ for (const auto& transceiver : mTransceivers) {
+ transceiver->Sender()->GetPipeline()->UpdateSinkIdentity(
+ doc->NodePrincipal(), mPeerIdentity);
+ }
+ }
+ return NS_OK;
+}
+
+nsresult PeerConnectionImpl::OnAlpnNegotiated(bool aPrivacyRequested) {
+ PC_AUTO_ENTER_API_CALL(false);
+ MOZ_DIAGNOSTIC_ASSERT(!mRequestedPrivacy ||
+ (*mRequestedPrivacy == PrincipalPrivacy::Private) ==
+ aPrivacyRequested);
+
+ mRequestedPrivacy = Some(aPrivacyRequested ? PrincipalPrivacy::Private
+ : PrincipalPrivacy::NonPrivate);
+ // This updates the MediaPipelines with a private PrincipalHandle. Note that
+ // MediaPipelineReceive has its own AlpnNegotiated handler so it can get
+ // signaled off-main to drop data until it receives the new PrincipalHandle
+ // from us.
+ UpdateMediaPipelines();
+ return NS_OK;
+}
+
+void PeerConnectionImpl::OnDtlsStateChange(const std::string& aTransportId,
+ TransportLayer::State aState) {
+ auto it = mTransportIdToRTCDtlsTransport.find(aTransportId);
+ if (it != mTransportIdToRTCDtlsTransport.end()) {
+ it->second->UpdateState(aState);
+ }
+ UpdateConnectionState();
+}
+
+RTCPeerConnectionState PeerConnectionImpl::GetNewConnectionState() const {
+ // closed The RTCPeerConnection object's [[IsClosed]] slot is true.
+ if (IsClosed()) {
+ return RTCPeerConnectionState::Closed;
+ }
+
+ // Would use a bitset, but that requires lots of static_cast<size_t>
+ // Oh well.
+ std::set<RTCDtlsTransportState> statesFound;
+ for (const auto& [id, dtlsTransport] : mTransportIdToRTCDtlsTransport) {
+ Unused << id;
+ statesFound.insert(dtlsTransport->State());
+ }
+
+ // failed The previous state doesn't apply and any RTCIceTransports are
+ // in the "failed" state or any RTCDtlsTransports are in the "failed" state.
+ // NOTE: "any RTCIceTransports are in the failed state" is equivalent to
+ // mIceConnectionState == Failed
+ if (mIceConnectionState == RTCIceConnectionState::Failed ||
+ statesFound.count(RTCDtlsTransportState::Failed)) {
+ return RTCPeerConnectionState::Failed;
+ }
+
+ // disconnected None of the previous states apply and any
+ // RTCIceTransports are in the "disconnected" state.
+ // NOTE: "any RTCIceTransports are in the disconnected state" is equivalent to
+ // mIceConnectionState == Disconnected.
+ if (mIceConnectionState == RTCIceConnectionState::Disconnected) {
+ return RTCPeerConnectionState::Disconnected;
+ }
+
+ // new None of the previous states apply and all RTCIceTransports are
+ // in the "new" or "closed" state, and all RTCDtlsTransports are in the "new"
+ // or "closed" state, or there are no transports.
+ // NOTE: "all RTCIceTransports are in the new or closed state" is equivalent
+ // to mIceConnectionState == New.
+ if (mIceConnectionState == RTCIceConnectionState::New &&
+ !statesFound.count(RTCDtlsTransportState::Connecting) &&
+ !statesFound.count(RTCDtlsTransportState::Connected) &&
+ !statesFound.count(RTCDtlsTransportState::Failed)) {
+ return RTCPeerConnectionState::New;
+ }
+
+ // No transports
+ if (statesFound.empty()) {
+ return RTCPeerConnectionState::New;
+ }
+
+ // connecting None of the previous states apply and any
+ // RTCIceTransport is in the "new" or "checking" state or any
+ // RTCDtlsTransport is in the "new" or "connecting" state.
+ // NOTE: "None of the previous states apply and any RTCIceTransport is in the
+ // "new" or "checking" state" is equivalent to mIceConnectionState ==
+ // Checking.
+ if (mIceConnectionState == RTCIceConnectionState::Checking ||
+ statesFound.count(RTCDtlsTransportState::New) ||
+ statesFound.count(RTCDtlsTransportState::Connecting)) {
+ return RTCPeerConnectionState::Connecting;
+ }
+
+ // connected None of the previous states apply and all RTCIceTransports are
+ // in the "connected", "completed" or "closed" state, and all
+ // RTCDtlsTransports are in the "connected" or "closed" state.
+ // NOTE: "None of the previous states apply and all RTCIceTransports are in
+ // the "connected", "completed" or "closed" state" is equivalent to
+ // mIceConnectionState == Connected.
+ if (mIceConnectionState == RTCIceConnectionState::Connected &&
+ !statesFound.count(RTCDtlsTransportState::New) &&
+ !statesFound.count(RTCDtlsTransportState::Failed) &&
+ !statesFound.count(RTCDtlsTransportState::Connecting)) {
+ return RTCPeerConnectionState::Connected;
+ }
+
+ // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ // THERE IS NO CATCH-ALL NONE-OF-THE-ABOVE IN THE SPEC! THIS IS REALLY BAD! !!
+ // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+ // Let's try to figure out how bad, precisely.
+ // Any one of these will cause us to bail above.
+ MOZ_ASSERT(mIceConnectionState != RTCIceConnectionState::Failed &&
+ mIceConnectionState != RTCIceConnectionState::Disconnected &&
+ mIceConnectionState != RTCIceConnectionState::Checking);
+ MOZ_ASSERT(!statesFound.count(RTCDtlsTransportState::New) &&
+ !statesFound.count(RTCDtlsTransportState::Connecting) &&
+ !statesFound.count(RTCDtlsTransportState::Failed));
+
+ // One of these must be set, or the empty() check would have failed above.
+ MOZ_ASSERT(statesFound.count(RTCDtlsTransportState::Connected) ||
+ statesFound.count(RTCDtlsTransportState::Closed));
+
+ // Here are our remaining possibilities:
+ // ICE connected, !statesFound.count(Connected), statesFound.count(Closed)
+ // ICE connected, statesFound.count(Connected), !statesFound.count(Closed)
+ // ICE connected, statesFound.count(Connected), statesFound.count(Closed)
+ // All three of these would result in returning Connected above.
+
+ // ICE new, !statesFound.count(Connected), statesFound.count(Closed)
+ // This results in returning New above. Whew.
+
+ // ICE new, statesFound.count(Connected), !statesFound.count(Closed)
+ // ICE new, statesFound.count(Connected), statesFound.count(Closed)
+ // These would make it all the way here! Very weird state though, for all
+ // ICE transports to be new/closed, but having a connected DTLS transport.
+ // Handle this as a non-transition, just in case.
+ return mConnectionState;
+}
+
+void PeerConnectionImpl::UpdateConnectionState() {
+ auto newState = GetNewConnectionState();
+ if (newState != mConnectionState) {
+ CSFLogDebug(LOGTAG, "%s: %d -> %d (%p)", __FUNCTION__,
+ static_cast<int>(mConnectionState), static_cast<int>(newState),
+ this);
+ mConnectionState = newState;
+ if (mConnectionState != RTCPeerConnectionState::Closed) {
+ JSErrorResult jrv;
+ mPCObserver->OnStateChange(PCObserverStateType::ConnectionState, jrv);
+ }
+ }
+}
+
+void PeerConnectionImpl::OnMediaError(const std::string& aError) {
+ CSFLogError(LOGTAG, "Encountered media error! %s", aError.c_str());
+ // TODO: Let content know about this somehow.
+}
+
+void PeerConnectionImpl::DumpPacket_m(size_t level, dom::mozPacketDumpType type,
+ bool sending,
+ UniquePtr<uint8_t[]>& packet,
+ size_t size) {
+ if (IsClosed()) {
+ return;
+ }
+
+ // TODO: Is this efficient? Should we try grabbing our JS ctx from somewhere
+ // else?
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(mWindow)) {
+ return;
+ }
+
+ JS::Rooted<JSObject*> jsobj(
+ jsapi.cx(),
+ JS::NewArrayBufferWithContents(jsapi.cx(), size, packet.release()));
+
+ RootedSpiderMonkeyInterface<ArrayBuffer> arrayBuffer(jsapi.cx());
+ if (!arrayBuffer.Init(jsobj)) {
+ return;
+ }
+
+ JSErrorResult jrv;
+ mPCObserver->OnPacket(level, type, sending, arrayBuffer, jrv);
+}
+
+bool PeerConnectionImpl::HostnameInPref(const char* aPref,
+ const nsCString& aHostName) {
+ auto HostInDomain = [](const nsCString& aHost, const nsCString& aPattern) {
+ int32_t patternOffset = 0;
+ int32_t hostOffset = 0;
+
+ // Act on '*.' wildcard in the left-most position in a domain pattern.
+ if (StringBeginsWith(aPattern, nsCString("*."))) {
+ patternOffset = 2;
+
+ // Ignore the lowest level sub-domain for the hostname.
+ hostOffset = aHost.FindChar('.') + 1;
+
+ if (hostOffset <= 1) {
+ // Reject a match between a wildcard and a TLD or '.foo' form.
+ return false;
+ }
+ }
+
+ nsDependentCString hostRoot(aHost, hostOffset);
+ return hostRoot.EqualsIgnoreCase(aPattern.BeginReading() + patternOffset);
+ };
+
+ nsCString domainList;
+ nsresult nr = Preferences::GetCString(aPref, domainList);
+
+ if (NS_FAILED(nr)) {
+ return false;
+ }
+
+ domainList.StripWhitespace();
+
+ if (domainList.IsEmpty() || aHostName.IsEmpty()) {
+ return false;
+ }
+
+ // Get UTF8 to ASCII domain name normalization service
+ nsresult rv;
+ nsCOMPtr<nsIIDNService> idnService =
+ do_GetService("@mozilla.org/network/idn-service;1", &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ // Test each domain name in the comma separated list
+ // after converting from UTF8 to ASCII. Each domain
+ // must match exactly or have a single leading '*.' wildcard.
+ for (const nsACString& each : domainList.Split(',')) {
+ nsCString domainPattern;
+ rv = idnService->ConvertUTF8toACE(each, domainPattern);
+ if (NS_SUCCEEDED(rv)) {
+ if (HostInDomain(aHostName, domainPattern)) {
+ return true;
+ }
+ } else {
+ NS_WARNING("Failed to convert UTF-8 host to ASCII");
+ }
+ }
+
+ return false;
+}
+
+nsresult PeerConnectionImpl::EnablePacketDump(unsigned long level,
+ dom::mozPacketDumpType type,
+ bool sending) {
+ return GetPacketDumper()->EnablePacketDump(level, type, sending);
+}
+
+nsresult PeerConnectionImpl::DisablePacketDump(unsigned long level,
+ dom::mozPacketDumpType type,
+ bool sending) {
+ return GetPacketDumper()->DisablePacketDump(level, type, sending);
+}
+
+void PeerConnectionImpl::StampTimecard(const char* aEvent) {
+ MOZ_ASSERT(NS_IsMainThread());
+ STAMP_TIMECARD(mTimeCard, aEvent);
+}
+
+void PeerConnectionImpl::SendWarningToConsole(const nsCString& aWarning) {
+ nsAutoString msg = NS_ConvertASCIItoUTF16(aWarning);
+ nsContentUtils::ReportToConsoleByWindowID(msg, nsIScriptError::warningFlag,
+ "WebRTC"_ns, mWindow->WindowID());
+}
+
+void PeerConnectionImpl::GetDefaultVideoCodecs(
+ std::vector<UniquePtr<JsepCodecDescription>>& aSupportedCodecs,
+ bool aUseRtx) {
+ // Supported video codecs.
+ // Note: order here implies priority for building offers!
+ aSupportedCodecs.emplace_back(
+ JsepVideoCodecDescription::CreateDefaultVP8(aUseRtx));
+ aSupportedCodecs.emplace_back(
+ JsepVideoCodecDescription::CreateDefaultVP9(aUseRtx));
+ aSupportedCodecs.emplace_back(
+ JsepVideoCodecDescription::CreateDefaultH264_1(aUseRtx));
+ aSupportedCodecs.emplace_back(
+ JsepVideoCodecDescription::CreateDefaultH264_0(aUseRtx));
+ aSupportedCodecs.emplace_back(
+ JsepVideoCodecDescription::CreateDefaultUlpFec());
+ aSupportedCodecs.emplace_back(
+ JsepApplicationCodecDescription::CreateDefault());
+ aSupportedCodecs.emplace_back(JsepVideoCodecDescription::CreateDefaultRed());
+}
+
+void PeerConnectionImpl::GetDefaultAudioCodecs(
+ std::vector<UniquePtr<JsepCodecDescription>>& aSupportedCodecs) {
+ aSupportedCodecs.emplace_back(JsepAudioCodecDescription::CreateDefaultOpus());
+ aSupportedCodecs.emplace_back(JsepAudioCodecDescription::CreateDefaultG722());
+ aSupportedCodecs.emplace_back(JsepAudioCodecDescription::CreateDefaultPCMU());
+ aSupportedCodecs.emplace_back(JsepAudioCodecDescription::CreateDefaultPCMA());
+ aSupportedCodecs.emplace_back(
+ JsepAudioCodecDescription::CreateDefaultTelephoneEvent());
+}
+
+void PeerConnectionImpl::GetDefaultRtpExtensions(
+ std::vector<RtpExtensionHeader>& aRtpExtensions) {
+ RtpExtensionHeader audioLevel = {JsepMediaType::kAudio,
+ SdpDirectionAttribute::Direction::kSendrecv,
+ webrtc::RtpExtension::kAudioLevelUri};
+ aRtpExtensions.push_back(audioLevel);
+
+ RtpExtensionHeader csrcAudioLevels = {
+ JsepMediaType::kAudio, SdpDirectionAttribute::Direction::kRecvonly,
+ webrtc::RtpExtension::kCsrcAudioLevelsUri};
+ aRtpExtensions.push_back(csrcAudioLevels);
+
+ RtpExtensionHeader mid = {JsepMediaType::kAudioVideo,
+ SdpDirectionAttribute::Direction::kSendrecv,
+ webrtc::RtpExtension::kMidUri};
+ aRtpExtensions.push_back(mid);
+
+ RtpExtensionHeader absSendTime = {JsepMediaType::kVideo,
+ SdpDirectionAttribute::Direction::kSendrecv,
+ webrtc::RtpExtension::kAbsSendTimeUri};
+ aRtpExtensions.push_back(absSendTime);
+
+ RtpExtensionHeader timestampOffset = {
+ JsepMediaType::kVideo, SdpDirectionAttribute::Direction::kSendrecv,
+ webrtc::RtpExtension::kTimestampOffsetUri};
+ aRtpExtensions.push_back(timestampOffset);
+
+ RtpExtensionHeader playoutDelay = {
+ JsepMediaType::kVideo, SdpDirectionAttribute::Direction::kRecvonly,
+ webrtc::RtpExtension::kPlayoutDelayUri};
+ aRtpExtensions.push_back(playoutDelay);
+
+ RtpExtensionHeader transportSequenceNumber = {
+ JsepMediaType::kVideo, SdpDirectionAttribute::Direction::kSendrecv,
+ webrtc::RtpExtension::kTransportSequenceNumberUri};
+ aRtpExtensions.push_back(transportSequenceNumber);
+}
+
+void PeerConnectionImpl::GetCapabilities(
+ const nsAString& aKind, dom::Nullable<dom::RTCRtpCapabilities>& aResult,
+ sdp::Direction aDirection) {
+ std::vector<UniquePtr<JsepCodecDescription>> codecs;
+ std::vector<RtpExtensionHeader> headers;
+ auto mediaType = JsepMediaType::kNone;
+
+ if (aKind.EqualsASCII("video")) {
+ GetDefaultVideoCodecs(codecs, true);
+ mediaType = JsepMediaType::kVideo;
+ } else if (aKind.EqualsASCII("audio")) {
+ GetDefaultAudioCodecs(codecs);
+ mediaType = JsepMediaType::kAudio;
+ } else {
+ return;
+ }
+
+ GetDefaultRtpExtensions(headers);
+
+ // Use the codecs for kind to fill out the RTCRtpCodecCapability
+ for (const auto& codec : codecs) {
+ // To avoid misleading information on codec capabilities skip those
+ // not signaled for audio/video (webrtc-datachannel)
+ // and any disabled by default (ulpfec and red).
+ if (codec->mName == "webrtc-datachannel" || codec->mName == "ulpfec" ||
+ codec->mName == "red") {
+ continue;
+ }
+
+ dom::RTCRtpCodecCapability capability;
+ capability.mMimeType = aKind + NS_ConvertASCIItoUTF16("/" + codec->mName);
+ capability.mClockRate = codec->mClock;
+
+ if (codec->mChannels) {
+ capability.mChannels.Construct(codec->mChannels);
+ }
+
+ UniquePtr<SdpFmtpAttributeList::Parameters> params;
+ codec->ApplyConfigToFmtp(params);
+
+ if (params != nullptr) {
+ std::ostringstream paramsString;
+ params->Serialize(paramsString);
+ nsTString<char16_t> fmtp;
+ fmtp.AssignASCII(paramsString.str());
+ capability.mSdpFmtpLine.Construct(fmtp);
+ }
+
+ if (!aResult.SetValue().mCodecs.AppendElement(capability, fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ }
+
+ // We need to manually add rtx for video.
+ if (mediaType == JsepMediaType::kVideo) {
+ dom::RTCRtpCodecCapability capability;
+ capability.mMimeType = aKind + NS_ConvertASCIItoUTF16("/rtx");
+ capability.mClockRate = 90000;
+ if (!aResult.SetValue().mCodecs.AppendElement(capability, fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ }
+
+ // Add headers that match the direction and media type requested.
+ for (const auto& header : headers) {
+ if ((header.direction & aDirection) && (header.mMediaType & mediaType)) {
+ dom::RTCRtpHeaderExtensionCapability rtpHeader;
+ rtpHeader.mUri.AssignASCII(header.extensionname);
+ if (!aResult.SetValue().mHeaderExtensions.AppendElement(rtpHeader,
+ fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ }
+ }
+}
+
+void PeerConnectionImpl::SetupPreferredCodecs(
+ std::vector<UniquePtr<JsepCodecDescription>>& aPreferredCodecs) {
+ bool useRtx =
+ Preferences::GetBool("media.peerconnection.video.use_rtx", false);
+
+ GetDefaultVideoCodecs(aPreferredCodecs, useRtx);
+ GetDefaultAudioCodecs(aPreferredCodecs);
+
+ // With red update the redundant encodings list
+ for (auto& videoCodec : aPreferredCodecs) {
+ if (videoCodec->mName == "red") {
+ JsepVideoCodecDescription& red =
+ static_cast<JsepVideoCodecDescription&>(*videoCodec);
+ red.UpdateRedundantEncodings(aPreferredCodecs);
+ }
+ }
+}
+
+void PeerConnectionImpl::SetupPreferredRtpExtensions(
+ std::vector<RtpExtensionHeader>& aPreferredheaders) {
+ GetDefaultRtpExtensions(aPreferredheaders);
+
+ if (!Preferences::GetBool("media.navigator.video.use_transport_cc", false)) {
+ aPreferredheaders.erase(
+ std::remove_if(
+ aPreferredheaders.begin(), aPreferredheaders.end(),
+ [&](const RtpExtensionHeader& header) {
+ return header.extensionname ==
+ webrtc::RtpExtension::kTransportSequenceNumberUri;
+ }),
+ aPreferredheaders.end());
+ }
+}
+
+nsresult PeerConnectionImpl::CalculateFingerprint(
+ const std::string& algorithm, std::vector<uint8_t>* fingerprint) const {
+ DtlsDigest digest(algorithm);
+
+ MOZ_ASSERT(fingerprint);
+ const UniqueCERTCertificate& cert = mCertificate->Certificate();
+ nsresult rv = DtlsIdentity::ComputeFingerprint(cert, &digest);
+ if (NS_FAILED(rv)) {
+ CSFLogError(LOGTAG, "Unable to calculate certificate fingerprint, rv=%u",
+ static_cast<unsigned>(rv));
+ return rv;
+ }
+ *fingerprint = digest.value_;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::GetFingerprint(char** fingerprint) {
+ MOZ_ASSERT(fingerprint);
+ MOZ_ASSERT(mCertificate);
+ std::vector<uint8_t> fp;
+ nsresult rv = CalculateFingerprint(DtlsIdentity::DEFAULT_HASH_ALGORITHM, &fp);
+ NS_ENSURE_SUCCESS(rv, rv);
+ std::ostringstream os;
+ os << DtlsIdentity::DEFAULT_HASH_ALGORITHM << ' '
+ << SdpFingerprintAttributeList::FormatFingerprint(fp);
+ std::string fpStr = os.str();
+
+ char* tmp = new char[fpStr.size() + 1];
+ std::copy(fpStr.begin(), fpStr.end(), tmp);
+ tmp[fpStr.size()] = '\0';
+
+ *fingerprint = tmp;
+ return NS_OK;
+}
+
+void PeerConnectionImpl::GetCurrentLocalDescription(nsAString& aSDP) const {
+ aSDP = NS_ConvertASCIItoUTF16(mCurrentLocalDescription.c_str());
+}
+
+void PeerConnectionImpl::GetPendingLocalDescription(nsAString& aSDP) const {
+ aSDP = NS_ConvertASCIItoUTF16(mPendingLocalDescription.c_str());
+}
+
+void PeerConnectionImpl::GetCurrentRemoteDescription(nsAString& aSDP) const {
+ aSDP = NS_ConvertASCIItoUTF16(mCurrentRemoteDescription.c_str());
+}
+
+void PeerConnectionImpl::GetPendingRemoteDescription(nsAString& aSDP) const {
+ aSDP = NS_ConvertASCIItoUTF16(mPendingRemoteDescription.c_str());
+}
+
+dom::Nullable<bool> PeerConnectionImpl::GetCurrentOfferer() const {
+ dom::Nullable<bool> result;
+ if (mCurrentOfferer.isSome()) {
+ result.SetValue(*mCurrentOfferer);
+ }
+ return result;
+}
+
+dom::Nullable<bool> PeerConnectionImpl::GetPendingOfferer() const {
+ dom::Nullable<bool> result;
+ if (mPendingOfferer.isSome()) {
+ result.SetValue(*mPendingOfferer);
+ }
+ return result;
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::SignalingState(RTCSignalingState* aState) {
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ MOZ_ASSERT(aState);
+
+ *aState = mSignalingState;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::IceConnectionState(RTCIceConnectionState* aState) {
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ MOZ_ASSERT(aState);
+
+ *aState = mIceConnectionState;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::IceGatheringState(RTCIceGatheringState* aState) {
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ MOZ_ASSERT(aState);
+
+ *aState = mIceGatheringState;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::ConnectionState(RTCPeerConnectionState* aState) {
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ MOZ_ASSERT(aState);
+
+ *aState = mConnectionState;
+ return NS_OK;
+}
+
+nsresult PeerConnectionImpl::CheckApiState(bool assert_ice_ready) const {
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ MOZ_ASSERT(mTrickle || !assert_ice_ready ||
+ (mIceGatheringState == RTCIceGatheringState::Complete));
+
+ if (IsClosed()) {
+ CSFLogError(LOGTAG, "%s: called API while closed", __FUNCTION__);
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+void PeerConnectionImpl::StoreFinalStats(
+ UniquePtr<RTCStatsReportInternal>&& report) {
+ using namespace Telemetry;
+
+ report->mClosed = true;
+
+ for (const auto& inboundRtpStats : report->mInboundRtpStreamStats) {
+ bool isVideo = (inboundRtpStats.mId.Value().Find(u"video") != -1);
+ if (!isVideo) {
+ continue;
+ }
+ if (inboundRtpStats.mDiscardedPackets.WasPassed() &&
+ report->mCallDurationMs.WasPassed()) {
+ double mins = report->mCallDurationMs.Value() / (1000 * 60);
+ if (mins > 0) {
+ Accumulate(
+ WEBRTC_VIDEO_DECODER_DISCARDED_PACKETS_PER_CALL_PPM,
+ uint32_t(double(inboundRtpStats.mDiscardedPackets.Value()) / mins));
+ }
+ }
+ }
+
+ // Finally, store the stats
+ mFinalStats = std::move(report);
+}
+
+NS_IMETHODIMP
+PeerConnectionImpl::Close() {
+ CSFLogDebug(LOGTAG, "%s: for %s", __FUNCTION__, mHandle.c_str());
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+
+ if (IsClosed()) {
+ return NS_OK;
+ }
+
+ STAMP_TIMECARD(mTimeCard, "Close");
+
+ // When ICE completes, we record some telemetry. We do this at the end of the
+ // call because we want to make sure we've waited for all trickle ICE
+ // candidates to come in; this can happen well after we've transitioned to
+ // connected. As a bonus, this allows us to detect race conditions where a
+ // stats dispatch happens right as the PC closes.
+ RecordEndOfCallTelemetry();
+
+ CSFLogInfo(LOGTAG,
+ "%s: Closing PeerConnectionImpl %s; "
+ "ending call",
+ __FUNCTION__, mHandle.c_str());
+ if (mJsepSession) {
+ mJsepSession->Close();
+ }
+ if (mDataConnection) {
+ CSFLogInfo(LOGTAG, "%s: Destroying DataChannelConnection %p for %s",
+ __FUNCTION__, (void*)mDataConnection.get(), mHandle.c_str());
+ mDataConnection->Destroy();
+ mDataConnection =
+ nullptr; // it may not go away until the runnables are dead
+ }
+
+ if (mStunAddrsRequest) {
+ for (const auto& hostname : mRegisteredMDNSHostnames) {
+ mStunAddrsRequest->SendUnregisterMDNSHostname(
+ nsCString(hostname.c_str()));
+ }
+ mRegisteredMDNSHostnames.clear();
+ mStunAddrsRequest->Cancel();
+ mStunAddrsRequest = nullptr;
+ }
+
+ for (auto& transceiver : mTransceivers) {
+ transceiver->Close();
+ }
+
+ mTransportIdToRTCDtlsTransport.clear();
+
+ mQueuedIceCtxOperations.clear();
+
+ mOperations.Clear();
+
+ // Uncount this connection as active on the inner window upon close.
+ if (mWindow && mActiveOnWindow) {
+ mWindow->RemovePeerConnection();
+ mActiveOnWindow = false;
+ }
+
+ mSignalingState = RTCSignalingState::Closed;
+ mConnectionState = RTCPeerConnectionState::Closed;
+
+ if (!mTransportHandler) {
+ // We were never initialized, apparently.
+ return NS_OK;
+ }
+
+ // Clear any resources held by libwebrtc through our Call instance.
+ RefPtr<GenericPromise> callDestroyPromise;
+ if (mCall) {
+ // Make sure the compiler does not get confused and try to acquire a
+ // reference to this thread _after_ we null out mCall.
+ auto callThread = mCall->mCallThread;
+ callDestroyPromise =
+ InvokeAsync(callThread, __func__, [call = std::move(mCall)]() {
+ call->Destroy();
+ return GenericPromise::CreateAndResolve(
+ true, "PCImpl->WebRtcCallWrapper::Destroy");
+ });
+ } else {
+ callDestroyPromise = GenericPromise::CreateAndResolve(true, __func__);
+ }
+
+ mFinalStatsQuery =
+ GetStats(nullptr, true)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [this, self = RefPtr<PeerConnectionImpl>(this)](
+ UniquePtr<dom::RTCStatsReportInternal>&& aReport) mutable {
+ StoreFinalStats(std::move(aReport));
+ return GenericNonExclusivePromise::CreateAndResolve(true,
+ __func__);
+ },
+ [](nsresult aError) {
+ return GenericNonExclusivePromise::CreateAndResolve(true,
+ __func__);
+ });
+
+ // 1. Allow final stats query to complete.
+ // 2. Tear down call, if necessary. We do this before we shut down the
+ // transport handler, so RTCP BYE can be sent.
+ // 3. Unhook from the signal handler (sigslot) for transport stuff. This must
+ // be done before we tear down the transport handler.
+ // 4. Tear down the transport handler, and deregister from PeerConnectionCtx.
+ // When we deregister from PeerConnectionCtx, our final stats (if any)
+ // will be stored.
+ MOZ_RELEASE_ASSERT(mSTSThread);
+ mFinalStatsQuery
+ ->Then(GetMainThreadSerialEventTarget(), __func__,
+ [callDestroyPromise]() mutable { return callDestroyPromise; })
+ ->Then(
+ mSTSThread, __func__,
+ [signalHandler = std::move(mSignalHandler)]() mutable {
+ CSFLogDebug(
+ LOGTAG,
+ "Destroying PeerConnectionImpl::SignalHandler on STS thread");
+ return GenericPromise::CreateAndResolve(
+ true, "PeerConnectionImpl::~SignalHandler");
+ })
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [this, self = RefPtr<PeerConnectionImpl>(this)]() mutable {
+ CSFLogDebug(LOGTAG, "PCImpl->mTransportHandler::RemoveTransports");
+ mTransportHandler->RemoveTransportsExcept(std::set<std::string>());
+ if (mPrivateWindow) {
+ mTransportHandler->ExitPrivateMode();
+ }
+ mTransportHandler = nullptr;
+ if (PeerConnectionCtx::isActive()) {
+ // If we're shutting down xpcom, this Instance will be unset
+ // before calling Close() on all remaining PCs, to avoid
+ // reentrancy.
+ PeerConnectionCtx::GetInstance()->RemovePeerConnection(mHandle);
+ }
+ });
+
+ return NS_OK;
+}
+
+void PeerConnectionImpl::BreakCycles() {
+ for (auto& transceiver : mTransceivers) {
+ transceiver->BreakCycles();
+ }
+ mTransceivers.Clear();
+}
+
+bool PeerConnectionImpl::HasPendingSetParameters() const {
+ for (const auto& transceiver : mTransceivers) {
+ if (transceiver->Sender()->HasPendingSetParameters()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void PeerConnectionImpl::InvalidateLastReturnedParameters() {
+ for (const auto& transceiver : mTransceivers) {
+ transceiver->Sender()->InvalidateLastReturnedParameters();
+ }
+}
+
+nsresult PeerConnectionImpl::SetConfiguration(
+ const RTCConfiguration& aConfiguration) {
+ nsresult rv = mTransportHandler->SetIceConfig(
+ aConfiguration.mIceServers, aConfiguration.mIceTransportPolicy);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ JsepBundlePolicy bundlePolicy;
+ switch (aConfiguration.mBundlePolicy) {
+ case dom::RTCBundlePolicy::Balanced:
+ bundlePolicy = kBundleBalanced;
+ break;
+ case dom::RTCBundlePolicy::Max_compat:
+ bundlePolicy = kBundleMaxCompat;
+ break;
+ case dom::RTCBundlePolicy::Max_bundle:
+ bundlePolicy = kBundleMaxBundle;
+ break;
+ default:
+ MOZ_CRASH();
+ }
+
+ // Ignore errors, since those ought to be handled earlier.
+ Unused << mJsepSession->SetBundlePolicy(bundlePolicy);
+
+ if (!aConfiguration.mPeerIdentity.IsEmpty()) {
+ mPeerIdentity = new PeerIdentity(aConfiguration.mPeerIdentity);
+ mRequestedPrivacy = Some(PrincipalPrivacy::Private);
+ }
+
+ auto proxyConfig = GetProxyConfig();
+ if (proxyConfig) {
+ // Note that this could check if PrivacyRequested() is set on the PC and
+ // remove "webrtc" from the ALPN list. But that would only work if the PC
+ // was constructed with a peerIdentity constraint, not when isolated
+ // streams are added. If we ever need to signal to the proxy that the
+ // media is isolated, then we would need to restructure this code.
+ mTransportHandler->SetProxyConfig(std::move(*proxyConfig));
+ }
+
+ // Store the configuration for about:webrtc
+ StoreConfigurationForAboutWebrtc(aConfiguration);
+
+ return NS_OK;
+}
+
+RTCSctpTransport* PeerConnectionImpl::GetSctp() const {
+ return mSctpTransport.get();
+}
+
+void PeerConnectionImpl::RestartIce() {
+ RestartIceNoRenegotiationNeeded();
+ // Update the negotiation-needed flag for connection.
+ UpdateNegotiationNeeded();
+}
+
+// webrtc-pc does not specify any situations where this is done, but the JSEP
+// spec does, in some situations due to setConfiguration.
+void PeerConnectionImpl::RestartIceNoRenegotiationNeeded() {
+ // Empty connection.[[LocalIceCredentialsToReplace]], and populate it with
+ // all ICE credentials (ice-ufrag and ice-pwd as defined in section 15.4 of
+ // [RFC5245]) found in connection.[[CurrentLocalDescription]], as well as all
+ // ICE credentials found in connection.[[PendingLocalDescription]].
+ mLocalIceCredentialsToReplace = mJsepSession->GetLocalIceCredentials();
+}
+
+bool PeerConnectionImpl::PluginCrash(uint32_t aPluginID,
+ const nsAString& aPluginName) {
+ // fire an event to the DOM window if this is "ours"
+ if (!AnyCodecHasPluginID(aPluginID)) {
+ return false;
+ }
+
+ CSFLogError(LOGTAG, "%s: Our plugin %llu crashed", __FUNCTION__,
+ static_cast<unsigned long long>(aPluginID));
+
+ RefPtr<Document> doc = mWindow->GetExtantDoc();
+ if (!doc) {
+ NS_WARNING("Couldn't get document for PluginCrashed event!");
+ return true;
+ }
+
+ PluginCrashedEventInit init;
+ init.mPluginID = aPluginID;
+ init.mPluginName = aPluginName;
+ init.mSubmittedCrashReport = false;
+ init.mGmpPlugin = true;
+ init.mBubbles = true;
+ init.mCancelable = true;
+
+ RefPtr<PluginCrashedEvent> event =
+ PluginCrashedEvent::Constructor(doc, u"PluginCrashed"_ns, init);
+
+ event->SetTrusted(true);
+ event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
+
+ nsCOMPtr<nsPIDOMWindowInner> window = mWindow;
+ EventDispatcher::DispatchDOMEvent(window, nullptr, event, nullptr, nullptr);
+
+ return true;
+}
+
+void PeerConnectionImpl::RecordEndOfCallTelemetry() {
+ if (!mCallTelemStarted) {
+ return;
+ }
+ MOZ_RELEASE_ASSERT(!mCallTelemEnded, "Don't end telemetry twice");
+ MOZ_RELEASE_ASSERT(mJsepSession,
+ "Call telemetry only starts after jsep session start");
+ MOZ_RELEASE_ASSERT(mJsepSession->GetNegotiations() > 0,
+ "Call telemetry only starts after first connection");
+
+ // Bitmask used for WEBRTC/LOOP_CALL_TYPE telemetry reporting
+ static const uint32_t kAudioTypeMask = 1;
+ static const uint32_t kVideoTypeMask = 2;
+ static const uint32_t kDataChannelTypeMask = 4;
+
+ // Report end-of-call Telemetry
+ Telemetry::Accumulate(Telemetry::WEBRTC_RENEGOTIATIONS,
+ mJsepSession->GetNegotiations() - 1);
+ Telemetry::Accumulate(Telemetry::WEBRTC_MAX_VIDEO_SEND_TRACK,
+ mMaxSending[SdpMediaSection::MediaType::kVideo]);
+ Telemetry::Accumulate(Telemetry::WEBRTC_MAX_VIDEO_RECEIVE_TRACK,
+ mMaxReceiving[SdpMediaSection::MediaType::kVideo]);
+ Telemetry::Accumulate(Telemetry::WEBRTC_MAX_AUDIO_SEND_TRACK,
+ mMaxSending[SdpMediaSection::MediaType::kAudio]);
+ Telemetry::Accumulate(Telemetry::WEBRTC_MAX_AUDIO_RECEIVE_TRACK,
+ mMaxReceiving[SdpMediaSection::MediaType::kAudio]);
+ // DataChannels appear in both Sending and Receiving
+ Telemetry::Accumulate(Telemetry::WEBRTC_DATACHANNEL_NEGOTIATED,
+ mMaxSending[SdpMediaSection::MediaType::kApplication]);
+ // Enumerated/bitmask: 1 = Audio, 2 = Video, 4 = DataChannel
+ // A/V = 3, A/V/D = 7, etc
+ uint32_t type = 0;
+ if (mMaxSending[SdpMediaSection::MediaType::kAudio] ||
+ mMaxReceiving[SdpMediaSection::MediaType::kAudio]) {
+ type = kAudioTypeMask;
+ }
+ if (mMaxSending[SdpMediaSection::MediaType::kVideo] ||
+ mMaxReceiving[SdpMediaSection::MediaType::kVideo]) {
+ type |= kVideoTypeMask;
+ }
+ if (mMaxSending[SdpMediaSection::MediaType::kApplication]) {
+ type |= kDataChannelTypeMask;
+ }
+ Telemetry::Accumulate(Telemetry::WEBRTC_CALL_TYPE, type);
+
+ MOZ_RELEASE_ASSERT(mWindow);
+ auto found = sCallDurationTimers.find(mWindow->WindowID());
+ if (found != sCallDurationTimers.end()) {
+ found->second.UnregisterConnection((type & kAudioTypeMask) ||
+ (type & kVideoTypeMask));
+ if (found->second.IsStopped()) {
+ sCallDurationTimers.erase(found);
+ }
+ }
+ mCallTelemEnded = true;
+}
+
+DOMMediaStream* PeerConnectionImpl::GetReceiveStream(
+ const std::string& aId) const {
+ nsString wanted = NS_ConvertASCIItoUTF16(aId.c_str());
+ for (auto& stream : mReceiveStreams) {
+ nsString id;
+ stream->GetId(id);
+ if (id == wanted) {
+ return stream;
+ }
+ }
+ return nullptr;
+}
+
+DOMMediaStream* PeerConnectionImpl::CreateReceiveStream(
+ const std::string& aId) {
+ mReceiveStreams.AppendElement(new DOMMediaStream(mWindow));
+ mReceiveStreams.LastElement()->AssignId(NS_ConvertASCIItoUTF16(aId.c_str()));
+ return mReceiveStreams.LastElement();
+}
+
+already_AddRefed<dom::Promise> PeerConnectionImpl::OnSetDescriptionSuccess(
+ dom::RTCSdpType aSdpType, bool aRemote, ErrorResult& aError) {
+ CSFLogDebug(LOGTAG, __FUNCTION__);
+
+ RefPtr<dom::Promise> p = MakePromise(aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ DoSetDescriptionSuccessPostProcessing(aSdpType, aRemote, p);
+
+ return p.forget();
+}
+
+void PeerConnectionImpl::DoSetDescriptionSuccessPostProcessing(
+ dom::RTCSdpType aSdpType, bool aRemote, const RefPtr<dom::Promise>& aP) {
+ // Spec says we queue a task for all the stuff that ends up back in JS
+ GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
+ __func__,
+ [this, self = RefPtr<PeerConnectionImpl>(this), aSdpType, aRemote, aP] {
+ if (IsClosed()) {
+ // Yes, we do not settle the promise here. Yes, this is what the spec
+ // wants.
+ return;
+ }
+
+ MOZ_ASSERT(mUncommittedJsepSession);
+
+ // sRD/sLD needs to be redone in certain circumstances
+ bool needsRedo = HasPendingSetParameters();
+ if (!needsRedo && aRemote && (aSdpType == dom::RTCSdpType::Offer)) {
+ for (auto& transceiver : mTransceivers) {
+ if (!mUncommittedJsepSession->GetTransceiver(
+ transceiver->GetJsepTransceiverId())) {
+ needsRedo = true;
+ break;
+ }
+ }
+ }
+
+ if (needsRedo) {
+ // Spec says to abort, and re-do the sRD!
+ // This happens either when there is a SetParameters call in
+ // flight (that will race against the [[SendEncodings]]
+ // modification caused by sRD(offer)), or when addTrack has been
+ // called while sRD(offer) was in progress.
+ mUncommittedJsepSession.reset(mJsepSession->Clone());
+ JsepSession::Result result;
+ if (aRemote) {
+ mUncommittedJsepSession->SetRemoteDescription(
+ ToJsepSdpType(aSdpType), mRemoteRequestedSDP);
+ } else {
+ mUncommittedJsepSession->SetLocalDescription(
+ ToJsepSdpType(aSdpType), mLocalRequestedSDP);
+ }
+ if (result.mError.isSome()) {
+ // wat
+ nsCString error(
+ "When redoing sRD/sLD because it raced against "
+ "addTrack or setParameters, we encountered a failure that "
+ "did not happen "
+ "the first time. This should never happen. The error was: ");
+ error += mUncommittedJsepSession->GetLastError().c_str();
+ aP->MaybeRejectWithOperationError(error);
+ MOZ_ASSERT(false);
+ } else {
+ DoSetDescriptionSuccessPostProcessing(aSdpType, aRemote, aP);
+ }
+ return;
+ }
+
+ for (auto& transceiver : mTransceivers) {
+ if (!mUncommittedJsepSession->GetTransceiver(
+ transceiver->GetJsepTransceiverId())) {
+ // sLD, or sRD(answer), just make sure the new transceiver is
+ // added, no need to re-do anything.
+ mUncommittedJsepSession->AddTransceiver(
+ transceiver->GetJsepTransceiver());
+ }
+ }
+
+ mJsepSession = std::move(mUncommittedJsepSession);
+
+ auto newSignalingState = GetSignalingState();
+ SyncFromJsep();
+ if (aRemote || aSdpType == dom::RTCSdpType::Pranswer ||
+ aSdpType == dom::RTCSdpType::Answer) {
+ InvalidateLastReturnedParameters();
+ }
+
+ // Section 4.4.1.5 Set the RTCSessionDescription:
+ if (aSdpType == dom::RTCSdpType::Rollback) {
+ // - step 4.5.10, type is rollback
+ RollbackRTCDtlsTransports();
+ } else if (!(aRemote && aSdpType == dom::RTCSdpType::Offer)) {
+ // - step 4.5.9 type is not rollback
+ // - step 4.5.9.1 when remote is false
+ // - step 4.5.9.2.13 when remote is true, type answer or pranswer
+ // More simply: not rollback, and not for remote offers.
+ bool markAsStable = aSdpType == dom::RTCSdpType::Offer &&
+ mSignalingState == RTCSignalingState::Stable;
+ UpdateRTCDtlsTransports(markAsStable);
+ }
+
+ // Did we just apply a local description?
+ if (!aRemote) {
+ // We'd like to handle this in PeerConnectionImpl::UpdateNetworkState.
+ // Unfortunately, if the WiFi switch happens quickly, we never see
+ // that state change. We need to detect the ice restart here and
+ // reset the PeerConnectionImpl's stun addresses so they are
+ // regathered when PeerConnectionImpl::GatherIfReady is called.
+ if (mJsepSession->IsIceRestarting()) {
+ ResetStunAddrsForIceRestart();
+ }
+ EnsureTransports(*mJsepSession);
+ }
+
+ if (mJsepSession->GetState() == kJsepStateStable) {
+ if (aSdpType != dom::RTCSdpType::Rollback) {
+ // We need this initted for UpdateTransports
+ InitializeDataChannel();
+ }
+
+ // If we're rolling back a local offer, we might need to remove some
+ // transports, and stomp some MediaPipeline setup, but nothing further
+ // needs to be done.
+ UpdateTransports(*mJsepSession, mForceIceTcp);
+ if (NS_FAILED(UpdateMediaPipelines())) {
+ CSFLogError(LOGTAG, "Error Updating MediaPipelines");
+ NS_ASSERTION(
+ false,
+ "Error Updating MediaPipelines in OnSetDescriptionSuccess()");
+ aP->MaybeRejectWithOperationError("Error Updating MediaPipelines");
+ }
+
+ if (aSdpType != dom::RTCSdpType::Rollback) {
+ StartIceChecks(*mJsepSession);
+ }
+
+ // Telemetry: record info on the current state of
+ // streams/renegotiations/etc Note: this code gets run on rollbacks as
+ // well!
+
+ // Update the max channels used with each direction for each type
+ uint16_t receiving[SdpMediaSection::kMediaTypes];
+ uint16_t sending[SdpMediaSection::kMediaTypes];
+ mJsepSession->CountTracksAndDatachannels(receiving, sending);
+ for (size_t i = 0; i < SdpMediaSection::kMediaTypes; i++) {
+ if (mMaxReceiving[i] < receiving[i]) {
+ mMaxReceiving[i] = receiving[i];
+ }
+ if (mMaxSending[i] < sending[i]) {
+ mMaxSending[i] = sending[i];
+ }
+ }
+ }
+
+ mPendingRemoteDescription =
+ mJsepSession->GetRemoteDescription(kJsepDescriptionPending);
+ mCurrentRemoteDescription =
+ mJsepSession->GetRemoteDescription(kJsepDescriptionCurrent);
+ mPendingLocalDescription =
+ mJsepSession->GetLocalDescription(kJsepDescriptionPending);
+ mCurrentLocalDescription =
+ mJsepSession->GetLocalDescription(kJsepDescriptionCurrent);
+ mPendingOfferer = mJsepSession->IsPendingOfferer();
+ mCurrentOfferer = mJsepSession->IsCurrentOfferer();
+
+ if (aSdpType == dom::RTCSdpType::Answer) {
+ std::set<std::pair<std::string, std::string>> iceCredentials =
+ mJsepSession->GetLocalIceCredentials();
+ std::vector<std::pair<std::string, std::string>>
+ iceCredentialsNotReplaced;
+ std::set_intersection(mLocalIceCredentialsToReplace.begin(),
+ mLocalIceCredentialsToReplace.end(),
+ iceCredentials.begin(), iceCredentials.end(),
+ std::back_inserter(iceCredentialsNotReplaced));
+
+ if (iceCredentialsNotReplaced.empty()) {
+ mLocalIceCredentialsToReplace.clear();
+ }
+ }
+
+ if (newSignalingState == RTCSignalingState::Stable) {
+ mNegotiationNeeded = false;
+ UpdateNegotiationNeeded();
+ }
+
+ // Spec does not actually tell us to do this, but that is probably a
+ // spec bug.
+ UpdateConnectionState();
+
+ JSErrorResult jrv;
+ if (newSignalingState != mSignalingState) {
+ mSignalingState = newSignalingState;
+ mPCObserver->OnStateChange(PCObserverStateType::SignalingState, jrv);
+ }
+
+ if (aRemote) {
+ dom::RTCRtpReceiver::StreamAssociationChanges changes;
+ for (const auto& transceiver : mTransceivers) {
+ transceiver->Receiver()->UpdateStreams(&changes);
+ }
+
+ for (const auto& receiver : changes.mReceiversToMute) {
+ // This sets the muted state for the recv track and all its clones.
+ receiver->SetTrackMuteFromRemoteSdp();
+ }
+
+ for (const auto& association : changes.mStreamAssociationsRemoved) {
+ RefPtr<DOMMediaStream> stream =
+ GetReceiveStream(association.mStreamId);
+ if (stream && stream->HasTrack(*association.mTrack)) {
+ stream->RemoveTrackInternal(association.mTrack);
+ }
+ }
+
+ // TODO(Bug 1241291): For legacy event, remove eventually
+ std::vector<RefPtr<DOMMediaStream>> newStreams;
+
+ for (const auto& association : changes.mStreamAssociationsAdded) {
+ RefPtr<DOMMediaStream> stream =
+ GetReceiveStream(association.mStreamId);
+ if (!stream) {
+ stream = CreateReceiveStream(association.mStreamId);
+ newStreams.push_back(stream);
+ }
+
+ if (!stream->HasTrack(*association.mTrack)) {
+ stream->AddTrackInternal(association.mTrack);
+ }
+ }
+
+ // Make sure to wait until after we've calculated track changes before
+ // doing this.
+ for (size_t i = 0; i < mTransceivers.Length();) {
+ auto& transceiver = mTransceivers[i];
+ if (transceiver->ShouldRemove()) {
+ mTransceivers[i]->Close();
+ mTransceivers[i]->SetRemovedFromPc();
+ mTransceivers.RemoveElementAt(i);
+ } else {
+ ++i;
+ }
+ }
+
+ for (const auto& trackEvent : changes.mTrackEvents) {
+ dom::Sequence<OwningNonNull<DOMMediaStream>> streams;
+ for (const auto& id : trackEvent.mStreamIds) {
+ RefPtr<DOMMediaStream> stream = GetReceiveStream(id);
+ if (!stream) {
+ MOZ_ASSERT(false);
+ continue;
+ }
+ if (!streams.AppendElement(*stream, fallible)) {
+ // XXX(Bug 1632090) Instead of extending the array 1-by-1 (which
+ // might involve multiple reallocations) and potentially
+ // crashing here, SetCapacity could be called outside the loop
+ // once.
+ mozalloc_handle_oom(0);
+ }
+ }
+ mPCObserver->FireTrackEvent(*trackEvent.mReceiver, streams, jrv);
+ }
+
+ // TODO(Bug 1241291): Legacy event, remove eventually
+ for (const auto& stream : newStreams) {
+ mPCObserver->FireStreamEvent(*stream, jrv);
+ }
+ }
+ aP->MaybeResolveWithUndefined();
+ }));
+}
+
+void PeerConnectionImpl::OnSetDescriptionError() {
+ mUncommittedJsepSession = nullptr;
+}
+
+RTCSignalingState PeerConnectionImpl::GetSignalingState() const {
+ switch (mJsepSession->GetState()) {
+ case kJsepStateStable:
+ return RTCSignalingState::Stable;
+ break;
+ case kJsepStateHaveLocalOffer:
+ return RTCSignalingState::Have_local_offer;
+ break;
+ case kJsepStateHaveRemoteOffer:
+ return RTCSignalingState::Have_remote_offer;
+ break;
+ case kJsepStateHaveLocalPranswer:
+ return RTCSignalingState::Have_local_pranswer;
+ break;
+ case kJsepStateHaveRemotePranswer:
+ return RTCSignalingState::Have_remote_pranswer;
+ break;
+ case kJsepStateClosed:
+ return RTCSignalingState::Closed;
+ break;
+ }
+ MOZ_CRASH("Invalid JSEP state");
+}
+
+bool PeerConnectionImpl::IsClosed() const {
+ return mSignalingState == RTCSignalingState::Closed;
+}
+
+PeerConnectionWrapper::PeerConnectionWrapper(const std::string& handle)
+ : impl_(nullptr) {
+ if (PeerConnectionCtx::isActive()) {
+ impl_ = PeerConnectionCtx::GetInstance()->GetPeerConnection(handle);
+ }
+}
+
+const RefPtr<MediaTransportHandler> PeerConnectionImpl::GetTransportHandler()
+ const {
+ return mTransportHandler;
+}
+
+const std::string& PeerConnectionImpl::GetHandle() { return mHandle; }
+
+const std::string& PeerConnectionImpl::GetName() {
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ return mName;
+}
+
+void PeerConnectionImpl::CandidateReady(const std::string& candidate,
+ const std::string& transportId,
+ const std::string& ufrag) {
+ STAMP_TIMECARD(mTimeCard, "Ice Candidate gathered");
+ PC_AUTO_ENTER_API_CALL_VOID_RETURN(false);
+
+ if (mForceIceTcp && std::string::npos != candidate.find(" UDP ")) {
+ CSFLogWarn(LOGTAG, "Blocking local UDP candidate: %s", candidate.c_str());
+ STAMP_TIMECARD(mTimeCard, "UDP Ice Candidate blocked");
+ return;
+ }
+
+ // One of the very few places we still use level; required by the JSEP API
+ uint16_t level = 0;
+ std::string mid;
+ bool skipped = false;
+
+ if (mUncommittedJsepSession) {
+ // An sLD or sRD is in progress, and while that is the case, we need to add
+ // the candidate to both the current JSEP engine, and the uncommitted JSEP
+ // engine. We ignore errors because the spec says to only take into account
+ // the current/pending local descriptions when determining whether to
+ // surface the candidate to content, which does not take into account any
+ // in-progress sRD/sLD.
+ Unused << mUncommittedJsepSession->AddLocalIceCandidate(
+ candidate, transportId, ufrag, &level, &mid, &skipped);
+ }
+
+ nsresult res = mJsepSession->AddLocalIceCandidate(
+ candidate, transportId, ufrag, &level, &mid, &skipped);
+
+ if (NS_FAILED(res)) {
+ std::string errorString = mJsepSession->GetLastError();
+
+ STAMP_TIMECARD(mTimeCard, "Local Ice Candidate invalid");
+ CSFLogError(LOGTAG,
+ "Failed to incorporate local candidate into SDP:"
+ " res = %u, candidate = %s, transport-id = %s,"
+ " error = %s",
+ static_cast<unsigned>(res), candidate.c_str(),
+ transportId.c_str(), errorString.c_str());
+ return;
+ }
+
+ if (skipped) {
+ STAMP_TIMECARD(mTimeCard, "Local Ice Candidate skipped");
+ CSFLogInfo(LOGTAG,
+ "Skipped adding local candidate %s (transport-id %s) "
+ "to SDP, this typically happens because the m-section "
+ "is bundled, which means it doesn't make sense for it "
+ "to have its own transport-related attributes.",
+ candidate.c_str(), transportId.c_str());
+ return;
+ }
+
+ mPendingLocalDescription =
+ mJsepSession->GetLocalDescription(kJsepDescriptionPending);
+ mCurrentLocalDescription =
+ mJsepSession->GetLocalDescription(kJsepDescriptionCurrent);
+ CSFLogInfo(LOGTAG, "Passing local candidate to content: %s",
+ candidate.c_str());
+ SendLocalIceCandidateToContent(level, mid, candidate, ufrag);
+}
+
+void PeerConnectionImpl::SendLocalIceCandidateToContent(
+ uint16_t level, const std::string& mid, const std::string& candidate,
+ const std::string& ufrag) {
+ STAMP_TIMECARD(mTimeCard, "Send Ice Candidate to content");
+ JSErrorResult rv;
+ mPCObserver->OnIceCandidate(level, ObString(mid.c_str()),
+ ObString(candidate.c_str()),
+ ObString(ufrag.c_str()), rv);
+}
+
+void PeerConnectionImpl::IceConnectionStateChange(
+ dom::RTCIceConnectionState domState) {
+ PC_AUTO_ENTER_API_CALL_VOID_RETURN(false);
+
+ CSFLogDebug(LOGTAG, "%s: %d -> %d", __FUNCTION__,
+ static_cast<int>(mIceConnectionState),
+ static_cast<int>(domState));
+
+ if (domState == mIceConnectionState) {
+ // no work to be done since the states are the same.
+ // this can happen during ICE rollback situations.
+ return;
+ }
+
+ mIceConnectionState = domState;
+
+ // Would be nice if we had a means of converting one of these dom enums
+ // to a string that wasn't almost as much text as this switch statement...
+ switch (mIceConnectionState) {
+ case RTCIceConnectionState::New:
+ STAMP_TIMECARD(mTimeCard, "Ice state: new");
+ break;
+ case RTCIceConnectionState::Checking:
+ // For telemetry
+ mIceStartTime = TimeStamp::Now();
+ STAMP_TIMECARD(mTimeCard, "Ice state: checking");
+ break;
+ case RTCIceConnectionState::Connected:
+ STAMP_TIMECARD(mTimeCard, "Ice state: connected");
+ StartCallTelem();
+ break;
+ case RTCIceConnectionState::Completed:
+ STAMP_TIMECARD(mTimeCard, "Ice state: completed");
+ break;
+ case RTCIceConnectionState::Failed:
+ STAMP_TIMECARD(mTimeCard, "Ice state: failed");
+ break;
+ case RTCIceConnectionState::Disconnected:
+ STAMP_TIMECARD(mTimeCard, "Ice state: disconnected");
+ break;
+ case RTCIceConnectionState::Closed:
+ STAMP_TIMECARD(mTimeCard, "Ice state: closed");
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected mIceConnectionState!");
+ }
+
+ WrappableJSErrorResult rv;
+ mPCObserver->OnStateChange(PCObserverStateType::IceConnectionState, rv);
+ UpdateConnectionState();
+}
+
+void PeerConnectionImpl::OnCandidateFound(const std::string& aTransportId,
+ const CandidateInfo& aCandidateInfo) {
+ if (mStunAddrsRequest && !aCandidateInfo.mMDNSAddress.empty()) {
+ MOZ_ASSERT(!aCandidateInfo.mActualAddress.empty());
+
+ if (mCanRegisterMDNSHostnamesDirectly) {
+ auto itor = mRegisteredMDNSHostnames.find(aCandidateInfo.mMDNSAddress);
+
+ // We'll see the address twice if we're generating both UDP and TCP
+ // candidates.
+ if (itor == mRegisteredMDNSHostnames.end()) {
+ mRegisteredMDNSHostnames.insert(aCandidateInfo.mMDNSAddress);
+ mStunAddrsRequest->SendRegisterMDNSHostname(
+ nsCString(aCandidateInfo.mMDNSAddress.c_str()),
+ nsCString(aCandidateInfo.mActualAddress.c_str()));
+ }
+ } else {
+ mMDNSHostnamesToRegister.emplace(aCandidateInfo.mMDNSAddress,
+ aCandidateInfo.mActualAddress);
+ }
+ }
+
+ if (!aCandidateInfo.mDefaultHostRtp.empty()) {
+ UpdateDefaultCandidate(aCandidateInfo.mDefaultHostRtp,
+ aCandidateInfo.mDefaultPortRtp,
+ aCandidateInfo.mDefaultHostRtcp,
+ aCandidateInfo.mDefaultPortRtcp, aTransportId);
+ }
+ CandidateReady(aCandidateInfo.mCandidate, aTransportId,
+ aCandidateInfo.mUfrag);
+}
+
+void PeerConnectionImpl::IceGatheringStateChange(
+ dom::RTCIceGatheringState state) {
+ PC_AUTO_ENTER_API_CALL_VOID_RETURN(false);
+
+ CSFLogDebug(LOGTAG, "%s %d", __FUNCTION__, static_cast<int>(state));
+ if (mIceGatheringState == state) {
+ return;
+ }
+
+ mIceGatheringState = state;
+
+ // Would be nice if we had a means of converting one of these dom enums
+ // to a string that wasn't almost as much text as this switch statement...
+ switch (mIceGatheringState) {
+ case RTCIceGatheringState::New:
+ STAMP_TIMECARD(mTimeCard, "Ice gathering state: new");
+ break;
+ case RTCIceGatheringState::Gathering:
+ STAMP_TIMECARD(mTimeCard, "Ice gathering state: gathering");
+ break;
+ case RTCIceGatheringState::Complete:
+ STAMP_TIMECARD(mTimeCard, "Ice gathering state: complete");
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected mIceGatheringState!");
+ }
+
+ JSErrorResult rv;
+ mPCObserver->OnStateChange(PCObserverStateType::IceGatheringState, rv);
+}
+
+void PeerConnectionImpl::UpdateDefaultCandidate(
+ const std::string& defaultAddr, uint16_t defaultPort,
+ const std::string& defaultRtcpAddr, uint16_t defaultRtcpPort,
+ const std::string& transportId) {
+ CSFLogDebug(LOGTAG, "%s", __FUNCTION__);
+ mJsepSession->UpdateDefaultCandidate(
+ defaultAddr, defaultPort, defaultRtcpAddr, defaultRtcpPort, transportId);
+ if (mUncommittedJsepSession) {
+ mUncommittedJsepSession->UpdateDefaultCandidate(
+ defaultAddr, defaultPort, defaultRtcpAddr, defaultRtcpPort,
+ transportId);
+ }
+}
+
+static UniquePtr<dom::RTCStatsCollection> GetDataChannelStats_s(
+ const RefPtr<DataChannelConnection>& aDataConnection,
+ const DOMHighResTimeStamp aTimestamp) {
+ UniquePtr<dom::RTCStatsCollection> report(new dom::RTCStatsCollection);
+ if (aDataConnection) {
+ aDataConnection->AppendStatsToReport(report, aTimestamp);
+ }
+ return report;
+}
+
+RefPtr<dom::RTCStatsPromise> PeerConnectionImpl::GetDataChannelStats(
+ const RefPtr<DataChannelConnection>& aDataChannelConnection,
+ const DOMHighResTimeStamp aTimestamp) {
+ // Gather stats from DataChannels
+ return InvokeAsync(
+ GetMainThreadSerialEventTarget(), __func__,
+ [aDataChannelConnection, aTimestamp]() {
+ return dom::RTCStatsPromise::CreateAndResolve(
+ GetDataChannelStats_s(aDataChannelConnection, aTimestamp),
+ __func__);
+ });
+}
+
+void PeerConnectionImpl::CollectConduitTelemetryData() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsTArray<RefPtr<VideoSessionConduit>> conduits;
+ for (const auto& transceiver : mTransceivers) {
+ if (RefPtr<MediaSessionConduit> conduit = transceiver->GetConduit()) {
+ conduit->AsVideoSessionConduit().apply(
+ [&](const auto& aVideo) { conduits.AppendElement(aVideo); });
+ }
+ }
+
+ if (!conduits.IsEmpty() && mCall) {
+ mCall->mCallThread->Dispatch(
+ NS_NewRunnableFunction(__func__, [conduits = std::move(conduits)] {
+ for (const auto& conduit : conduits) {
+ conduit->CollectTelemetryData();
+ }
+ }));
+ }
+}
+
+nsTArray<dom::RTCCodecStats> PeerConnectionImpl::GetCodecStats(
+ DOMHighResTimeStamp aNow) {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsTArray<dom::RTCCodecStats> result;
+
+ struct CodecComparator {
+ bool operator()(const JsepCodecDescription* aA,
+ const JsepCodecDescription* aB) const {
+ return aA->StatsId() < aB->StatsId();
+ }
+ };
+
+ // transportId -> codec; per direction (whether the codecType
+ // shall be "encode", "decode" or absent (if a codec exists in both maps for a
+ // transport)). These do the bookkeeping to ensure codec stats get coalesced
+ // to transport level.
+ std::map<std::string, std::set<JsepCodecDescription*, CodecComparator>>
+ sendCodecMap;
+ std::map<std::string, std::set<JsepCodecDescription*, CodecComparator>>
+ recvCodecMap;
+
+ // Find all JsepCodecDescription instances we want to turn into codec stats.
+ for (const auto& transceiver : mTransceivers) {
+ // TODO: Grab these from the JSEP transceivers instead
+ auto sendCodecs = transceiver->GetNegotiatedSendCodecs();
+ auto recvCodecs = transceiver->GetNegotiatedRecvCodecs();
+
+ const std::string transportId = transceiver->GetTransportId();
+ // This ensures both codec maps have the same size.
+ auto& sendMap = sendCodecMap[transportId];
+ auto& recvMap = recvCodecMap[transportId];
+
+ sendCodecs.apply([&](const auto& aCodecs) {
+ for (const auto& codec : aCodecs) {
+ sendMap.insert(codec.get());
+ }
+ });
+ recvCodecs.apply([&](const auto& aCodecs) {
+ for (const auto& codec : aCodecs) {
+ recvMap.insert(codec.get());
+ }
+ });
+ }
+
+ auto createCodecStat = [&](const JsepCodecDescription* aCodec,
+ const nsString& aTransportId,
+ Maybe<RTCCodecType> aCodecType) {
+ uint16_t pt;
+ {
+ DebugOnly<bool> rv = aCodec->GetPtAsInt(&pt);
+ MOZ_ASSERT(rv);
+ }
+ nsString mimeType;
+ mimeType.AppendPrintf(
+ "%s/%s", aCodec->Type() == SdpMediaSection::kVideo ? "video" : "audio",
+ aCodec->mName.c_str());
+ nsString id = aTransportId;
+ id.Append(u"_");
+ id.Append(aCodec->StatsId());
+
+ dom::RTCCodecStats codec;
+ codec.mId.Construct(std::move(id));
+ codec.mTimestamp.Construct(aNow);
+ codec.mType.Construct(RTCStatsType::Codec);
+ codec.mPayloadType = pt;
+ if (aCodecType) {
+ codec.mCodecType.Construct(*aCodecType);
+ }
+ codec.mTransportId = aTransportId;
+ codec.mMimeType = std::move(mimeType);
+ codec.mClockRate.Construct(aCodec->mClock);
+ if (aCodec->Type() == SdpMediaSection::MediaType::kAudio) {
+ codec.mChannels.Construct(aCodec->mChannels);
+ }
+ if (aCodec->mSdpFmtpLine) {
+ codec.mSdpFmtpLine.Construct(
+ NS_ConvertUTF8toUTF16(aCodec->mSdpFmtpLine->c_str()));
+ }
+
+ result.AppendElement(std::move(codec));
+ };
+
+ // Create codec stats for the gathered codec descriptions, sorted primarily
+ // by transportId, secondarily by payload type (from StatsId()).
+ for (const auto& [transportId, sendCodecs] : sendCodecMap) {
+ const auto& recvCodecs = recvCodecMap[transportId];
+ const nsString tid = NS_ConvertASCIItoUTF16(transportId);
+ AutoTArray<JsepCodecDescription*, 16> bidirectionalCodecs;
+ AutoTArray<JsepCodecDescription*, 16> unidirectionalCodecs;
+ std::set_intersection(sendCodecs.cbegin(), sendCodecs.cend(),
+ recvCodecs.cbegin(), recvCodecs.cend(),
+ MakeBackInserter(bidirectionalCodecs),
+ CodecComparator());
+ std::set_symmetric_difference(sendCodecs.cbegin(), sendCodecs.cend(),
+ recvCodecs.cbegin(), recvCodecs.cend(),
+ MakeBackInserter(unidirectionalCodecs),
+ CodecComparator());
+ for (const auto* codec : bidirectionalCodecs) {
+ createCodecStat(codec, tid, Nothing());
+ }
+ for (const auto* codec : unidirectionalCodecs) {
+ createCodecStat(
+ codec, tid,
+ Some(codec->mDirection == sdp::kSend ? RTCCodecType::Encode
+ : RTCCodecType::Decode));
+ }
+ }
+
+ return result;
+}
+
+RefPtr<dom::RTCStatsReportPromise> PeerConnectionImpl::GetStats(
+ dom::MediaStreamTrack* aSelector, bool aInternalStats) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mFinalStatsQuery) {
+ // This case should be _extremely_ rare; this will basically only happen
+ // when WebrtcGlobalInformation tries to get our stats while we are tearing
+ // down.
+ return mFinalStatsQuery->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [this, self = RefPtr<PeerConnectionImpl>(this)]() {
+ UniquePtr<dom::RTCStatsReportInternal> finalStats =
+ MakeUnique<dom::RTCStatsReportInternal>();
+ // Might not be set if this encountered some error.
+ if (mFinalStats) {
+ *finalStats = *mFinalStats;
+ }
+ return RTCStatsReportPromise::CreateAndResolve(std::move(finalStats),
+ __func__);
+ });
+ }
+
+ nsTArray<RefPtr<dom::RTCStatsPromise>> promises;
+ DOMHighResTimeStamp now = mTimestampMaker.GetNow().ToDom();
+
+ nsTArray<dom::RTCCodecStats> codecStats = GetCodecStats(now);
+ std::set<std::string> transportIds;
+
+ if (!aSelector) {
+ // There might not be any senders/receivers if we're DataChannel only, so we
+ // don't handle the null selector case in the loop below.
+ transportIds.insert("");
+ }
+
+ nsTArray<
+ std::tuple<RTCRtpTransceiver*, RefPtr<RTCStatsPromise::AllPromiseType>>>
+ transceiverStatsPromises;
+ for (const auto& transceiver : mTransceivers) {
+ const bool sendSelected = transceiver->Sender()->HasTrack(aSelector);
+ const bool recvSelected = transceiver->Receiver()->HasTrack(aSelector);
+ if (!sendSelected && !recvSelected) {
+ continue;
+ }
+
+ if (aSelector) {
+ transportIds.insert(transceiver->GetTransportId());
+ }
+
+ nsTArray<RefPtr<RTCStatsPromise>> rtpStreamPromises;
+ // Get all rtp stream stats for the given selector. Then filter away any
+ // codec stat not related to the selector, and assign codec ids to the
+ // stream stats.
+ // Skips the ICE stats; we do our own queries based on |transportIds| to
+ // avoid duplicates
+ if (sendSelected) {
+ rtpStreamPromises.AppendElements(
+ transceiver->Sender()->GetStatsInternal(true));
+ }
+ if (recvSelected) {
+ rtpStreamPromises.AppendElements(
+ transceiver->Receiver()->GetStatsInternal(true));
+ }
+ transceiverStatsPromises.AppendElement(
+ std::make_tuple(transceiver.get(),
+ RTCStatsPromise::All(GetMainThreadSerialEventTarget(),
+ rtpStreamPromises)));
+ }
+
+ promises.AppendElement(RTCRtpTransceiver::ApplyCodecStats(
+ std::move(codecStats), std::move(transceiverStatsPromises)));
+
+ for (const auto& transportId : transportIds) {
+ promises.AppendElement(mTransportHandler->GetIceStats(transportId, now));
+ }
+
+ promises.AppendElement(GetDataChannelStats(mDataConnection, now));
+
+ auto pcStatsCollection = MakeUnique<dom::RTCStatsCollection>();
+ RTCPeerConnectionStats pcStats;
+ pcStats.mTimestamp.Construct(now);
+ pcStats.mType.Construct(RTCStatsType::Peer_connection);
+ pcStats.mId.Construct(NS_ConvertUTF8toUTF16(mHandle.c_str()));
+ pcStats.mDataChannelsOpened.Construct(mDataChannelsOpened);
+ pcStats.mDataChannelsClosed.Construct(mDataChannelsClosed);
+ if (!pcStatsCollection->mPeerConnectionStats.AppendElement(std::move(pcStats),
+ fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ promises.AppendElement(RTCStatsPromise::CreateAndResolve(
+ std::move(pcStatsCollection), __func__));
+
+ // This is what we're going to return; all the stuff in |promises| will be
+ // accumulated here.
+ UniquePtr<dom::RTCStatsReportInternal> report(
+ new dom::RTCStatsReportInternal);
+ report->mPcid = NS_ConvertASCIItoUTF16(mName.c_str());
+ if (mWindow && mWindow->GetBrowsingContext()) {
+ report->mBrowserId = mWindow->GetBrowsingContext()->BrowserId();
+ }
+ report->mConfiguration.Construct(mJsConfiguration);
+ // TODO(bug 1589416): We need to do better here.
+ if (!mIceStartTime.IsNull()) {
+ report->mCallDurationMs.Construct(
+ (TimeStamp::Now() - mIceStartTime).ToMilliseconds());
+ }
+ report->mIceRestarts = mIceRestartCount;
+ report->mIceRollbacks = mIceRollbackCount;
+ report->mClosed = false;
+ report->mTimestamp = now;
+
+ if (aInternalStats && mJsepSession) {
+ for (const auto& candidate : mRawTrickledCandidates) {
+ if (!report->mRawRemoteCandidates.AppendElement(
+ NS_ConvertASCIItoUTF16(candidate.c_str()), fallible)) {
+ // XXX(Bug 1632090) Instead of extending the array 1-by-1 (which might
+ // involve multiple reallocations) and potentially crashing here,
+ // SetCapacity could be called outside the loop once.
+ mozalloc_handle_oom(0);
+ }
+ }
+
+ if (mJsepSession) {
+ // TODO we probably should report Current and Pending SDPs here
+ // separately. Plus the raw SDP we got from JS (mLocalRequestedSDP).
+ // And if it's the offer or answer would also be nice.
+ std::string localDescription =
+ mJsepSession->GetLocalDescription(kJsepDescriptionPendingOrCurrent);
+ std::string remoteDescription =
+ mJsepSession->GetRemoteDescription(kJsepDescriptionPendingOrCurrent);
+ if (!report->mSdpHistory.AppendElements(mSdpHistory, fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ if (mJsepSession->IsPendingOfferer().isSome()) {
+ report->mOfferer.Construct(*mJsepSession->IsPendingOfferer());
+ } else if (mJsepSession->IsCurrentOfferer().isSome()) {
+ report->mOfferer.Construct(*mJsepSession->IsCurrentOfferer());
+ } else {
+ // Silly.
+ report->mOfferer.Construct(false);
+ }
+ }
+ }
+
+ return dom::RTCStatsPromise::All(GetMainThreadSerialEventTarget(), promises)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [report = std::move(report), idGen = mIdGenerator](
+ nsTArray<UniquePtr<dom::RTCStatsCollection>> aStats) mutable {
+ idGen->RewriteIds(std::move(aStats), report.get());
+ return dom::RTCStatsReportPromise::CreateAndResolve(
+ std::move(report), __func__);
+ },
+ [](nsresult rv) {
+ return dom::RTCStatsReportPromise::CreateAndReject(rv, __func__);
+ });
+}
+
+void PeerConnectionImpl::RecordIceRestartStatistics(JsepSdpType type) {
+ switch (type) {
+ case mozilla::kJsepSdpOffer:
+ case mozilla::kJsepSdpPranswer:
+ break;
+ case mozilla::kJsepSdpAnswer:
+ ++mIceRestartCount;
+ break;
+ case mozilla::kJsepSdpRollback:
+ ++mIceRollbackCount;
+ break;
+ }
+}
+
+void PeerConnectionImpl::StoreConfigurationForAboutWebrtc(
+ const dom::RTCConfiguration& aConfig) {
+ // This will only be called once, when the PeerConnection is initially
+ // configured, at least until setConfiguration is implemented
+ // see https://bugzilla.mozilla.org/show_bug.cgi?id=1253706
+ // @TODO bug 1739451 call this from setConfiguration
+ mJsConfiguration.mIceServers.Clear();
+ for (const auto& server : aConfig.mIceServers) {
+ RTCIceServerInternal internal;
+ internal.mCredentialProvided = server.mCredential.WasPassed();
+ internal.mUserNameProvided = server.mUsername.WasPassed();
+ if (server.mUrl.WasPassed()) {
+ if (!internal.mUrls.AppendElement(server.mUrl.Value(), fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ }
+ if (server.mUrls.WasPassed()) {
+ for (const auto& url : server.mUrls.Value().GetAsStringSequence()) {
+ if (!internal.mUrls.AppendElement(url, fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ }
+ }
+ if (!mJsConfiguration.mIceServers.AppendElement(internal, fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ }
+ mJsConfiguration.mSdpSemantics.Reset();
+ if (aConfig.mSdpSemantics.WasPassed()) {
+ mJsConfiguration.mSdpSemantics.Construct(aConfig.mSdpSemantics.Value());
+ }
+
+ mJsConfiguration.mIceTransportPolicy.Reset();
+ mJsConfiguration.mIceTransportPolicy.Construct(aConfig.mIceTransportPolicy);
+ mJsConfiguration.mBundlePolicy.Reset();
+ mJsConfiguration.mBundlePolicy.Construct(aConfig.mBundlePolicy);
+ mJsConfiguration.mPeerIdentityProvided = !aConfig.mPeerIdentity.IsEmpty();
+ mJsConfiguration.mCertificatesProvided = !aConfig.mCertificates.Length();
+}
+
+dom::Sequence<dom::RTCSdpParsingErrorInternal>
+PeerConnectionImpl::GetLastSdpParsingErrors() const {
+ const auto& sdpErrors = mJsepSession->GetLastSdpParsingErrors();
+ dom::Sequence<dom::RTCSdpParsingErrorInternal> domErrors;
+ if (!domErrors.SetCapacity(domErrors.Length(), fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ for (const auto& error : sdpErrors) {
+ mozilla::dom::RTCSdpParsingErrorInternal internal;
+ internal.mLineNumber = error.first;
+ if (!AppendASCIItoUTF16(MakeStringSpan(error.second.c_str()),
+ internal.mError, fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ if (!domErrors.AppendElement(std::move(internal), fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ }
+ return domErrors;
+}
+
+// Telemetry for when calls start
+void PeerConnectionImpl::StartCallTelem() {
+ if (mCallTelemStarted) {
+ return;
+ }
+ MOZ_RELEASE_ASSERT(mWindow);
+ uint64_t windowId = mWindow->WindowID();
+ auto found = sCallDurationTimers.find(windowId);
+ if (found == sCallDurationTimers.end()) {
+ found =
+ sCallDurationTimers.emplace(windowId, PeerConnectionAutoTimer()).first;
+ }
+ found->second.RegisterConnection();
+ mCallTelemStarted = true;
+
+ // Increment session call counter
+ // If we want to track Loop calls independently here, we need two
+ // histograms.
+ //
+ // NOTE: As of bug 1654248 landing we are no longer counting renegotiations
+ // as separate calls. Expect numbers to drop compared to
+ // WEBRTC_CALL_COUNT_2.
+ Telemetry::Accumulate(Telemetry::WEBRTC_CALL_COUNT_3, 1);
+}
+
+void PeerConnectionImpl::StunAddrsHandler::OnMDNSQueryComplete(
+ const nsCString& hostname, const Maybe<nsCString>& address) {
+ MOZ_ASSERT(NS_IsMainThread());
+ PeerConnectionWrapper pcw(mPcHandle);
+ if (!pcw.impl()) {
+ return;
+ }
+ auto itor = pcw.impl()->mQueriedMDNSHostnames.find(hostname.BeginReading());
+ if (itor != pcw.impl()->mQueriedMDNSHostnames.end()) {
+ if (address) {
+ for (auto& cand : itor->second) {
+ // Replace obfuscated address with actual address
+ std::string obfuscatedAddr = cand.mTokenizedCandidate[4];
+ cand.mTokenizedCandidate[4] = address->BeginReading();
+ std::ostringstream o;
+ for (size_t i = 0; i < cand.mTokenizedCandidate.size(); ++i) {
+ o << cand.mTokenizedCandidate[i];
+ if (i + 1 != cand.mTokenizedCandidate.size()) {
+ o << " ";
+ }
+ }
+ std::string mungedCandidate = o.str();
+ pcw.impl()->StampTimecard("Done looking up mDNS name");
+ pcw.impl()->mTransportHandler->AddIceCandidate(
+ cand.mTransportId, mungedCandidate, cand.mUfrag, obfuscatedAddr);
+ }
+ } else {
+ pcw.impl()->StampTimecard("Failed looking up mDNS name");
+ }
+ pcw.impl()->mQueriedMDNSHostnames.erase(itor);
+ }
+}
+
+void PeerConnectionImpl::StunAddrsHandler::OnStunAddrsAvailable(
+ const mozilla::net::NrIceStunAddrArray& addrs) {
+ CSFLogInfo(LOGTAG, "%s: receiving (%d) stun addrs", __FUNCTION__,
+ (int)addrs.Length());
+ PeerConnectionWrapper pcw(mPcHandle);
+ if (!pcw.impl()) {
+ return;
+ }
+ pcw.impl()->mStunAddrs = addrs.Clone();
+ pcw.impl()->mLocalAddrsRequestState = STUN_ADDR_REQUEST_COMPLETE;
+ pcw.impl()->FlushIceCtxOperationQueueIfReady();
+ // If parent process returns 0 STUN addresses, change ICE connection
+ // state to failed.
+ if (!pcw.impl()->mStunAddrs.Length()) {
+ pcw.impl()->IceConnectionStateChange(dom::RTCIceConnectionState::Failed);
+ }
+}
+
+void PeerConnectionImpl::InitLocalAddrs() {
+ if (mLocalAddrsRequestState == STUN_ADDR_REQUEST_PENDING) {
+ return;
+ }
+ if (mStunAddrsRequest) {
+ mLocalAddrsRequestState = STUN_ADDR_REQUEST_PENDING;
+ mStunAddrsRequest->SendGetStunAddrs();
+ } else {
+ mLocalAddrsRequestState = STUN_ADDR_REQUEST_COMPLETE;
+ }
+}
+
+bool PeerConnectionImpl::ShouldForceProxy() const {
+ if (Preferences::GetBool("media.peerconnection.ice.proxy_only", false)) {
+ return true;
+ }
+
+ bool isPBM = false;
+ // This complicated null check is being extra conservative to avoid
+ // introducing crashes. It may not be needed.
+ if (mWindow && mWindow->GetExtantDoc() &&
+ mWindow->GetExtantDoc()->GetPrincipal() &&
+ mWindow->GetExtantDoc()
+ ->GetPrincipal()
+ ->OriginAttributesRef()
+ .mPrivateBrowsingId > 0) {
+ isPBM = true;
+ }
+
+ if (isPBM && Preferences::GetBool(
+ "media.peerconnection.ice.proxy_only_if_pbmode", false)) {
+ return true;
+ }
+
+ if (!Preferences::GetBool(
+ "media.peerconnection.ice.proxy_only_if_behind_proxy", false)) {
+ return false;
+ }
+
+ // Ok, we're supposed to be proxy_only, but only if a proxy is configured.
+ // Let's just see if the document was loaded via a proxy.
+
+ nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal = GetChannel();
+ if (!httpChannelInternal) {
+ return false;
+ }
+
+ bool proxyUsed = false;
+ Unused << httpChannelInternal->GetIsProxyUsed(&proxyUsed);
+ return proxyUsed;
+}
+
+void PeerConnectionImpl::EnsureTransports(const JsepSession& aSession) {
+ mJsepSession->ForEachTransceiver([this,
+ self = RefPtr<PeerConnectionImpl>(this)](
+ const JsepTransceiver& transceiver) {
+ if (transceiver.HasOwnTransport()) {
+ mTransportHandler->EnsureProvisionalTransport(
+ transceiver.mTransport.mTransportId,
+ transceiver.mTransport.mLocalUfrag, transceiver.mTransport.mLocalPwd,
+ transceiver.mTransport.mComponents);
+ }
+ });
+
+ GatherIfReady();
+}
+
+void PeerConnectionImpl::UpdateRTCDtlsTransports(bool aMarkAsStable) {
+ mJsepSession->ForEachTransceiver(
+ [this, self = RefPtr<PeerConnectionImpl>(this)](
+ const JsepTransceiver& jsepTransceiver) {
+ std::string transportId = jsepTransceiver.mTransport.mTransportId;
+ if (transportId.empty()) {
+ return;
+ }
+ if (!mTransportIdToRTCDtlsTransport.count(transportId)) {
+ mTransportIdToRTCDtlsTransport.emplace(
+ transportId, new RTCDtlsTransport(GetParentObject()));
+ }
+ });
+
+ for (auto& transceiver : mTransceivers) {
+ std::string transportId = transceiver->GetTransportId();
+ if (transportId.empty()) {
+ continue;
+ }
+ if (mTransportIdToRTCDtlsTransport.count(transportId)) {
+ transceiver->SetDtlsTransport(mTransportIdToRTCDtlsTransport[transportId],
+ aMarkAsStable);
+ }
+ }
+
+ // Spec says we only update the RTCSctpTransport when negotiation completes
+}
+
+void PeerConnectionImpl::RollbackRTCDtlsTransports() {
+ for (auto& transceiver : mTransceivers) {
+ transceiver->RollbackToStableDtlsTransport();
+ }
+}
+
+void PeerConnectionImpl::RemoveRTCDtlsTransportsExcept(
+ const std::set<std::string>& aTransportIds) {
+ for (auto iter = mTransportIdToRTCDtlsTransport.begin();
+ iter != mTransportIdToRTCDtlsTransport.end();) {
+ if (!aTransportIds.count(iter->first)) {
+ iter = mTransportIdToRTCDtlsTransport.erase(iter);
+ } else {
+ ++iter;
+ }
+ }
+}
+
+nsresult PeerConnectionImpl::UpdateTransports(const JsepSession& aSession,
+ const bool forceIceTcp) {
+ std::set<std::string> finalTransports;
+ Maybe<std::string> sctpTransport;
+ mJsepSession->ForEachTransceiver(
+ [&, this, self = RefPtr<PeerConnectionImpl>(this)](
+ const JsepTransceiver& transceiver) {
+ if (transceiver.GetMediaType() == SdpMediaSection::kApplication &&
+ transceiver.HasTransport()) {
+ sctpTransport = Some(transceiver.mTransport.mTransportId);
+ }
+
+ if (transceiver.HasOwnTransport()) {
+ finalTransports.insert(transceiver.mTransport.mTransportId);
+ UpdateTransport(transceiver, forceIceTcp);
+ }
+ });
+
+ // clean up the unused RTCDtlsTransports
+ RemoveRTCDtlsTransportsExcept(finalTransports);
+
+ mTransportHandler->RemoveTransportsExcept(finalTransports);
+
+ for (const auto& transceiverImpl : mTransceivers) {
+ transceiverImpl->UpdateTransport();
+ }
+
+ if (sctpTransport.isSome()) {
+ auto it = mTransportIdToRTCDtlsTransport.find(*sctpTransport);
+ if (it == mTransportIdToRTCDtlsTransport.end()) {
+ // What?
+ MOZ_ASSERT(false);
+ return NS_ERROR_FAILURE;
+ }
+ if (!mDataConnection) {
+ // What?
+ MOZ_ASSERT(false);
+ return NS_ERROR_FAILURE;
+ }
+ RefPtr<RTCDtlsTransport> dtlsTransport = it->second;
+ // Why on earth does the spec use a floating point for this?
+ double maxMessageSize =
+ static_cast<double>(mDataConnection->GetMaxMessageSize());
+ Nullable<uint16_t> maxChannels;
+
+ if (!mSctpTransport) {
+ mSctpTransport = new RTCSctpTransport(GetParentObject(), *dtlsTransport,
+ maxMessageSize, maxChannels);
+ } else {
+ mSctpTransport->SetTransport(*dtlsTransport);
+ mSctpTransport->SetMaxMessageSize(maxMessageSize);
+ mSctpTransport->SetMaxChannels(maxChannels);
+ }
+ } else {
+ mSctpTransport = nullptr;
+ }
+
+ return NS_OK;
+}
+
+void PeerConnectionImpl::UpdateTransport(const JsepTransceiver& aTransceiver,
+ bool aForceIceTcp) {
+ std::string ufrag;
+ std::string pwd;
+ std::vector<std::string> candidates;
+ size_t components = 0;
+
+ const JsepTransport& transport = aTransceiver.mTransport;
+ unsigned level = aTransceiver.GetLevel();
+
+ CSFLogDebug(LOGTAG, "ACTIVATING TRANSPORT! - PC %s: level=%u components=%u",
+ mHandle.c_str(), (unsigned)level,
+ (unsigned)transport.mComponents);
+
+ ufrag = transport.mIce->GetUfrag();
+ pwd = transport.mIce->GetPassword();
+ candidates = transport.mIce->GetCandidates();
+ components = transport.mComponents;
+ if (aForceIceTcp) {
+ candidates.erase(
+ std::remove_if(candidates.begin(), candidates.end(),
+ [](const std::string& s) {
+ return s.find(" UDP ") != std::string::npos ||
+ s.find(" udp ") != std::string::npos;
+ }),
+ candidates.end());
+ }
+
+ nsTArray<uint8_t> keyDer;
+ nsTArray<uint8_t> certDer;
+ nsresult rv = Identity()->Serialize(&keyDer, &certDer);
+ if (NS_FAILED(rv)) {
+ CSFLogError(LOGTAG, "%s: Failed to serialize DTLS identity: %d",
+ __FUNCTION__, (int)rv);
+ return;
+ }
+
+ DtlsDigestList digests;
+ for (const auto& fingerprint :
+ transport.mDtls->GetFingerprints().mFingerprints) {
+ std::ostringstream ss;
+ ss << fingerprint.hashFunc;
+ digests.emplace_back(ss.str(), fingerprint.fingerprint);
+ }
+
+ mTransportHandler->ActivateTransport(
+ transport.mTransportId, transport.mLocalUfrag, transport.mLocalPwd,
+ components, ufrag, pwd, keyDer, certDer, Identity()->auth_type(),
+ transport.mDtls->GetRole() == JsepDtlsTransport::kJsepDtlsClient, digests,
+ PrivacyRequested());
+
+ for (auto& candidate : candidates) {
+ AddIceCandidate("candidate:" + candidate, transport.mTransportId, ufrag);
+ }
+}
+
+nsresult PeerConnectionImpl::UpdateMediaPipelines() {
+ for (RefPtr<RTCRtpTransceiver>& transceiver : mTransceivers) {
+ transceiver->ResetSync();
+ }
+
+ for (RefPtr<RTCRtpTransceiver>& transceiver : mTransceivers) {
+ if (!transceiver->IsVideo()) {
+ nsresult rv = transceiver->SyncWithMatchingVideoConduits(mTransceivers);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ transceiver->UpdatePrincipalPrivacy(PrivacyRequested()
+ ? PrincipalPrivacy::Private
+ : PrincipalPrivacy::NonPrivate);
+
+ nsresult rv = transceiver->UpdateConduit();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ return NS_OK;
+}
+
+void PeerConnectionImpl::StartIceChecks(const JsepSession& aSession) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mCanRegisterMDNSHostnamesDirectly) {
+ for (auto& pair : mMDNSHostnamesToRegister) {
+ mRegisteredMDNSHostnames.insert(pair.first);
+ mStunAddrsRequest->SendRegisterMDNSHostname(
+ nsCString(pair.first.c_str()), nsCString(pair.second.c_str()));
+ }
+ mMDNSHostnamesToRegister.clear();
+ mCanRegisterMDNSHostnamesDirectly = true;
+ }
+
+ std::vector<std::string> attributes;
+ if (aSession.RemoteIsIceLite()) {
+ attributes.push_back("ice-lite");
+ }
+
+ if (!aSession.GetIceOptions().empty()) {
+ attributes.push_back("ice-options:");
+ for (const auto& option : aSession.GetIceOptions()) {
+ attributes.back() += option + ' ';
+ }
+ }
+
+ nsCOMPtr<nsIRunnable> runnable(
+ WrapRunnable(mTransportHandler, &MediaTransportHandler::StartIceChecks,
+ aSession.IsIceControlling(), attributes));
+
+ PerformOrEnqueueIceCtxOperation(runnable);
+}
+
+bool PeerConnectionImpl::GetPrefDefaultAddressOnly() const {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ uint64_t winId = mWindow->WindowID();
+
+ bool default_address_only = Preferences::GetBool(
+ "media.peerconnection.ice.default_address_only", false);
+ default_address_only |=
+ !MediaManager::Get()->IsActivelyCapturingOrHasAPermission(winId);
+ return default_address_only;
+}
+
+bool PeerConnectionImpl::GetPrefObfuscateHostAddresses() const {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ uint64_t winId = mWindow->WindowID();
+
+ bool obfuscate_host_addresses = Preferences::GetBool(
+ "media.peerconnection.ice.obfuscate_host_addresses", false);
+ obfuscate_host_addresses &=
+ !MediaManager::Get()->IsActivelyCapturingOrHasAPermission(winId);
+ obfuscate_host_addresses &= !PeerConnectionImpl::HostnameInPref(
+ "media.peerconnection.ice.obfuscate_host_addresses.blocklist", mHostname);
+ obfuscate_host_addresses &= XRE_IsContentProcess();
+
+ return obfuscate_host_addresses;
+}
+
+PeerConnectionImpl::SignalHandler::SignalHandler(PeerConnectionImpl* aPc,
+ MediaTransportHandler* aSource)
+ : mHandle(aPc->GetHandle()),
+ mSource(aSource),
+ mSTSThread(aPc->GetSTSThread()) {
+ ConnectSignals();
+}
+
+PeerConnectionImpl::SignalHandler::~SignalHandler() {
+ ASSERT_ON_THREAD(mSTSThread);
+}
+
+void PeerConnectionImpl::SignalHandler::ConnectSignals() {
+ mSource->SignalGatheringStateChange.connect(
+ this, &PeerConnectionImpl::SignalHandler::IceGatheringStateChange_s);
+ mSource->SignalConnectionStateChange.connect(
+ this, &PeerConnectionImpl::SignalHandler::IceConnectionStateChange_s);
+ mSource->SignalCandidate.connect(
+ this, &PeerConnectionImpl::SignalHandler::OnCandidateFound_s);
+ mSource->SignalAlpnNegotiated.connect(
+ this, &PeerConnectionImpl::SignalHandler::AlpnNegotiated_s);
+ mSource->SignalStateChange.connect(
+ this, &PeerConnectionImpl::SignalHandler::ConnectionStateChange_s);
+ mSource->SignalRtcpStateChange.connect(
+ this, &PeerConnectionImpl::SignalHandler::ConnectionStateChange_s);
+}
+
+void PeerConnectionImpl::AddIceCandidate(const std::string& aCandidate,
+ const std::string& aTransportId,
+ const std::string& aUfrag) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!aTransportId.empty());
+
+ bool obfuscate_host_addresses = Preferences::GetBool(
+ "media.peerconnection.ice.obfuscate_host_addresses", false);
+
+ if (obfuscate_host_addresses && !RelayOnly()) {
+ std::vector<std::string> tokens;
+ TokenizeCandidate(aCandidate, tokens);
+
+ if (tokens.size() > 4) {
+ std::string addr = tokens[4];
+
+ // Check for address ending with .local
+ size_t nPeriods = std::count(addr.begin(), addr.end(), '.');
+ size_t dotLocalLength = 6; // length of ".local"
+
+ if (nPeriods == 1 &&
+ addr.rfind(".local") + dotLocalLength == addr.length()) {
+ if (mStunAddrsRequest) {
+ PendingIceCandidate cand;
+ cand.mTokenizedCandidate = std::move(tokens);
+ cand.mTransportId = aTransportId;
+ cand.mUfrag = aUfrag;
+ mQueriedMDNSHostnames[addr].push_back(cand);
+
+ GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
+ "PeerConnectionImpl::SendQueryMDNSHostname",
+ [self = RefPtr<PeerConnectionImpl>(this), addr]() mutable {
+ if (self->mStunAddrsRequest) {
+ self->StampTimecard("Look up mDNS name");
+ self->mStunAddrsRequest->SendQueryMDNSHostname(
+ nsCString(nsAutoCString(addr.c_str())));
+ }
+ NS_ReleaseOnMainThread(
+ "PeerConnectionImpl::SendQueryMDNSHostname", self.forget());
+ }));
+ }
+ // TODO: Bug 1535690, we don't want to tell the ICE context that remote
+ // trickle is done if we are waiting to resolve a mDNS candidate.
+ return;
+ }
+ }
+ }
+
+ mTransportHandler->AddIceCandidate(aTransportId, aCandidate, aUfrag, "");
+}
+
+void PeerConnectionImpl::UpdateNetworkState(bool online) {
+ if (mTransportHandler) {
+ mTransportHandler->UpdateNetworkState(online);
+ }
+}
+
+void PeerConnectionImpl::FlushIceCtxOperationQueueIfReady() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (IsIceCtxReady()) {
+ for (auto& queuedIceCtxOperation : mQueuedIceCtxOperations) {
+ queuedIceCtxOperation->Run();
+ }
+ mQueuedIceCtxOperations.clear();
+ }
+}
+
+void PeerConnectionImpl::PerformOrEnqueueIceCtxOperation(
+ nsIRunnable* runnable) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (IsIceCtxReady()) {
+ runnable->Run();
+ } else {
+ mQueuedIceCtxOperations.push_back(runnable);
+ }
+}
+
+void PeerConnectionImpl::GatherIfReady() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Init local addrs here so that if we re-gather after an ICE restart
+ // resulting from changing WiFi networks, we get new local addrs.
+ // Otherwise, we would reuse the addrs from the original WiFi network
+ // and the ICE restart will fail.
+ if (!mStunAddrs.Length()) {
+ InitLocalAddrs();
+ }
+
+ // If we had previously queued gathering or ICE start, unqueue them
+ mQueuedIceCtxOperations.clear();
+ nsCOMPtr<nsIRunnable> runnable(WrapRunnable(
+ RefPtr<PeerConnectionImpl>(this), &PeerConnectionImpl::EnsureIceGathering,
+ GetPrefDefaultAddressOnly(), GetPrefObfuscateHostAddresses()));
+
+ PerformOrEnqueueIceCtxOperation(runnable);
+}
+
+already_AddRefed<nsIHttpChannelInternal> PeerConnectionImpl::GetChannel()
+ const {
+ Document* doc = mWindow->GetExtantDoc();
+ if (NS_WARN_IF(!doc)) {
+ NS_WARNING("Unable to get document from window");
+ return nullptr;
+ }
+
+ if (!doc->GetDocumentURI()->SchemeIs("file")) {
+ nsIChannel* channel = doc->GetChannel();
+ if (!channel) {
+ NS_WARNING("Unable to get channel from document");
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal =
+ do_QueryInterface(channel);
+ if (NS_WARN_IF(!httpChannelInternal)) {
+ CSFLogInfo(LOGTAG, "%s: Document does not have an HTTP channel",
+ __FUNCTION__);
+ return nullptr;
+ }
+ return httpChannelInternal.forget();
+ }
+ return nullptr;
+}
+
+nsresult PeerConnectionImpl::SetTargetForDefaultLocalAddressLookup() {
+ nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal = GetChannel();
+ if (!httpChannelInternal) {
+ return NS_OK;
+ }
+
+ nsCString remoteIp;
+ nsresult rv = httpChannelInternal->GetRemoteAddress(remoteIp);
+ if (NS_FAILED(rv) || remoteIp.IsEmpty()) {
+ CSFLogError(LOGTAG, "%s: Failed to get remote IP address: %d", __FUNCTION__,
+ (int)rv);
+ return rv;
+ }
+
+ int32_t remotePort;
+ rv = httpChannelInternal->GetRemotePort(&remotePort);
+ if (NS_FAILED(rv)) {
+ CSFLogError(LOGTAG, "%s: Failed to get remote port number: %d",
+ __FUNCTION__, (int)rv);
+ return rv;
+ }
+
+ mTransportHandler->SetTargetForDefaultLocalAddressLookup(remoteIp.get(),
+ remotePort);
+
+ return NS_OK;
+}
+
+void PeerConnectionImpl::EnsureIceGathering(bool aDefaultRouteOnly,
+ bool aObfuscateHostAddresses) {
+ if (!mTargetForDefaultLocalAddressLookupIsSet) {
+ nsresult rv = SetTargetForDefaultLocalAddressLookup();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Unable to set target for default local address lookup");
+ }
+ mTargetForDefaultLocalAddressLookupIsSet = true;
+ }
+
+ // Make sure we don't call StartIceGathering if we're in e10s mode
+ // and we received no STUN addresses from the parent process. In the
+ // absence of previously provided STUN addresses, StartIceGathering will
+ // attempt to gather them (as in non-e10s mode), and this will cause a
+ // sandboxing exception in e10s mode.
+ if (!mStunAddrs.Length() && XRE_IsContentProcess()) {
+ CSFLogInfo(LOGTAG, "%s: No STUN addresses returned from parent process",
+ __FUNCTION__);
+ return;
+ }
+
+ mTransportHandler->StartIceGathering(aDefaultRouteOnly,
+ aObfuscateHostAddresses, mStunAddrs);
+}
+
+already_AddRefed<dom::RTCRtpTransceiver> PeerConnectionImpl::CreateTransceiver(
+ const std::string& aId, bool aIsVideo, const RTCRtpTransceiverInit& aInit,
+ dom::MediaStreamTrack* aSendTrack, bool aAddTrackMagic, ErrorResult& aRv) {
+ PeerConnectionCtx* ctx = PeerConnectionCtx::GetInstance();
+ if (!mCall) {
+ mCall = WebrtcCallWrapper::Create(
+ GetTimestampMaker(),
+ media::ShutdownBlockingTicket::Create(
+ u"WebrtcCallWrapper shutdown blocker"_ns,
+ NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__),
+ ctx->GetSharedWebrtcState());
+ }
+
+ if (aAddTrackMagic) {
+ mJsepSession->ApplyToTransceiver(aId, [](JsepTransceiver& aTransceiver) {
+ aTransceiver.SetAddTrackMagic();
+ });
+ }
+
+ RefPtr<RTCRtpTransceiver> transceiver = new RTCRtpTransceiver(
+ mWindow, PrivacyRequested(), this, mTransportHandler, mJsepSession.get(),
+ aId, aIsVideo, mSTSThread.get(), aSendTrack, mCall.get(), mIdGenerator);
+
+ transceiver->Init(aInit, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ if (aSendTrack) {
+ // implement checking for peerIdentity (where failure == black/silence)
+ Document* doc = mWindow->GetExtantDoc();
+ if (doc) {
+ transceiver->Sender()->GetPipeline()->UpdateSinkIdentity(
+ doc->NodePrincipal(), GetPeerIdentity());
+ } else {
+ MOZ_CRASH();
+ aRv = NS_ERROR_FAILURE;
+ return nullptr; // Don't remove this till we know it's safe.
+ }
+ }
+
+ return transceiver.forget();
+}
+
+std::string PeerConnectionImpl::GetTransportIdMatchingSendTrack(
+ const dom::MediaStreamTrack& aTrack) const {
+ for (const RefPtr<RTCRtpTransceiver>& transceiver : mTransceivers) {
+ if (transceiver->Sender()->HasTrack(&aTrack)) {
+ return transceiver->GetTransportId();
+ }
+ }
+ return std::string();
+}
+
+void PeerConnectionImpl::SignalHandler::IceGatheringStateChange_s(
+ dom::RTCIceGatheringState aState) {
+ ASSERT_ON_THREAD(mSTSThread);
+
+ GetMainThreadSerialEventTarget()->Dispatch(
+ NS_NewRunnableFunction(__func__,
+ [handle = mHandle, aState] {
+ PeerConnectionWrapper wrapper(handle);
+ if (wrapper.impl()) {
+ wrapper.impl()->IceGatheringStateChange(
+ aState);
+ }
+ }),
+ NS_DISPATCH_NORMAL);
+}
+
+void PeerConnectionImpl::SignalHandler::IceConnectionStateChange_s(
+ dom::RTCIceConnectionState aState) {
+ ASSERT_ON_THREAD(mSTSThread);
+
+ GetMainThreadSerialEventTarget()->Dispatch(
+ NS_NewRunnableFunction(__func__,
+ [handle = mHandle, aState] {
+ PeerConnectionWrapper wrapper(handle);
+ if (wrapper.impl()) {
+ wrapper.impl()->IceConnectionStateChange(
+ aState);
+ }
+ }),
+ NS_DISPATCH_NORMAL);
+}
+
+void PeerConnectionImpl::SignalHandler::OnCandidateFound_s(
+ const std::string& aTransportId, const CandidateInfo& aCandidateInfo) {
+ ASSERT_ON_THREAD(mSTSThread);
+ CSFLogDebug(LOGTAG, "%s: %s", __FUNCTION__, aTransportId.c_str());
+
+ MOZ_ASSERT(!aCandidateInfo.mUfrag.empty());
+
+ GetMainThreadSerialEventTarget()->Dispatch(
+ NS_NewRunnableFunction(__func__,
+ [handle = mHandle, aTransportId, aCandidateInfo] {
+ PeerConnectionWrapper wrapper(handle);
+ if (wrapper.impl()) {
+ wrapper.impl()->OnCandidateFound(
+ aTransportId, aCandidateInfo);
+ }
+ }),
+ NS_DISPATCH_NORMAL);
+}
+
+void PeerConnectionImpl::SignalHandler::AlpnNegotiated_s(
+ const std::string& aAlpn, bool aPrivacyRequested) {
+ MOZ_DIAGNOSTIC_ASSERT((aAlpn == "c-webrtc") == aPrivacyRequested);
+ GetMainThreadSerialEventTarget()->Dispatch(
+ NS_NewRunnableFunction(__func__,
+ [handle = mHandle, aPrivacyRequested] {
+ PeerConnectionWrapper wrapper(handle);
+ if (wrapper.impl()) {
+ wrapper.impl()->OnAlpnNegotiated(
+ aPrivacyRequested);
+ }
+ }),
+ NS_DISPATCH_NORMAL);
+}
+
+void PeerConnectionImpl::SignalHandler::ConnectionStateChange_s(
+ const std::string& aTransportId, TransportLayer::State aState) {
+ GetMainThreadSerialEventTarget()->Dispatch(
+ NS_NewRunnableFunction(__func__,
+ [handle = mHandle, aTransportId, aState] {
+ PeerConnectionWrapper wrapper(handle);
+ if (wrapper.impl()) {
+ wrapper.impl()->OnDtlsStateChange(aTransportId,
+ aState);
+ }
+ }),
+ NS_DISPATCH_NORMAL);
+}
+
+/**
+ * Tells you if any local track is isolated to a specific peer identity.
+ * Obviously, we want all the tracks to be isolated equally so that they can
+ * all be sent or not. We check once when we are setting a local description
+ * and that determines if we flip the "privacy requested" bit on. Once the bit
+ * is on, all media originating from this peer connection is isolated.
+ *
+ * @returns true if any track has a peerIdentity set on it
+ */
+bool PeerConnectionImpl::AnyLocalTrackHasPeerIdentity() const {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ for (const RefPtr<RTCRtpTransceiver>& transceiver : mTransceivers) {
+ if (transceiver->Sender()->GetTrack() &&
+ transceiver->Sender()->GetTrack()->GetPeerIdentity()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool PeerConnectionImpl::AnyCodecHasPluginID(uint64_t aPluginID) {
+ for (RefPtr<RTCRtpTransceiver>& transceiver : mTransceivers) {
+ if (transceiver->ConduitHasPluginID(aPluginID)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+std::unique_ptr<NrSocketProxyConfig> PeerConnectionImpl::GetProxyConfig()
+ const {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mForceProxy &&
+ Preferences::GetBool("media.peerconnection.disable_http_proxy", false)) {
+ return nullptr;
+ }
+
+ nsCString alpn = "webrtc,c-webrtc"_ns;
+ auto* browserChild = BrowserChild::GetFrom(mWindow);
+ if (!browserChild) {
+ // Android doesn't have browser child apparently...
+ return nullptr;
+ }
+
+ Document* doc = mWindow->GetExtantDoc();
+ if (NS_WARN_IF(!doc)) {
+ NS_WARNING("Unable to get document from window");
+ return nullptr;
+ }
+
+ TabId id = browserChild->GetTabId();
+ nsCOMPtr<nsILoadInfo> loadInfo =
+ new net::LoadInfo(doc->NodePrincipal(), doc->NodePrincipal(), doc, 0,
+ nsIContentPolicy::TYPE_INVALID);
+
+ Maybe<net::LoadInfoArgs> loadInfoArgs;
+ MOZ_ALWAYS_SUCCEEDS(
+ mozilla::ipc::LoadInfoToLoadInfoArgs(loadInfo, &loadInfoArgs));
+ return std::unique_ptr<NrSocketProxyConfig>(new NrSocketProxyConfig(
+ net::WebrtcProxyConfig(id, alpn, *loadInfoArgs, mForceProxy)));
+}
+
+std::map<uint64_t, PeerConnectionAutoTimer>
+ PeerConnectionImpl::sCallDurationTimers;
+} // namespace mozilla
diff --git a/dom/media/webrtc/jsapi/PeerConnectionImpl.h b/dom/media/webrtc/jsapi/PeerConnectionImpl.h
new file mode 100644
index 0000000000..66af1aa0e9
--- /dev/null
+++ b/dom/media/webrtc/jsapi/PeerConnectionImpl.h
@@ -0,0 +1,969 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _PEER_CONNECTION_IMPL_H_
+#define _PEER_CONNECTION_IMPL_H_
+
+#include <string>
+#include <vector>
+#include <map>
+#include <cmath>
+
+#include "prlock.h"
+#include "mozilla/RefPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsPIDOMWindow.h"
+#include "nsIUUIDGenerator.h"
+#include "nsIThread.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/Attributes.h"
+
+// Work around nasty macro in webrtc/voice_engine/voice_engine_defines.h
+#ifdef GetLastError
+# undef GetLastError
+#endif
+
+#include "jsep/JsepSession.h"
+#include "jsep/JsepSessionImpl.h"
+#include "sdp/SdpMediaSection.h"
+
+#include "mozilla/ErrorResult.h"
+#include "jsapi/PacketDumper.h"
+#include "mozilla/dom/RTCPeerConnectionBinding.h" // mozPacketDumpType, maybe move?
+#include "mozilla/dom/PeerConnectionImplBinding.h" // ChainedOperation
+#include "mozilla/dom/RTCRtpCapabilitiesBinding.h"
+#include "mozilla/dom/RTCRtpTransceiverBinding.h"
+#include "mozilla/dom/RTCConfigurationBinding.h"
+#include "PrincipalChangeObserver.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+
+#include "mozilla/TimeStamp.h"
+#include "mozilla/net/DataChannel.h"
+#include "VideoUtils.h"
+#include "VideoSegment.h"
+#include "mozilla/dom/RTCStatsReportBinding.h"
+#include "mozilla/PeerIdentity.h"
+#include "RTCStatsIdGenerator.h"
+#include "RTCStatsReport.h"
+
+#include "mozilla/net/StunAddrsRequestChild.h"
+#include "MediaTransportHandler.h"
+#include "nsIHttpChannelInternal.h"
+#include "RTCDtlsTransport.h"
+#include "RTCRtpTransceiver.h"
+
+namespace test {
+#ifdef USE_FAKE_PCOBSERVER
+class AFakePCObserver;
+#endif
+} // namespace test
+
+class nsDOMDataChannel;
+class nsIPrincipal;
+
+namespace mozilla {
+struct CandidateInfo;
+class DataChannel;
+class DtlsIdentity;
+class MediaPipeline;
+class MediaPipelineReceive;
+class MediaPipelineTransmit;
+enum class PrincipalPrivacy : uint8_t;
+class SharedWebrtcState;
+
+namespace dom {
+class RTCCertificate;
+struct RTCConfiguration;
+struct RTCRtpSourceEntry;
+struct RTCIceServer;
+struct RTCOfferOptions;
+struct RTCRtpParameters;
+class RTCRtpSender;
+class MediaStreamTrack;
+
+#ifdef USE_FAKE_PCOBSERVER
+typedef test::AFakePCObserver PeerConnectionObserver;
+typedef const char* PCObserverString;
+#else
+class PeerConnectionObserver;
+typedef NS_ConvertUTF8toUTF16 PCObserverString;
+#endif
+} // namespace dom
+} // namespace mozilla
+
+#if defined(__cplusplus) && __cplusplus >= 201103L
+typedef struct Timecard Timecard;
+#else
+# include "common/time_profiling/timecard.h"
+#endif
+
+// To preserve blame, convert nsresult to ErrorResult with wrappers. These
+// macros help declare wrappers w/function being wrapped when there are no
+// differences.
+
+#define NS_IMETHODIMP_TO_ERRORRESULT(func, rv, ...) \
+ NS_IMETHODIMP func(__VA_ARGS__); \
+ void func(__VA_ARGS__, rv)
+
+#define NS_IMETHODIMP_TO_ERRORRESULT_RETREF(resulttype, func, rv, ...) \
+ NS_IMETHODIMP func(__VA_ARGS__, resulttype** result); \
+ already_AddRefed<resulttype> func(__VA_ARGS__, rv)
+
+namespace mozilla {
+
+using mozilla::DtlsIdentity;
+using mozilla::ErrorResult;
+using mozilla::PeerIdentity;
+using mozilla::dom::PeerConnectionObserver;
+using mozilla::dom::RTCConfiguration;
+using mozilla::dom::RTCIceServer;
+using mozilla::dom::RTCOfferOptions;
+
+class PeerConnectionWrapper;
+class RemoteSourceStreamInfo;
+
+// Uuid Generator
+class PCUuidGenerator : public mozilla::JsepUuidGenerator {
+ public:
+ virtual bool Generate(std::string* idp) override;
+ virtual mozilla::JsepUuidGenerator* Clone() const override {
+ return new PCUuidGenerator(*this);
+ }
+
+ private:
+ nsCOMPtr<nsIUUIDGenerator> mGenerator;
+};
+
+// This is a variation of Telemetry::AutoTimer that keeps a reference
+// count and records the elapsed time when the count falls to zero. The
+// elapsed time is recorded in seconds.
+struct PeerConnectionAutoTimer {
+ PeerConnectionAutoTimer()
+ : mRefCnt(0), mStart(TimeStamp::Now()), mUsedAV(false){};
+ void RegisterConnection();
+ void UnregisterConnection(bool aContainedAV);
+ bool IsStopped();
+
+ private:
+ int64_t mRefCnt;
+ TimeStamp mStart;
+ bool mUsedAV;
+};
+
+// Enter an API call and check that the state is OK,
+// the PC isn't closed, etc.
+#define PC_AUTO_ENTER_API_CALL(assert_ice_ready) \
+ do { \
+ /* do/while prevents res from conflicting with locals */ \
+ nsresult res = CheckApiState(assert_ice_ready); \
+ if (NS_FAILED(res)) return res; \
+ } while (0)
+#define PC_AUTO_ENTER_API_CALL_VOID_RETURN(assert_ice_ready) \
+ do { \
+ /* do/while prevents res from conflicting with locals */ \
+ nsresult res = CheckApiState(assert_ice_ready); \
+ if (NS_FAILED(res)) return; \
+ } while (0)
+#define PC_AUTO_ENTER_API_CALL_NO_CHECK() CheckThread()
+
+class PeerConnectionImpl final
+ : public nsISupports,
+ public nsWrapperCache,
+ public mozilla::DataChannelConnection::DataConnectionListener {
+ struct Internal; // Avoid exposing c includes to bindings
+
+ public:
+ explicit PeerConnectionImpl(
+ const mozilla::dom::GlobalObject* aGlobal = nullptr);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(PeerConnectionImpl)
+
+ struct RtpExtensionHeader {
+ JsepMediaType mMediaType;
+ SdpDirectionAttribute::Direction direction;
+ std::string extensionname;
+ };
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+ nsPIDOMWindowInner* GetParentObject() const;
+
+ static already_AddRefed<PeerConnectionImpl> Constructor(
+ const mozilla::dom::GlobalObject& aGlobal);
+
+ // DataConnection observers
+ void NotifyDataChannel(already_AddRefed<mozilla::DataChannel> aChannel)
+ // PeerConnectionImpl only inherits from mozilla::DataChannelConnection
+ // inside libxul.
+ override;
+
+ void NotifyDataChannelOpen(DataChannel*) override;
+
+ void NotifyDataChannelClosed(DataChannel*) override;
+
+ void NotifySctpConnected() override;
+
+ void NotifySctpClosed() override;
+
+ const RefPtr<MediaTransportHandler> GetTransportHandler() const;
+
+ // Handle system to allow weak references to be passed through C code
+ virtual const std::string& GetHandle();
+
+ // Name suitable for exposing to content
+ virtual const std::string& GetName();
+
+ // ICE events
+ void IceConnectionStateChange(dom::RTCIceConnectionState state);
+ void IceGatheringStateChange(dom::RTCIceGatheringState state);
+ void OnCandidateFound(const std::string& aTransportId,
+ const CandidateInfo& aCandidateInfo);
+ void UpdateDefaultCandidate(const std::string& defaultAddr,
+ uint16_t defaultPort,
+ const std::string& defaultRtcpAddr,
+ uint16_t defaultRtcpPort,
+ const std::string& transportId);
+
+ static void ListenThread(void* aData);
+ static void ConnectThread(void* aData);
+
+ // Get the STS thread
+ nsISerialEventTarget* GetSTSThread() {
+ PC_AUTO_ENTER_API_CALL_NO_CHECK();
+ return mSTSThread;
+ }
+
+ nsresult Initialize(PeerConnectionObserver& aObserver,
+ nsGlobalWindowInner* aWindow);
+
+ // Initialize PeerConnection from an RTCConfiguration object (JS entrypoint)
+ void Initialize(PeerConnectionObserver& aObserver,
+ nsGlobalWindowInner& aWindow, ErrorResult& rv);
+
+ void SetCertificate(mozilla::dom::RTCCertificate& aCertificate);
+ const RefPtr<mozilla::dom::RTCCertificate>& Certificate() const;
+ // This is a hack to support external linkage.
+ RefPtr<DtlsIdentity> Identity() const;
+
+ NS_IMETHODIMP_TO_ERRORRESULT(CreateOffer, ErrorResult& rv,
+ const RTCOfferOptions& aOptions) {
+ rv = CreateOffer(aOptions);
+ }
+
+ NS_IMETHODIMP CreateAnswer();
+ void CreateAnswer(ErrorResult& rv) { rv = CreateAnswer(); }
+
+ NS_IMETHODIMP CreateOffer(const mozilla::JsepOfferOptions& aConstraints);
+
+ NS_IMETHODIMP SetLocalDescription(int32_t aAction, const char* aSDP);
+
+ void SetLocalDescription(int32_t aAction, const nsAString& aSDP,
+ ErrorResult& rv) {
+ rv = SetLocalDescription(aAction, NS_ConvertUTF16toUTF8(aSDP).get());
+ }
+
+ NS_IMETHODIMP SetRemoteDescription(int32_t aAction, const char* aSDP);
+
+ void SetRemoteDescription(int32_t aAction, const nsAString& aSDP,
+ ErrorResult& rv) {
+ rv = SetRemoteDescription(aAction, NS_ConvertUTF16toUTF8(aSDP).get());
+ }
+
+ already_AddRefed<dom::Promise> GetStats(dom::MediaStreamTrack* aSelector);
+
+ void GetRemoteStreams(nsTArray<RefPtr<DOMMediaStream>>& aStreamsOut) const;
+
+ NS_IMETHODIMP AddIceCandidate(const char* aCandidate, const char* aMid,
+ const char* aUfrag,
+ const dom::Nullable<unsigned short>& aLevel);
+
+ void AddIceCandidate(const nsAString& aCandidate, const nsAString& aMid,
+ const nsAString& aUfrag,
+ const dom::Nullable<unsigned short>& aLevel,
+ ErrorResult& rv) {
+ rv = AddIceCandidate(NS_ConvertUTF16toUTF8(aCandidate).get(),
+ NS_ConvertUTF16toUTF8(aMid).get(),
+ NS_ConvertUTF16toUTF8(aUfrag).get(), aLevel);
+ }
+
+ void UpdateNetworkState(bool online);
+
+ NS_IMETHODIMP CloseStreams();
+
+ void CloseStreams(ErrorResult& rv) { rv = CloseStreams(); }
+
+ already_AddRefed<dom::RTCRtpTransceiver> AddTransceiver(
+ const dom::RTCRtpTransceiverInit& aInit, const nsAString& aKind,
+ dom::MediaStreamTrack* aSendTrack, bool aAddTrackMagic, ErrorResult& aRv);
+
+ bool CheckNegotiationNeeded();
+ bool CreatedSender(const dom::RTCRtpSender& aSender) const;
+
+ // test-only
+ NS_IMETHODIMP_TO_ERRORRESULT(EnablePacketDump, ErrorResult& rv,
+ unsigned long level, dom::mozPacketDumpType type,
+ bool sending) {
+ rv = EnablePacketDump(level, type, sending);
+ }
+
+ // test-only
+ NS_IMETHODIMP_TO_ERRORRESULT(DisablePacketDump, ErrorResult& rv,
+ unsigned long level, dom::mozPacketDumpType type,
+ bool sending) {
+ rv = DisablePacketDump(level, type, sending);
+ }
+
+ void GetPeerIdentity(nsAString& peerIdentity) {
+ if (mPeerIdentity) {
+ peerIdentity = mPeerIdentity->ToString();
+ return;
+ }
+
+ peerIdentity.SetIsVoid(true);
+ }
+
+ const PeerIdentity* GetPeerIdentity() const { return mPeerIdentity; }
+ NS_IMETHODIMP_TO_ERRORRESULT(SetPeerIdentity, ErrorResult& rv,
+ const nsAString& peerIdentity) {
+ rv = SetPeerIdentity(peerIdentity);
+ }
+
+ const std::string& GetIdAsAscii() const { return mName; }
+
+ void GetId(nsAString& id) { id = NS_ConvertASCIItoUTF16(mName.c_str()); }
+
+ void SetId(const nsAString& id) { mName = NS_ConvertUTF16toUTF8(id).get(); }
+
+ // this method checks to see if we've made a promise to protect media.
+ bool PrivacyRequested() const {
+ return mRequestedPrivacy.valueOr(PrincipalPrivacy::NonPrivate) ==
+ PrincipalPrivacy::Private;
+ }
+
+ NS_IMETHODIMP GetFingerprint(char** fingerprint);
+ void GetFingerprint(nsAString& fingerprint) {
+ char* tmp;
+ nsresult rv = GetFingerprint(&tmp);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ fingerprint.AssignASCII(tmp);
+ delete[] tmp;
+ }
+
+ void GetCurrentLocalDescription(nsAString& aSDP) const;
+ void GetPendingLocalDescription(nsAString& aSDP) const;
+
+ void GetCurrentRemoteDescription(nsAString& aSDP) const;
+ void GetPendingRemoteDescription(nsAString& aSDP) const;
+
+ dom::Nullable<bool> GetCurrentOfferer() const;
+ dom::Nullable<bool> GetPendingOfferer() const;
+
+ NS_IMETHODIMP SignalingState(mozilla::dom::RTCSignalingState* aState);
+
+ mozilla::dom::RTCSignalingState SignalingState() {
+ mozilla::dom::RTCSignalingState state;
+ SignalingState(&state);
+ return state;
+ }
+
+ NS_IMETHODIMP IceConnectionState(mozilla::dom::RTCIceConnectionState* aState);
+
+ mozilla::dom::RTCIceConnectionState IceConnectionState() {
+ mozilla::dom::RTCIceConnectionState state;
+ IceConnectionState(&state);
+ return state;
+ }
+
+ NS_IMETHODIMP IceGatheringState(mozilla::dom::RTCIceGatheringState* aState);
+
+ mozilla::dom::RTCIceGatheringState IceGatheringState() {
+ return mIceGatheringState;
+ }
+
+ NS_IMETHODIMP ConnectionState(mozilla::dom::RTCPeerConnectionState* aState);
+
+ mozilla::dom::RTCPeerConnectionState ConnectionState() {
+ mozilla::dom::RTCPeerConnectionState state;
+ ConnectionState(&state);
+ return state;
+ }
+
+ NS_IMETHODIMP Close();
+
+ void Close(ErrorResult& rv) { rv = Close(); }
+
+ // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY bool PluginCrash(uint32_t aPluginID,
+ const nsAString& aPluginName);
+
+ NS_IMETHODIMP_TO_ERRORRESULT(SetConfiguration, ErrorResult& rv,
+ const RTCConfiguration& aConfiguration) {
+ rv = SetConfiguration(aConfiguration);
+ }
+
+ dom::RTCSctpTransport* GetSctp() const;
+
+ void RestartIce();
+ void RestartIceNoRenegotiationNeeded();
+
+ void RecordEndOfCallTelemetry();
+
+ nsresult InitializeDataChannel();
+
+ NS_IMETHODIMP_TO_ERRORRESULT_RETREF(nsDOMDataChannel, CreateDataChannel,
+ ErrorResult& rv, const nsAString& aLabel,
+ const nsAString& aProtocol,
+ uint16_t aType, bool outOfOrderAllowed,
+ uint16_t aMaxTime, uint16_t aMaxNum,
+ bool aExternalNegotiated,
+ uint16_t aStream);
+
+ // Base class for chained operations. Necessary right now because some
+ // operations come from JS (in the form of dom::ChainedOperation), and others
+ // come from c++ (dom::ChainedOperation is very unwieldy and arcane to build
+ // in c++). Once we stop using JSImpl, we should be able to simplify this.
+ class Operation : public dom::PromiseNativeHandler {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(Operation)
+ Operation(PeerConnectionImpl* aPc, ErrorResult& aError);
+ MOZ_CAN_RUN_SCRIPT
+ void Call(ErrorResult& aError);
+ dom::Promise* GetPromise() { return mPromise; }
+ MOZ_CAN_RUN_SCRIPT
+ void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) override;
+
+ MOZ_CAN_RUN_SCRIPT
+ void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) override;
+
+ protected:
+ MOZ_CAN_RUN_SCRIPT
+ virtual RefPtr<dom::Promise> CallImpl(ErrorResult& aError) = 0;
+ virtual ~Operation();
+ // This is the promise p from https://w3c.github.io/webrtc-pc/#dfn-chain
+ // This will be a content promise, since we return this to the caller of
+ // Chain.
+ RefPtr<dom::Promise> mPromise;
+ RefPtr<PeerConnectionImpl> mPc;
+ };
+
+ class JSOperation final : public Operation {
+ public:
+ JSOperation(PeerConnectionImpl* aPc, dom::ChainedOperation& aOp,
+ ErrorResult& aError);
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(JSOperation, Operation)
+
+ private:
+ MOZ_CAN_RUN_SCRIPT
+ RefPtr<dom::Promise> CallImpl(ErrorResult& aError) override;
+ ~JSOperation() = default;
+ RefPtr<dom::ChainedOperation> mOperation;
+ };
+
+ MOZ_CAN_RUN_SCRIPT
+ already_AddRefed<dom::Promise> Chain(dom::ChainedOperation& aOperation,
+ ErrorResult& aError);
+ MOZ_CAN_RUN_SCRIPT
+ already_AddRefed<dom::Promise> Chain(const RefPtr<Operation>& aOperation,
+ ErrorResult& aError);
+ already_AddRefed<dom::Promise> MakePromise(ErrorResult& aError) const;
+
+ void UpdateNegotiationNeeded();
+
+ void GetTransceivers(
+ nsTArray<RefPtr<dom::RTCRtpTransceiver>>& aTransceiversOut) {
+ aTransceiversOut = mTransceivers.Clone();
+ }
+
+ // Gets the RTC Signaling State of the JSEP session
+ dom::RTCSignalingState GetSignalingState() const;
+
+ already_AddRefed<dom::Promise> OnSetDescriptionSuccess(
+ dom::RTCSdpType aSdpType, bool aRemote, ErrorResult& aError);
+
+ void OnSetDescriptionError();
+
+ bool IsClosed() const;
+
+ // called when DTLS connects; we only need this once
+ nsresult OnAlpnNegotiated(bool aPrivacyRequested);
+
+ void OnDtlsStateChange(const std::string& aTransportId,
+ TransportLayer::State aState);
+ void UpdateConnectionState();
+ dom::RTCPeerConnectionState GetNewConnectionState() const;
+
+ // initialize telemetry for when calls start
+ void StartCallTelem();
+
+ // Gets all codec stats for all transports, coalesced to transport level.
+ nsTArray<dom::RTCCodecStats> GetCodecStats(DOMHighResTimeStamp aNow);
+
+ RefPtr<dom::RTCStatsReportPromise> GetStats(dom::MediaStreamTrack* aSelector,
+ bool aInternalStats);
+
+ void CollectConduitTelemetryData();
+
+ void OnMediaError(const std::string& aError);
+
+ void DumpPacket_m(size_t level, dom::mozPacketDumpType type, bool sending,
+ UniquePtr<uint8_t[]>& packet, size_t size);
+
+ const dom::RTCStatsTimestampMaker& GetTimestampMaker() const {
+ return mTimestampMaker;
+ }
+
+ // Utility function, given a string pref and an URI, returns whether or not
+ // the URI occurs in the pref. Wildcards are supported (e.g. *.example.com)
+ // and multiple hostnames can be present, separated by commas.
+ static bool HostnameInPref(const char* aPrefList, const nsCString& aHostName);
+
+ void StampTimecard(const char* aEvent);
+
+ bool RelayOnly() const {
+ return mJsConfiguration.mIceTransportPolicy.WasPassed() &&
+ mJsConfiguration.mIceTransportPolicy.Value() ==
+ dom::RTCIceTransportPolicy::Relay;
+ }
+
+ RefPtr<PacketDumper> GetPacketDumper() {
+ if (!mPacketDumper) {
+ mPacketDumper = new PacketDumper(mHandle);
+ }
+
+ return mPacketDumper;
+ }
+
+ nsString GenerateUUID() const {
+ std::string result;
+ if (!mUuidGen->Generate(&result)) {
+ MOZ_CRASH();
+ }
+ return NS_ConvertUTF8toUTF16(result.c_str());
+ }
+
+ bool ShouldAllowOldSetParameters() const { return mAllowOldSetParameters; }
+
+ nsCString GetHostname() const { return mHostname; }
+ nsCString GetEffectiveTLDPlus1() const { return mEffectiveTLDPlus1; }
+
+ void SendWarningToConsole(const nsCString& aWarning);
+
+ const UniquePtr<dom::RTCStatsReportInternal>& GetFinalStats() const {
+ return mFinalStats;
+ }
+
+ void DisableLongTermStats() { mDisableLongTermStats = true; }
+
+ bool LongTermStatsIsDisabled() const { return mDisableLongTermStats; }
+
+ static void GetDefaultVideoCodecs(
+ std::vector<UniquePtr<JsepCodecDescription>>& aSupportedCodecs,
+ bool aUseRtx);
+
+ static void GetDefaultAudioCodecs(
+ std::vector<UniquePtr<JsepCodecDescription>>& aSupportedCodecs);
+
+ static void GetDefaultRtpExtensions(
+ std::vector<RtpExtensionHeader>& aRtpExtensions);
+
+ static void GetCapabilities(const nsAString& aKind,
+ dom::Nullable<dom::RTCRtpCapabilities>& aResult,
+ sdp::Direction aDirection);
+ static void SetupPreferredCodecs(
+ std::vector<UniquePtr<JsepCodecDescription>>& aPreferredCodecs);
+
+ static void SetupPreferredRtpExtensions(
+ std::vector<RtpExtensionHeader>& aPreferredheaders);
+
+ private:
+ virtual ~PeerConnectionImpl();
+ PeerConnectionImpl(const PeerConnectionImpl& rhs);
+ PeerConnectionImpl& operator=(PeerConnectionImpl);
+
+ RefPtr<dom::RTCStatsPromise> GetDataChannelStats(
+ const RefPtr<DataChannelConnection>& aDataChannelConnection,
+ const DOMHighResTimeStamp aTimestamp);
+ nsresult CalculateFingerprint(const std::string& algorithm,
+ std::vector<uint8_t>* fingerprint) const;
+ nsresult ConfigureJsepSessionCodecs();
+
+ NS_IMETHODIMP EnsureDataConnection(uint16_t aLocalPort, uint16_t aNumstreams,
+ uint32_t aMaxMessageSize, bool aMMSSet);
+
+ nsresult CheckApiState(bool assert_ice_ready) const;
+ void StoreFinalStats(UniquePtr<dom::RTCStatsReportInternal>&& report);
+ void CheckThread() const { MOZ_ASSERT(NS_IsMainThread(), "Wrong thread"); }
+
+ // test-only: called from AddRIDExtension and AddRIDFilter
+ // for simulcast mochitests.
+ RefPtr<MediaPipeline> GetMediaPipelineForTrack(
+ dom::MediaStreamTrack& aRecvTrack);
+
+ void CandidateReady(const std::string& candidate,
+ const std::string& transportId, const std::string& ufrag);
+ void SendLocalIceCandidateToContent(uint16_t level, const std::string& mid,
+ const std::string& candidate,
+ const std::string& ufrag);
+
+ nsresult GetDatachannelParameters(uint32_t* channels, uint16_t* localport,
+ uint16_t* remoteport,
+ uint32_t* maxmessagesize, bool* mmsset,
+ std::string* transportId,
+ bool* client) const;
+
+ nsresult AddRtpTransceiverToJsepSession(JsepTransceiver& transceiver);
+
+ void RecordIceRestartStatistics(JsepSdpType type);
+
+ void StoreConfigurationForAboutWebrtc(const RTCConfiguration& aConfig);
+
+ dom::Sequence<dom::RTCSdpParsingErrorInternal> GetLastSdpParsingErrors()
+ const;
+
+ MOZ_CAN_RUN_SCRIPT
+ void RunNextOperation(ErrorResult& aError);
+
+ void SyncToJsep();
+ void SyncFromJsep();
+
+ void DoSetDescriptionSuccessPostProcessing(dom::RTCSdpType aSdpType,
+ bool aRemote,
+ const RefPtr<dom::Promise>& aP);
+
+ // Timecard used to measure processing time. This should be the first class
+ // attribute so that we accurately measure the time required to instantiate
+ // any other attributes of this class.
+ Timecard* mTimeCard;
+
+ // Configuration used to initialize the PeerConnection
+ dom::RTCConfigurationInternal mJsConfiguration;
+
+ mozilla::dom::RTCSignalingState mSignalingState;
+
+ // ICE State
+ mozilla::dom::RTCIceConnectionState mIceConnectionState;
+ mozilla::dom::RTCIceGatheringState mIceGatheringState;
+
+ mozilla::dom::RTCPeerConnectionState mConnectionState;
+
+ RefPtr<PeerConnectionObserver> mPCObserver;
+
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+
+ // The SDP sent in from JS
+ std::string mLocalRequestedSDP;
+ std::string mRemoteRequestedSDP;
+ // Only accessed from main
+ mozilla::dom::Sequence<mozilla::dom::RTCSdpHistoryEntryInternal> mSdpHistory;
+ std::string mPendingLocalDescription;
+ std::string mPendingRemoteDescription;
+ std::string mCurrentLocalDescription;
+ std::string mCurrentRemoteDescription;
+ Maybe<bool> mPendingOfferer;
+ Maybe<bool> mCurrentOfferer;
+
+ // DTLS fingerprint
+ std::string mFingerprint;
+ std::string mRemoteFingerprint;
+
+ // identity-related fields
+ // The entity on the other end of the peer-to-peer connection;
+ // void if they are not yet identified, and no identity setting has been set
+ RefPtr<PeerIdentity> mPeerIdentity;
+ // The certificate we are using.
+ RefPtr<mozilla::dom::RTCCertificate> mCertificate;
+ // Whether an app should be prevented from accessing media produced by the PC
+ // If this is true, then media will not be sent until mPeerIdentity matches
+ // local streams PeerIdentity; and remote streams are protected from content
+ //
+ // This can be false if mPeerIdentity is set, in the case where identity is
+ // provided, but the media is not protected from the app on either side
+ Maybe<PrincipalPrivacy> mRequestedPrivacy;
+
+ // A handle to refer to this PC with
+ std::string mHandle;
+
+ // A name for this PC that we are willing to expose to content.
+ std::string mName;
+ nsCString mHostname;
+ nsCString mEffectiveTLDPlus1;
+
+ // The target to run stuff on
+ nsCOMPtr<nsISerialEventTarget> mSTSThread;
+
+ // DataConnection that's used to get all the DataChannels
+ RefPtr<mozilla::DataChannelConnection> mDataConnection;
+ unsigned int mDataChannelsOpened = 0;
+ unsigned int mDataChannelsClosed = 0;
+
+ bool mForceIceTcp;
+ RefPtr<MediaTransportHandler> mTransportHandler;
+
+ // The JSEP negotiation session.
+ mozilla::UniquePtr<PCUuidGenerator> mUuidGen;
+ mozilla::UniquePtr<mozilla::JsepSession> mJsepSession;
+ // There are lots of error cases where we want to abandon an sRD/sLD _after_
+ // it has already been applied to the JSEP engine, and revert back to the
+ // previous state. We also want to ensure that the various modifications
+ // to the JSEP engine are not exposed to JS until the sRD/sLD completes,
+ // which is why we have a new "uncommitted" JSEP engine.
+ mozilla::UniquePtr<mozilla::JsepSession> mUncommittedJsepSession;
+ unsigned long mIceRestartCount;
+ unsigned long mIceRollbackCount;
+
+ // The following are used for Telemetry:
+ bool mCallTelemStarted = false;
+ bool mCallTelemEnded = false;
+
+ // We _could_ make mFinalStatsQuery be an RTCStatsReportPromise, but that
+ // would require RTCStatsReportPromise to no longer be exclusive, which is
+ // a bit of a hassle, and not very performant.
+ RefPtr<GenericNonExclusivePromise> mFinalStatsQuery;
+ UniquePtr<dom::RTCStatsReportInternal> mFinalStats;
+ bool mDisableLongTermStats = false;
+
+ // Start time of ICE.
+ mozilla::TimeStamp mIceStartTime;
+ // Hold PeerConnectionAutoTimer instances for each window.
+ static std::map<uint64_t, PeerConnectionAutoTimer> sCallDurationTimers;
+
+ bool mHaveConfiguredCodecs;
+
+ bool mTrickle;
+
+ bool mPrivateWindow;
+
+ // Whether this PeerConnection is being counted as active by mWindow
+ bool mActiveOnWindow;
+
+ // storage for Telemetry data
+ uint16_t mMaxReceiving[SdpMediaSection::kMediaTypes];
+ uint16_t mMaxSending[SdpMediaSection::kMediaTypes];
+
+ // used to store the raw trickle candidate string for display
+ // on the about:webrtc raw candidates table.
+ std::vector<std::string> mRawTrickledCandidates;
+
+ dom::RTCStatsTimestampMaker mTimestampMaker;
+
+ RefPtr<RTCStatsIdGenerator> mIdGenerator;
+ // Ordinarily, I would use a std::map here, but this used to be a JS Map
+ // which iterates in insertion order, and I want to avoid changing this.
+ nsTArray<RefPtr<DOMMediaStream>> mReceiveStreams;
+
+ DOMMediaStream* GetReceiveStream(const std::string& aId) const;
+ DOMMediaStream* CreateReceiveStream(const std::string& aId);
+
+ void InitLocalAddrs(); // for stun local address IPC request
+ bool ShouldForceProxy() const;
+ std::unique_ptr<NrSocketProxyConfig> GetProxyConfig() const;
+
+ class StunAddrsHandler : public net::StunAddrsListener {
+ public:
+ explicit StunAddrsHandler(PeerConnectionImpl* aPc)
+ : mPcHandle(aPc->GetHandle()) {}
+
+ void OnMDNSQueryComplete(const nsCString& hostname,
+ const Maybe<nsCString>& address) override;
+
+ void OnStunAddrsAvailable(
+ const mozilla::net::NrIceStunAddrArray& addrs) override;
+
+ private:
+ // This class is not cycle-collected, so we must avoid grabbing a strong
+ // reference.
+ const std::string mPcHandle;
+ virtual ~StunAddrsHandler() {}
+ };
+
+ // Manage ICE transports.
+ void UpdateTransport(const JsepTransceiver& aTransceiver, bool aForceIceTcp);
+
+ void GatherIfReady();
+ void FlushIceCtxOperationQueueIfReady();
+ void PerformOrEnqueueIceCtxOperation(nsIRunnable* runnable);
+ nsresult SetTargetForDefaultLocalAddressLookup();
+ void EnsureIceGathering(bool aDefaultRouteOnly, bool aObfuscateHostAddresses);
+
+ bool GetPrefDefaultAddressOnly() const;
+ bool GetPrefObfuscateHostAddresses() const;
+
+ bool IsIceCtxReady() const {
+ return mLocalAddrsRequestState == STUN_ADDR_REQUEST_COMPLETE;
+ }
+
+ // Ensure ICE transports exist that we might need when offer/answer concludes
+ void EnsureTransports(const JsepSession& aSession);
+
+ void UpdateRTCDtlsTransports(bool aMarkAsStable);
+ void RollbackRTCDtlsTransports();
+ void RemoveRTCDtlsTransportsExcept(
+ const std::set<std::string>& aTransportIds);
+
+ // Activate ICE transports at the conclusion of offer/answer,
+ // or when rollback occurs.
+ nsresult UpdateTransports(const JsepSession& aSession,
+ const bool forceIceTcp);
+
+ void ResetStunAddrsForIceRestart() { mStunAddrs.Clear(); }
+
+ // Start ICE checks.
+ void StartIceChecks(const JsepSession& session);
+
+ // Process a trickle ICE candidate.
+ void AddIceCandidate(const std::string& candidate,
+ const std::string& aTransportId,
+ const std::string& aUFrag);
+
+ // Handle complete media pipelines.
+ // This updates codec parameters, starts/stops send/receive, and other
+ // stuff that doesn't necessarily require negotiation. This can be called at
+ // any time, not just when an offer/answer exchange completes.
+ nsresult UpdateMediaPipelines();
+
+ already_AddRefed<dom::RTCRtpTransceiver> CreateTransceiver(
+ const std::string& aId, bool aIsVideo,
+ const dom::RTCRtpTransceiverInit& aInit,
+ dom::MediaStreamTrack* aSendTrack, bool aAddTrackMagic, ErrorResult& aRv);
+
+ std::string GetTransportIdMatchingSendTrack(
+ const dom::MediaStreamTrack& aTrack) const;
+
+ // this determines if any track is peerIdentity constrained
+ bool AnyLocalTrackHasPeerIdentity() const;
+
+ bool AnyCodecHasPluginID(uint64_t aPluginID);
+
+ already_AddRefed<nsIHttpChannelInternal> GetChannel() const;
+
+ void BreakCycles();
+
+ bool HasPendingSetParameters() const;
+ void InvalidateLastReturnedParameters();
+
+ RefPtr<WebrtcCallWrapper> mCall;
+
+ // See Bug 1642419, this can be removed when all sites are working with RTX.
+ bool mRtxIsAllowed = true;
+
+ nsTArray<RefPtr<Operation>> mOperations;
+ bool mChainingOperation = false;
+ bool mUpdateNegotiationNeededFlagOnEmptyChain = false;
+ bool mNegotiationNeeded = false;
+ std::set<std::pair<std::string, std::string>> mLocalIceCredentialsToReplace;
+
+ nsTArray<RefPtr<dom::RTCRtpTransceiver>> mTransceivers;
+ std::map<std::string, RefPtr<dom::RTCDtlsTransport>>
+ mTransportIdToRTCDtlsTransport;
+ RefPtr<dom::RTCSctpTransport> mSctpTransport;
+
+ // Used whenever we need to dispatch a runnable to STS to tweak something
+ // on our ICE ctx, but are not ready to do so at the moment (eg; we are
+ // waiting to get a callback with our http proxy config before we start
+ // gathering or start checking)
+ std::vector<nsCOMPtr<nsIRunnable>> mQueuedIceCtxOperations;
+
+ // Set if prefs dictate that we should force the use of a web proxy.
+ bool mForceProxy = false;
+
+ // Used to cancel incoming stun addrs response
+ RefPtr<net::StunAddrsRequestChild> mStunAddrsRequest;
+
+ enum StunAddrRequestState {
+ STUN_ADDR_REQUEST_NONE,
+ STUN_ADDR_REQUEST_PENDING,
+ STUN_ADDR_REQUEST_COMPLETE
+ };
+ // Used to track the state of the stun addr IPC request
+ StunAddrRequestState mLocalAddrsRequestState = STUN_ADDR_REQUEST_NONE;
+
+ // Used to store the result of the stun addr IPC request
+ nsTArray<NrIceStunAddr> mStunAddrs;
+
+ // Used to ensure the target for default local address lookup is only set
+ // once.
+ bool mTargetForDefaultLocalAddressLookupIsSet = false;
+
+ // Keep track of local hostnames to register. Registration is deferred
+ // until StartIceChecks has run. Accessed on main thread only.
+ std::map<std::string, std::string> mMDNSHostnamesToRegister;
+ bool mCanRegisterMDNSHostnamesDirectly = false;
+
+ // Used to store the mDNS hostnames that we have registered
+ std::set<std::string> mRegisteredMDNSHostnames;
+
+ // web-compat stopgap
+ bool mAllowOldSetParameters = false;
+
+ // Used to store the mDNS hostnames that we have queried
+ struct PendingIceCandidate {
+ std::vector<std::string> mTokenizedCandidate;
+ std::string mTransportId;
+ std::string mUfrag;
+ };
+ std::map<std::string, std::list<PendingIceCandidate>> mQueriedMDNSHostnames;
+
+ // Connecting PCImpl to sigslot is not safe, because sigslot takes strong
+ // references without any reference counting, and JS holds refcounted strong
+ // references to PCImpl (meaning JS can cause PCImpl to be destroyed). This
+ // is not ref-counted (since sigslot holds onto non-refcounted strong refs)
+ // Must be destroyed on STS. Holds a weak reference to PCImpl.
+ class SignalHandler : public sigslot::has_slots<> {
+ public:
+ SignalHandler(PeerConnectionImpl* aPc, MediaTransportHandler* aSource);
+ virtual ~SignalHandler();
+
+ void ConnectSignals();
+
+ // ICE events
+ void IceGatheringStateChange_s(dom::RTCIceGatheringState aState);
+ void IceConnectionStateChange_s(dom::RTCIceConnectionState aState);
+ void OnCandidateFound_s(const std::string& aTransportId,
+ const CandidateInfo& aCandidateInfo);
+ void AlpnNegotiated_s(const std::string& aAlpn, bool aPrivacyRequested);
+ void ConnectionStateChange_s(const std::string& aTransportId,
+ TransportLayer::State aState);
+
+ private:
+ const std::string mHandle;
+ RefPtr<MediaTransportHandler> mSource;
+ RefPtr<nsISerialEventTarget> mSTSThread;
+ };
+
+ mozilla::UniquePtr<SignalHandler> mSignalHandler;
+
+ // Make absolutely sure our refcount does not go to 0 before Close() is called
+ // This is because Close does a stats query, which needs the
+ // PeerConnectionImpl to stick around until the query is done.
+ RefPtr<PeerConnectionImpl> mKungFuDeathGrip;
+ RefPtr<PacketDumper> mPacketDumper;
+
+ public:
+ // these are temporary until the DataChannel Listen/Connect API is removed
+ unsigned short listenPort;
+ unsigned short connectPort;
+ char* connectStr; // XXX ownership/free
+};
+
+// This is what is returned when you acquire on a handle
+class PeerConnectionWrapper {
+ public:
+ explicit PeerConnectionWrapper(const std::string& handle);
+
+ PeerConnectionImpl* impl() { return impl_; }
+
+ private:
+ RefPtr<PeerConnectionImpl> impl_;
+};
+
+} // namespace mozilla
+
+#undef NS_IMETHODIMP_TO_ERRORRESULT
+#undef NS_IMETHODIMP_TO_ERRORRESULT_RETREF
+#endif // _PEER_CONNECTION_IMPL_H_
diff --git a/dom/media/webrtc/jsapi/RTCDTMFSender.cpp b/dom/media/webrtc/jsapi/RTCDTMFSender.cpp
new file mode 100644
index 0000000000..30355aca26
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCDTMFSender.cpp
@@ -0,0 +1,159 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "RTCDTMFSender.h"
+#include "libwebrtcglue/MediaConduitInterface.h"
+#include "transport/logging.h"
+#include "RTCRtpTransceiver.h"
+#include "nsITimer.h"
+#include "mozilla/dom/RTCDTMFSenderBinding.h"
+#include "mozilla/dom/RTCDTMFToneChangeEvent.h"
+#include <algorithm>
+#include <bitset>
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(RTCDTMFSender, DOMEventTargetHelper,
+ mTransceiver, mSendTimer)
+
+NS_IMPL_ADDREF_INHERITED(RTCDTMFSender, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(RTCDTMFSender, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCDTMFSender)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
+ NS_INTERFACE_MAP_ENTRY(nsINamed)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+LazyLogModule gDtmfLog("RTCDTMFSender");
+
+RTCDTMFSender::RTCDTMFSender(nsPIDOMWindowInner* aWindow,
+ RTCRtpTransceiver* aTransceiver)
+ : DOMEventTargetHelper(aWindow), mTransceiver(aTransceiver) {}
+
+JSObject* RTCDTMFSender::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return RTCDTMFSender_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+static int GetDTMFToneCode(uint16_t c) {
+ const char* DTMF_TONECODES = "0123456789*#ABCD";
+
+ if (c == ',') {
+ // , is a special character indicating a 2 second delay
+ return -1;
+ }
+
+ const char* i = strchr(DTMF_TONECODES, c);
+ MOZ_ASSERT(i);
+ return static_cast<int>(i - DTMF_TONECODES);
+}
+
+static std::bitset<256> GetCharacterBitset(const std::string& aCharsInSet) {
+ std::bitset<256> result;
+ for (auto c : aCharsInSet) {
+ result[c] = true;
+ }
+ return result;
+}
+
+static bool IsUnrecognizedChar(const char c) {
+ static const std::bitset<256> recognized =
+ GetCharacterBitset("0123456789ABCD#*,");
+ return !recognized[c];
+}
+
+void RTCDTMFSender::SetPayloadType(int32_t aPayloadType,
+ int32_t aPayloadFrequency) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mPayloadType = Some(aPayloadType);
+ mPayloadFrequency = Some(aPayloadFrequency);
+}
+
+void RTCDTMFSender::InsertDTMF(const nsAString& aTones, uint32_t aDuration,
+ uint32_t aInterToneGap, ErrorResult& aRv) {
+ if (!mTransceiver->CanSendDTMF()) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ std::string utf8Tones = NS_ConvertUTF16toUTF8(aTones).get();
+
+ std::transform(utf8Tones.begin(), utf8Tones.end(), utf8Tones.begin(),
+ [](const char c) { return std::toupper(c); });
+
+ if (std::any_of(utf8Tones.begin(), utf8Tones.end(), IsUnrecognizedChar)) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_CHARACTER_ERR);
+ return;
+ }
+
+ CopyUTF8toUTF16(utf8Tones, mToneBuffer);
+ mDuration = std::clamp(aDuration, 40U, 6000U);
+ mInterToneGap = std::clamp(aInterToneGap, 30U, 6000U);
+
+ if (mToneBuffer.Length()) {
+ StartPlayout(0);
+ }
+}
+
+void RTCDTMFSender::StopPlayout() {
+ if (mSendTimer) {
+ mSendTimer->Cancel();
+ mSendTimer = nullptr;
+ }
+}
+
+void RTCDTMFSender::StartPlayout(uint32_t aDelay) {
+ if (!mSendTimer) {
+ mSendTimer = NS_NewTimer();
+ mSendTimer->InitWithCallback(this, aDelay, nsITimer::TYPE_ONE_SHOT);
+ }
+}
+
+nsresult RTCDTMFSender::Notify(nsITimer* timer) {
+ MOZ_ASSERT(NS_IsMainThread());
+ StopPlayout();
+
+ if (!mTransceiver->IsSending()) {
+ return NS_OK;
+ }
+
+ RTCDTMFToneChangeEventInit init;
+ if (!mToneBuffer.IsEmpty()) {
+ uint16_t toneChar = mToneBuffer.CharAt(0);
+ int tone = GetDTMFToneCode(toneChar);
+
+ init.mTone.Assign(toneChar);
+
+ mToneBuffer.Cut(0, 1);
+
+ if (tone == -1) {
+ StartPlayout(2000);
+ } else {
+ // Reset delay if necessary
+ StartPlayout(mDuration + mInterToneGap);
+ mDtmfEvent.Notify(DtmfEvent(mPayloadType.ref(), mPayloadFrequency.ref(),
+ tone, mDuration));
+ }
+ }
+
+ RefPtr<RTCDTMFToneChangeEvent> event =
+ RTCDTMFToneChangeEvent::Constructor(this, u"tonechange"_ns, init);
+ DispatchTrustedEvent(event);
+
+ return NS_OK;
+}
+
+nsresult RTCDTMFSender::GetName(nsACString& aName) {
+ aName.AssignLiteral("RTCDTMFSender");
+ return NS_OK;
+}
+
+void RTCDTMFSender::GetToneBuffer(nsAString& aOutToneBuffer) {
+ aOutToneBuffer = mToneBuffer;
+}
+
+} // namespace mozilla::dom
+
+#undef LOGTAG
diff --git a/dom/media/webrtc/jsapi/RTCDTMFSender.h b/dom/media/webrtc/jsapi/RTCDTMFSender.h
new file mode 100644
index 0000000000..14be7eb6ee
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCDTMFSender.h
@@ -0,0 +1,78 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _RTCDTMFSender_h_
+#define _RTCDTMFSender_h_
+
+#include "MediaEventSource.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/RefPtr.h"
+#include "js/RootingAPI.h"
+#include "nsITimer.h"
+
+class nsPIDOMWindowInner;
+class nsITimer;
+
+namespace mozilla {
+class AudioSessionConduit;
+
+struct DtmfEvent {
+ DtmfEvent(int aPayloadType, int aPayloadFrequency, int aEventCode,
+ int aLengthMs)
+ : mPayloadType(aPayloadType),
+ mPayloadFrequency(aPayloadFrequency),
+ mEventCode(aEventCode),
+ mLengthMs(aLengthMs) {}
+ const int mPayloadType;
+ const int mPayloadFrequency;
+ const int mEventCode;
+ const int mLengthMs;
+};
+
+namespace dom {
+class RTCRtpTransceiver;
+
+class RTCDTMFSender : public DOMEventTargetHelper,
+ public nsITimerCallback,
+ public nsINamed {
+ public:
+ RTCDTMFSender(nsPIDOMWindowInner* aWindow, RTCRtpTransceiver* aTransceiver);
+
+ // nsISupports
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(RTCDTMFSender, DOMEventTargetHelper)
+
+ // webidl
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+ void SetPayloadType(int32_t aPayloadType, int32_t aPayloadFrequency);
+ void InsertDTMF(const nsAString& aTones, uint32_t aDuration,
+ uint32_t aInterToneGap, ErrorResult& aRv);
+ void GetToneBuffer(nsAString& aOutToneBuffer);
+ IMPL_EVENT_HANDLER(tonechange)
+
+ void StopPlayout();
+
+ MediaEventSource<DtmfEvent>& OnDtmfEvent() { return mDtmfEvent; }
+
+ private:
+ virtual ~RTCDTMFSender() = default;
+
+ void StartPlayout(uint32_t aDelay);
+
+ RefPtr<RTCRtpTransceiver> mTransceiver;
+ MediaEventProducer<DtmfEvent> mDtmfEvent;
+ Maybe<int32_t> mPayloadType;
+ Maybe<int32_t> mPayloadFrequency;
+ nsString mToneBuffer;
+ uint32_t mDuration = 0;
+ uint32_t mInterToneGap = 0;
+ nsCOMPtr<nsITimer> mSendTimer;
+};
+
+} // namespace dom
+} // namespace mozilla
+#endif // _RTCDTMFSender_h_
diff --git a/dom/media/webrtc/jsapi/RTCDtlsTransport.cpp b/dom/media/webrtc/jsapi/RTCDtlsTransport.cpp
new file mode 100644
index 0000000000..442e9c7a17
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCDtlsTransport.cpp
@@ -0,0 +1,69 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "RTCDtlsTransport.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/EventBinding.h"
+#include "mozilla/dom/RTCDtlsTransportBinding.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(RTCDtlsTransport, DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(RTCDtlsTransport, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(RTCDtlsTransport, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCDtlsTransport)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+RTCDtlsTransport::RTCDtlsTransport(nsPIDOMWindowInner* aWindow)
+ : DOMEventTargetHelper(aWindow), mState(RTCDtlsTransportState::New) {}
+
+JSObject* RTCDtlsTransport::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return RTCDtlsTransport_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void RTCDtlsTransport::UpdateState(TransportLayer::State aState) {
+ if (mState == RTCDtlsTransportState::Closed) {
+ return;
+ }
+
+ RTCDtlsTransportState newState = mState;
+ switch (aState) {
+ case TransportLayer::TS_NONE:
+ break;
+ case TransportLayer::TS_INIT:
+ break;
+ case TransportLayer::TS_CONNECTING:
+ newState = RTCDtlsTransportState::Connecting;
+ break;
+ case TransportLayer::TS_OPEN:
+ newState = RTCDtlsTransportState::Connected;
+ break;
+ case TransportLayer::TS_CLOSED:
+ newState = RTCDtlsTransportState::Closed;
+ break;
+ case TransportLayer::TS_ERROR:
+ newState = RTCDtlsTransportState::Failed;
+ break;
+ }
+
+ if (newState == mState) {
+ return;
+ }
+
+ mState = newState;
+
+ EventInit init;
+ init.mBubbles = false;
+ init.mCancelable = false;
+
+ RefPtr<Event> event = Event::Constructor(this, u"statechange"_ns, init);
+
+ DispatchTrustedEvent(event);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webrtc/jsapi/RTCDtlsTransport.h b/dom/media/webrtc/jsapi/RTCDtlsTransport.h
new file mode 100644
index 0000000000..74a4b5f618
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCDtlsTransport.h
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _RTCDtlsTransport_h_
+#define _RTCDtlsTransport_h_
+
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/RefPtr.h"
+#include "js/RootingAPI.h"
+#include "transport/transportlayer.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla::dom {
+
+enum class RTCDtlsTransportState : uint8_t;
+
+class RTCDtlsTransport : public DOMEventTargetHelper {
+ public:
+ explicit RTCDtlsTransport(nsPIDOMWindowInner* aWindow);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(RTCDtlsTransport,
+ DOMEventTargetHelper)
+
+ // webidl
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+ IMPL_EVENT_HANDLER(statechange)
+ RTCDtlsTransportState State() const { return mState; }
+
+ void UpdateState(TransportLayer::State aState);
+
+ private:
+ virtual ~RTCDtlsTransport() = default;
+
+ RTCDtlsTransportState mState;
+};
+
+} // namespace mozilla::dom
+#endif // _RTCDtlsTransport_h_
diff --git a/dom/media/webrtc/jsapi/RTCRtpReceiver.cpp b/dom/media/webrtc/jsapi/RTCRtpReceiver.cpp
new file mode 100644
index 0000000000..136aa5142f
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCRtpReceiver.cpp
@@ -0,0 +1,942 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "RTCRtpReceiver.h"
+#include "PeerConnectionImpl.h"
+#include "mozilla/dom/RTCRtpCapabilitiesBinding.h"
+#include "transport/logging.h"
+#include "mozilla/dom/MediaStreamTrack.h"
+#include "mozilla/dom/Promise.h"
+#include "nsPIDOMWindow.h"
+#include "PrincipalHandle.h"
+#include "nsIPrincipal.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/NullPrincipal.h"
+#include "MediaTrackGraph.h"
+#include "RemoteTrackSource.h"
+#include "libwebrtcglue/RtpRtcpConfig.h"
+#include "nsString.h"
+#include "mozilla/dom/AudioStreamTrack.h"
+#include "mozilla/dom/VideoStreamTrack.h"
+#include "MediaTransportHandler.h"
+#include "jsep/JsepTransceiver.h"
+#include "mozilla/dom/RTCRtpReceiverBinding.h"
+#include "mozilla/dom/RTCRtpSourcesBinding.h"
+#include "RTCStatsReport.h"
+#include "mozilla/Preferences.h"
+#include "PeerConnectionCtx.h"
+#include "RTCRtpTransceiver.h"
+#include "libwebrtcglue/AudioConduit.h"
+#include "call/call.h"
+
+namespace mozilla::dom {
+
+LazyLogModule gReceiverLog("RTCRtpReceiver");
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(RTCRtpReceiver)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(RTCRtpReceiver)
+ // We do not do anything here, we wait for BreakCycles to be called
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(RTCRtpReceiver)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow, mPc, mTransceiver, mTrack,
+ mTrackSource)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(RTCRtpReceiver)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(RTCRtpReceiver)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCRtpReceiver)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+static PrincipalHandle GetPrincipalHandle(nsPIDOMWindowInner* aWindow,
+ PrincipalPrivacy aPrivacy) {
+ // Set the principal used for creating the tracks. This makes the track
+ // data (audio/video samples) accessible to the receiving page. We're
+ // only certain that privacy hasn't been requested if we're connected.
+ nsCOMPtr<nsIScriptObjectPrincipal> winPrincipal = do_QueryInterface(aWindow);
+ RefPtr<nsIPrincipal> principal = winPrincipal->GetPrincipal();
+ if (NS_WARN_IF(!principal)) {
+ principal = NullPrincipal::CreateWithoutOriginAttributes();
+ } else if (aPrivacy == PrincipalPrivacy::Private) {
+ principal = NullPrincipal::CreateWithInheritedAttributes(principal);
+ }
+ return MakePrincipalHandle(principal);
+}
+
+#define INIT_CANONICAL(name, val) \
+ name(AbstractThread::MainThread(), val, \
+ "RTCRtpReceiver::" #name " (Canonical)")
+
+RTCRtpReceiver::RTCRtpReceiver(
+ nsPIDOMWindowInner* aWindow, PrincipalPrivacy aPrivacy,
+ PeerConnectionImpl* aPc, MediaTransportHandler* aTransportHandler,
+ AbstractThread* aCallThread, nsISerialEventTarget* aStsThread,
+ MediaSessionConduit* aConduit, RTCRtpTransceiver* aTransceiver,
+ const TrackingId& aTrackingId)
+ : mWatchManager(this, AbstractThread::MainThread()),
+ mWindow(aWindow),
+ mPc(aPc),
+ mCallThread(aCallThread),
+ mStsThread(aStsThread),
+ mTransportHandler(aTransportHandler),
+ mTransceiver(aTransceiver),
+ INIT_CANONICAL(mSsrc, 0),
+ INIT_CANONICAL(mVideoRtxSsrc, 0),
+ INIT_CANONICAL(mLocalRtpExtensions, RtpExtList()),
+ INIT_CANONICAL(mAudioCodecs, std::vector<AudioCodecConfig>()),
+ INIT_CANONICAL(mVideoCodecs, std::vector<VideoCodecConfig>()),
+ INIT_CANONICAL(mVideoRtpRtcpConfig, Nothing()),
+ INIT_CANONICAL(mReceiving, false) {
+ PrincipalHandle principalHandle = GetPrincipalHandle(aWindow, aPrivacy);
+ const bool isAudio = aConduit->type() == MediaSessionConduit::AUDIO;
+
+ MediaTrackGraph* graph = MediaTrackGraph::GetInstance(
+ isAudio ? MediaTrackGraph::AUDIO_THREAD_DRIVER
+ : MediaTrackGraph::SYSTEM_THREAD_DRIVER,
+ aWindow, MediaTrackGraph::REQUEST_DEFAULT_SAMPLE_RATE,
+ MediaTrackGraph::DEFAULT_OUTPUT_DEVICE);
+
+ if (isAudio) {
+ auto* source = graph->CreateSourceTrack(MediaSegment::AUDIO);
+ mTrackSource = MakeAndAddRef<RemoteTrackSource>(
+ source, this, principalHandle, u"remote audio"_ns, aTrackingId);
+ mTrack = MakeAndAddRef<AudioStreamTrack>(aWindow, source, mTrackSource);
+ mPipeline = MakeAndAddRef<MediaPipelineReceiveAudio>(
+ mPc->GetHandle(), aTransportHandler, aCallThread, mStsThread.get(),
+ *aConduit->AsAudioSessionConduit(), mTrackSource->Stream(), aTrackingId,
+ principalHandle, aPrivacy);
+ } else {
+ auto* source = graph->CreateSourceTrack(MediaSegment::VIDEO);
+ mTrackSource = MakeAndAddRef<RemoteTrackSource>(
+ source, this, principalHandle, u"remote video"_ns, aTrackingId);
+ mTrack = MakeAndAddRef<VideoStreamTrack>(aWindow, source, mTrackSource);
+ mPipeline = MakeAndAddRef<MediaPipelineReceiveVideo>(
+ mPc->GetHandle(), aTransportHandler, aCallThread, mStsThread.get(),
+ *aConduit->AsVideoSessionConduit(), mTrackSource->Stream(), aTrackingId,
+ principalHandle, aPrivacy);
+ }
+
+ mPipeline->InitControl(this);
+
+ // Spec says remote tracks start out muted.
+ mTrackSource->SetMuted(true);
+
+ // Until Bug 1232234 is fixed, we'll get extra RTCP BYES during renegotiation,
+ // so we'll disable muting on RTCP BYE and timeout for now.
+ if (Preferences::GetBool("media.peerconnection.mute_on_bye_or_timeout",
+ false)) {
+ mRtcpByeListener = aConduit->RtcpByeEvent().Connect(
+ GetMainThreadSerialEventTarget(), this, &RTCRtpReceiver::OnRtcpBye);
+ mRtcpTimeoutListener = aConduit->RtcpTimeoutEvent().Connect(
+ GetMainThreadSerialEventTarget(), this, &RTCRtpReceiver::OnRtcpTimeout);
+ }
+
+ mWatchManager.Watch(mReceiveTrackMute,
+ &RTCRtpReceiver::UpdateReceiveTrackMute);
+}
+
+#undef INIT_CANONICAL
+
+RTCRtpReceiver::~RTCRtpReceiver() { MOZ_ASSERT(!mPipeline); }
+
+JSObject* RTCRtpReceiver::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return RTCRtpReceiver_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+RTCDtlsTransport* RTCRtpReceiver::GetTransport() const {
+ if (!mTransceiver) {
+ return nullptr;
+ }
+ return mTransceiver->GetDtlsTransport();
+}
+
+void RTCRtpReceiver::GetCapabilities(
+ const GlobalObject&, const nsAString& aKind,
+ Nullable<dom::RTCRtpCapabilities>& aResult) {
+ PeerConnectionImpl::GetCapabilities(aKind, aResult, sdp::Direction::kRecv);
+}
+
+already_AddRefed<Promise> RTCRtpReceiver::GetStats(ErrorResult& aError) {
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);
+ RefPtr<Promise> promise = Promise::Create(global, aError);
+ if (NS_WARN_IF(aError.Failed())) {
+ return nullptr;
+ }
+
+ if (NS_WARN_IF(!mTransceiver)) {
+ // TODO(bug 1056433): When we stop nulling this out when the PC is closed
+ // (or when the transceiver is stopped), we can remove this code. We
+ // resolve instead of reject in order to make this eventual change in
+ // behavior a little smaller.
+ promise->MaybeResolve(new RTCStatsReport(mWindow));
+ return promise.forget();
+ }
+
+ mTransceiver->ChainToDomPromiseWithCodecStats(GetStatsInternal(), promise);
+ return promise.forget();
+}
+
+nsTArray<RefPtr<RTCStatsPromise>> RTCRtpReceiver::GetStatsInternal(
+ bool aSkipIceStats) {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsTArray<RefPtr<RTCStatsPromise>> promises(3);
+
+ if (!mPipeline) {
+ return promises;
+ }
+
+ if (!mHaveStartedReceiving) {
+ return promises;
+ }
+
+ nsString recvTrackId;
+ MOZ_ASSERT(mTrack);
+ if (mTrack) {
+ mTrack->GetId(recvTrackId);
+ }
+
+ {
+ // Add bandwidth estimation stats
+ promises.AppendElement(InvokeAsync(
+ mCallThread, __func__,
+ [conduit = mPipeline->mConduit, recvTrackId]() mutable {
+ auto report = MakeUnique<dom::RTCStatsCollection>();
+ const Maybe<webrtc::Call::Stats> stats = conduit->GetCallStats();
+ stats.apply([&](const auto& aStats) {
+ dom::RTCBandwidthEstimationInternal bw;
+ bw.mTrackIdentifier = recvTrackId;
+ bw.mSendBandwidthBps.Construct(aStats.send_bandwidth_bps / 8);
+ bw.mMaxPaddingBps.Construct(aStats.max_padding_bitrate_bps / 8);
+ bw.mReceiveBandwidthBps.Construct(aStats.recv_bandwidth_bps / 8);
+ bw.mPacerDelayMs.Construct(aStats.pacer_delay_ms);
+ if (aStats.rtt_ms >= 0) {
+ bw.mRttMs.Construct(aStats.rtt_ms);
+ }
+ if (!report->mBandwidthEstimations.AppendElement(std::move(bw),
+ fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ });
+ return RTCStatsPromise::CreateAndResolve(std::move(report), __func__);
+ }));
+ }
+
+ promises.AppendElement(
+ InvokeAsync(
+ mCallThread, __func__,
+ [pipeline = mPipeline, recvTrackId] {
+ auto report = MakeUnique<dom::RTCStatsCollection>();
+ auto asAudio = pipeline->mConduit->AsAudioSessionConduit();
+ auto asVideo = pipeline->mConduit->AsVideoSessionConduit();
+
+ nsString kind = asVideo.isNothing() ? u"audio"_ns : u"video"_ns;
+ nsString idstr = kind + u"_"_ns;
+ idstr.AppendInt(static_cast<uint32_t>(pipeline->Level()));
+
+ Maybe<uint32_t> ssrc = pipeline->mConduit->GetRemoteSSRC();
+
+ // Add frame history
+ asVideo.apply([&](const auto& conduit) {
+ if (conduit->AddFrameHistory(&report->mVideoFrameHistories)) {
+ auto& history = report->mVideoFrameHistories.LastElement();
+ history.mTrackIdentifier = recvTrackId;
+ }
+ });
+
+ // TODO(@@NG):ssrcs handle Conduits having multiple stats at the
+ // same level.
+ // This is pending spec work.
+ // Gather pipeline stats.
+ nsString localId = u"inbound_rtp_"_ns + idstr;
+ nsString remoteId;
+
+ auto constructCommonRemoteOutboundRtpStats =
+ [&](RTCRemoteOutboundRtpStreamStats& aRemote,
+ const DOMHighResTimeStamp& aTimestamp) {
+ remoteId = u"inbound_rtcp_"_ns + idstr;
+ aRemote.mTimestamp.Construct(aTimestamp);
+ aRemote.mId.Construct(remoteId);
+ aRemote.mType.Construct(RTCStatsType::Remote_outbound_rtp);
+ ssrc.apply([&](uint32_t aSsrc) { aRemote.mSsrc = aSsrc; });
+ aRemote.mKind = kind;
+ aRemote.mMediaType.Construct(
+ kind); // mediaType is the old name for kind.
+ aRemote.mLocalId.Construct(localId);
+ };
+
+ auto constructCommonInboundRtpStats =
+ [&](RTCInboundRtpStreamStats& aLocal) {
+ aLocal.mTrackIdentifier = recvTrackId;
+ aLocal.mTimestamp.Construct(
+ pipeline->GetTimestampMaker().GetNow().ToDom());
+ aLocal.mId.Construct(localId);
+ aLocal.mType.Construct(RTCStatsType::Inbound_rtp);
+ ssrc.apply([&](uint32_t aSsrc) { aLocal.mSsrc = aSsrc; });
+ aLocal.mKind = kind;
+ aLocal.mMediaType.Construct(
+ kind); // mediaType is the old name for kind.
+ if (remoteId.Length()) {
+ aLocal.mRemoteId.Construct(remoteId);
+ }
+ };
+
+ asAudio.apply([&](auto& aConduit) {
+ Maybe<webrtc::AudioReceiveStreamInterface::Stats> audioStats =
+ aConduit->GetReceiverStats();
+ if (audioStats.isNothing()) {
+ return;
+ }
+
+ if (!audioStats->last_packet_received_timestamp_ms) {
+ // By spec: "The lifetime of all RTP monitored objects starts
+ // when the RTP stream is first used: When the first RTP packet
+ // is sent or received on the SSRC it represents"
+ return;
+ }
+
+ // First, fill in remote stat with rtcp sender data, if present.
+ if (audioStats->last_sender_report_timestamp_ms) {
+ RTCRemoteOutboundRtpStreamStats remote;
+ constructCommonRemoteOutboundRtpStats(
+ remote,
+ RTCStatsTimestamp::FromNtp(
+ aConduit->GetTimestampMaker(),
+ webrtc::Timestamp::Millis(
+ *audioStats->last_sender_report_timestamp_ms) +
+ webrtc::TimeDelta::Seconds(webrtc::kNtpJan1970))
+ .ToDom());
+ remote.mPacketsSent.Construct(
+ audioStats->sender_reports_packets_sent);
+ remote.mBytesSent.Construct(
+ audioStats->sender_reports_bytes_sent);
+ remote.mRemoteTimestamp.Construct(
+ *audioStats->last_sender_report_remote_timestamp_ms);
+ if (!report->mRemoteOutboundRtpStreamStats.AppendElement(
+ std::move(remote), fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ }
+
+ // Then, fill in local side (with cross-link to remote only if
+ // present)
+ RTCInboundRtpStreamStats local;
+ constructCommonInboundRtpStats(local);
+ local.mJitter.Construct(audioStats->jitter_ms / 1000.0);
+ local.mPacketsLost.Construct(audioStats->packets_lost);
+ local.mPacketsReceived.Construct(audioStats->packets_rcvd);
+ local.mPacketsDiscarded.Construct(audioStats->packets_discarded);
+ local.mBytesReceived.Construct(audioStats->payload_bytes_rcvd);
+ // Always missing from libwebrtc stats
+ // if (audioStats->estimated_playout_ntp_timestamp_ms) {
+ // local.mEstimatedPlayoutTimestamp.Construct(
+ // RTCStatsTimestamp::FromNtp(
+ // aConduit->GetTimestampMaker(),
+ // webrtc::Timestamp::Millis(
+ // *audioStats->estimated_playout_ntp_timestamp_ms))
+ // .ToDom());
+ // }
+ local.mJitterBufferDelay.Construct(
+ audioStats->jitter_buffer_delay_seconds);
+ local.mJitterBufferEmittedCount.Construct(
+ audioStats->jitter_buffer_emitted_count);
+ local.mTotalSamplesReceived.Construct(
+ audioStats->total_samples_received);
+ local.mConcealedSamples.Construct(audioStats->concealed_samples);
+ local.mSilentConcealedSamples.Construct(
+ audioStats->silent_concealed_samples);
+ if (audioStats->last_packet_received_timestamp_ms) {
+ local.mLastPacketReceivedTimestamp.Construct(
+ RTCStatsTimestamp::FromNtp(
+ aConduit->GetTimestampMaker(),
+ webrtc::Timestamp::Millis(
+ *audioStats->last_packet_received_timestamp_ms) +
+ webrtc::TimeDelta::Seconds(webrtc::kNtpJan1970))
+ .ToDom());
+ }
+ local.mHeaderBytesReceived.Construct(
+ audioStats->header_and_padding_bytes_rcvd);
+ local.mFecPacketsReceived.Construct(
+ audioStats->fec_packets_received);
+ local.mFecPacketsDiscarded.Construct(
+ audioStats->fec_packets_discarded);
+ local.mConcealmentEvents.Construct(
+ audioStats->concealment_events);
+
+ local.mInsertedSamplesForDeceleration.Construct(
+ audioStats->inserted_samples_for_deceleration);
+ local.mRemovedSamplesForAcceleration.Construct(
+ audioStats->removed_samples_for_acceleration);
+ if (audioStats->audio_level >= 0 &&
+ audioStats->audio_level <= 32767) {
+ local.mAudioLevel.Construct(audioStats->audio_level / 32767.0);
+ }
+ local.mTotalAudioEnergy.Construct(
+ audioStats->total_output_energy);
+ local.mTotalSamplesDuration.Construct(
+ audioStats->total_output_duration);
+
+ if (!report->mInboundRtpStreamStats.AppendElement(
+ std::move(local), fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ });
+
+ asVideo.apply([&](auto& aConduit) {
+ Maybe<webrtc::VideoReceiveStreamInterface::Stats> videoStats =
+ aConduit->GetReceiverStats();
+ if (videoStats.isNothing()) {
+ return;
+ }
+
+ if (!videoStats->rtp_stats.last_packet_received_timestamp_ms) {
+ // By spec: "The lifetime of all RTP monitored objects starts
+ // when the RTP stream is first used: When the first RTP packet
+ // is sent or received on the SSRC it represents"
+ return;
+ }
+
+ // First, fill in remote stat with rtcp sender data, if present.
+ if (videoStats->rtcp_sender_ntp_timestamp_ms) {
+ RTCRemoteOutboundRtpStreamStats remote;
+ constructCommonRemoteOutboundRtpStats(
+ remote, RTCStatsTimestamp::FromNtp(
+ aConduit->GetTimestampMaker(),
+ webrtc::Timestamp::Millis(
+ videoStats->rtcp_sender_ntp_timestamp_ms))
+ .ToDom());
+ remote.mPacketsSent.Construct(
+ videoStats->rtcp_sender_packets_sent);
+ remote.mBytesSent.Construct(
+ videoStats->rtcp_sender_octets_sent);
+ remote.mRemoteTimestamp.Construct(
+ (webrtc::TimeDelta::Millis(
+ videoStats->rtcp_sender_remote_ntp_timestamp_ms) -
+ webrtc::TimeDelta::Seconds(webrtc::kNtpJan1970))
+ .ms());
+ if (!report->mRemoteOutboundRtpStreamStats.AppendElement(
+ std::move(remote), fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ }
+
+ // Then, fill in local side (with cross-link to remote only if
+ // present)
+ RTCInboundRtpStreamStats local;
+ constructCommonInboundRtpStats(local);
+ local.mJitter.Construct(
+ static_cast<double>(videoStats->rtp_stats.jitter) /
+ webrtc::kVideoPayloadTypeFrequency);
+ local.mPacketsLost.Construct(videoStats->rtp_stats.packets_lost);
+ local.mPacketsReceived.Construct(
+ videoStats->rtp_stats.packet_counter.packets);
+ local.mPacketsDiscarded.Construct(videoStats->packets_discarded);
+ local.mDiscardedPackets.Construct(videoStats->packets_discarded);
+ local.mBytesReceived.Construct(
+ videoStats->rtp_stats.packet_counter.payload_bytes);
+
+ // Fill in packet type statistics
+ local.mNackCount.Construct(
+ videoStats->rtcp_packet_type_counts.nack_packets);
+ local.mFirCount.Construct(
+ videoStats->rtcp_packet_type_counts.fir_packets);
+ local.mPliCount.Construct(
+ videoStats->rtcp_packet_type_counts.pli_packets);
+
+ // Lastly, fill in video decoder stats
+ local.mFramesDecoded.Construct(videoStats->frames_decoded);
+
+ local.mFramesPerSecond.Construct(videoStats->decode_frame_rate);
+ local.mFrameWidth.Construct(videoStats->width);
+ local.mFrameHeight.Construct(videoStats->height);
+ // XXX: key_frames + delta_frames may undercount frames because
+ // they were dropped in FrameBuffer::InsertFrame. (bug 1766553)
+ local.mFramesReceived.Construct(
+ videoStats->frame_counts.key_frames +
+ videoStats->frame_counts.delta_frames);
+ local.mJitterBufferDelay.Construct(
+ videoStats->jitter_buffer_delay_seconds);
+ local.mJitterBufferEmittedCount.Construct(
+ videoStats->jitter_buffer_emitted_count);
+
+ if (videoStats->qp_sum) {
+ local.mQpSum.Construct(videoStats->qp_sum.value());
+ }
+ local.mTotalDecodeTime.Construct(
+ double(videoStats->total_decode_time.ms()) / 1000);
+ local.mTotalInterFrameDelay.Construct(
+ videoStats->total_inter_frame_delay);
+ local.mTotalSquaredInterFrameDelay.Construct(
+ videoStats->total_squared_inter_frame_delay);
+ if (videoStats->rtp_stats.last_packet_received_timestamp_ms) {
+ local.mLastPacketReceivedTimestamp.Construct(
+ RTCStatsTimestamp::FromNtp(
+ aConduit->GetTimestampMaker(),
+ webrtc::Timestamp::Millis(
+ *videoStats->rtp_stats
+ .last_packet_received_timestamp_ms) +
+ webrtc::TimeDelta::Seconds(webrtc::kNtpJan1970))
+ .ToDom());
+ }
+ local.mHeaderBytesReceived.Construct(
+ videoStats->rtp_stats.packet_counter.header_bytes +
+ videoStats->rtp_stats.packet_counter.padding_bytes);
+ local.mTotalProcessingDelay.Construct(
+ videoStats->total_processing_delay.seconds<double>());
+ /*
+ * Potential new stats that are now available upstream
+ .if (videoStats->estimated_playout_ntp_timestamp_ms) {
+ local.mEstimatedPlayoutTimestamp.Construct(
+ RTCStatsTimestamp::FromNtp(
+ aConduit->GetTimestampMaker(),
+ webrtc::Timestamp::Millis(
+ *videoStats->estimated_playout_ntp_timestamp_ms))
+ .ToDom());
+ }
+ */
+ // Not including frames dropped in the rendering pipe, which
+ // is not of webrtc's concern anyway?!
+ local.mFramesDropped.Construct(videoStats->frames_dropped);
+ if (!report->mInboundRtpStreamStats.AppendElement(
+ std::move(local), fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ });
+ return RTCStatsPromise::CreateAndResolve(std::move(report),
+ __func__);
+ })
+ ->Then(
+ mStsThread, __func__,
+ [pipeline = mPipeline](UniquePtr<RTCStatsCollection> aReport) {
+ // Fill in Contributing Source statistics
+ if (!aReport->mInboundRtpStreamStats.IsEmpty() &&
+ aReport->mInboundRtpStreamStats[0].mId.WasPassed()) {
+ pipeline->GetContributingSourceStats(
+ aReport->mInboundRtpStreamStats[0].mId.Value(),
+ aReport->mRtpContributingSourceStats);
+ }
+ return RTCStatsPromise::CreateAndResolve(std::move(aReport),
+ __func__);
+ },
+ [] {
+ MOZ_CRASH("Unexpected reject");
+ return RTCStatsPromise::CreateAndReject(NS_ERROR_UNEXPECTED,
+ __func__);
+ }));
+
+ if (!aSkipIceStats && GetJsepTransceiver().mTransport.mComponents) {
+ promises.AppendElement(mTransportHandler->GetIceStats(
+ GetJsepTransceiver().mTransport.mTransportId,
+ mPipeline->GetTimestampMaker().GetNow().ToDom()));
+ }
+
+ return promises;
+}
+
+void RTCRtpReceiver::SetJitterBufferTarget(
+ const Nullable<DOMHighResTimeStamp>& aTargetMs, ErrorResult& aError) {
+ // Spec says jitter buffer target cannot be negative or larger than 4000
+ // milliseconds and to throw RangeError if it is. If an invalid value is
+ // received we return early to preserve the current JitterBufferTarget
+ // internal slot and jitter buffer values.
+ if (mPipeline && mPipeline->mConduit) {
+ if (!aTargetMs.IsNull() &&
+ (aTargetMs.Value() < 0.0 || aTargetMs.Value() > 4000.0)) {
+ aError.ThrowRangeError<MSG_VALUE_OUT_OF_RANGE>("jitterBufferTarget");
+ return;
+ }
+
+ mJitterBufferTarget.reset();
+
+ if (!aTargetMs.IsNull()) {
+ mJitterBufferTarget = Some(aTargetMs.Value());
+ }
+ // If aJitterBufferTarget is null then we are resetting the jitter buffer so
+ // pass the default target of 0.0.
+ mPipeline->mConduit->SetJitterBufferTarget(
+ mJitterBufferTarget.valueOr(0.0));
+ }
+}
+
+void RTCRtpReceiver::GetContributingSources(
+ nsTArray<RTCRtpContributingSource>& aSources) {
+ // Duplicate code...
+ if (mPipeline && mPipeline->mConduit) {
+ nsTArray<dom::RTCRtpSourceEntry> sources;
+ mPipeline->mConduit->GetRtpSources(sources);
+ sources.RemoveElementsBy([](const dom::RTCRtpSourceEntry& aEntry) {
+ return aEntry.mSourceType != dom::RTCRtpSourceEntryType::Contributing;
+ });
+ aSources.ReplaceElementsAt(0, aSources.Length(), sources.Elements(),
+ sources.Length());
+ }
+}
+
+void RTCRtpReceiver::GetSynchronizationSources(
+ nsTArray<dom::RTCRtpSynchronizationSource>& aSources) {
+ // Duplicate code...
+ if (mPipeline && mPipeline->mConduit) {
+ nsTArray<dom::RTCRtpSourceEntry> sources;
+ mPipeline->mConduit->GetRtpSources(sources);
+ sources.RemoveElementsBy([](const dom::RTCRtpSourceEntry& aEntry) {
+ return aEntry.mSourceType != dom::RTCRtpSourceEntryType::Synchronization;
+ });
+ aSources.ReplaceElementsAt(0, aSources.Length(), sources.Elements(),
+ sources.Length());
+ }
+}
+
+nsPIDOMWindowInner* RTCRtpReceiver::GetParentObject() const { return mWindow; }
+
+void RTCRtpReceiver::Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+ mWatchManager.Shutdown();
+ if (mPipeline) {
+ mPipeline->Shutdown();
+ mPipeline = nullptr;
+ }
+ if (mTrackSource) {
+ mTrackSource->Destroy();
+ }
+ mCallThread = nullptr;
+ mRtcpByeListener.DisconnectIfExists();
+ mRtcpTimeoutListener.DisconnectIfExists();
+ mUnmuteListener.DisconnectIfExists();
+}
+
+void RTCRtpReceiver::BreakCycles() {
+ mWindow = nullptr;
+ mPc = nullptr;
+ mTrack = nullptr;
+ mTrackSource = nullptr;
+}
+
+void RTCRtpReceiver::UpdateTransport() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mHaveSetupTransport) {
+ mPipeline->SetLevel(GetJsepTransceiver().GetLevel());
+ mHaveSetupTransport = true;
+ }
+
+ UniquePtr<MediaPipelineFilter> filter;
+
+ auto const& details = GetJsepTransceiver().mRecvTrack.GetNegotiatedDetails();
+ if (GetJsepTransceiver().HasBundleLevel() && details) {
+ std::vector<webrtc::RtpExtension> extmaps;
+ details->ForEachRTPHeaderExtension(
+ [&extmaps](const SdpExtmapAttributeList::Extmap& extmap) {
+ extmaps.emplace_back(extmap.extensionname, extmap.entry);
+ });
+ filter = MakeUnique<MediaPipelineFilter>(extmaps);
+
+ // Add remote SSRCs so we can distinguish which RTP packets actually
+ // belong to this pipeline (also RTCP sender reports).
+ for (uint32_t ssrc : GetJsepTransceiver().mRecvTrack.GetSsrcs()) {
+ filter->AddRemoteSSRC(ssrc);
+ }
+ for (uint32_t ssrc : GetJsepTransceiver().mRecvTrack.GetRtxSsrcs()) {
+ filter->AddRemoteSSRC(ssrc);
+ }
+ auto mid = Maybe<std::string>();
+ if (GetMid() != "") {
+ mid = Some(GetMid());
+ }
+ filter->SetRemoteMediaStreamId(mid);
+
+ // Add unique payload types as a last-ditch fallback
+ auto uniquePts = GetJsepTransceiver()
+ .mRecvTrack.GetNegotiatedDetails()
+ ->GetUniquePayloadTypes();
+ for (unsigned char& uniquePt : uniquePts) {
+ filter->AddUniquePT(uniquePt);
+ }
+ }
+
+ mPipeline->UpdateTransport_m(GetJsepTransceiver().mTransport.mTransportId,
+ std::move(filter));
+}
+
+void RTCRtpReceiver::UpdateConduit() {
+ if (mPipeline->mConduit->type() == MediaSessionConduit::VIDEO) {
+ UpdateVideoConduit();
+ } else {
+ UpdateAudioConduit();
+ }
+
+ if ((mReceiving = mTransceiver->IsReceiving())) {
+ mHaveStartedReceiving = true;
+ }
+}
+
+void RTCRtpReceiver::UpdateVideoConduit() {
+ RefPtr<VideoSessionConduit> conduit =
+ *mPipeline->mConduit->AsVideoSessionConduit();
+
+ // NOTE(pkerr) - this is new behavior. Needed because the
+ // CreateVideoReceiveStream method of the Call API will assert (in debug)
+ // and fail if a value is not provided for the remote_ssrc that will be used
+ // by the far-end sender.
+ if (!GetJsepTransceiver().mRecvTrack.GetSsrcs().empty()) {
+ MOZ_LOG(gReceiverLog, LogLevel::Debug,
+ ("%s[%s]: %s Setting remote SSRC %u", mPc->GetHandle().c_str(),
+ GetMid().c_str(), __FUNCTION__,
+ GetJsepTransceiver().mRecvTrack.GetSsrcs().front()));
+ uint32_t rtxSsrc =
+ GetJsepTransceiver().mRecvTrack.GetRtxSsrcs().empty()
+ ? 0
+ : GetJsepTransceiver().mRecvTrack.GetRtxSsrcs().front();
+ mSsrc = GetJsepTransceiver().mRecvTrack.GetSsrcs().front();
+ mVideoRtxSsrc = rtxSsrc;
+
+ // TODO (bug 1423041) once we pay attention to receiving MID's in RTP
+ // packets (see bug 1405495) we could make this depending on the presence of
+ // MID in the RTP packets instead of relying on the signaling.
+ // In any case, do not disable SSRC changes if no SSRCs were negotiated
+ if (GetJsepTransceiver().HasBundleLevel() &&
+ (!GetJsepTransceiver().mRecvTrack.GetNegotiatedDetails() ||
+ !GetJsepTransceiver().mRecvTrack.GetNegotiatedDetails()->GetExt(
+ webrtc::RtpExtension::kMidUri))) {
+ mCallThread->Dispatch(
+ NewRunnableMethod("VideoSessionConduit::DisableSsrcChanges", conduit,
+ &VideoSessionConduit::DisableSsrcChanges));
+ }
+ }
+
+ if (GetJsepTransceiver().mRecvTrack.GetNegotiatedDetails() &&
+ GetJsepTransceiver().mRecvTrack.GetActive()) {
+ const auto& details(
+ *GetJsepTransceiver().mRecvTrack.GetNegotiatedDetails());
+
+ {
+ std::vector<webrtc::RtpExtension> extmaps;
+ // @@NG read extmap from track
+ details.ForEachRTPHeaderExtension(
+ [&extmaps](const SdpExtmapAttributeList::Extmap& extmap) {
+ extmaps.emplace_back(extmap.extensionname, extmap.entry);
+ });
+ mLocalRtpExtensions = extmaps;
+ }
+
+ std::vector<VideoCodecConfig> configs;
+ RTCRtpTransceiver::NegotiatedDetailsToVideoCodecConfigs(details, &configs);
+ if (configs.empty()) {
+ // TODO: Are we supposed to plumb this error back to JS? This does not
+ // seem like a failure to set an answer, it just means that codec
+ // negotiation failed. For now, we're just doing the same thing we do
+ // if negotiation as a whole failed.
+ MOZ_LOG(gReceiverLog, LogLevel::Error,
+ ("%s[%s]: %s No video codecs were negotiated (recv).",
+ mPc->GetHandle().c_str(), GetMid().c_str(), __FUNCTION__));
+ return;
+ }
+
+ mVideoCodecs = configs;
+ mVideoRtpRtcpConfig = Some(details.GetRtpRtcpConfig());
+ }
+}
+
+void RTCRtpReceiver::UpdateAudioConduit() {
+ RefPtr<AudioSessionConduit> conduit =
+ *mPipeline->mConduit->AsAudioSessionConduit();
+
+ if (!GetJsepTransceiver().mRecvTrack.GetSsrcs().empty()) {
+ MOZ_LOG(gReceiverLog, LogLevel::Debug,
+ ("%s[%s]: %s Setting remote SSRC %u", mPc->GetHandle().c_str(),
+ GetMid().c_str(), __FUNCTION__,
+ GetJsepTransceiver().mRecvTrack.GetSsrcs().front()));
+ mSsrc = GetJsepTransceiver().mRecvTrack.GetSsrcs().front();
+
+ // TODO (bug 1423041) once we pay attention to receiving MID's in RTP
+ // packets (see bug 1405495) we could make this depending on the presence of
+ // MID in the RTP packets instead of relying on the signaling.
+ // In any case, do not disable SSRC changes if no SSRCs were negotiated
+ if (GetJsepTransceiver().HasBundleLevel() &&
+ (!GetJsepTransceiver().mRecvTrack.GetNegotiatedDetails() ||
+ !GetJsepTransceiver().mRecvTrack.GetNegotiatedDetails()->GetExt(
+ webrtc::RtpExtension::kMidUri))) {
+ mCallThread->Dispatch(
+ NewRunnableMethod("AudioSessionConduit::DisableSsrcChanges", conduit,
+ &AudioSessionConduit::DisableSsrcChanges));
+ }
+ }
+
+ if (GetJsepTransceiver().mRecvTrack.GetNegotiatedDetails() &&
+ GetJsepTransceiver().mRecvTrack.GetActive()) {
+ const auto& details(
+ *GetJsepTransceiver().mRecvTrack.GetNegotiatedDetails());
+ std::vector<AudioCodecConfig> configs;
+ RTCRtpTransceiver::NegotiatedDetailsToAudioCodecConfigs(details, &configs);
+ if (configs.empty()) {
+ // TODO: Are we supposed to plumb this error back to JS? This does not
+ // seem like a failure to set an answer, it just means that codec
+ // negotiation failed. For now, we're just doing the same thing we do
+ // if negotiation as a whole failed.
+ MOZ_LOG(gReceiverLog, LogLevel::Error,
+ ("%s[%s]: %s No audio codecs were negotiated (recv)",
+ mPc->GetHandle().c_str(), GetMid().c_str(), __FUNCTION__));
+ return;
+ }
+
+ // Ensure conduit knows about extensions prior to creating streams
+ {
+ std::vector<webrtc::RtpExtension> extmaps;
+ // @@NG read extmap from track
+ details.ForEachRTPHeaderExtension(
+ [&extmaps](const SdpExtmapAttributeList::Extmap& extmap) {
+ extmaps.emplace_back(extmap.extensionname, extmap.entry);
+ });
+ mLocalRtpExtensions = extmaps;
+ }
+
+ mAudioCodecs = configs;
+ }
+}
+
+void RTCRtpReceiver::Stop() {
+ MOZ_ASSERT(mTransceiver->Stopped());
+ mReceiving = false;
+}
+
+bool RTCRtpReceiver::HasTrack(const dom::MediaStreamTrack* aTrack) const {
+ return !aTrack || (mTrack == aTrack);
+}
+
+void RTCRtpReceiver::SyncFromJsep(const JsepTransceiver& aJsepTransceiver) {
+ if (!mPipeline) {
+ return;
+ }
+
+ // Spec says we set [[Receptive]] to true on sLD(sendrecv/recvonly), and to
+ // false on sRD(recvonly/inactive), sLD(sendonly/inactive), or when stop()
+ // is called.
+ bool wasReceptive = mReceptive;
+ mReceptive = aJsepTransceiver.mRecvTrack.GetReceptive();
+ if (!wasReceptive && mReceptive) {
+ mUnmuteListener = mPipeline->mConduit->RtpPacketEvent().Connect(
+ GetMainThreadSerialEventTarget(), this, &RTCRtpReceiver::OnRtpPacket);
+ } else if (wasReceptive && !mReceptive) {
+ mUnmuteListener.DisconnectIfExists();
+ }
+}
+
+void RTCRtpReceiver::SyncToJsep(JsepTransceiver& aJsepTransceiver) const {}
+
+void RTCRtpReceiver::UpdateStreams(StreamAssociationChanges* aChanges) {
+ // We don't sort and use set_difference, because we need to report the
+ // added/removed streams in the order that they appear in the SDP.
+ std::set<std::string> newIds(
+ GetJsepTransceiver().mRecvTrack.GetStreamIds().begin(),
+ GetJsepTransceiver().mRecvTrack.GetStreamIds().end());
+ MOZ_ASSERT(GetJsepTransceiver().mRecvTrack.GetRemoteSetSendBit() ||
+ newIds.empty());
+ bool needsTrackEvent = false;
+ for (const auto& id : mStreamIds) {
+ if (!newIds.count(id)) {
+ aChanges->mStreamAssociationsRemoved.push_back({mTrack, id});
+ }
+ }
+
+ std::set<std::string> oldIds(mStreamIds.begin(), mStreamIds.end());
+ for (const auto& id : GetJsepTransceiver().mRecvTrack.GetStreamIds()) {
+ if (!oldIds.count(id)) {
+ needsTrackEvent = true;
+ aChanges->mStreamAssociationsAdded.push_back({mTrack, id});
+ }
+ }
+
+ mStreamIds = GetJsepTransceiver().mRecvTrack.GetStreamIds();
+
+ if (mRemoteSetSendBit !=
+ GetJsepTransceiver().mRecvTrack.GetRemoteSetSendBit()) {
+ mRemoteSetSendBit = GetJsepTransceiver().mRecvTrack.GetRemoteSetSendBit();
+ if (mRemoteSetSendBit) {
+ needsTrackEvent = true;
+ } else {
+ aChanges->mReceiversToMute.push_back(this);
+ }
+ }
+
+ if (needsTrackEvent) {
+ aChanges->mTrackEvents.push_back({this, mStreamIds});
+ }
+}
+
+void RTCRtpReceiver::UpdatePrincipalPrivacy(PrincipalPrivacy aPrivacy) {
+ if (!mPipeline) {
+ return;
+ }
+
+ if (aPrivacy != PrincipalPrivacy::Private) {
+ return;
+ }
+
+ mPipeline->SetPrivatePrincipal(GetPrincipalHandle(mWindow, aPrivacy));
+}
+
+// test-only: adds fake CSRCs and audio data
+void RTCRtpReceiver::MozInsertAudioLevelForContributingSource(
+ const uint32_t aSource, const DOMHighResTimeStamp aTimestamp,
+ const uint32_t aRtpTimestamp, const bool aHasLevel, const uint8_t aLevel) {
+ if (!mPipeline || mPipeline->IsVideo() || !mPipeline->mConduit) {
+ return;
+ }
+ mPipeline->mConduit->InsertAudioLevelForContributingSource(
+ aSource, aTimestamp, aRtpTimestamp, aHasLevel, aLevel);
+}
+
+void RTCRtpReceiver::OnRtcpBye() { mReceiveTrackMute = true; }
+
+void RTCRtpReceiver::OnRtcpTimeout() { mReceiveTrackMute = true; }
+
+void RTCRtpReceiver::SetTrackMuteFromRemoteSdp() {
+ MOZ_ASSERT(!mReceptive,
+ "PeerConnectionImpl should have blocked unmute events prior to "
+ "firing mute");
+ mReceiveTrackMute = true;
+ // Set the mute state (and fire the mute event) synchronously. Unmute is
+ // handled asynchronously after receiving RTP packets.
+ UpdateReceiveTrackMute();
+ MOZ_ASSERT(mTrack->Muted(), "Muted state was indeed set synchronously");
+}
+
+void RTCRtpReceiver::OnRtpPacket() {
+ MOZ_ASSERT(mReceptive, "We should not be registered unless this is set!");
+ // We should be registered since we're currently getting a callback.
+ mUnmuteListener.Disconnect();
+ if (mReceptive) {
+ mReceiveTrackMute = false;
+ }
+}
+
+void RTCRtpReceiver::UpdateReceiveTrackMute() {
+ if (!mTrack) {
+ return;
+ }
+ if (!mTrackSource) {
+ return;
+ }
+ // This sets the muted state for mTrack and all its clones.
+ // Idempotent -- only reacts to changes.
+ mTrackSource->SetMuted(mReceiveTrackMute);
+}
+
+std::string RTCRtpReceiver::GetMid() const {
+ return mTransceiver->GetMidAscii();
+}
+
+JsepTransceiver& RTCRtpReceiver::GetJsepTransceiver() {
+ MOZ_ASSERT(mTransceiver);
+ return mTransceiver->GetJsepTransceiver();
+}
+
+const JsepTransceiver& RTCRtpReceiver::GetJsepTransceiver() const {
+ MOZ_ASSERT(mTransceiver);
+ return mTransceiver->GetJsepTransceiver();
+}
+
+} // namespace mozilla::dom
+
+#undef LOGTAG
diff --git a/dom/media/webrtc/jsapi/RTCRtpReceiver.h b/dom/media/webrtc/jsapi/RTCRtpReceiver.h
new file mode 100644
index 0000000000..2c050bceb1
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCRtpReceiver.h
@@ -0,0 +1,198 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _RTCRtpReceiver_h_
+#define _RTCRtpReceiver_h_
+
+#include "nsISupports.h"
+#include "nsWrapperCache.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/StateMirroring.h"
+#include "mozilla/Maybe.h"
+#include "js/RootingAPI.h"
+#include "libwebrtcglue/RtpRtcpConfig.h"
+#include "nsTArray.h"
+#include "mozilla/dom/RTCRtpCapabilitiesBinding.h"
+#include "mozilla/dom/RTCStatsReportBinding.h"
+#include "PerformanceRecorder.h"
+#include "RTCStatsReport.h"
+#include "transportbridge/MediaPipeline.h"
+#include <vector>
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+class MediaSessionConduit;
+class MediaTransportHandler;
+class JsepTransceiver;
+class PeerConnectionImpl;
+enum class PrincipalPrivacy : uint8_t;
+class RemoteTrackSource;
+
+namespace dom {
+class MediaStreamTrack;
+class Promise;
+class RTCDtlsTransport;
+struct RTCRtpCapabilities;
+struct RTCRtpContributingSource;
+struct RTCRtpSynchronizationSource;
+class RTCRtpTransceiver;
+
+class RTCRtpReceiver : public nsISupports,
+ public nsWrapperCache,
+ public MediaPipelineReceiveControlInterface {
+ public:
+ RTCRtpReceiver(nsPIDOMWindowInner* aWindow, PrincipalPrivacy aPrivacy,
+ PeerConnectionImpl* aPc,
+ MediaTransportHandler* aTransportHandler,
+ AbstractThread* aCallThread, nsISerialEventTarget* aStsThread,
+ MediaSessionConduit* aConduit, RTCRtpTransceiver* aTransceiver,
+ const TrackingId& aTrackingId);
+
+ // nsISupports
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(RTCRtpReceiver)
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // webidl
+ MediaStreamTrack* Track() const { return mTrack; }
+ RTCDtlsTransport* GetTransport() const;
+ static void GetCapabilities(const GlobalObject&, const nsAString& aKind,
+ Nullable<dom::RTCRtpCapabilities>& aResult);
+ already_AddRefed<Promise> GetStats(ErrorResult& aError);
+ void GetContributingSources(
+ nsTArray<dom::RTCRtpContributingSource>& aSources);
+ void GetSynchronizationSources(
+ nsTArray<dom::RTCRtpSynchronizationSource>& aSources);
+ // test-only: insert fake CSRCs and audio levels for testing
+ void MozInsertAudioLevelForContributingSource(
+ const uint32_t aSource, const DOMHighResTimeStamp aTimestamp,
+ const uint32_t aRtpTimestamp, const bool aHasLevel, const uint8_t aLevel);
+
+ nsPIDOMWindowInner* GetParentObject() const;
+ nsTArray<RefPtr<RTCStatsPromise>> GetStatsInternal(
+ bool aSkipIceStats = false);
+ Nullable<DOMHighResTimeStamp> GetJitterBufferTarget(
+ ErrorResult& aError) const {
+ return mJitterBufferTarget.isSome() ? Nullable(mJitterBufferTarget.value())
+ : Nullable<DOMHighResTimeStamp>();
+ }
+ void SetJitterBufferTarget(const Nullable<DOMHighResTimeStamp>& aTargetMs,
+ ErrorResult& aError);
+
+ void Shutdown();
+ void BreakCycles();
+ // Terminal state, reached through stopping RTCRtpTransceiver.
+ void Stop();
+ bool HasTrack(const dom::MediaStreamTrack* aTrack) const;
+ void SyncToJsep(JsepTransceiver& aJsepTransceiver) const;
+ void SyncFromJsep(const JsepTransceiver& aJsepTransceiver);
+ const std::vector<std::string>& GetStreamIds() const { return mStreamIds; }
+
+ struct StreamAssociation {
+ RefPtr<MediaStreamTrack> mTrack;
+ std::string mStreamId;
+ };
+
+ struct TrackEventInfo {
+ RefPtr<RTCRtpReceiver> mReceiver;
+ std::vector<std::string> mStreamIds;
+ };
+
+ struct StreamAssociationChanges {
+ std::vector<RefPtr<RTCRtpReceiver>> mReceiversToMute;
+ std::vector<StreamAssociation> mStreamAssociationsRemoved;
+ std::vector<StreamAssociation> mStreamAssociationsAdded;
+ std::vector<TrackEventInfo> mTrackEvents;
+ };
+
+ // This is called when we set an answer (ie; when the transport is finalized).
+ void UpdateTransport();
+ void UpdateConduit();
+
+ // This is called when we set a remote description; may be an offer or answer.
+ void UpdateStreams(StreamAssociationChanges* aChanges);
+
+ // Called when the privacy-needed state changes on the fly, as a result of
+ // ALPN negotiation.
+ void UpdatePrincipalPrivacy(PrincipalPrivacy aPrivacy);
+
+ void OnRtcpBye();
+ void OnRtcpTimeout();
+
+ void SetTrackMuteFromRemoteSdp();
+ void OnRtpPacket();
+ void UpdateUnmuteBlockingState();
+ void UpdateReceiveTrackMute();
+
+ AbstractCanonical<Ssrc>* CanonicalSsrc() { return &mSsrc; }
+ AbstractCanonical<Ssrc>* CanonicalVideoRtxSsrc() { return &mVideoRtxSsrc; }
+ AbstractCanonical<RtpExtList>* CanonicalLocalRtpExtensions() {
+ return &mLocalRtpExtensions;
+ }
+
+ AbstractCanonical<std::vector<AudioCodecConfig>>* CanonicalAudioCodecs() {
+ return &mAudioCodecs;
+ }
+
+ AbstractCanonical<std::vector<VideoCodecConfig>>* CanonicalVideoCodecs() {
+ return &mVideoCodecs;
+ }
+ AbstractCanonical<Maybe<RtpRtcpConfig>>* CanonicalVideoRtpRtcpConfig() {
+ return &mVideoRtpRtcpConfig;
+ }
+ AbstractCanonical<bool>* CanonicalReceiving() override { return &mReceiving; }
+
+ private:
+ virtual ~RTCRtpReceiver();
+
+ void UpdateVideoConduit();
+ void UpdateAudioConduit();
+
+ std::string GetMid() const;
+ JsepTransceiver& GetJsepTransceiver();
+ const JsepTransceiver& GetJsepTransceiver() const;
+
+ WatchManager<RTCRtpReceiver> mWatchManager;
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ RefPtr<PeerConnectionImpl> mPc;
+ bool mHaveStartedReceiving = false;
+ bool mHaveSetupTransport = false;
+ RefPtr<AbstractThread> mCallThread;
+ nsCOMPtr<nsISerialEventTarget> mStsThread;
+ RefPtr<dom::MediaStreamTrack> mTrack;
+ RefPtr<RemoteTrackSource> mTrackSource;
+ RefPtr<MediaPipelineReceive> mPipeline;
+ RefPtr<MediaTransportHandler> mTransportHandler;
+ RefPtr<RTCRtpTransceiver> mTransceiver;
+ // This is [[AssociatedRemoteMediaStreams]], basically. We do not keep the
+ // streams themselves here, because that would require this object to know
+ // where the stream list for the whole RTCPeerConnection lives..
+ std::vector<std::string> mStreamIds;
+ bool mRemoteSetSendBit = false;
+ Watchable<bool> mReceiveTrackMute{true, "RTCRtpReceiver::mReceiveTrackMute"};
+ // This corresponds to the [[Receptive]] slot on RTCRtpTransceiver.
+ // Its only purpose is suppressing unmute events if true.
+ bool mReceptive = false;
+ // This is the [[JitterBufferTarget]] internal slot.
+ Maybe<DOMHighResTimeStamp> mJitterBufferTarget;
+
+ MediaEventListener mRtcpByeListener;
+ MediaEventListener mRtcpTimeoutListener;
+ MediaEventListener mUnmuteListener;
+
+ Canonical<Ssrc> mSsrc;
+ Canonical<Ssrc> mVideoRtxSsrc;
+ Canonical<RtpExtList> mLocalRtpExtensions;
+ Canonical<std::vector<AudioCodecConfig>> mAudioCodecs;
+ Canonical<std::vector<VideoCodecConfig>> mVideoCodecs;
+ Canonical<Maybe<RtpRtcpConfig>> mVideoRtpRtcpConfig;
+ Canonical<bool> mReceiving;
+};
+
+} // namespace dom
+} // namespace mozilla
+#endif // _RTCRtpReceiver_h_
diff --git a/dom/media/webrtc/jsapi/RTCRtpSender.cpp b/dom/media/webrtc/jsapi/RTCRtpSender.cpp
new file mode 100644
index 0000000000..568a83e8d1
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCRtpSender.cpp
@@ -0,0 +1,1654 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "RTCRtpSender.h"
+#include "transport/logging.h"
+#include "mozilla/dom/MediaStreamTrack.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/glean/GleanMetrics.h"
+#include "nsPIDOMWindow.h"
+#include "nsString.h"
+#include "mozilla/dom/VideoStreamTrack.h"
+#include "jsep/JsepTransceiver.h"
+#include "mozilla/dom/RTCRtpSenderBinding.h"
+#include "RTCStatsReport.h"
+#include "mozilla/Preferences.h"
+#include "RTCRtpTransceiver.h"
+#include "PeerConnectionImpl.h"
+#include "libwebrtcglue/AudioConduit.h"
+#include <vector>
+#include "call/call.h"
+
+namespace mozilla::dom {
+
+LazyLogModule gSenderLog("RTCRtpSender");
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(RTCRtpSender)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(RTCRtpSender)
+ // We do not do anything here, we wait for BreakCycles to be called
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(RTCRtpSender)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow, mPc, mSenderTrack, mTransceiver,
+ mStreams, mDtmf)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(RTCRtpSender)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(RTCRtpSender)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCRtpSender)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+#define INIT_CANONICAL(name, val) \
+ name(AbstractThread::MainThread(), val, "RTCRtpSender::" #name " (Canonical)")
+
+RTCRtpSender::RTCRtpSender(nsPIDOMWindowInner* aWindow, PeerConnectionImpl* aPc,
+ MediaTransportHandler* aTransportHandler,
+ AbstractThread* aCallThread,
+ nsISerialEventTarget* aStsThread,
+ MediaSessionConduit* aConduit,
+ dom::MediaStreamTrack* aTrack,
+ const Sequence<RTCRtpEncodingParameters>& aEncodings,
+ RTCRtpTransceiver* aTransceiver)
+ : mWatchManager(this, AbstractThread::MainThread()),
+ mWindow(aWindow),
+ mPc(aPc),
+ mSenderTrack(aTrack),
+ mTransportHandler(aTransportHandler),
+ mTransceiver(aTransceiver),
+ INIT_CANONICAL(mSsrcs, Ssrcs()),
+ INIT_CANONICAL(mVideoRtxSsrcs, Ssrcs()),
+ INIT_CANONICAL(mLocalRtpExtensions, RtpExtList()),
+ INIT_CANONICAL(mAudioCodec, Nothing()),
+ INIT_CANONICAL(mVideoCodec, Nothing()),
+ INIT_CANONICAL(mVideoRtpRtcpConfig, Nothing()),
+ INIT_CANONICAL(mVideoCodecMode, webrtc::VideoCodecMode::kRealtimeVideo),
+ INIT_CANONICAL(mCname, std::string()),
+ INIT_CANONICAL(mTransmitting, false) {
+ mPipeline = new MediaPipelineTransmit(
+ mPc->GetHandle(), aTransportHandler, aCallThread, aStsThread,
+ aConduit->type() == MediaSessionConduit::VIDEO, aConduit);
+ mPipeline->InitControl(this);
+
+ if (aConduit->type() == MediaSessionConduit::AUDIO) {
+ mDtmf = new RTCDTMFSender(aWindow, mTransceiver);
+ }
+ mPipeline->SetTrack(mSenderTrack);
+
+ mozilla::glean::rtcrtpsender::count.Add(1);
+
+ if (mPc->ShouldAllowOldSetParameters()) {
+ mAllowOldSetParameters = true;
+ mozilla::glean::rtcrtpsender::count_setparameters_compat.Add(1);
+ }
+
+ if (aEncodings.Length()) {
+ // This sender was created by addTransceiver with sendEncodings.
+ mParameters.mEncodings = aEncodings;
+ mSimulcastEnvelopeSet = true;
+ mozilla::glean::rtcrtpsender::used_sendencodings.AddToNumerator(1);
+ } else {
+ // This sender was created by addTrack, sRD(offer), or addTransceiver
+ // without sendEncodings.
+ RTCRtpEncodingParameters defaultEncoding;
+ defaultEncoding.mActive = true;
+ if (aConduit->type() == MediaSessionConduit::VIDEO) {
+ defaultEncoding.mScaleResolutionDownBy.Construct(1.0f);
+ }
+ Unused << mParameters.mEncodings.AppendElement(defaultEncoding, fallible);
+ UpdateRestorableEncodings(mParameters.mEncodings);
+ MaybeGetJsepRids();
+ }
+
+ if (mDtmf) {
+ mWatchManager.Watch(mTransmitting, &RTCRtpSender::UpdateDtmfSender);
+ }
+}
+
+#undef INIT_CANONICAL
+
+RTCRtpSender::~RTCRtpSender() = default;
+
+JSObject* RTCRtpSender::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return RTCRtpSender_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+RTCDtlsTransport* RTCRtpSender::GetTransport() const {
+ if (!mTransceiver) {
+ return nullptr;
+ }
+ return mTransceiver->GetDtlsTransport();
+}
+
+RTCDTMFSender* RTCRtpSender::GetDtmf() const { return mDtmf; }
+
+already_AddRefed<Promise> RTCRtpSender::GetStats(ErrorResult& aError) {
+ RefPtr<Promise> promise = MakePromise(aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+ if (NS_WARN_IF(!mPipeline)) {
+ // TODO(bug 1056433): When we stop nulling this out when the PC is closed
+ // (or when the transceiver is stopped), we can remove this code. We
+ // resolve instead of reject in order to make this eventual change in
+ // behavior a little smaller.
+ promise->MaybeResolve(new RTCStatsReport(mWindow));
+ return promise.forget();
+ }
+
+ if (!mSenderTrack) {
+ promise->MaybeResolve(new RTCStatsReport(mWindow));
+ return promise.forget();
+ }
+
+ mTransceiver->ChainToDomPromiseWithCodecStats(GetStatsInternal(), promise);
+ return promise.forget();
+}
+
+nsTArray<RefPtr<dom::RTCStatsPromise>> RTCRtpSender::GetStatsInternal(
+ bool aSkipIceStats) {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsTArray<RefPtr<RTCStatsPromise>> promises(2);
+ if (!mSenderTrack || !mPipeline) {
+ return promises;
+ }
+
+ nsAutoString trackName;
+ if (auto track = mPipeline->GetTrack()) {
+ track->GetId(trackName);
+ }
+
+ {
+ // Add bandwidth estimation stats
+ promises.AppendElement(InvokeAsync(
+ mPipeline->mCallThread, __func__,
+ [conduit = mPipeline->mConduit, trackName]() mutable {
+ auto report = MakeUnique<dom::RTCStatsCollection>();
+ Maybe<webrtc::Call::Stats> stats = conduit->GetCallStats();
+ stats.apply([&](const auto aStats) {
+ dom::RTCBandwidthEstimationInternal bw;
+ bw.mTrackIdentifier = trackName;
+ bw.mSendBandwidthBps.Construct(aStats.send_bandwidth_bps / 8);
+ bw.mMaxPaddingBps.Construct(aStats.max_padding_bitrate_bps / 8);
+ bw.mReceiveBandwidthBps.Construct(aStats.recv_bandwidth_bps / 8);
+ bw.mPacerDelayMs.Construct(aStats.pacer_delay_ms);
+ if (aStats.rtt_ms >= 0) {
+ bw.mRttMs.Construct(aStats.rtt_ms);
+ }
+ if (!report->mBandwidthEstimations.AppendElement(std::move(bw),
+ fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ });
+ return RTCStatsPromise::CreateAndResolve(std::move(report), __func__);
+ }));
+ }
+
+ promises.AppendElement(InvokeAsync(
+ mPipeline->mCallThread, __func__, [pipeline = mPipeline, trackName] {
+ auto report = MakeUnique<dom::RTCStatsCollection>();
+ auto asAudio = pipeline->mConduit->AsAudioSessionConduit();
+ auto asVideo = pipeline->mConduit->AsVideoSessionConduit();
+
+ nsString kind = asVideo.isNothing() ? u"audio"_ns : u"video"_ns;
+ nsString idstr = kind + u"_"_ns;
+ idstr.AppendInt(static_cast<uint32_t>(pipeline->Level()));
+
+ for (uint32_t ssrc : pipeline->mConduit->GetLocalSSRCs()) {
+ nsString localId = u"outbound_rtp_"_ns + idstr + u"_"_ns;
+ localId.AppendInt(ssrc);
+ nsString remoteId;
+ Maybe<uint16_t> base_seq =
+ pipeline->mConduit->RtpSendBaseSeqFor(ssrc);
+
+ auto constructCommonRemoteInboundRtpStats =
+ [&](RTCRemoteInboundRtpStreamStats& aRemote,
+ const webrtc::ReportBlockData& aRtcpData) {
+ remoteId = u"outbound_rtcp_"_ns + idstr + u"_"_ns;
+ remoteId.AppendInt(ssrc);
+ aRemote.mTimestamp.Construct(
+ RTCStatsTimestamp::FromNtp(
+ pipeline->GetTimestampMaker(),
+ webrtc::Timestamp::Micros(
+ aRtcpData.report_block_timestamp_utc_us()) +
+ webrtc::TimeDelta::Seconds(webrtc::kNtpJan1970))
+ .ToDom());
+ aRemote.mId.Construct(remoteId);
+ aRemote.mType.Construct(RTCStatsType::Remote_inbound_rtp);
+ aRemote.mSsrc = ssrc;
+ aRemote.mKind = kind;
+ aRemote.mMediaType.Construct(
+ kind); // mediaType is the old name for kind.
+ aRemote.mLocalId.Construct(localId);
+ if (base_seq) {
+ if (aRtcpData.report_block()
+ .extended_highest_sequence_number < *base_seq) {
+ aRemote.mPacketsReceived.Construct(0);
+ } else {
+ aRemote.mPacketsReceived.Construct(
+ aRtcpData.report_block()
+ .extended_highest_sequence_number -
+ aRtcpData.report_block().packets_lost - *base_seq + 1);
+ }
+ }
+ };
+
+ auto constructCommonOutboundRtpStats =
+ [&](RTCOutboundRtpStreamStats& aLocal) {
+ aLocal.mSsrc = ssrc;
+ aLocal.mTimestamp.Construct(
+ pipeline->GetTimestampMaker().GetNow().ToDom());
+ aLocal.mId.Construct(localId);
+ aLocal.mType.Construct(RTCStatsType::Outbound_rtp);
+ aLocal.mKind = kind;
+ aLocal.mMediaType.Construct(
+ kind); // mediaType is the old name for kind.
+ if (remoteId.Length()) {
+ aLocal.mRemoteId.Construct(remoteId);
+ }
+ };
+
+ asAudio.apply([&](auto& aConduit) {
+ Maybe<webrtc::AudioSendStream::Stats> audioStats =
+ aConduit->GetSenderStats();
+ if (audioStats.isNothing()) {
+ return;
+ }
+
+ if (audioStats->packets_sent == 0) {
+ // By spec: "The lifetime of all RTP monitored objects starts
+ // when the RTP stream is first used: When the first RTP packet
+ // is sent or received on the SSRC it represents"
+ return;
+ }
+
+ // First, fill in remote stat with rtcp receiver data, if present.
+ // ReceiverReports have less information than SenderReports, so fill
+ // in what we can.
+ Maybe<webrtc::ReportBlockData> reportBlockData;
+ {
+ if (const auto remoteSsrc = aConduit->GetRemoteSSRC();
+ remoteSsrc) {
+ for (auto& data : audioStats->report_block_datas) {
+ if (data.report_block().source_ssrc == ssrc &&
+ data.report_block().sender_ssrc == *remoteSsrc) {
+ reportBlockData.emplace(data);
+ break;
+ }
+ }
+ }
+ }
+ reportBlockData.apply([&](auto& aReportBlockData) {
+ RTCRemoteInboundRtpStreamStats remote;
+ constructCommonRemoteInboundRtpStats(remote, aReportBlockData);
+ if (audioStats->jitter_ms >= 0) {
+ remote.mJitter.Construct(audioStats->jitter_ms / 1000.0);
+ }
+ if (audioStats->packets_lost >= 0) {
+ remote.mPacketsLost.Construct(audioStats->packets_lost);
+ }
+ if (audioStats->rtt_ms >= 0) {
+ remote.mRoundTripTime.Construct(
+ static_cast<double>(audioStats->rtt_ms) / 1000.0);
+ }
+ remote.mFractionLost.Construct(audioStats->fraction_lost);
+ remote.mTotalRoundTripTime.Construct(
+ double(aReportBlockData.sum_rtt_ms()) / 1000);
+ remote.mRoundTripTimeMeasurements.Construct(
+ aReportBlockData.num_rtts());
+ if (!report->mRemoteInboundRtpStreamStats.AppendElement(
+ std::move(remote), fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ });
+
+ // Then, fill in local side (with cross-link to remote only if
+ // present)
+ RTCOutboundRtpStreamStats local;
+ constructCommonOutboundRtpStats(local);
+ local.mPacketsSent.Construct(audioStats->packets_sent);
+ local.mBytesSent.Construct(audioStats->payload_bytes_sent);
+ local.mNackCount.Construct(
+ audioStats->rtcp_packet_type_counts.nack_packets);
+ local.mHeaderBytesSent.Construct(
+ audioStats->header_and_padding_bytes_sent);
+ local.mRetransmittedPacketsSent.Construct(
+ audioStats->retransmitted_packets_sent);
+ local.mRetransmittedBytesSent.Construct(
+ audioStats->retransmitted_bytes_sent);
+ /*
+ * Potential new stats that are now available upstream.
+ * Note: when we last tried exposing this we were getting
+ * targetBitrate for audio was ending up as 0. We did not
+ * investigate why.
+ local.mTargetBitrate.Construct(audioStats->target_bitrate_bps);
+ */
+ if (!report->mOutboundRtpStreamStats.AppendElement(std::move(local),
+ fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ });
+
+ asVideo.apply([&](auto& aConduit) {
+ Maybe<webrtc::VideoSendStream::Stats> videoStats =
+ aConduit->GetSenderStats();
+ if (videoStats.isNothing()) {
+ return;
+ }
+
+ Maybe<webrtc::VideoSendStream::StreamStats> streamStats;
+ auto kv = videoStats->substreams.find(ssrc);
+ if (kv != videoStats->substreams.end()) {
+ streamStats = Some(kv->second);
+ }
+
+ if (!streamStats) {
+ // By spec: "The lifetime of all RTP monitored objects starts
+ // when the RTP stream is first used: When the first RTP packet
+ // is sent or received on the SSRC it represents"
+ return;
+ }
+
+ aConduit->GetAssociatedLocalRtxSSRC(ssrc).apply(
+ [&](const auto rtxSsrc) {
+ auto kv = videoStats->substreams.find(rtxSsrc);
+ if (kv != videoStats->substreams.end()) {
+ streamStats->rtp_stats.Add(kv->second.rtp_stats);
+ }
+ });
+
+ if (streamStats->rtp_stats.first_packet_time_ms == -1) {
+ return;
+ }
+
+ // First, fill in remote stat with rtcp receiver data, if present.
+ // ReceiverReports have less information than SenderReports, so fill
+ // in what we can.
+ if (streamStats->report_block_data) {
+ const webrtc::ReportBlockData& rtcpReportData =
+ *streamStats->report_block_data;
+ RTCRemoteInboundRtpStreamStats remote;
+ remote.mJitter.Construct(
+ static_cast<double>(rtcpReportData.report_block().jitter) /
+ webrtc::kVideoPayloadTypeFrequency);
+ remote.mPacketsLost.Construct(
+ rtcpReportData.report_block().packets_lost);
+ if (rtcpReportData.has_rtt()) {
+ remote.mRoundTripTime.Construct(
+ static_cast<double>(rtcpReportData.last_rtt_ms()) / 1000.0);
+ }
+ constructCommonRemoteInboundRtpStats(remote, rtcpReportData);
+ remote.mTotalRoundTripTime.Construct(
+ streamStats->report_block_data->sum_rtt_ms() / 1000.0);
+ remote.mFractionLost.Construct(
+ static_cast<float>(
+ rtcpReportData.report_block().fraction_lost) /
+ (1 << 8));
+ remote.mRoundTripTimeMeasurements.Construct(
+ streamStats->report_block_data->num_rtts());
+ if (!report->mRemoteInboundRtpStreamStats.AppendElement(
+ std::move(remote), fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ }
+
+ // Then, fill in local side (with cross-link to remote only if
+ // present)
+ RTCOutboundRtpStreamStats local;
+ constructCommonOutboundRtpStats(local);
+ local.mPacketsSent.Construct(
+ streamStats->rtp_stats.transmitted.packets);
+ local.mBytesSent.Construct(
+ streamStats->rtp_stats.transmitted.payload_bytes);
+ local.mNackCount.Construct(
+ streamStats->rtcp_packet_type_counts.nack_packets);
+ local.mFirCount.Construct(
+ streamStats->rtcp_packet_type_counts.fir_packets);
+ local.mPliCount.Construct(
+ streamStats->rtcp_packet_type_counts.pli_packets);
+ local.mFramesEncoded.Construct(streamStats->frames_encoded);
+ if (streamStats->qp_sum) {
+ local.mQpSum.Construct(*streamStats->qp_sum);
+ }
+ local.mHeaderBytesSent.Construct(
+ streamStats->rtp_stats.transmitted.header_bytes +
+ streamStats->rtp_stats.transmitted.padding_bytes);
+ local.mRetransmittedPacketsSent.Construct(
+ streamStats->rtp_stats.retransmitted.packets);
+ local.mRetransmittedBytesSent.Construct(
+ streamStats->rtp_stats.retransmitted.payload_bytes);
+ local.mTotalEncodedBytesTarget.Construct(
+ videoStats->total_encoded_bytes_target);
+ local.mFrameWidth.Construct(streamStats->width);
+ local.mFrameHeight.Construct(streamStats->height);
+ local.mFramesSent.Construct(streamStats->frames_encoded);
+ local.mHugeFramesSent.Construct(streamStats->huge_frames_sent);
+ local.mTotalEncodeTime.Construct(
+ double(streamStats->total_encode_time_ms) / 1000.);
+ /*
+ * Potential new stats that are now available upstream.
+ local.mTargetBitrate.Construct(videoStats->target_media_bitrate_bps);
+ */
+ if (!report->mOutboundRtpStreamStats.AppendElement(std::move(local),
+ fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ });
+ }
+
+ auto constructCommonMediaSourceStats =
+ [&](RTCMediaSourceStats& aStats) {
+ nsString id = u"mediasource_"_ns + idstr + trackName;
+ aStats.mTimestamp.Construct(
+ pipeline->GetTimestampMaker().GetNow().ToDom());
+ aStats.mId.Construct(id);
+ aStats.mType.Construct(RTCStatsType::Media_source);
+ aStats.mTrackIdentifier = trackName;
+ aStats.mKind = kind;
+ };
+
+ // TODO(bug 1804678): Use RTCAudioSourceStats/RTCVideoSourceStats
+ RTCMediaSourceStats mediaSourceStats;
+ constructCommonMediaSourceStats(mediaSourceStats);
+ if (!report->mMediaSourceStats.AppendElement(
+ std::move(mediaSourceStats), fallible)) {
+ mozalloc_handle_oom(0);
+ }
+
+ return RTCStatsPromise::CreateAndResolve(std::move(report), __func__);
+ }));
+
+ if (!aSkipIceStats && GetJsepTransceiver().mTransport.mComponents) {
+ promises.AppendElement(mTransportHandler->GetIceStats(
+ GetJsepTransceiver().mTransport.mTransportId,
+ mPipeline->GetTimestampMaker().GetNow().ToDom()));
+ }
+
+ return promises;
+}
+
+void RTCRtpSender::GetCapabilities(const GlobalObject&, const nsAString& aKind,
+ Nullable<dom::RTCRtpCapabilities>& aResult) {
+ PeerConnectionImpl::GetCapabilities(aKind, aResult, sdp::Direction::kSend);
+}
+
+void RTCRtpSender::WarnAboutBadSetParameters(const nsCString& aError) {
+ nsCString warning(
+ "WARNING! Invalid setParameters call detected! The good news? Firefox "
+ "supports sendEncodings in addTransceiver now, so we ask that you switch "
+ "over to using the parameters code you use for other browsers. Thank you "
+ "for your patience and support. The specific error was: ");
+ warning += aError;
+ mPc->SendWarningToConsole(warning);
+}
+
+nsCString RTCRtpSender::GetEffectiveTLDPlus1() const {
+ return mPc->GetEffectiveTLDPlus1();
+}
+
+already_AddRefed<Promise> RTCRtpSender::SetParameters(
+ const dom::RTCRtpSendParameters& aParameters, ErrorResult& aError) {
+ dom::RTCRtpSendParameters paramsCopy(aParameters);
+ // When the setParameters method is called, the user agent MUST run the
+ // following steps:
+ // Let parameters be the method's first argument.
+ // Let sender be the RTCRtpSender object on which setParameters is invoked.
+ // Let transceiver be the RTCRtpTransceiver object associated with sender
+ // (i.e.sender is transceiver.[[Sender]]).
+
+ RefPtr<dom::Promise> p = MakePromise(aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ if (mPc->IsClosed()) {
+ p->MaybeRejectWithInvalidStateError("Peer connection is closed");
+ return p.forget();
+ }
+
+ // If transceiver.[[Stopped]] is true, return a promise rejected with a newly
+ // created InvalidStateError.
+ if (mTransceiver->Stopped()) {
+ p->MaybeRejectWithInvalidStateError("This sender's transceiver is stopped");
+ return p.forget();
+ }
+
+ // If sender.[[LastReturnedParameters]] is null, return a promise rejected
+ // with a newly created InvalidStateError.
+ if (!mLastReturnedParameters.isSome()) {
+ nsCString error(
+ "Cannot call setParameters without first calling getParameters");
+ if (mAllowOldSetParameters) {
+ if (!mHaveWarnedBecauseNoGetParameters) {
+ mHaveWarnedBecauseNoGetParameters = true;
+ mozilla::glean::rtcrtpsender_setparameters::warn_no_getparameters
+ .AddToNumerator(1);
+#ifdef EARLY_BETA_OR_EARLIER
+ mozilla::glean::rtcrtpsender_setparameters::blame_no_getparameters
+ .Get(GetEffectiveTLDPlus1())
+ .Add(1);
+#endif
+ }
+ WarnAboutBadSetParameters(error);
+ } else {
+ if (!mHaveFailedBecauseNoGetParameters) {
+ mHaveFailedBecauseNoGetParameters = true;
+ mozilla::glean::rtcrtpsender_setparameters::fail_no_getparameters
+ .AddToNumerator(1);
+ }
+ p->MaybeRejectWithInvalidStateError(error);
+ return p.forget();
+ }
+ }
+
+ // According to the spec, our consistency checking is based on
+ // [[LastReturnedParameters]], but if we're letting
+ // [[LastReturnedParameters]]==null slide, we still want to do
+ // consistency checking on _something_ so we can warn implementers if they
+ // are messing that up also. Just find something, _anything_, to do that
+ // checking with.
+ // TODO(bug 1803388): Remove this stuff once it is no longer needed.
+ // TODO(bug 1803389): Remove the glean errors once they are no longer needed.
+ Maybe<RTCRtpSendParameters> oldParams;
+ if (mAllowOldSetParameters) {
+ if (mLastReturnedParameters.isSome()) {
+ oldParams = mLastReturnedParameters;
+ } else if (mPendingParameters.isSome()) {
+ oldParams = mPendingParameters;
+ } else {
+ oldParams = Some(mParameters);
+ }
+ MOZ_ASSERT(oldParams.isSome());
+ } else {
+ oldParams = mLastReturnedParameters;
+ }
+ MOZ_ASSERT(oldParams.isSome());
+
+ // Validate parameters by running the following steps:
+ // Let encodings be parameters.encodings.
+ // Let codecs be parameters.codecs.
+ // Let N be the number of RTCRtpEncodingParameters stored in
+ // sender.[[SendEncodings]].
+ // If any of the following conditions are met,
+ // return a promise rejected with a newly created InvalidModificationError:
+
+ bool pendingRidChangeFromCompatMode = false;
+ // encodings.length is different from N.
+ if (paramsCopy.mEncodings.Length() != oldParams->mEncodings.Length()) {
+ nsCString error("Cannot change the number of encodings with setParameters");
+ if (!mAllowOldSetParameters) {
+ if (!mHaveFailedBecauseEncodingCountChange) {
+ mHaveFailedBecauseEncodingCountChange = true;
+ mozilla::glean::rtcrtpsender_setparameters::fail_length_changed
+ .AddToNumerator(1);
+ }
+ p->MaybeRejectWithInvalidModificationError(error);
+ return p.forget();
+ }
+ // Make sure we don't use the old rids in SyncToJsep while we wait for the
+ // queued task below to update mParameters.
+ pendingRidChangeFromCompatMode = true;
+ mSimulcastEnvelopeSet = true;
+ if (!mHaveWarnedBecauseEncodingCountChange) {
+ mHaveWarnedBecauseEncodingCountChange = true;
+ mozilla::glean::rtcrtpsender_setparameters::warn_length_changed
+ .AddToNumerator(1);
+#ifdef EARLY_BETA_OR_EARLIER
+ mozilla::glean::rtcrtpsender_setparameters::blame_length_changed
+ .Get(GetEffectiveTLDPlus1())
+ .Add(1);
+#endif
+ }
+ WarnAboutBadSetParameters(error);
+ } else {
+ // encodings has been re-ordered.
+ for (size_t i = 0; i < paramsCopy.mEncodings.Length(); ++i) {
+ const auto& oldEncoding = oldParams->mEncodings[i];
+ const auto& newEncoding = paramsCopy.mEncodings[i];
+ if (oldEncoding.mRid != newEncoding.mRid) {
+ nsCString error("Cannot change rid, or reorder encodings");
+ if (!mHaveFailedBecauseRidChange) {
+ mHaveFailedBecauseRidChange = true;
+ mozilla::glean::rtcrtpsender_setparameters::fail_rid_changed
+ .AddToNumerator(1);
+ }
+ p->MaybeRejectWithInvalidModificationError(error);
+ return p.forget();
+ }
+ }
+ }
+
+ // TODO(bug 1803388): Handle this in webidl, once we stop allowing the old
+ // setParameters style.
+ if (!paramsCopy.mTransactionId.WasPassed()) {
+ nsCString error("transactionId is not set!");
+ if (!mAllowOldSetParameters) {
+ if (!mHaveFailedBecauseNoTransactionId) {
+ mHaveFailedBecauseNoTransactionId = true;
+ mozilla::glean::rtcrtpsender_setparameters::fail_no_transactionid
+ .AddToNumerator(1);
+ }
+ p->MaybeRejectWithTypeError(error);
+ return p.forget();
+ }
+ if (!mHaveWarnedBecauseNoTransactionId) {
+ mHaveWarnedBecauseNoTransactionId = true;
+ mozilla::glean::rtcrtpsender_setparameters::warn_no_transactionid
+ .AddToNumerator(1);
+#ifdef EARLY_BETA_OR_EARLIER
+ mozilla::glean::rtcrtpsender_setparameters::blame_no_transactionid
+ .Get(GetEffectiveTLDPlus1())
+ .Add(1);
+#endif
+ }
+ WarnAboutBadSetParameters(error);
+ } else if (oldParams->mTransactionId != paramsCopy.mTransactionId) {
+ // Any parameter in parameters is marked as a Read-only parameter (such as
+ // RID) and has a value that is different from the corresponding parameter
+ // value in sender.[[LastReturnedParameters]]. Note that this also applies
+ // to transactionId.
+ nsCString error(
+ "Cannot change transaction id: call getParameters, modify the result, "
+ "and then call setParameters");
+ if (!mAllowOldSetParameters) {
+ if (!mHaveFailedBecauseStaleTransactionId) {
+ mHaveFailedBecauseStaleTransactionId = true;
+ mozilla::glean::rtcrtpsender_setparameters::fail_stale_transactionid
+ .AddToNumerator(1);
+ }
+ p->MaybeRejectWithInvalidModificationError(error);
+ return p.forget();
+ }
+ if (!mHaveWarnedBecauseStaleTransactionId) {
+ mHaveWarnedBecauseStaleTransactionId = true;
+ mozilla::glean::rtcrtpsender_setparameters::warn_stale_transactionid
+ .AddToNumerator(1);
+#ifdef EARLY_BETA_OR_EARLIER
+ mozilla::glean::rtcrtpsender_setparameters::blame_stale_transactionid
+ .Get(GetEffectiveTLDPlus1())
+ .Add(1);
+#endif
+ }
+ WarnAboutBadSetParameters(error);
+ }
+
+ // This could conceivably happen if we are allowing the old setParameters
+ // behavior.
+ if (!paramsCopy.mEncodings.Length()) {
+ nsCString error("Cannot set an empty encodings array");
+ if (!mAllowOldSetParameters) {
+ if (!mHaveFailedBecauseNoEncodings) {
+ mHaveFailedBecauseNoEncodings = true;
+ mozilla::glean::rtcrtpsender_setparameters::fail_no_encodings
+ .AddToNumerator(1);
+ }
+
+ p->MaybeRejectWithInvalidModificationError(error);
+ return p.forget();
+ }
+ // TODO: Add some warning telemetry here
+ WarnAboutBadSetParameters(error);
+ // Just don't do this; it's stupid.
+ paramsCopy.mEncodings = oldParams->mEncodings;
+ }
+
+ // TODO: Verify remaining read-only parameters
+ // headerExtensions (bug 1765851)
+ // rtcp (bug 1765852)
+ // codecs (bug 1534687)
+
+ // CheckAndRectifyEncodings handles the following steps:
+ // If transceiver kind is "audio", remove the scaleResolutionDownBy member
+ // from all encodings that contain one.
+ //
+ // If transceiver kind is "video", and any encoding in encodings contains a
+ // scaleResolutionDownBy member whose value is less than 1.0, return a
+ // promise rejected with a newly created RangeError.
+ //
+ // Verify that each encoding in encodings has a maxFramerate member whose
+ // value is greater than or equal to 0.0. If one of the maxFramerate values
+ // does not meet this requirement, return a promise rejected with a newly
+ // created RangeError.
+ ErrorResult rv;
+ CheckAndRectifyEncodings(paramsCopy.mEncodings, mTransceiver->IsVideo(), rv);
+ if (rv.Failed()) {
+ if (!mHaveFailedBecauseOtherError) {
+ mHaveFailedBecauseOtherError = true;
+ mozilla::glean::rtcrtpsender_setparameters::fail_other.AddToNumerator(1);
+ }
+ p->MaybeReject(std::move(rv));
+ return p.forget();
+ }
+
+ // If transceiver kind is "video", then for each encoding in encodings that
+ // doesn't contain a scaleResolutionDownBy member, add a
+ // scaleResolutionDownBy member with the value 1.0.
+ if (mTransceiver->IsVideo()) {
+ for (auto& encoding : paramsCopy.mEncodings) {
+ if (!encoding.mScaleResolutionDownBy.WasPassed()) {
+ encoding.mScaleResolutionDownBy.Construct(1.0);
+ }
+ }
+ }
+
+ // Let p be a new promise. (see above)
+
+ // In parallel, configure the media stack to use parameters to transmit
+ // sender.[[SenderTrack]].
+ // Right now this is infallible. That may change someday.
+
+ // We need to put this in a member variable, since MaybeUpdateConduit needs it
+ // This also allows PeerConnectionImpl to detect when there is a pending
+ // setParameters, which has implcations for the handling of
+ // setRemoteDescription.
+ mPendingRidChangeFromCompatMode = pendingRidChangeFromCompatMode;
+ mPendingParameters = Some(paramsCopy);
+ uint32_t serialNumber = ++mNumSetParametersCalls;
+ MaybeUpdateConduit();
+
+ // If the media stack is successfully configured with parameters,
+ // queue a task to run the following steps:
+ GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
+ __func__,
+ [this, self = RefPtr<RTCRtpSender>(this), p, paramsCopy, serialNumber] {
+ // Set sender.[[LastReturnedParameters]] to null.
+ mLastReturnedParameters = Nothing();
+ // Set sender.[[SendEncodings]] to parameters.encodings.
+ mParameters = paramsCopy;
+ UpdateRestorableEncodings(mParameters.mEncodings);
+ // Only clear mPendingParameters if it matches; there could have been
+ // back-to-back calls to setParameters, and we only want to clear this
+ // if no subsequent setParameters is pending.
+ if (serialNumber == mNumSetParametersCalls) {
+ mPendingParameters = Nothing();
+ // Ok, nothing has called SyncToJsep while this async task was
+ // pending. No need for special handling anymore.
+ mPendingRidChangeFromCompatMode = false;
+ }
+ MOZ_ASSERT(mParameters.mEncodings.Length());
+ // Resolve p with undefined.
+ p->MaybeResolveWithUndefined();
+ }));
+
+ // Return p.
+ return p.forget();
+}
+
+// static
+void RTCRtpSender::CheckAndRectifyEncodings(
+ Sequence<RTCRtpEncodingParameters>& aEncodings, bool aVideo,
+ ErrorResult& aRv) {
+ // If any encoding contains a rid member whose value does not conform to the
+ // grammar requirements specified in Section 10 of [RFC8851], throw a
+ // TypeError.
+ for (const auto& encoding : aEncodings) {
+ if (encoding.mRid.WasPassed()) {
+ std::string utf8Rid = NS_ConvertUTF16toUTF8(encoding.mRid.Value()).get();
+ std::string error;
+ if (!SdpRidAttributeList::CheckRidValidity(utf8Rid, &error)) {
+ aRv.ThrowTypeError(nsCString(error));
+ return;
+ }
+ if (utf8Rid.size() > SdpRidAttributeList::kMaxRidLength) {
+ std::ostringstream ss;
+ ss << "Rid can be at most " << SdpRidAttributeList::kMaxRidLength
+ << " characters long (due to internal limitations)";
+ aRv.ThrowTypeError(nsCString(ss.str()));
+ return;
+ }
+ }
+ }
+
+ if (aEncodings.Length() > 1) {
+ // If some but not all encodings contain a rid member, throw a TypeError.
+ // rid must be set if there is more than one encoding
+ // NOTE: Since rid is read-only, and the number of encodings cannot grow,
+ // this should never happen in setParameters.
+ for (const auto& encoding : aEncodings) {
+ if (!encoding.mRid.WasPassed()) {
+ aRv.ThrowTypeError("Missing rid");
+ return;
+ }
+ }
+
+ // If any encoding contains a rid member whose value is the same as that of
+ // a rid contained in another encoding in sendEncodings, throw a TypeError.
+ // NOTE: Since rid is read-only, and the number of encodings cannot grow,
+ // this should never happen in setParameters.
+ std::set<nsString> uniqueRids;
+ for (const auto& encoding : aEncodings) {
+ if (uniqueRids.count(encoding.mRid.Value())) {
+ aRv.ThrowTypeError("Duplicate rid");
+ return;
+ }
+ uniqueRids.insert(encoding.mRid.Value());
+ }
+ }
+ // TODO: ptime/adaptivePtime validation (bug 1733647)
+
+ // If kind is "audio", remove the scaleResolutionDownBy member from all
+ // encodings that contain one.
+ if (!aVideo) {
+ for (auto& encoding : aEncodings) {
+ if (encoding.mScaleResolutionDownBy.WasPassed()) {
+ encoding.mScaleResolutionDownBy.Reset();
+ }
+ if (encoding.mMaxFramerate.WasPassed()) {
+ encoding.mMaxFramerate.Reset();
+ }
+ }
+ }
+
+ // If any encoding contains a scaleResolutionDownBy member whose value is
+ // less than 1.0, throw a RangeError.
+ for (const auto& encoding : aEncodings) {
+ if (encoding.mScaleResolutionDownBy.WasPassed()) {
+ if (encoding.mScaleResolutionDownBy.Value() < 1.0f) {
+ aRv.ThrowRangeError("scaleResolutionDownBy must be >= 1.0");
+ return;
+ }
+ }
+ }
+
+ // Verify that the value of each maxFramerate member in sendEncodings that is
+ // defined is greater than 0.0. If one of the maxFramerate values does not
+ // meet this requirement, throw a RangeError.
+ for (const auto& encoding : aEncodings) {
+ if (encoding.mMaxFramerate.WasPassed()) {
+ if (encoding.mMaxFramerate.Value() < 0.0f) {
+ aRv.ThrowRangeError("maxFramerate must be non-negative");
+ return;
+ }
+ }
+ }
+}
+
+void RTCRtpSender::GetParameters(RTCRtpSendParameters& aParameters) {
+ MOZ_ASSERT(mParameters.mEncodings.Length());
+ // If sender.[[LastReturnedParameters]] is not null, return
+ // sender.[[LastReturnedParameters]], and abort these steps.
+ if (mLastReturnedParameters.isSome()) {
+ aParameters = *mLastReturnedParameters;
+ return;
+ }
+
+ // Let result be a new RTCRtpSendParameters dictionary constructed as follows:
+
+ // transactionId is set to a new unique identifier
+ aParameters.mTransactionId.Construct(mPc->GenerateUUID());
+
+ // encodings is set to the value of the [[SendEncodings]] internal slot.
+ aParameters.mEncodings = mParameters.mEncodings;
+
+ // The headerExtensions sequence is populated based on the header extensions
+ // that have been negotiated for sending
+ // TODO(bug 1765851): We do not support this yet
+ // aParameters.mHeaderExtensions.Construct();
+
+ // codecs is set to the value of the [[SendCodecs]] internal slot
+ // TODO(bug 1534687): We do not support this yet
+
+ // rtcp.cname is set to the CNAME of the associated RTCPeerConnection.
+ // rtcp.reducedSize is set to true if reduced-size RTCP has been negotiated
+ // for sending, and false otherwise.
+ // TODO(bug 1765852): We do not support this yet
+ aParameters.mRtcp.Construct();
+ aParameters.mRtcp.Value().mCname.Construct();
+ aParameters.mRtcp.Value().mReducedSize.Construct(false);
+ aParameters.mHeaderExtensions.Construct();
+ aParameters.mCodecs.Construct();
+
+ // Set sender.[[LastReturnedParameters]] to result.
+ mLastReturnedParameters = Some(aParameters);
+
+ // Queue a task that sets sender.[[LastReturnedParameters]] to null.
+ GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
+ __func__, [this, self = RefPtr<RTCRtpSender>(this)] {
+ mLastReturnedParameters = Nothing();
+ }));
+}
+
+bool operator==(const RTCRtpEncodingParameters& a1,
+ const RTCRtpEncodingParameters& a2) {
+ // webidl does not generate types that are equality comparable
+ return a1.mActive == a2.mActive && a1.mFec == a2.mFec &&
+ a1.mMaxBitrate == a2.mMaxBitrate &&
+ a1.mMaxFramerate == a2.mMaxFramerate && a1.mPriority == a2.mPriority &&
+ a1.mRid == a2.mRid && a1.mRtx == a2.mRtx &&
+ a1.mScaleResolutionDownBy == a2.mScaleResolutionDownBy &&
+ a1.mSsrc == a2.mSsrc;
+}
+
+// static
+void RTCRtpSender::ApplyJsEncodingToConduitEncoding(
+ const RTCRtpEncodingParameters& aJsEncoding,
+ VideoCodecConfig::Encoding* aConduitEncoding) {
+ aConduitEncoding->active = aJsEncoding.mActive;
+ if (aJsEncoding.mMaxBitrate.WasPassed()) {
+ aConduitEncoding->constraints.maxBr = aJsEncoding.mMaxBitrate.Value();
+ }
+ if (aJsEncoding.mMaxFramerate.WasPassed()) {
+ aConduitEncoding->constraints.maxFps =
+ Some(aJsEncoding.mMaxFramerate.Value());
+ }
+ if (aJsEncoding.mScaleResolutionDownBy.WasPassed()) {
+ // Optional does not have a valueOr, despite being based on Maybe
+ // :(
+ aConduitEncoding->constraints.scaleDownBy =
+ aJsEncoding.mScaleResolutionDownBy.Value();
+ } else {
+ aConduitEncoding->constraints.scaleDownBy = 1.0f;
+ }
+}
+
+void RTCRtpSender::UpdateRestorableEncodings(
+ const Sequence<RTCRtpEncodingParameters>& aEncodings) {
+ MOZ_ASSERT(aEncodings.Length());
+
+ if (GetJsepTransceiver().mSendTrack.GetNegotiatedDetails()) {
+ // Once initial negotiation completes, we are no longer allowed to restore
+ // the unicast encoding.
+ mUnicastEncoding.reset();
+ } else if (mParameters.mEncodings.Length() == 1 &&
+ !mParameters.mEncodings[0].mRid.WasPassed()) {
+ // If we have not completed the initial negotiation, and we currently are
+ // ridless unicast, we need to save our unicast encoding in case a
+ // rollback occurs.
+ mUnicastEncoding = Some(mParameters.mEncodings[0]);
+ }
+}
+
+Sequence<RTCRtpEncodingParameters> RTCRtpSender::ToSendEncodings(
+ const std::vector<std::string>& aRids) const {
+ MOZ_ASSERT(!aRids.empty());
+
+ Sequence<RTCRtpEncodingParameters> result;
+ // If sendEncodings is given as input to this algorithm, and is non-empty,
+ // set the [[SendEncodings]] slot to sendEncodings.
+ for (const auto& rid : aRids) {
+ MOZ_ASSERT(!rid.empty());
+ RTCRtpEncodingParameters encoding;
+ encoding.mActive = true;
+ encoding.mRid.Construct(NS_ConvertUTF8toUTF16(rid.c_str()));
+ Unused << result.AppendElement(encoding, fallible);
+ }
+
+ // If sendEncodings is non-empty, set each encoding's scaleResolutionDownBy
+ // to 2^(length of sendEncodings - encoding index - 1).
+ if (mTransceiver->IsVideo()) {
+ double scale = 1.0f;
+ for (auto it = result.rbegin(); it != result.rend(); ++it) {
+ it->mScaleResolutionDownBy.Construct(scale);
+ scale *= 2;
+ }
+ }
+
+ return result;
+}
+
+void RTCRtpSender::MaybeGetJsepRids() {
+ MOZ_ASSERT(!mSimulcastEnvelopeSet);
+ MOZ_ASSERT(mParameters.mEncodings.Length());
+
+ auto jsepRids = GetJsepTransceiver().mSendTrack.GetRids();
+ if (!jsepRids.empty()) {
+ UpdateRestorableEncodings(mParameters.mEncodings);
+ if (jsepRids.size() != 1 || !jsepRids[0].empty()) {
+ // JSEP is using at least one rid. Stomp our single ridless encoding
+ mParameters.mEncodings = ToSendEncodings(jsepRids);
+ }
+ mSimulcastEnvelopeSet = true;
+ mSimulcastEnvelopeSetByJSEP = true;
+ }
+}
+
+Sequence<RTCRtpEncodingParameters> RTCRtpSender::GetMatchingEncodings(
+ const std::vector<std::string>& aRids) const {
+ Sequence<RTCRtpEncodingParameters> result;
+
+ if (!aRids.empty() && !aRids[0].empty()) {
+ // Simulcast, or unicast with rid
+ for (const auto& encoding : mParameters.mEncodings) {
+ for (const auto& rid : aRids) {
+ auto utf16Rid = NS_ConvertUTF8toUTF16(rid.c_str());
+ if (!encoding.mRid.WasPassed() || (utf16Rid == encoding.mRid.Value())) {
+ auto encodingCopy(encoding);
+ if (!encodingCopy.mRid.WasPassed()) {
+ encodingCopy.mRid.Construct(NS_ConvertUTF8toUTF16(rid.c_str()));
+ }
+ Unused << result.AppendElement(encodingCopy, fallible);
+ break;
+ }
+ }
+ }
+ }
+
+ // If we're allowing the old setParameters behavior, we _might_ be able to
+ // get into this situation even if there were rids above. Be extra careful.
+ // Under normal circumstances, this just handles the ridless case.
+ if (!result.Length()) {
+ // Unicast with no specified rid. Restore mUnicastEncoding, if
+ // it exists, otherwise pick the first encoding.
+ if (mUnicastEncoding.isSome()) {
+ Unused << result.AppendElement(*mUnicastEncoding, fallible);
+ } else {
+ Unused << result.AppendElement(mParameters.mEncodings[0], fallible);
+ }
+ }
+
+ return result;
+}
+
+void RTCRtpSender::SetStreams(
+ const Sequence<OwningNonNull<DOMMediaStream>>& aStreams, ErrorResult& aRv) {
+ if (mPc->IsClosed()) {
+ aRv.ThrowInvalidStateError(
+ "Cannot call setStreams if the peer connection is closed");
+ return;
+ }
+
+ SetStreamsImpl(aStreams);
+ mPc->UpdateNegotiationNeeded();
+}
+
+void RTCRtpSender::SetStreamsImpl(
+ const Sequence<OwningNonNull<DOMMediaStream>>& aStreams) {
+ mStreams.Clear();
+ std::set<nsString> ids;
+ for (const auto& stream : aStreams) {
+ nsString id;
+ stream->GetId(id);
+ if (!ids.count(id)) {
+ ids.insert(id);
+ mStreams.AppendElement(stream);
+ }
+ }
+}
+
+void RTCRtpSender::GetStreams(nsTArray<RefPtr<DOMMediaStream>>& aStreams) {
+ aStreams = mStreams.Clone();
+}
+
+class ReplaceTrackOperation final : public PeerConnectionImpl::Operation {
+ public:
+ ReplaceTrackOperation(PeerConnectionImpl* aPc,
+ const RefPtr<RTCRtpTransceiver>& aTransceiver,
+ const RefPtr<MediaStreamTrack>& aTrack,
+ ErrorResult& aError);
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ReplaceTrackOperation,
+ PeerConnectionImpl::Operation)
+
+ private:
+ MOZ_CAN_RUN_SCRIPT
+ RefPtr<dom::Promise> CallImpl(ErrorResult& aError) override;
+ ~ReplaceTrackOperation() = default;
+ RefPtr<RTCRtpTransceiver> mTransceiver;
+ RefPtr<MediaStreamTrack> mNewTrack;
+};
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(ReplaceTrackOperation,
+ PeerConnectionImpl::Operation, mTransceiver,
+ mNewTrack)
+
+NS_IMPL_ADDREF_INHERITED(ReplaceTrackOperation, PeerConnectionImpl::Operation)
+NS_IMPL_RELEASE_INHERITED(ReplaceTrackOperation, PeerConnectionImpl::Operation)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ReplaceTrackOperation)
+NS_INTERFACE_MAP_END_INHERITING(PeerConnectionImpl::Operation)
+
+ReplaceTrackOperation::ReplaceTrackOperation(
+ PeerConnectionImpl* aPc, const RefPtr<RTCRtpTransceiver>& aTransceiver,
+ const RefPtr<MediaStreamTrack>& aTrack, ErrorResult& aError)
+ : PeerConnectionImpl::Operation(aPc, aError),
+ mTransceiver(aTransceiver),
+ mNewTrack(aTrack) {}
+
+RefPtr<dom::Promise> ReplaceTrackOperation::CallImpl(ErrorResult& aError) {
+ RefPtr<RTCRtpSender> sender = mTransceiver->Sender();
+ // If transceiver.[[Stopped]] is true, return a promise rejected with a newly
+ // created InvalidStateError.
+ if (mTransceiver->Stopped()) {
+ RefPtr<dom::Promise> error = sender->MakePromise(aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+ MOZ_LOG(gSenderLog, LogLevel::Debug,
+ ("%s Cannot call replaceTrack when transceiver is stopped",
+ __FUNCTION__));
+ error->MaybeRejectWithInvalidStateError(
+ "Cannot call replaceTrack when transceiver is stopped");
+ return error;
+ }
+
+ // Let p be a new promise.
+ RefPtr<dom::Promise> p = sender->MakePromise(aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ if (!sender->SeamlessTrackSwitch(mNewTrack)) {
+ MOZ_LOG(gSenderLog, LogLevel::Info,
+ ("%s Could not seamlessly replace track", __FUNCTION__));
+ p->MaybeRejectWithInvalidModificationError(
+ "Could not seamlessly replace track");
+ return p;
+ }
+
+ // Queue a task that runs the following steps:
+ GetMainThreadSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
+ __func__, [p, sender, track = mNewTrack]() MOZ_CAN_RUN_SCRIPT_BOUNDARY {
+ // If connection.[[IsClosed]] is true, abort these steps.
+ // Set sender.[[SenderTrack]] to withTrack.
+ if (sender->SetSenderTrackWithClosedCheck(track)) {
+ // Resolve p with undefined.
+ p->MaybeResolveWithUndefined();
+ }
+ }));
+
+ // Return p.
+ return p;
+}
+
+already_AddRefed<dom::Promise> RTCRtpSender::ReplaceTrack(
+ dom::MediaStreamTrack* aWithTrack, ErrorResult& aError) {
+ // If withTrack is non-null and withTrack.kind differs from the transceiver
+ // kind of transceiver, return a promise rejected with a newly created
+ // TypeError.
+ if (aWithTrack) {
+ nsString newKind;
+ aWithTrack->GetKind(newKind);
+ nsString oldKind;
+ mTransceiver->GetKind(oldKind);
+ if (newKind != oldKind) {
+ RefPtr<dom::Promise> error = MakePromise(aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+ error->MaybeRejectWithTypeError(
+ "Cannot replaceTrack with a different kind!");
+ return error.forget();
+ }
+ }
+
+ MOZ_LOG(gSenderLog, LogLevel::Debug,
+ ("%s[%s]: %s (%p to %p)", mPc->GetHandle().c_str(), GetMid().c_str(),
+ __FUNCTION__, mSenderTrack.get(), aWithTrack));
+
+ // Return the result of chaining the following steps to connection's
+ // operations chain:
+ RefPtr<PeerConnectionImpl::Operation> op =
+ new ReplaceTrackOperation(mPc, mTransceiver, aWithTrack, aError);
+ if (aError.Failed()) {
+ return nullptr;
+ }
+ // Static analysis forces us to use a temporary.
+ auto pc = mPc;
+ return pc->Chain(op, aError);
+}
+
+nsPIDOMWindowInner* RTCRtpSender::GetParentObject() const { return mWindow; }
+
+already_AddRefed<dom::Promise> RTCRtpSender::MakePromise(
+ ErrorResult& aError) const {
+ return mPc->MakePromise(aError);
+}
+
+bool RTCRtpSender::SeamlessTrackSwitch(
+ const RefPtr<MediaStreamTrack>& aWithTrack) {
+ // We do not actually update mSenderTrack here! Spec says that happens in a
+ // queued task after this is done (this happens in
+ // SetSenderTrackWithClosedCheck).
+
+ mPipeline->SetTrack(aWithTrack);
+
+ MaybeUpdateConduit();
+
+ // There may eventually be cases where a renegotiation is necessary to switch.
+ return true;
+}
+
+void RTCRtpSender::SetTrack(const RefPtr<MediaStreamTrack>& aTrack) {
+ // Used for RTCPeerConnection.removeTrack and RTCPeerConnection.addTrack
+ mSenderTrack = aTrack;
+ SeamlessTrackSwitch(aTrack);
+ if (aTrack) {
+ // RFC says (in the section on remote rollback):
+ // However, an RtpTransceiver MUST NOT be removed if a track was attached
+ // to the RtpTransceiver via the addTrack method.
+ mAddTrackCalled = true;
+ }
+}
+
+bool RTCRtpSender::SetSenderTrackWithClosedCheck(
+ const RefPtr<MediaStreamTrack>& aTrack) {
+ if (!mPc->IsClosed()) {
+ mSenderTrack = aTrack;
+ return true;
+ }
+
+ return false;
+}
+
+void RTCRtpSender::Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+ mWatchManager.Shutdown();
+ mPipeline->Shutdown();
+ mPipeline = nullptr;
+}
+
+void RTCRtpSender::BreakCycles() {
+ mWindow = nullptr;
+ mPc = nullptr;
+ mSenderTrack = nullptr;
+ mTransceiver = nullptr;
+ mStreams.Clear();
+ mDtmf = nullptr;
+}
+
+void RTCRtpSender::UpdateTransport() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mHaveSetupTransport) {
+ mPipeline->SetLevel(GetJsepTransceiver().GetLevel());
+ mHaveSetupTransport = true;
+ }
+
+ mPipeline->UpdateTransport_m(GetJsepTransceiver().mTransport.mTransportId,
+ nullptr);
+}
+
+void RTCRtpSender::MaybeUpdateConduit() {
+ // NOTE(pkerr) - the Call API requires the both local_ssrc and remote_ssrc be
+ // set to a non-zero value or the CreateVideo...Stream call will fail.
+ if (NS_WARN_IF(GetJsepTransceiver().mSendTrack.GetSsrcs().empty())) {
+ MOZ_ASSERT(
+ false,
+ "No local ssrcs! This is a bug in the jsep engine, and should never "
+ "happen!");
+ return;
+ }
+
+ if (!mPipeline) {
+ return;
+ }
+
+ bool wasTransmitting = mTransmitting;
+
+ if (mPipeline->mConduit->type() == MediaSessionConduit::VIDEO) {
+ Maybe<VideoConfig> newConfig = GetNewVideoConfig();
+ if (newConfig.isSome()) {
+ ApplyVideoConfig(*newConfig);
+ }
+ } else {
+ Maybe<AudioConfig> newConfig = GetNewAudioConfig();
+ if (newConfig.isSome()) {
+ ApplyAudioConfig(*newConfig);
+ }
+ }
+
+ if (!mSenderTrack && !wasTransmitting && mTransmitting) {
+ MOZ_LOG(gSenderLog, LogLevel::Debug,
+ ("%s[%s]: %s Starting transmit conduit without send track!",
+ mPc->GetHandle().c_str(), GetMid().c_str(), __FUNCTION__));
+ }
+}
+
+void RTCRtpSender::SyncFromJsep(const JsepTransceiver& aJsepTransceiver) {
+ if (!mSimulcastEnvelopeSet) {
+ // JSEP is establishing the simulcast envelope for the first time, right now
+ // This is the addTrack (or addTransceiver without sendEncodings) case.
+ MaybeGetJsepRids();
+ } else if (!aJsepTransceiver.mSendTrack.GetNegotiatedDetails() ||
+ !aJsepTransceiver.mSendTrack.IsInHaveRemote()) {
+ // Spec says that we do not update our encodings until we're in stable,
+ // _unless_ this is the first negotiation.
+ std::vector<std::string> rids = aJsepTransceiver.mSendTrack.GetRids();
+ if (mSimulcastEnvelopeSetByJSEP && rids.empty()) {
+ // JSEP previously set the simulcast envelope, but now it has no opinion
+ // regarding unicast/simulcast. This can only happen on rollback of the
+ // initial remote offer.
+ mParameters.mEncodings = GetMatchingEncodings(rids);
+ MOZ_ASSERT(mParameters.mEncodings.Length());
+ mSimulcastEnvelopeSetByJSEP = false;
+ mSimulcastEnvelopeSet = false;
+ } else if (!rids.empty()) {
+ // JSEP has an opinion on the simulcast envelope, which trumps anything
+ // we have already.
+ mParameters.mEncodings = GetMatchingEncodings(rids);
+ MOZ_ASSERT(mParameters.mEncodings.Length());
+ }
+ }
+
+ MaybeUpdateConduit();
+}
+
+void RTCRtpSender::SyncToJsep(JsepTransceiver& aJsepTransceiver) const {
+ std::vector<std::string> streamIds;
+ for (const auto& stream : mStreams) {
+ nsString wideStreamId;
+ stream->GetId(wideStreamId);
+ std::string streamId = NS_ConvertUTF16toUTF8(wideStreamId).get();
+ MOZ_ASSERT(!streamId.empty());
+ streamIds.push_back(streamId);
+ }
+
+ aJsepTransceiver.mSendTrack.UpdateStreamIds(streamIds);
+
+ if (mSimulcastEnvelopeSet) {
+ std::vector<std::string> rids;
+ Maybe<RTCRtpSendParameters> parameters;
+ if (mPendingRidChangeFromCompatMode) {
+ // *sigh* If we have just let a setParameters change our rids, but we have
+ // not yet updated mParameters because the queued task hasn't run yet,
+ // we want to set the _new_ rids on the JsepTrack. So, we are forced to
+ // grab them from mPendingParameters.
+ parameters = mPendingParameters;
+ } else {
+ parameters = Some(mParameters);
+ }
+ for (const auto& encoding : parameters->mEncodings) {
+ if (encoding.mRid.WasPassed()) {
+ rids.push_back(NS_ConvertUTF16toUTF8(encoding.mRid.Value()).get());
+ } else {
+ rids.push_back("");
+ }
+ }
+
+ aJsepTransceiver.mSendTrack.SetRids(rids);
+ }
+
+ if (mTransceiver->IsVideo()) {
+ aJsepTransceiver.mSendTrack.SetMaxEncodings(webrtc::kMaxSimulcastStreams);
+ } else {
+ aJsepTransceiver.mSendTrack.SetMaxEncodings(1);
+ }
+
+ if (mAddTrackCalled) {
+ aJsepTransceiver.SetOnlyExistsBecauseOfSetRemote(false);
+ }
+}
+
+Maybe<RTCRtpSender::VideoConfig> RTCRtpSender::GetNewVideoConfig() {
+ // It is possible for SDP to signal that there is a send track, but there not
+ // actually be a send track, according to the specification; all that needs to
+ // happen is for the transceiver to be configured to send...
+ if (!GetJsepTransceiver().mSendTrack.GetNegotiatedDetails()) {
+ return Nothing();
+ }
+
+ VideoConfig oldConfig;
+ oldConfig.mSsrcs = mSsrcs;
+ oldConfig.mLocalRtpExtensions = mLocalRtpExtensions;
+ oldConfig.mCname = mCname;
+ oldConfig.mTransmitting = mTransmitting;
+ oldConfig.mVideoRtxSsrcs = mVideoRtxSsrcs;
+ oldConfig.mVideoCodec = mVideoCodec;
+ oldConfig.mVideoRtpRtcpConfig = mVideoRtpRtcpConfig;
+ oldConfig.mVideoCodecMode = mVideoCodecMode;
+
+ VideoConfig newConfig(oldConfig);
+
+ UpdateBaseConfig(&newConfig);
+
+ newConfig.mVideoRtxSsrcs = GetJsepTransceiver().mSendTrack.GetRtxSsrcs();
+
+ const JsepTrackNegotiatedDetails details(
+ *GetJsepTransceiver().mSendTrack.GetNegotiatedDetails());
+
+ if (mSenderTrack) {
+ RefPtr<mozilla::dom::VideoStreamTrack> videotrack =
+ mSenderTrack->AsVideoStreamTrack();
+
+ if (!videotrack) {
+ MOZ_CRASH(
+ "In ConfigureVideoCodecMode, mSenderTrack is not video! This should "
+ "never happen!");
+ }
+
+ dom::MediaSourceEnum source = videotrack->GetSource().GetMediaSource();
+ switch (source) {
+ case dom::MediaSourceEnum::Browser:
+ case dom::MediaSourceEnum::Screen:
+ case dom::MediaSourceEnum::Window:
+ case dom::MediaSourceEnum::Application:
+ newConfig.mVideoCodecMode = webrtc::VideoCodecMode::kScreensharing;
+ break;
+
+ case dom::MediaSourceEnum::Camera:
+ case dom::MediaSourceEnum::Other:
+ // Other is used by canvas capture, which we treat as realtime video.
+ // This seems debatable, but we've been doing it this way for a long
+ // time, so this is likely fine.
+ newConfig.mVideoCodecMode = webrtc::VideoCodecMode::kRealtimeVideo;
+ break;
+
+ case dom::MediaSourceEnum::Microphone:
+ case dom::MediaSourceEnum::AudioCapture:
+ case dom::MediaSourceEnum::EndGuard_:
+ MOZ_ASSERT(false);
+ break;
+ }
+ }
+
+ std::vector<VideoCodecConfig> configs;
+ RTCRtpTransceiver::NegotiatedDetailsToVideoCodecConfigs(details, &configs);
+
+ if (configs.empty()) {
+ // TODO: Are we supposed to plumb this error back to JS? This does not
+ // seem like a failure to set an answer, it just means that codec
+ // negotiation failed. For now, we're just doing the same thing we do
+ // if negotiation as a whole failed.
+ MOZ_LOG(gSenderLog, LogLevel::Error,
+ ("%s[%s]: %s No video codecs were negotiated (send).",
+ mPc->GetHandle().c_str(), GetMid().c_str(), __FUNCTION__));
+ return Nothing();
+ }
+
+ newConfig.mVideoCodec = Some(configs[0]);
+ // Spec says that we start using new parameters right away, _before_ we
+ // update the parameters that are visible to JS (ie; mParameters).
+ const RTCRtpSendParameters& parameters =
+ mPendingParameters.isSome() ? *mPendingParameters : mParameters;
+ for (VideoCodecConfig::Encoding& conduitEncoding :
+ newConfig.mVideoCodec->mEncodings) {
+ for (const RTCRtpEncodingParameters& jsEncoding : parameters.mEncodings) {
+ std::string rid;
+ if (jsEncoding.mRid.WasPassed()) {
+ rid = NS_ConvertUTF16toUTF8(jsEncoding.mRid.Value()).get();
+ }
+ if (conduitEncoding.rid == rid) {
+ ApplyJsEncodingToConduitEncoding(jsEncoding, &conduitEncoding);
+ break;
+ }
+ }
+ }
+
+ newConfig.mVideoRtpRtcpConfig = Some(details.GetRtpRtcpConfig());
+
+ if (newConfig == oldConfig) {
+ MOZ_LOG(gSenderLog, LogLevel::Debug,
+ ("%s[%s]: %s No change in video config", mPc->GetHandle().c_str(),
+ GetMid().c_str(), __FUNCTION__));
+ return Nothing();
+ }
+
+ if (newConfig.mVideoCodec.isSome()) {
+ MOZ_ASSERT(newConfig.mSsrcs.size() ==
+ newConfig.mVideoCodec->mEncodings.size());
+ }
+ return Some(newConfig);
+}
+
+Maybe<RTCRtpSender::AudioConfig> RTCRtpSender::GetNewAudioConfig() {
+ AudioConfig oldConfig;
+ oldConfig.mSsrcs = mSsrcs;
+ oldConfig.mLocalRtpExtensions = mLocalRtpExtensions;
+ oldConfig.mCname = mCname;
+ oldConfig.mTransmitting = mTransmitting;
+ oldConfig.mAudioCodec = mAudioCodec;
+
+ AudioConfig newConfig(oldConfig);
+
+ UpdateBaseConfig(&newConfig);
+
+ if (GetJsepTransceiver().mSendTrack.GetNegotiatedDetails() &&
+ GetJsepTransceiver().mSendTrack.GetActive()) {
+ const auto& details(
+ *GetJsepTransceiver().mSendTrack.GetNegotiatedDetails());
+
+ std::vector<AudioCodecConfig> configs;
+ RTCRtpTransceiver::NegotiatedDetailsToAudioCodecConfigs(details, &configs);
+ if (configs.empty()) {
+ // TODO: Are we supposed to plumb this error back to JS? This does not
+ // seem like a failure to set an answer, it just means that codec
+ // negotiation failed. For now, we're just doing the same thing we do
+ // if negotiation as a whole failed.
+ MOZ_LOG(gSenderLog, LogLevel::Error,
+ ("%s[%s]: %s No audio codecs were negotiated (send)",
+ mPc->GetHandle().c_str(), GetMid().c_str(), __FUNCTION__));
+ return Nothing();
+ }
+
+ std::vector<AudioCodecConfig> dtmfConfigs;
+ std::copy_if(
+ configs.begin(), configs.end(), std::back_inserter(dtmfConfigs),
+ [](const auto& value) { return value.mName == "telephone-event"; });
+
+ const AudioCodecConfig& sendCodec = configs[0];
+
+ if (!dtmfConfigs.empty()) {
+ // There is at least one telephone-event codec.
+ // We primarily choose the codec whose frequency matches the send codec.
+ // Secondarily we choose the one with the lowest frequency.
+ auto dtmfIterator =
+ std::find_if(dtmfConfigs.begin(), dtmfConfigs.end(),
+ [&sendCodec](const auto& dtmfCodec) {
+ return dtmfCodec.mFreq == sendCodec.mFreq;
+ });
+ if (dtmfIterator == dtmfConfigs.end()) {
+ dtmfIterator = std::min_element(
+ dtmfConfigs.begin(), dtmfConfigs.end(),
+ [](const auto& a, const auto& b) { return a.mFreq < b.mFreq; });
+ }
+ MOZ_ASSERT(dtmfIterator != dtmfConfigs.end());
+ newConfig.mDtmfPt = dtmfIterator->mType;
+ newConfig.mDtmfFreq = dtmfIterator->mFreq;
+ }
+
+ newConfig.mAudioCodec = Some(sendCodec);
+ }
+
+ if (newConfig == oldConfig) {
+ MOZ_LOG(gSenderLog, LogLevel::Debug,
+ ("%s[%s]: %s No change in audio config", mPc->GetHandle().c_str(),
+ GetMid().c_str(), __FUNCTION__));
+ return Nothing();
+ }
+
+ return Some(newConfig);
+}
+
+void RTCRtpSender::UpdateBaseConfig(BaseConfig* aConfig) {
+ aConfig->mSsrcs = GetJsepTransceiver().mSendTrack.GetSsrcs();
+ aConfig->mCname = GetJsepTransceiver().mSendTrack.GetCNAME();
+
+ if (GetJsepTransceiver().mSendTrack.GetNegotiatedDetails() &&
+ GetJsepTransceiver().mSendTrack.GetActive()) {
+ const auto& details(
+ *GetJsepTransceiver().mSendTrack.GetNegotiatedDetails());
+ {
+ std::vector<webrtc::RtpExtension> extmaps;
+ // @@NG read extmap from track
+ details.ForEachRTPHeaderExtension(
+ [&extmaps](const SdpExtmapAttributeList::Extmap& extmap) {
+ extmaps.emplace_back(extmap.extensionname, extmap.entry);
+ });
+ aConfig->mLocalRtpExtensions = extmaps;
+ }
+ }
+ // RTCRtpTransceiver::IsSending is updated after negotiation completes, in a
+ // queued task (which we may be in right now). Don't use
+ // JsepTrack::GetActive, because that updates before the queued task, which
+ // is too early for some of the things we interact with here (eg;
+ // RTCDTMFSender).
+ aConfig->mTransmitting = mTransceiver->IsSending();
+}
+
+void RTCRtpSender::ApplyVideoConfig(const VideoConfig& aConfig) {
+ if (aConfig.mVideoCodec.isSome()) {
+ MOZ_ASSERT(aConfig.mSsrcs.size() == aConfig.mVideoCodec->mEncodings.size());
+ }
+
+ mSsrcs = aConfig.mSsrcs;
+ mCname = aConfig.mCname;
+ mLocalRtpExtensions = aConfig.mLocalRtpExtensions;
+
+ mVideoRtxSsrcs = aConfig.mVideoRtxSsrcs;
+ mVideoCodec = aConfig.mVideoCodec;
+ mVideoRtpRtcpConfig = aConfig.mVideoRtpRtcpConfig;
+ mVideoCodecMode = aConfig.mVideoCodecMode;
+
+ mTransmitting = aConfig.mTransmitting;
+}
+
+void RTCRtpSender::ApplyAudioConfig(const AudioConfig& aConfig) {
+ mTransmitting = false;
+
+ mSsrcs = aConfig.mSsrcs;
+ mCname = aConfig.mCname;
+ mLocalRtpExtensions = aConfig.mLocalRtpExtensions;
+
+ mAudioCodec = aConfig.mAudioCodec;
+
+ if (aConfig.mDtmfPt >= 0) {
+ mDtmf->SetPayloadType(aConfig.mDtmfPt, aConfig.mDtmfFreq);
+ }
+
+ mTransmitting = aConfig.mTransmitting;
+}
+
+void RTCRtpSender::Stop() {
+ MOZ_ASSERT(mTransceiver->Stopped());
+ mTransmitting = false;
+}
+
+bool RTCRtpSender::HasTrack(const dom::MediaStreamTrack* aTrack) const {
+ if (!mSenderTrack) {
+ return false;
+ }
+
+ if (!aTrack) {
+ return true;
+ }
+
+ return mSenderTrack.get() == aTrack;
+}
+
+RefPtr<MediaPipelineTransmit> RTCRtpSender::GetPipeline() const {
+ return mPipeline;
+}
+
+std::string RTCRtpSender::GetMid() const { return mTransceiver->GetMidAscii(); }
+
+JsepTransceiver& RTCRtpSender::GetJsepTransceiver() {
+ return mTransceiver->GetJsepTransceiver();
+}
+
+void RTCRtpSender::UpdateDtmfSender() {
+ if (!mDtmf) {
+ return;
+ }
+
+ if (mTransmitting) {
+ return;
+ }
+
+ mDtmf->StopPlayout();
+}
+
+} // namespace mozilla::dom
+
+#undef LOGTAG
diff --git a/dom/media/webrtc/jsapi/RTCRtpSender.h b/dom/media/webrtc/jsapi/RTCRtpSender.h
new file mode 100644
index 0000000000..0c1282e0db
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCRtpSender.h
@@ -0,0 +1,260 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _RTCRtpSender_h_
+#define _RTCRtpSender_h_
+
+#include "nsISupports.h"
+#include "nsWrapperCache.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/StateMirroring.h"
+#include "mozilla/Maybe.h"
+#include "js/RootingAPI.h"
+#include "libwebrtcglue/RtpRtcpConfig.h"
+#include "nsTArray.h"
+#include "mozilla/dom/RTCStatsReportBinding.h"
+#include "mozilla/dom/RTCRtpCapabilitiesBinding.h"
+#include "mozilla/dom/RTCRtpParametersBinding.h"
+#include "RTCStatsReport.h"
+#include "jsep/JsepTrack.h"
+#include "transportbridge/MediaPipeline.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+class MediaSessionConduit;
+class MediaTransportHandler;
+class JsepTransceiver;
+class PeerConnectionImpl;
+class DOMMediaStream;
+
+namespace dom {
+class MediaStreamTrack;
+class Promise;
+class RTCDtlsTransport;
+class RTCDTMFSender;
+struct RTCRtpCapabilities;
+class RTCRtpTransceiver;
+
+class RTCRtpSender : public nsISupports,
+ public nsWrapperCache,
+ public MediaPipelineTransmitControlInterface {
+ public:
+ RTCRtpSender(nsPIDOMWindowInner* aWindow, PeerConnectionImpl* aPc,
+ MediaTransportHandler* aTransportHandler,
+ AbstractThread* aCallThread, nsISerialEventTarget* aStsThread,
+ MediaSessionConduit* aConduit, dom::MediaStreamTrack* aTrack,
+ const Sequence<RTCRtpEncodingParameters>& aEncodings,
+ RTCRtpTransceiver* aTransceiver);
+
+ // nsISupports
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(RTCRtpSender)
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // webidl
+ MediaStreamTrack* GetTrack() const { return mSenderTrack; }
+ RTCDtlsTransport* GetTransport() const;
+ RTCDTMFSender* GetDtmf() const;
+ MOZ_CAN_RUN_SCRIPT
+ already_AddRefed<Promise> ReplaceTrack(MediaStreamTrack* aWithTrack,
+ ErrorResult& aError);
+ already_AddRefed<Promise> GetStats(ErrorResult& aError);
+ static void GetCapabilities(const GlobalObject&, const nsAString& kind,
+ Nullable<dom::RTCRtpCapabilities>& result);
+ already_AddRefed<Promise> SetParameters(
+ const dom::RTCRtpSendParameters& aParameters, ErrorResult& aError);
+ // Not a simple getter, so not const
+ // See https://w3c.github.io/webrtc-pc/#dom-rtcrtpsender-getparameters
+ void GetParameters(RTCRtpSendParameters& aParameters);
+
+ static void CheckAndRectifyEncodings(
+ Sequence<RTCRtpEncodingParameters>& aEncodings, bool aVideo,
+ ErrorResult& aRv);
+
+ nsPIDOMWindowInner* GetParentObject() const;
+ nsTArray<RefPtr<RTCStatsPromise>> GetStatsInternal(
+ bool aSkipIceStats = false);
+
+ void SetStreams(const Sequence<OwningNonNull<DOMMediaStream>>& aStreams,
+ ErrorResult& aRv);
+ // ChromeOnly webidl
+ void GetStreams(nsTArray<RefPtr<DOMMediaStream>>& aStreams);
+ // ChromeOnly webidl
+ void SetStreamsImpl(const Sequence<OwningNonNull<DOMMediaStream>>& aStreams);
+ // ChromeOnly webidl
+ void SetTrack(const RefPtr<MediaStreamTrack>& aTrack);
+ void Shutdown();
+ void BreakCycles();
+ // Terminal state, reached through stopping RTCRtpTransceiver.
+ void Stop();
+ bool HasTrack(const dom::MediaStreamTrack* aTrack) const;
+ bool IsMyPc(const PeerConnectionImpl* aPc) const { return mPc.get() == aPc; }
+ RefPtr<MediaPipelineTransmit> GetPipeline() const;
+ already_AddRefed<dom::Promise> MakePromise(ErrorResult& aError) const;
+ bool SeamlessTrackSwitch(const RefPtr<MediaStreamTrack>& aWithTrack);
+ bool SetSenderTrackWithClosedCheck(const RefPtr<MediaStreamTrack>& aTrack);
+
+ // This is called when we set an answer (ie; when the transport is finalized).
+ void UpdateTransport();
+ void SyncToJsep(JsepTransceiver& aJsepTransceiver) const;
+ void SyncFromJsep(const JsepTransceiver& aJsepTransceiver);
+ void MaybeUpdateConduit();
+
+ AbstractCanonical<Ssrcs>* CanonicalSsrcs() { return &mSsrcs; }
+ AbstractCanonical<Ssrcs>* CanonicalVideoRtxSsrcs() { return &mVideoRtxSsrcs; }
+ AbstractCanonical<RtpExtList>* CanonicalLocalRtpExtensions() {
+ return &mLocalRtpExtensions;
+ }
+
+ AbstractCanonical<Maybe<AudioCodecConfig>>* CanonicalAudioCodec() {
+ return &mAudioCodec;
+ }
+
+ AbstractCanonical<Maybe<VideoCodecConfig>>* CanonicalVideoCodec() {
+ return &mVideoCodec;
+ }
+ AbstractCanonical<Maybe<RtpRtcpConfig>>* CanonicalVideoRtpRtcpConfig() {
+ return &mVideoRtpRtcpConfig;
+ }
+ AbstractCanonical<webrtc::VideoCodecMode>* CanonicalVideoCodecMode() {
+ return &mVideoCodecMode;
+ }
+ AbstractCanonical<std::string>* CanonicalCname() { return &mCname; }
+ AbstractCanonical<bool>* CanonicalTransmitting() override {
+ return &mTransmitting;
+ }
+
+ bool HasPendingSetParameters() const { return mPendingParameters.isSome(); }
+ void InvalidateLastReturnedParameters() {
+ mLastReturnedParameters = Nothing();
+ }
+
+ private:
+ virtual ~RTCRtpSender();
+
+ std::string GetMid() const;
+ JsepTransceiver& GetJsepTransceiver();
+ static void ApplyJsEncodingToConduitEncoding(
+ const RTCRtpEncodingParameters& aJsEncoding,
+ VideoCodecConfig::Encoding* aConduitEncoding);
+ void UpdateRestorableEncodings(
+ const Sequence<RTCRtpEncodingParameters>& aEncodings);
+ Sequence<RTCRtpEncodingParameters> GetMatchingEncodings(
+ const std::vector<std::string>& aRids) const;
+ Sequence<RTCRtpEncodingParameters> ToSendEncodings(
+ const std::vector<std::string>& aRids) const;
+ void MaybeGetJsepRids();
+ void UpdateDtmfSender();
+
+ void WarnAboutBadSetParameters(const nsCString& aError);
+ nsCString GetEffectiveTLDPlus1() const;
+
+ WatchManager<RTCRtpSender> mWatchManager;
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ RefPtr<PeerConnectionImpl> mPc;
+ RefPtr<dom::MediaStreamTrack> mSenderTrack;
+ bool mAddTrackCalled = false;
+ RTCRtpSendParameters mParameters;
+ Maybe<RTCRtpSendParameters> mPendingParameters;
+ uint32_t mNumSetParametersCalls = 0;
+ // When JSEP goes from simulcast to unicast without a rid, and we started out
+ // as unicast without a rid, we are supposed to restore that unicast encoding
+ // from before.
+ Maybe<RTCRtpEncodingParameters> mUnicastEncoding;
+ bool mSimulcastEnvelopeSet = false;
+ bool mSimulcastEnvelopeSetByJSEP = false;
+ bool mPendingRidChangeFromCompatMode = false;
+ Maybe<RTCRtpSendParameters> mLastReturnedParameters;
+ RefPtr<MediaPipelineTransmit> mPipeline;
+ RefPtr<MediaTransportHandler> mTransportHandler;
+ RefPtr<RTCRtpTransceiver> mTransceiver;
+ nsTArray<RefPtr<DOMMediaStream>> mStreams;
+ bool mHaveSetupTransport = false;
+ // TODO(bug 1803388): Remove this stuff once it is no longer needed.
+ bool mAllowOldSetParameters = false;
+
+ // TODO(bug 1803388): Remove the glean warnings once they are no longer needed
+ bool mHaveWarnedBecauseNoGetParameters = false;
+ bool mHaveWarnedBecauseEncodingCountChange = false;
+ bool mHaveWarnedBecauseNoTransactionId = false;
+ bool mHaveWarnedBecauseStaleTransactionId = false;
+ // TODO(bug 1803389): Remove the glean errors once they are no longer needed.
+ bool mHaveFailedBecauseNoGetParameters = false;
+ bool mHaveFailedBecauseEncodingCountChange = false;
+ bool mHaveFailedBecauseRidChange = false;
+ bool mHaveFailedBecauseNoTransactionId = false;
+ bool mHaveFailedBecauseStaleTransactionId = false;
+ bool mHaveFailedBecauseNoEncodings = false;
+ bool mHaveFailedBecauseOtherError = false;
+
+ RefPtr<dom::RTCDTMFSender> mDtmf;
+
+ class BaseConfig {
+ public:
+ // TODO(bug 1744116): Use = default here
+ bool operator==(const BaseConfig& aOther) const {
+ return mSsrcs == aOther.mSsrcs &&
+ mLocalRtpExtensions == aOther.mLocalRtpExtensions &&
+ mCname == aOther.mCname && mTransmitting == aOther.mTransmitting;
+ }
+ Ssrcs mSsrcs;
+ RtpExtList mLocalRtpExtensions;
+ std::string mCname;
+ bool mTransmitting = false;
+ };
+
+ class VideoConfig : public BaseConfig {
+ public:
+ // TODO(bug 1744116): Use = default here
+ bool operator==(const VideoConfig& aOther) const {
+ return BaseConfig::operator==(aOther) &&
+ mVideoRtxSsrcs == aOther.mVideoRtxSsrcs &&
+ mVideoCodec == aOther.mVideoCodec &&
+ mVideoRtpRtcpConfig == aOther.mVideoRtpRtcpConfig &&
+ mVideoCodecMode == aOther.mVideoCodecMode;
+ }
+ Ssrcs mVideoRtxSsrcs;
+ Maybe<VideoCodecConfig> mVideoCodec;
+ Maybe<RtpRtcpConfig> mVideoRtpRtcpConfig;
+ webrtc::VideoCodecMode mVideoCodecMode =
+ webrtc::VideoCodecMode::kRealtimeVideo;
+ };
+
+ class AudioConfig : public BaseConfig {
+ public:
+ // TODO(bug 1744116): Use = default here
+ bool operator==(const AudioConfig& aOther) const {
+ return BaseConfig::operator==(aOther) &&
+ mAudioCodec == aOther.mAudioCodec && mDtmfPt == aOther.mDtmfPt &&
+ mDtmfFreq == aOther.mDtmfFreq;
+ }
+ Maybe<AudioCodecConfig> mAudioCodec;
+ int32_t mDtmfPt = -1;
+ int32_t mDtmfFreq = 0;
+ };
+
+ Maybe<VideoConfig> GetNewVideoConfig();
+ Maybe<AudioConfig> GetNewAudioConfig();
+ void UpdateBaseConfig(BaseConfig* aConfig);
+ void ApplyVideoConfig(const VideoConfig& aConfig);
+ void ApplyAudioConfig(const AudioConfig& aConfig);
+
+ Canonical<Ssrcs> mSsrcs;
+ Canonical<Ssrcs> mVideoRtxSsrcs;
+ Canonical<RtpExtList> mLocalRtpExtensions;
+
+ Canonical<Maybe<AudioCodecConfig>> mAudioCodec;
+ Canonical<Maybe<VideoCodecConfig>> mVideoCodec;
+ Canonical<Maybe<RtpRtcpConfig>> mVideoRtpRtcpConfig;
+ Canonical<webrtc::VideoCodecMode> mVideoCodecMode;
+ Canonical<std::string> mCname;
+ Canonical<bool> mTransmitting;
+};
+
+} // namespace dom
+} // namespace mozilla
+#endif // _RTCRtpSender_h_
diff --git a/dom/media/webrtc/jsapi/RTCRtpTransceiver.cpp b/dom/media/webrtc/jsapi/RTCRtpTransceiver.cpp
new file mode 100644
index 0000000000..bf4f74dd5d
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCRtpTransceiver.cpp
@@ -0,0 +1,1080 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "jsapi/RTCRtpTransceiver.h"
+#include "mozilla/UniquePtr.h"
+#include <algorithm>
+#include <string>
+#include <vector>
+#include "libwebrtcglue/AudioConduit.h"
+#include "libwebrtcglue/VideoConduit.h"
+#include "MediaTrackGraph.h"
+#include "transportbridge/MediaPipeline.h"
+#include "transportbridge/MediaPipelineFilter.h"
+#include "jsep/JsepTrack.h"
+#include "sdp/SdpHelper.h"
+#include "MediaTrackGraphImpl.h"
+#include "transport/logging.h"
+#include "MediaEngine.h"
+#include "nsIPrincipal.h"
+#include "MediaSegment.h"
+#include "RemoteTrackSource.h"
+#include "libwebrtcglue/RtpRtcpConfig.h"
+#include "MediaTransportHandler.h"
+#include "mozilla/dom/RTCRtpReceiverBinding.h"
+#include "mozilla/dom/RTCRtpSenderBinding.h"
+#include "mozilla/dom/RTCRtpTransceiverBinding.h"
+#include "mozilla/dom/Promise.h"
+#include "RTCDtlsTransport.h"
+#include "RTCRtpReceiver.h"
+#include "RTCRtpSender.h"
+#include "RTCDTMFSender.h"
+#include "systemservices/MediaUtils.h"
+#include "libwebrtcglue/WebrtcCallWrapper.h"
+#include "libwebrtcglue/WebrtcGmpVideoCodec.h"
+#include "utils/PerformanceRecorder.h"
+
+namespace mozilla {
+
+using namespace dom;
+
+namespace {
+struct ConduitControlState : public AudioConduitControlInterface,
+ public VideoConduitControlInterface {
+ ConduitControlState(RTCRtpTransceiver* aTransceiver, RTCRtpSender* aSender,
+ RTCRtpReceiver* aReceiver)
+ : mTransceiver(new nsMainThreadPtrHolder<RTCRtpTransceiver>(
+ "ConduitControlState::mTransceiver", aTransceiver, false)),
+ mSender(new nsMainThreadPtrHolder<dom::RTCRtpSender>(
+ "ConduitControlState::mSender", aSender, false)),
+ mReceiver(new nsMainThreadPtrHolder<dom::RTCRtpReceiver>(
+ "ConduitControlState::mReceiver", aReceiver, false)) {}
+
+ const nsMainThreadPtrHandle<RTCRtpTransceiver> mTransceiver;
+ const nsMainThreadPtrHandle<RTCRtpSender> mSender;
+ const nsMainThreadPtrHandle<RTCRtpReceiver> mReceiver;
+
+ // MediaConduitControlInterface
+ AbstractCanonical<bool>* CanonicalReceiving() override {
+ return mReceiver->CanonicalReceiving();
+ }
+ AbstractCanonical<bool>* CanonicalTransmitting() override {
+ return mSender->CanonicalTransmitting();
+ }
+ AbstractCanonical<Ssrcs>* CanonicalLocalSsrcs() override {
+ return mSender->CanonicalSsrcs();
+ }
+ AbstractCanonical<std::string>* CanonicalLocalCname() override {
+ return mSender->CanonicalCname();
+ }
+ AbstractCanonical<std::string>* CanonicalMid() override {
+ return mTransceiver->CanonicalMid();
+ }
+ AbstractCanonical<Ssrc>* CanonicalRemoteSsrc() override {
+ return mReceiver->CanonicalSsrc();
+ }
+ AbstractCanonical<std::string>* CanonicalSyncGroup() override {
+ return mTransceiver->CanonicalSyncGroup();
+ }
+ AbstractCanonical<RtpExtList>* CanonicalLocalRecvRtpExtensions() override {
+ return mReceiver->CanonicalLocalRtpExtensions();
+ }
+ AbstractCanonical<RtpExtList>* CanonicalLocalSendRtpExtensions() override {
+ return mSender->CanonicalLocalRtpExtensions();
+ }
+
+ // AudioConduitControlInterface
+ AbstractCanonical<Maybe<AudioCodecConfig>>* CanonicalAudioSendCodec()
+ override {
+ return mSender->CanonicalAudioCodec();
+ }
+ AbstractCanonical<std::vector<AudioCodecConfig>>* CanonicalAudioRecvCodecs()
+ override {
+ return mReceiver->CanonicalAudioCodecs();
+ }
+ MediaEventSource<DtmfEvent>& OnDtmfEvent() override {
+ return mSender->GetDtmf()->OnDtmfEvent();
+ }
+
+ // VideoConduitControlInterface
+ AbstractCanonical<Ssrcs>* CanonicalLocalVideoRtxSsrcs() override {
+ return mSender->CanonicalVideoRtxSsrcs();
+ }
+ AbstractCanonical<Ssrc>* CanonicalRemoteVideoRtxSsrc() override {
+ return mReceiver->CanonicalVideoRtxSsrc();
+ }
+ AbstractCanonical<Maybe<VideoCodecConfig>>* CanonicalVideoSendCodec()
+ override {
+ return mSender->CanonicalVideoCodec();
+ }
+ AbstractCanonical<Maybe<RtpRtcpConfig>>* CanonicalVideoSendRtpRtcpConfig()
+ override {
+ return mSender->CanonicalVideoRtpRtcpConfig();
+ }
+ AbstractCanonical<std::vector<VideoCodecConfig>>* CanonicalVideoRecvCodecs()
+ override {
+ return mReceiver->CanonicalVideoCodecs();
+ }
+ AbstractCanonical<Maybe<RtpRtcpConfig>>* CanonicalVideoRecvRtpRtcpConfig()
+ override {
+ return mReceiver->CanonicalVideoRtpRtcpConfig();
+ }
+ AbstractCanonical<webrtc::VideoCodecMode>* CanonicalVideoCodecMode()
+ override {
+ return mSender->CanonicalVideoCodecMode();
+ }
+};
+} // namespace
+
+MOZ_MTLOG_MODULE("RTCRtpTransceiver")
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(RTCRtpTransceiver)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(RTCRtpTransceiver)
+ if (tmp->mHandlingUnlink) {
+ tmp->BreakCycles();
+ tmp->mHandlingUnlink = false;
+ }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(RTCRtpTransceiver)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow, mPc, mSendTrack, mReceiver,
+ mSender, mDtlsTransport,
+ mLastStableDtlsTransport)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(RTCRtpTransceiver)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(RTCRtpTransceiver)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCRtpTransceiver)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+#define INIT_CANONICAL(name, val) \
+ name(AbstractThread::MainThread(), val, \
+ "RTCRtpTransceiver::" #name " (Canonical)")
+
+RTCRtpTransceiver::RTCRtpTransceiver(
+ nsPIDOMWindowInner* aWindow, bool aPrivacyNeeded, PeerConnectionImpl* aPc,
+ MediaTransportHandler* aTransportHandler, JsepSession* aJsepSession,
+ const std::string& aTransceiverId, bool aIsVideo,
+ nsISerialEventTarget* aStsThread, dom::MediaStreamTrack* aSendTrack,
+ WebrtcCallWrapper* aCallWrapper, RTCStatsIdGenerator* aIdGenerator)
+ : mWindow(aWindow),
+ mPc(aPc),
+ mTransportHandler(aTransportHandler),
+ mTransceiverId(aTransceiverId),
+ mJsepTransceiver(*aJsepSession->GetTransceiver(mTransceiverId)),
+ mStsThread(aStsThread),
+ mCallWrapper(aCallWrapper),
+ mSendTrack(aSendTrack),
+ mIdGenerator(aIdGenerator),
+ mPrincipalPrivacy(aPrivacyNeeded ? PrincipalPrivacy::Private
+ : PrincipalPrivacy::NonPrivate),
+ mIsVideo(aIsVideo),
+ INIT_CANONICAL(mMid, std::string()),
+ INIT_CANONICAL(mSyncGroup, std::string()) {}
+
+#undef INIT_CANONICAL
+
+RTCRtpTransceiver::~RTCRtpTransceiver() = default;
+
+SdpDirectionAttribute::Direction ToSdpDirection(
+ RTCRtpTransceiverDirection aDirection) {
+ switch (aDirection) {
+ case dom::RTCRtpTransceiverDirection::Sendrecv:
+ return SdpDirectionAttribute::Direction::kSendrecv;
+ case dom::RTCRtpTransceiverDirection::Sendonly:
+ return SdpDirectionAttribute::Direction::kSendonly;
+ case dom::RTCRtpTransceiverDirection::Recvonly:
+ return SdpDirectionAttribute::Direction::kRecvonly;
+ case dom::RTCRtpTransceiverDirection::Inactive:
+ return SdpDirectionAttribute::Direction::kInactive;
+ case dom::RTCRtpTransceiverDirection::EndGuard_:;
+ }
+ MOZ_CRASH("Invalid transceiver direction!");
+}
+
+static uint32_t sRemoteSourceId = 0;
+
+// TODO(bug 1401592): Once we implement the sendEncodings stuff, there will
+// need to be validation code in here.
+void RTCRtpTransceiver::Init(const RTCRtpTransceiverInit& aInit,
+ ErrorResult& aRv) {
+ TrackingId trackingId(TrackingId::Source::RTCRtpReceiver, sRemoteSourceId++,
+ TrackingId::TrackAcrossProcesses::Yes);
+ if (IsVideo()) {
+ InitVideo(trackingId);
+ } else {
+ InitAudio();
+ }
+
+ if (!IsValid()) {
+ aRv = NS_ERROR_UNEXPECTED;
+ return;
+ }
+
+ mReceiver = new RTCRtpReceiver(mWindow, mPrincipalPrivacy, mPc,
+ mTransportHandler, mCallWrapper->mCallThread,
+ mStsThread, mConduit, this, trackingId);
+
+ mSender = new RTCRtpSender(mWindow, mPc, mTransportHandler,
+ mCallWrapper->mCallThread, mStsThread, mConduit,
+ mSendTrack, aInit.mSendEncodings, this);
+
+ if (mConduit) {
+ InitConduitControl();
+ }
+
+ mSender->SetStreamsImpl(aInit.mStreams);
+ mDirection = aInit.mDirection;
+}
+
+void RTCRtpTransceiver::SetDtlsTransport(dom::RTCDtlsTransport* aDtlsTransport,
+ bool aStable) {
+ mDtlsTransport = aDtlsTransport;
+ if (aStable) {
+ mLastStableDtlsTransport = mDtlsTransport;
+ }
+}
+
+void RTCRtpTransceiver::RollbackToStableDtlsTransport() {
+ mDtlsTransport = mLastStableDtlsTransport;
+}
+
+void RTCRtpTransceiver::InitAudio() {
+ mConduit = AudioSessionConduit::Create(mCallWrapper, mStsThread);
+
+ if (!mConduit) {
+ MOZ_MTLOG(ML_ERROR, mPc->GetHandle()
+ << "[" << mMid.Ref() << "]: " << __FUNCTION__
+ << ": Failed to create AudioSessionConduit");
+ // TODO(bug 1422897): We need a way to record this when it happens in the
+ // wild.
+ }
+}
+
+void RTCRtpTransceiver::InitVideo(const TrackingId& aRecvTrackingId) {
+ VideoSessionConduit::Options options;
+ options.mVideoLatencyTestEnable =
+ Preferences::GetBool("media.video.test_latency", false);
+ options.mMinBitrate = std::max(
+ 0,
+ Preferences::GetInt("media.peerconnection.video.min_bitrate", 0) * 1000);
+ options.mStartBitrate = std::max(
+ 0, Preferences::GetInt("media.peerconnection.video.start_bitrate", 0) *
+ 1000);
+ options.mPrefMaxBitrate = std::max(
+ 0,
+ Preferences::GetInt("media.peerconnection.video.max_bitrate", 0) * 1000);
+ if (options.mMinBitrate != 0 &&
+ options.mMinBitrate < kViEMinCodecBitrate_bps) {
+ options.mMinBitrate = kViEMinCodecBitrate_bps;
+ }
+ if (options.mStartBitrate < options.mMinBitrate) {
+ options.mStartBitrate = options.mMinBitrate;
+ }
+ if (options.mPrefMaxBitrate &&
+ options.mStartBitrate > options.mPrefMaxBitrate) {
+ options.mStartBitrate = options.mPrefMaxBitrate;
+ }
+ // XXX We'd love if this was a live param for testing adaptation/etc
+ // in automation
+ options.mMinBitrateEstimate =
+ std::max(0, Preferences::GetInt(
+ "media.peerconnection.video.min_bitrate_estimate", 0) *
+ 1000);
+ options.mSpatialLayers = std::max(
+ 1, Preferences::GetInt("media.peerconnection.video.svc.spatial", 0));
+ options.mTemporalLayers = std::max(
+ 1, Preferences::GetInt("media.peerconnection.video.svc.temporal", 0));
+ options.mDenoising =
+ Preferences::GetBool("media.peerconnection.video.denoising", false);
+ options.mLockScaling =
+ Preferences::GetBool("media.peerconnection.video.lock_scaling", false);
+
+ mConduit =
+ VideoSessionConduit::Create(mCallWrapper, mStsThread, std::move(options),
+ mPc->GetHandle(), aRecvTrackingId);
+
+ if (!mConduit) {
+ MOZ_MTLOG(ML_ERROR, mPc->GetHandle()
+ << "[" << mMid.Ref() << "]: " << __FUNCTION__
+ << ": Failed to create VideoSessionConduit");
+ // TODO(bug 1422897): We need a way to record this when it happens in the
+ // wild.
+ }
+}
+
+void RTCRtpTransceiver::InitConduitControl() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mConduit);
+ ConduitControlState control(this, mSender, mReceiver);
+ mCallWrapper->mCallThread->Dispatch(NS_NewRunnableFunction(
+ __func__, [conduit = mConduit, control = std::move(control)]() mutable {
+ conduit->AsVideoSessionConduit().apply(
+ [&](VideoSessionConduit* aConduit) {
+ aConduit->InitControl(&control);
+ });
+ conduit->AsAudioSessionConduit().apply(
+ [&](AudioSessionConduit* aConduit) {
+ aConduit->InitControl(&control);
+ });
+ }));
+}
+
+void RTCRtpTransceiver::Close() {
+ // Called via PCImpl::Close -> PCImpl::CloseInt -> PCImpl::ShutdownMedia ->
+ // PCMedia::SelfDestruct. Satisfies step 7 of
+ // https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-close
+ mShutdown = true;
+ if (mDtlsTransport) {
+ mDtlsTransport->UpdateState(TransportLayer::TS_CLOSED);
+ }
+ StopImpl();
+}
+
+void RTCRtpTransceiver::BreakCycles() {
+ mSender->BreakCycles();
+ mReceiver->BreakCycles();
+ mWindow = nullptr;
+ mSendTrack = nullptr;
+ mSender = nullptr;
+ mReceiver = nullptr;
+ mDtlsTransport = nullptr;
+ mLastStableDtlsTransport = nullptr;
+ mPc = nullptr;
+}
+
+// TODO: Only called from one place in PeerConnectionImpl, synchronously, when
+// the JSEP engine has successfully completed an offer/answer exchange. This is
+// a bit squirrely, since identity validation happens asynchronously in
+// PeerConnection.jsm. This probably needs to happen once all the "in parallel"
+// steps have succeeded, but before we queue the task for JS observable state
+// updates.
+nsresult RTCRtpTransceiver::UpdateTransport() {
+ if (!mHasTransport) {
+ return NS_OK;
+ }
+
+ mReceiver->UpdateTransport();
+ mSender->UpdateTransport();
+ return NS_OK;
+}
+
+nsresult RTCRtpTransceiver::UpdateConduit() {
+ if (mStopped) {
+ return NS_OK;
+ }
+
+ mReceiver->UpdateConduit();
+ mSender->MaybeUpdateConduit();
+
+ return NS_OK;
+}
+
+void RTCRtpTransceiver::UpdatePrincipalPrivacy(PrincipalPrivacy aPrivacy) {
+ if (mPrincipalPrivacy == aPrivacy) {
+ return;
+ }
+
+ mPrincipalPrivacy = aPrivacy;
+ mReceiver->UpdatePrincipalPrivacy(mPrincipalPrivacy);
+}
+
+void RTCRtpTransceiver::ResetSync() { mSyncGroup = std::string(); }
+
+// TODO: Only called from one place in PeerConnectionImpl, synchronously, when
+// the JSEP engine has successfully completed an offer/answer exchange. This is
+// a bit squirrely, since identity validation happens asynchronously in
+// PeerConnection.jsm. This probably needs to happen once all the "in parallel"
+// steps have succeeded, but before we queue the task for JS observable state
+// updates.
+nsresult RTCRtpTransceiver::SyncWithMatchingVideoConduits(
+ nsTArray<RefPtr<RTCRtpTransceiver>>& transceivers) {
+ if (mStopped) {
+ return NS_OK;
+ }
+
+ if (IsVideo()) {
+ MOZ_MTLOG(ML_ERROR, mPc->GetHandle()
+ << "[" << mMid.Ref() << "]: " << __FUNCTION__
+ << " called when transceiver is not "
+ "video! This should never happen.");
+ MOZ_CRASH();
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ std::set<std::string> myReceiveStreamIds;
+ myReceiveStreamIds.insert(mReceiver->GetStreamIds().begin(),
+ mReceiver->GetStreamIds().end());
+
+ for (RefPtr<RTCRtpTransceiver>& transceiver : transceivers) {
+ if (!transceiver->IsValid()) {
+ continue;
+ }
+
+ if (!transceiver->IsVideo()) {
+ // |this| is an audio transceiver, so we skip other audio transceivers
+ continue;
+ }
+
+ // Maybe could make this more efficient by cacheing this set, but probably
+ // not worth it.
+ for (const std::string& streamId :
+ transceiver->Receiver()->GetStreamIds()) {
+ if (myReceiveStreamIds.count(streamId)) {
+ // Ok, we have one video, one non-video - cross the streams!
+ mSyncGroup = streamId;
+ transceiver->mSyncGroup = streamId;
+
+ MOZ_MTLOG(ML_DEBUG, mPc->GetHandle()
+ << "[" << mMid.Ref() << "]: " << __FUNCTION__
+ << " Syncing " << mConduit.get() << " to "
+ << transceiver->mConduit.get());
+
+ // The sync code in call.cc only permits sync between audio stream and
+ // one video stream. They take the first match, so there's no point in
+ // continuing here. If we want to change the default, we should sort
+ // video streams here and only call SetSyncGroup on the chosen stream.
+ break;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+bool RTCRtpTransceiver::ConduitHasPluginID(uint64_t aPluginID) {
+ return mConduit && mConduit->HasCodecPluginID(aPluginID);
+}
+
+void RTCRtpTransceiver::SyncFromJsep(const JsepSession& aSession) {
+ MOZ_MTLOG(ML_DEBUG, mPc->GetHandle()
+ << "[" << mMid.Ref() << "]: " << __FUNCTION__
+ << " Syncing from JSEP transceiver");
+ if (mShutdown) {
+ // Shutdown_m has already been called, probably due to pc.close(). Just
+ // nod and smile.
+ return;
+ }
+
+ mJsepTransceiver = *aSession.GetTransceiver(mTransceiverId);
+
+ // Transceivers can stop due to JSEP negotiation, so we need to check that
+ if (mJsepTransceiver.IsStopped()) {
+ StopImpl();
+ }
+
+ mReceiver->SyncFromJsep(mJsepTransceiver);
+ mSender->SyncFromJsep(mJsepTransceiver);
+
+ // mid from JSEP
+ if (mJsepTransceiver.IsAssociated()) {
+ mMid = mJsepTransceiver.GetMid();
+ } else {
+ mMid = std::string();
+ }
+
+ // currentDirection from JSEP, but not if "this transceiver has never been
+ // represented in an offer/answer exchange"
+ if (mJsepTransceiver.HasLevel() && mJsepTransceiver.IsNegotiated()) {
+ if (mJsepTransceiver.mRecvTrack.GetActive()) {
+ if (mJsepTransceiver.mSendTrack.GetActive()) {
+ mCurrentDirection.SetValue(dom::RTCRtpTransceiverDirection::Sendrecv);
+ mHasBeenUsedToSend = true;
+ } else {
+ mCurrentDirection.SetValue(dom::RTCRtpTransceiverDirection::Recvonly);
+ }
+ } else {
+ if (mJsepTransceiver.mSendTrack.GetActive()) {
+ mCurrentDirection.SetValue(dom::RTCRtpTransceiverDirection::Sendonly);
+ mHasBeenUsedToSend = true;
+ } else {
+ mCurrentDirection.SetValue(dom::RTCRtpTransceiverDirection::Inactive);
+ }
+ }
+ }
+
+ mShouldRemove = mJsepTransceiver.IsRemoved();
+ mHasTransport = mJsepTransceiver.HasLevel() && !mJsepTransceiver.IsStopped();
+}
+
+void RTCRtpTransceiver::SyncToJsep(JsepSession& aSession) const {
+ MOZ_MTLOG(ML_DEBUG, mPc->GetHandle()
+ << "[" << mMid.Ref() << "]: " << __FUNCTION__
+ << " Syncing to JSEP transceiver");
+
+ aSession.ApplyToTransceiver(
+ mTransceiverId, [this, self = RefPtr<const RTCRtpTransceiver>(this)](
+ JsepTransceiver& aTransceiver) {
+ mReceiver->SyncToJsep(aTransceiver);
+ mSender->SyncToJsep(aTransceiver);
+ aTransceiver.mJsDirection = ToSdpDirection(mDirection);
+ if (mStopped) {
+ aTransceiver.Stop();
+ }
+ });
+}
+
+void RTCRtpTransceiver::GetKind(nsAString& aKind) const {
+ // The transceiver kind of an RTCRtpTransceiver is defined by the kind of the
+ // associated RTCRtpReceiver's MediaStreamTrack object.
+ MOZ_ASSERT(mReceiver && mReceiver->Track());
+ mReceiver->Track()->GetKind(aKind);
+}
+
+void RTCRtpTransceiver::GetMid(nsAString& aMid) const {
+ if (!mMid.Ref().empty()) {
+ aMid = NS_ConvertUTF8toUTF16(mMid.Ref());
+ } else {
+ aMid.SetIsVoid(true);
+ }
+}
+
+std::string RTCRtpTransceiver::GetMidAscii() const {
+ if (mMid.Ref().empty()) {
+ return std::string();
+ }
+
+ return mMid.Ref();
+}
+
+void RTCRtpTransceiver::SetDirection(RTCRtpTransceiverDirection aDirection,
+ ErrorResult& aRv) {
+ if (mStopped) {
+ aRv.ThrowInvalidStateError("Transceiver is stopped!");
+ return;
+ }
+
+ if (aDirection == mDirection) {
+ return;
+ }
+
+ SetDirectionInternal(aDirection);
+
+ mPc->UpdateNegotiationNeeded();
+}
+
+void RTCRtpTransceiver::SetDirectionInternal(
+ RTCRtpTransceiverDirection aDirection) {
+ // We do not update the direction on the JsepTransceiver until sync
+ mDirection = aDirection;
+}
+
+bool RTCRtpTransceiver::ShouldRemove() const { return mShouldRemove; }
+
+bool RTCRtpTransceiver::CanSendDTMF() const {
+ // Spec says: "If connection's RTCPeerConnectionState is not "connected"
+ // return false." We don't support that right now. This is supposed to be
+ // true once ICE is complete, and _all_ DTLS handshakes are also complete. We
+ // don't really have access to the state of _all_ of our DTLS states either.
+ // Our pipeline _does_ know whether SRTP/SRTCP is ready, which happens
+ // immediately after our transport finishes DTLS (unless there was an error),
+ // so this is pretty close.
+ // TODO (bug 1265827): Base this on RTCPeerConnectionState instead.
+ // TODO (bug 1623193): Tighten this up
+ if (!IsSending() || !mSender->GetTrack()) {
+ return false;
+ }
+
+ // Ok, it looks like the connection is up and sending. Did we negotiate
+ // telephone-event?
+ const JsepTrackNegotiatedDetails* details =
+ mJsepTransceiver.mSendTrack.GetNegotiatedDetails();
+ if (NS_WARN_IF(!details || !details->GetEncodingCount())) {
+ // What?
+ return false;
+ }
+
+ for (size_t i = 0; i < details->GetEncodingCount(); ++i) {
+ const auto& encoding = details->GetEncoding(i);
+ for (const auto& codec : encoding.GetCodecs()) {
+ if (codec->mName == "telephone-event") {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+JSObject* RTCRtpTransceiver::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return dom::RTCRtpTransceiver_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsPIDOMWindowInner* RTCRtpTransceiver::GetParentObject() const {
+ return mWindow;
+}
+
+static void JsepCodecDescToAudioCodecConfig(
+ const JsepAudioCodecDescription& aCodec, Maybe<AudioCodecConfig>* aConfig) {
+ uint16_t pt;
+
+ // TODO(bug 1761272): Getting the pt for a JsepAudioCodecDescription should be
+ // infallible.
+ if (NS_WARN_IF(!aCodec.GetPtAsInt(&pt))) {
+ MOZ_MTLOG(ML_ERROR, "Invalid payload type: " << aCodec.mDefaultPt);
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ // libwebrtc crashes if we attempt to configure a mono recv codec
+ bool sendMono = aCodec.mForceMono && aCodec.mDirection == sdp::kSend;
+
+ *aConfig = Some(AudioCodecConfig(
+ pt, aCodec.mName, static_cast<int>(aCodec.mClock),
+ sendMono ? 1 : static_cast<int>(aCodec.mChannels), aCodec.mFECEnabled));
+ (*aConfig)->mMaxPlaybackRate = static_cast<int>(aCodec.mMaxPlaybackRate);
+ (*aConfig)->mDtmfEnabled = aCodec.mDtmfEnabled;
+ (*aConfig)->mDTXEnabled = aCodec.mDTXEnabled;
+ (*aConfig)->mMaxAverageBitrate = aCodec.mMaxAverageBitrate;
+ (*aConfig)->mFrameSizeMs = aCodec.mFrameSizeMs;
+ (*aConfig)->mMinFrameSizeMs = aCodec.mMinFrameSizeMs;
+ (*aConfig)->mMaxFrameSizeMs = aCodec.mMaxFrameSizeMs;
+ (*aConfig)->mCbrEnabled = aCodec.mCbrEnabled;
+}
+
+// TODO: This and the next function probably should move to JsepTransceiver
+Maybe<const std::vector<UniquePtr<JsepCodecDescription>>&>
+RTCRtpTransceiver::GetNegotiatedSendCodecs() const {
+ if (!mJsepTransceiver.mSendTrack.GetActive()) {
+ return Nothing();
+ }
+
+ const auto* details = mJsepTransceiver.mSendTrack.GetNegotiatedDetails();
+ if (!details) {
+ return Nothing();
+ }
+
+ if (details->GetEncodingCount() == 0) {
+ return Nothing();
+ }
+
+ return SomeRef(details->GetEncoding(0).GetCodecs());
+}
+
+Maybe<const std::vector<UniquePtr<JsepCodecDescription>>&>
+RTCRtpTransceiver::GetNegotiatedRecvCodecs() const {
+ if (!mJsepTransceiver.mRecvTrack.GetActive()) {
+ return Nothing();
+ }
+
+ const auto* details = mJsepTransceiver.mRecvTrack.GetNegotiatedDetails();
+ if (!details) {
+ return Nothing();
+ }
+
+ if (details->GetEncodingCount() == 0) {
+ return Nothing();
+ }
+
+ return SomeRef(details->GetEncoding(0).GetCodecs());
+}
+
+// TODO: Maybe move this someplace else?
+/*static*/
+void RTCRtpTransceiver::NegotiatedDetailsToAudioCodecConfigs(
+ const JsepTrackNegotiatedDetails& aDetails,
+ std::vector<AudioCodecConfig>* aConfigs) {
+ Maybe<AudioCodecConfig> telephoneEvent;
+
+ if (aDetails.GetEncodingCount()) {
+ for (const auto& codec : aDetails.GetEncoding(0).GetCodecs()) {
+ if (NS_WARN_IF(codec->Type() != SdpMediaSection::kAudio)) {
+ MOZ_ASSERT(false, "Codec is not audio! This is a JSEP bug.");
+ return;
+ }
+ Maybe<AudioCodecConfig> config;
+ const JsepAudioCodecDescription& audio =
+ static_cast<const JsepAudioCodecDescription&>(*codec);
+ JsepCodecDescToAudioCodecConfig(audio, &config);
+ if (config->mName == "telephone-event") {
+ telephoneEvent = std::move(config);
+ } else {
+ aConfigs->push_back(std::move(*config));
+ }
+ }
+ }
+
+ // Put telephone event at the back, because webrtc.org crashes if we don't
+ // If we need to do even more sorting, we should use std::sort.
+ if (telephoneEvent) {
+ aConfigs->push_back(std::move(*telephoneEvent));
+ }
+}
+
+auto RTCRtpTransceiver::GetActivePayloadTypes() const
+ -> RefPtr<ActivePayloadTypesPromise> {
+ if (!mConduit) {
+ return ActivePayloadTypesPromise::CreateAndResolve(PayloadTypes(),
+ __func__);
+ }
+
+ if (!mCallWrapper) {
+ return ActivePayloadTypesPromise::CreateAndResolve(PayloadTypes(),
+ __func__);
+ }
+
+ return InvokeAsync(mCallWrapper->mCallThread, __func__,
+ [conduit = mConduit]() {
+ PayloadTypes pts;
+ pts.mSendPayloadType = conduit->ActiveSendPayloadType();
+ pts.mRecvPayloadType = conduit->ActiveRecvPayloadType();
+ return ActivePayloadTypesPromise::CreateAndResolve(
+ std::move(pts), __func__);
+ });
+}
+
+static void JsepCodecDescToVideoCodecConfig(
+ const JsepVideoCodecDescription& aCodec, Maybe<VideoCodecConfig>* aConfig) {
+ uint16_t pt;
+
+ // TODO(bug 1761272): Getting the pt for a JsepVideoCodecDescription should be
+ // infallible.
+ if (NS_WARN_IF(!aCodec.GetPtAsInt(&pt))) {
+ MOZ_MTLOG(ML_ERROR, "Invalid payload type: " << aCodec.mDefaultPt);
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ UniquePtr<VideoCodecConfigH264> h264Config;
+
+ if (aCodec.mName == "H264") {
+ h264Config = MakeUnique<VideoCodecConfigH264>();
+ size_t spropSize = sizeof(h264Config->sprop_parameter_sets);
+ strncpy(h264Config->sprop_parameter_sets,
+ aCodec.mSpropParameterSets.c_str(), spropSize);
+ h264Config->sprop_parameter_sets[spropSize - 1] = '\0';
+ h264Config->packetization_mode =
+ static_cast<int>(aCodec.mPacketizationMode);
+ h264Config->profile_level_id = static_cast<int>(aCodec.mProfileLevelId);
+ h264Config->tias_bw = 0; // TODO(bug 1403206)
+ }
+
+ *aConfig = Some(VideoCodecConfig(pt, aCodec.mName, aCodec.mConstraints,
+ h264Config.get()));
+
+ (*aConfig)->mAckFbTypes = aCodec.mAckFbTypes;
+ (*aConfig)->mNackFbTypes = aCodec.mNackFbTypes;
+ (*aConfig)->mCcmFbTypes = aCodec.mCcmFbTypes;
+ (*aConfig)->mRembFbSet = aCodec.RtcpFbRembIsSet();
+ (*aConfig)->mFECFbSet = aCodec.mFECEnabled;
+ (*aConfig)->mTransportCCFbSet = aCodec.RtcpFbTransportCCIsSet();
+ if (aCodec.mFECEnabled) {
+ uint16_t pt;
+ if (SdpHelper::GetPtAsInt(aCodec.mREDPayloadType, &pt)) {
+ (*aConfig)->mREDPayloadType = pt;
+ }
+ if (SdpHelper::GetPtAsInt(aCodec.mULPFECPayloadType, &pt)) {
+ (*aConfig)->mULPFECPayloadType = pt;
+ }
+ }
+ if (aCodec.mRtxEnabled) {
+ uint16_t pt;
+ if (SdpHelper::GetPtAsInt(aCodec.mRtxPayloadType, &pt)) {
+ (*aConfig)->mRTXPayloadType = pt;
+ }
+ }
+}
+
+// TODO: Maybe move this someplace else?
+/*static*/
+void RTCRtpTransceiver::NegotiatedDetailsToVideoCodecConfigs(
+ const JsepTrackNegotiatedDetails& aDetails,
+ std::vector<VideoCodecConfig>* aConfigs) {
+ if (aDetails.GetEncodingCount()) {
+ for (const auto& codec : aDetails.GetEncoding(0).GetCodecs()) {
+ if (NS_WARN_IF(codec->Type() != SdpMediaSection::kVideo)) {
+ MOZ_ASSERT(false, "Codec is not video! This is a JSEP bug.");
+ return;
+ }
+ Maybe<VideoCodecConfig> config;
+ const JsepVideoCodecDescription& video =
+ static_cast<const JsepVideoCodecDescription&>(*codec);
+
+ JsepCodecDescToVideoCodecConfig(video, &config);
+
+ config->mTias = aDetails.GetTias();
+
+ for (size_t i = 0; i < aDetails.GetEncodingCount(); ++i) {
+ const JsepTrackEncoding& jsepEncoding(aDetails.GetEncoding(i));
+ if (jsepEncoding.HasFormat(video.mDefaultPt)) {
+ VideoCodecConfig::Encoding encoding;
+ encoding.rid = jsepEncoding.mRid;
+ config->mEncodings.push_back(encoding);
+ }
+ }
+
+ aConfigs->push_back(std::move(*config));
+ }
+ }
+}
+
+void RTCRtpTransceiver::Stop(ErrorResult& aRv) {
+ if (mPc->IsClosed()) {
+ aRv.ThrowInvalidStateError("Peer connection is closed");
+ return;
+ }
+
+ StopImpl();
+ mPc->UpdateNegotiationNeeded();
+}
+
+void RTCRtpTransceiver::StopImpl() {
+ if (mStopped) {
+ return;
+ }
+
+ if (mCallWrapper) {
+ auto conduit = std::move(mConduit);
+ (conduit ? conduit->Shutdown()
+ : GenericPromise::CreateAndResolve(true, __func__))
+ ->Then(GetMainThreadSerialEventTarget(), __func__,
+ [sender = mSender, receiver = mReceiver]() mutable {
+ // Shutdown pipelines when conduits are guaranteed shut down,
+ // so that all packets sent from conduits can be delivered.
+ sender->Shutdown();
+ receiver->Shutdown();
+ });
+ mCallWrapper = nullptr;
+ }
+ mStopped = true;
+ mCurrentDirection.SetNull();
+
+ mSender->Stop();
+ mReceiver->Stop();
+
+ auto self = nsMainThreadPtrHandle<RTCRtpTransceiver>(
+ new nsMainThreadPtrHolder<RTCRtpTransceiver>(
+ "RTCRtpTransceiver::StopImpl::self", this, false));
+ mStsThread->Dispatch(NS_NewRunnableFunction(
+ __func__, [self] { self->mTransportHandler = nullptr; }));
+}
+
+bool RTCRtpTransceiver::IsVideo() const { return mIsVideo; }
+
+bool RTCRtpTransceiver::IsSending() const {
+ return mCurrentDirection == Nullable(RTCRtpTransceiverDirection::Sendonly) ||
+ mCurrentDirection == Nullable(RTCRtpTransceiverDirection::Sendrecv);
+}
+
+bool RTCRtpTransceiver::IsReceiving() const {
+ return mCurrentDirection == Nullable(RTCRtpTransceiverDirection::Recvonly) ||
+ mCurrentDirection == Nullable(RTCRtpTransceiverDirection::Sendrecv);
+}
+
+void RTCRtpTransceiver::ChainToDomPromiseWithCodecStats(
+ nsTArray<RefPtr<RTCStatsPromise>> aStats,
+ const RefPtr<dom::Promise>& aDomPromise) {
+ nsTArray<RTCCodecStats> codecStats =
+ mPc->GetCodecStats(mPc->GetTimestampMaker().GetNow().ToDom());
+
+ AutoTArray<
+ std::tuple<RTCRtpTransceiver*, RefPtr<RTCStatsPromise::AllPromiseType>>,
+ 1>
+ statsPromises;
+ statsPromises.AppendElement(std::make_tuple(
+ this, RTCStatsPromise::All(GetMainThreadSerialEventTarget(), aStats)));
+
+ ApplyCodecStats(std::move(codecStats), std::move(statsPromises))
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [aDomPromise, window = mWindow,
+ idGen = mIdGenerator](UniquePtr<RTCStatsCollection> aStats) mutable {
+ // Rewrite ids and merge stats collections into the final report.
+ AutoTArray<UniquePtr<RTCStatsCollection>, 1> stats;
+ stats.AppendElement(std::move(aStats));
+
+ RTCStatsCollection opaqueStats;
+ idGen->RewriteIds(std::move(stats), &opaqueStats);
+
+ RefPtr<RTCStatsReport> report(new RTCStatsReport(window));
+ report->Incorporate(opaqueStats);
+
+ aDomPromise->MaybeResolve(std::move(report));
+ },
+ [aDomPromise](nsresult aError) {
+ aDomPromise->MaybeReject(NS_ERROR_FAILURE);
+ });
+}
+
+RefPtr<RTCStatsPromise> RTCRtpTransceiver::ApplyCodecStats(
+ nsTArray<RTCCodecStats> aCodecStats,
+ nsTArray<
+ std::tuple<RTCRtpTransceiver*, RefPtr<RTCStatsPromise::AllPromiseType>>>
+ aTransceiverStatsPromises) {
+ MOZ_ASSERT(NS_IsMainThread());
+ // The process here is roughly:
+ // - Gather all inputs to the codec filtering process, including:
+ // - Each transceiver's transportIds
+ // - Each transceiver's active payload types (resolved)
+ // - Each transceiver's resolved stats
+ //
+ // Waiting (async) for multiple promises of different types is not supported
+ // by the MozPromise API (bug 1752318), so we are a bit finicky here. We
+ // create media::Refcountables of the types we want to resolve, and let
+ // these be shared across Then-functions through RefPtrs.
+ //
+ // - For each active payload type in a transceiver:
+ // - Register the codec stats for this payload type and transport if we
+ // haven't already done so
+ // - If it was a send payload type, assign the codec stats id for this
+ // payload type and transport to the transceiver's outbound-rtp and
+ // remote-inbound-rtp stats as codecId
+ // - If it was a recv payload type, assign the codec stats id for this
+ // payload type and transport to the transceiver's inbound-rtp and
+ // remote-outbound-rtp stats as codecId
+ //
+ // - Flatten all transceiver stats collections into one, and set the
+ // registered codec stats on it
+
+ // Wrap codec stats in a Refcountable<> to allow sharing across promise
+ // handlers.
+ auto codecStats = MakeRefPtr<media::Refcountable<nsTArray<RTCCodecStats>>>();
+ *codecStats = std::move(aCodecStats);
+
+ struct IdComparator {
+ bool operator()(const RTCCodecStats& aA, const RTCCodecStats& aB) const {
+ return aA.mId.Value() < aB.mId.Value();
+ }
+ };
+
+ // Stores distinct codec stats by id; to avoid dupes within a transport.
+ auto finalCodecStats =
+ MakeRefPtr<media::Refcountable<std::set<RTCCodecStats, IdComparator>>>();
+
+ // All the transceiver rtp stream stats in a single array. These stats will,
+ // when resolved, contain codecIds.
+ nsTArray<RefPtr<RTCStatsPromise>> promises(
+ aTransceiverStatsPromises.Length());
+
+ for (const auto& [transceiver, allPromise] : aTransceiverStatsPromises) {
+ // Per transceiver, gather up what we need to assign codecId to this
+ // transceiver's rtp stream stats. Register codec stats while we're at it.
+ auto payloadTypes =
+ MakeRefPtr<media::Refcountable<RTCRtpTransceiver::PayloadTypes>>();
+ promises.AppendElement(
+ transceiver->GetActivePayloadTypes()
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [payloadTypes, allPromise = allPromise](
+ RTCRtpTransceiver::PayloadTypes aPayloadTypes) {
+ // Forward active payload types to the next Then-handler.
+ *payloadTypes = std::move(aPayloadTypes);
+ return allPromise;
+ },
+ [] {
+ MOZ_CRASH("Unexpected reject");
+ return RTCStatsPromise::AllPromiseType::CreateAndReject(
+ NS_ERROR_UNEXPECTED, __func__);
+ })
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [codecStats, finalCodecStats, payloadTypes,
+ transportId =
+ NS_ConvertASCIItoUTF16(transceiver->GetTransportId())](
+ nsTArray<UniquePtr<RTCStatsCollection>>
+ aTransceiverStats) mutable {
+ // We have all the data we need to register codec stats and
+ // assign codecIds for this transceiver's rtp stream stats.
+
+ auto report = MakeUnique<RTCStatsCollection>();
+ FlattenStats(std::move(aTransceiverStats), report.get());
+
+ // Find the codec stats we are looking for, based on the
+ // transportId and the active payload types.
+ Maybe<RTCCodecStats&> sendCodec;
+ Maybe<RTCCodecStats&> recvCodec;
+ for (auto& codec : *codecStats) {
+ if (payloadTypes->mSendPayloadType.isSome() ==
+ sendCodec.isSome() &&
+ payloadTypes->mRecvPayloadType.isSome() ==
+ recvCodec.isSome()) {
+ // We have found all the codec stats we were looking for.
+ break;
+ }
+ if (codec.mTransportId != transportId) {
+ continue;
+ }
+ if (payloadTypes->mSendPayloadType &&
+ *payloadTypes->mSendPayloadType ==
+ static_cast<int>(codec.mPayloadType) &&
+ (!codec.mCodecType.WasPassed() ||
+ codec.mCodecType.Value() == RTCCodecType::Encode)) {
+ MOZ_ASSERT(!sendCodec,
+ "At most one send codec stat per transceiver");
+ sendCodec = SomeRef(codec);
+ }
+ if (payloadTypes->mRecvPayloadType &&
+ *payloadTypes->mRecvPayloadType ==
+ static_cast<int>(codec.mPayloadType) &&
+ (!codec.mCodecType.WasPassed() ||
+ codec.mCodecType.Value() == RTCCodecType::Decode)) {
+ MOZ_ASSERT(!recvCodec,
+ "At most one recv codec stat per transceiver");
+ recvCodec = SomeRef(codec);
+ }
+ }
+
+ // Register and assign codecIds for the found codec stats.
+ if (sendCodec) {
+ finalCodecStats->insert(*sendCodec);
+ for (auto& stat : report->mOutboundRtpStreamStats) {
+ stat.mCodecId.Construct(sendCodec->mId.Value());
+ }
+ for (auto& stat : report->mRemoteInboundRtpStreamStats) {
+ stat.mCodecId.Construct(sendCodec->mId.Value());
+ }
+ }
+ if (recvCodec) {
+ finalCodecStats->insert(*recvCodec);
+ for (auto& stat : report->mInboundRtpStreamStats) {
+ stat.mCodecId.Construct(recvCodec->mId.Value());
+ }
+ for (auto& stat : report->mRemoteOutboundRtpStreamStats) {
+ stat.mCodecId.Construct(recvCodec->mId.Value());
+ }
+ }
+
+ return RTCStatsPromise::CreateAndResolve(std::move(report),
+ __func__);
+ },
+ [] {
+ MOZ_CRASH("Unexpected reject");
+ return RTCStatsPromise::CreateAndReject(NS_ERROR_UNEXPECTED,
+ __func__);
+ }));
+ }
+
+ return RTCStatsPromise::All(GetMainThreadSerialEventTarget(), promises)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [finalCodecStats = std::move(finalCodecStats)](
+ nsTArray<UniquePtr<RTCStatsCollection>> aStats) mutable {
+ auto finalStats = MakeUnique<RTCStatsCollection>();
+ FlattenStats(std::move(aStats), finalStats.get());
+ MOZ_ASSERT(finalStats->mCodecStats.IsEmpty());
+ if (!finalStats->mCodecStats.SetCapacity(finalCodecStats->size(),
+ fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ while (!finalCodecStats->empty()) {
+ auto node = finalCodecStats->extract(finalCodecStats->begin());
+ if (!finalStats->mCodecStats.AppendElement(
+ std::move(node.value()), fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ }
+ return RTCStatsPromise::CreateAndResolve(std::move(finalStats),
+ __func__);
+ },
+ [] {
+ MOZ_CRASH("Unexpected reject");
+ return RTCStatsPromise::CreateAndReject(NS_ERROR_UNEXPECTED,
+ __func__);
+ });
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/jsapi/RTCRtpTransceiver.h b/dom/media/webrtc/jsapi/RTCRtpTransceiver.h
new file mode 100644
index 0000000000..4830e465a0
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCRtpTransceiver.h
@@ -0,0 +1,243 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef _TRANSCEIVERIMPL_H_
+#define _TRANSCEIVERIMPL_H_
+
+#include <string>
+#include "mozilla/StateMirroring.h"
+#include "mozilla/RefPtr.h"
+#include "nsCOMPtr.h"
+#include "nsISerialEventTarget.h"
+#include "nsTArray.h"
+#include "mozilla/dom/MediaStreamTrack.h"
+#include "ErrorList.h"
+#include "jsep/JsepSession.h"
+#include "transport/transportlayer.h" // For TransportLayer::State
+#include "mozilla/dom/RTCRtpTransceiverBinding.h"
+#include "RTCStatsReport.h"
+
+class nsIPrincipal;
+
+namespace mozilla {
+class PeerIdentity;
+class MediaSessionConduit;
+class VideoSessionConduit;
+class AudioSessionConduit;
+struct AudioCodecConfig;
+class VideoCodecConfig; // Why is this a class, but AudioCodecConfig a struct?
+class MediaPipelineTransmit;
+class MediaPipeline;
+class MediaPipelineFilter;
+class MediaTransportHandler;
+class RTCStatsIdGenerator;
+class WebrtcCallWrapper;
+class JsepTrackNegotiatedDetails;
+class PeerConnectionImpl;
+enum class PrincipalPrivacy : uint8_t;
+
+namespace dom {
+class RTCDtlsTransport;
+class RTCDTMFSender;
+class RTCRtpTransceiver;
+struct RTCRtpSourceEntry;
+class RTCRtpReceiver;
+class RTCRtpSender;
+
+/**
+ * This is what ties all the various pieces that make up a transceiver
+ * together. This includes:
+ * MediaStreamTrack for rendering and capture
+ * MediaTransportHandler for RTP transmission/reception
+ * Audio/VideoConduit for feeding RTP/RTCP into webrtc.org for decoding, and
+ * feeding audio/video frames into webrtc.org for encoding into RTP/RTCP.
+ */
+class RTCRtpTransceiver : public nsISupports, public nsWrapperCache {
+ public:
+ /**
+ * |aSendTrack| might or might not be set.
+ */
+ RTCRtpTransceiver(
+ nsPIDOMWindowInner* aWindow, bool aPrivacyNeeded, PeerConnectionImpl* aPc,
+ MediaTransportHandler* aTransportHandler, JsepSession* aJsepSession,
+ const std::string& aTransceiverId, bool aIsVideo,
+ nsISerialEventTarget* aStsThread, MediaStreamTrack* aSendTrack,
+ WebrtcCallWrapper* aCallWrapper, RTCStatsIdGenerator* aIdGenerator);
+
+ void Init(const RTCRtpTransceiverInit& aInit, ErrorResult& aRv);
+
+ bool IsValid() const { return !!mConduit; }
+
+ nsresult UpdateTransport();
+
+ nsresult UpdateConduit();
+
+ void UpdatePrincipalPrivacy(PrincipalPrivacy aPrivacy);
+
+ void ResetSync();
+
+ nsresult SyncWithMatchingVideoConduits(
+ nsTArray<RefPtr<RTCRtpTransceiver>>& transceivers);
+
+ void Close();
+
+ void BreakCycles();
+
+ bool ConduitHasPluginID(uint64_t aPluginID);
+
+ // for webidl
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+ nsPIDOMWindowInner* GetParentObject() const;
+ RTCRtpReceiver* Receiver() const { return mReceiver; }
+ RTCRtpSender* Sender() const { return mSender; }
+ RTCDtlsTransport* GetDtlsTransport() const { return mDtlsTransport; }
+ void GetKind(nsAString& aKind) const;
+ void GetMid(nsAString& aMid) const;
+ RTCRtpTransceiverDirection Direction() const { return mDirection; }
+ void SetDirection(RTCRtpTransceiverDirection aDirection, ErrorResult& aRv);
+ Nullable<RTCRtpTransceiverDirection> GetCurrentDirection() {
+ return mCurrentDirection;
+ }
+ void Stop(ErrorResult& aRv);
+ void SetDirectionInternal(RTCRtpTransceiverDirection aDirection);
+ bool HasBeenUsedToSend() const { return mHasBeenUsedToSend; }
+
+ bool CanSendDTMF() const;
+ bool Stopped() const { return mStopped; }
+ void SyncToJsep(JsepSession& aSession) const;
+ void SyncFromJsep(const JsepSession& aSession);
+ std::string GetMidAscii() const;
+
+ void SetDtlsTransport(RTCDtlsTransport* aDtlsTransport, bool aStable);
+ void RollbackToStableDtlsTransport();
+
+ std::string GetTransportId() const {
+ return mJsepTransceiver.mTransport.mTransportId;
+ }
+
+ JsepTransceiver& GetJsepTransceiver() { return mJsepTransceiver; }
+
+ bool IsVideo() const;
+
+ bool IsSending() const;
+
+ bool IsReceiving() const;
+
+ bool ShouldRemove() const;
+
+ Maybe<const std::vector<UniquePtr<JsepCodecDescription>>&>
+ GetNegotiatedSendCodecs() const;
+
+ Maybe<const std::vector<UniquePtr<JsepCodecDescription>>&>
+ GetNegotiatedRecvCodecs() const;
+
+ struct PayloadTypes {
+ Maybe<int> mSendPayloadType;
+ Maybe<int> mRecvPayloadType;
+ };
+ using ActivePayloadTypesPromise = MozPromise<PayloadTypes, nsresult, true>;
+ RefPtr<ActivePayloadTypesPromise> GetActivePayloadTypes() const;
+
+ MediaSessionConduit* GetConduit() const { return mConduit; }
+
+ const std::string& GetJsepTransceiverId() const { return mTransceiverId; }
+
+ void SetRemovedFromPc() { mHandlingUnlink = true; }
+
+ // nsISupports
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(RTCRtpTransceiver)
+
+ static void NegotiatedDetailsToAudioCodecConfigs(
+ const JsepTrackNegotiatedDetails& aDetails,
+ std::vector<AudioCodecConfig>* aConfigs);
+
+ static void NegotiatedDetailsToVideoCodecConfigs(
+ const JsepTrackNegotiatedDetails& aDetails,
+ std::vector<VideoCodecConfig>* aConfigs);
+
+ /* Returns a promise that will contain the stats in aStats, along with the
+ * codec stats (which is a PC-wide thing) */
+ void ChainToDomPromiseWithCodecStats(nsTArray<RefPtr<RTCStatsPromise>> aStats,
+ const RefPtr<Promise>& aDomPromise);
+
+ /**
+ * Takes a set of codec stats (per-peerconnection) and a set of
+ * transceiver/transceiver-stats-promise tuples. Filters out all referenced
+ * codec stats based on the transceiver's transport and rtp stream stats.
+ * Finally returns the flattened stats containing the filtered codec stats and
+ * all given per-transceiver-stats.
+ */
+ static RefPtr<RTCStatsPromise> ApplyCodecStats(
+ nsTArray<RTCCodecStats> aCodecStats,
+ nsTArray<std::tuple<RTCRtpTransceiver*,
+ RefPtr<RTCStatsPromise::AllPromiseType>>>
+ aTransceiverStatsPromises);
+
+ AbstractCanonical<std::string>* CanonicalMid() { return &mMid; }
+ AbstractCanonical<std::string>* CanonicalSyncGroup() { return &mSyncGroup; }
+
+ private:
+ virtual ~RTCRtpTransceiver();
+ void InitAudio();
+ void InitVideo(const TrackingId& aRecvTrackingId);
+ void InitConduitControl();
+ void StopImpl();
+
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ RefPtr<PeerConnectionImpl> mPc;
+ RefPtr<MediaTransportHandler> mTransportHandler;
+ const std::string mTransceiverId;
+ // Copy of latest from the JSEP engine.
+ JsepTransceiver mJsepTransceiver;
+ nsCOMPtr<nsISerialEventTarget> mStsThread;
+ // state for webrtc.org that is shared between all transceivers
+ RefPtr<WebrtcCallWrapper> mCallWrapper;
+ RefPtr<MediaStreamTrack> mSendTrack;
+ RefPtr<RTCStatsIdGenerator> mIdGenerator;
+ RefPtr<MediaSessionConduit> mConduit;
+ // The spec says both RTCRtpReceiver and RTCRtpSender have a slot for
+ // an RTCDtlsTransport. They are always the same, so we'll store it
+ // here.
+ RefPtr<RTCDtlsTransport> mDtlsTransport;
+ // The spec says both RTCRtpReceiver and RTCRtpSender have a slot for
+ // a last stable state RTCDtlsTransport. They are always the same, so
+ // we'll store it here.
+ RefPtr<RTCDtlsTransport> mLastStableDtlsTransport;
+ RefPtr<RTCRtpReceiver> mReceiver;
+ RefPtr<RTCRtpSender> mSender;
+ RTCRtpTransceiverDirection mDirection = RTCRtpTransceiverDirection::Sendrecv;
+ Nullable<RTCRtpTransceiverDirection> mCurrentDirection;
+ bool mStopped = false;
+ bool mShutdown = false;
+ bool mHasBeenUsedToSend = false;
+ PrincipalPrivacy mPrincipalPrivacy;
+ bool mShouldRemove = false;
+ bool mHasTransport = false;
+ bool mIsVideo;
+ // This is really nasty. Most of the time, PeerConnectionImpl needs to be in
+ // charge of unlinking each RTCRtpTransceiver, because it needs to perform
+ // stats queries on its way out, which requires all of the RTCRtpTransceivers
+ // (and their transitive dependencies) to stick around until those stats
+ // queries are finished. However, when an RTCRtpTransceiver is removed from
+ // the PeerConnectionImpl due to negotiation, the PeerConnectionImpl
+ // releases its reference, which means the PeerConnectionImpl cannot be in
+ // charge of the unlink anymore. We cannot do the unlink when this reference
+ // is released either, because RTCRtpTransceiver might have some work it needs
+ // to do first. Also, JS may be maintaining a reference to the
+ // RTCRtpTransceiver (or one of its dependencies), which means it must remain
+ // fully functional after it is removed (meaning it cannot release any of its
+ // dependencies, or vice versa).
+ bool mHandlingUnlink = false;
+ std::string mTransportId;
+
+ Canonical<std::string> mMid;
+ Canonical<std::string> mSyncGroup;
+};
+
+} // namespace dom
+
+} // namespace mozilla
+
+#endif // _TRANSCEIVERIMPL_H_
diff --git a/dom/media/webrtc/jsapi/RTCSctpTransport.cpp b/dom/media/webrtc/jsapi/RTCSctpTransport.cpp
new file mode 100644
index 0000000000..63424968ae
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCSctpTransport.cpp
@@ -0,0 +1,53 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "RTCSctpTransport.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/EventBinding.h"
+#include "mozilla/dom/RTCSctpTransportBinding.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(RTCSctpTransport, DOMEventTargetHelper,
+ mDtlsTransport)
+
+NS_IMPL_ADDREF_INHERITED(RTCSctpTransport, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(RTCSctpTransport, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCSctpTransport)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+RTCSctpTransport::RTCSctpTransport(nsPIDOMWindowInner* aWindow,
+ RTCDtlsTransport& aDtlsTransport,
+ double aMaxMessageSize,
+ const Nullable<uint16_t>& aMaxChannels)
+ : DOMEventTargetHelper(aWindow),
+ mState(RTCSctpTransportState::Connecting),
+ mDtlsTransport(&aDtlsTransport),
+ mMaxMessageSize(aMaxMessageSize),
+ mMaxChannels(aMaxChannels) {}
+
+JSObject* RTCSctpTransport::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return RTCSctpTransport_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void RTCSctpTransport::UpdateState(RTCSctpTransportState aState) {
+ if (mState == RTCSctpTransportState::Closed || mState == aState) {
+ return;
+ }
+
+ mState = aState;
+
+ EventInit init;
+ init.mBubbles = false;
+ init.mCancelable = false;
+
+ RefPtr<Event> event = Event::Constructor(this, u"statechange"_ns, init);
+
+ DispatchTrustedEvent(event);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webrtc/jsapi/RTCSctpTransport.h b/dom/media/webrtc/jsapi/RTCSctpTransport.h
new file mode 100644
index 0000000000..8e21db5e73
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCSctpTransport.h
@@ -0,0 +1,65 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _RTCSctpTransport_h_
+#define _RTCSctpTransport_h_
+
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/RefPtr.h"
+#include "js/RootingAPI.h"
+#include "RTCDtlsTransport.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla::dom {
+
+enum class RTCSctpTransportState : uint8_t;
+
+class RTCSctpTransport : public DOMEventTargetHelper {
+ public:
+ explicit RTCSctpTransport(nsPIDOMWindowInner* aWindow,
+ RTCDtlsTransport& aDtlsTransport,
+ double aMaxMessageSize,
+ const Nullable<uint16_t>& aMaxChannels);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(RTCSctpTransport,
+ DOMEventTargetHelper)
+
+ // webidl
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+ IMPL_EVENT_HANDLER(statechange)
+
+ RTCDtlsTransport* Transport() const { return mDtlsTransport; }
+ RTCSctpTransportState State() const { return mState; }
+ double MaxMessageSize() const { return mMaxMessageSize; }
+ Nullable<uint16_t> GetMaxChannels() const { return mMaxChannels; }
+
+ void SetTransport(RTCDtlsTransport& aTransport) {
+ mDtlsTransport = &aTransport;
+ }
+
+ void SetMaxMessageSize(double aMaxMessageSize) {
+ mMaxMessageSize = aMaxMessageSize;
+ }
+
+ void SetMaxChannels(const Nullable<uint16_t>& aMaxChannels) {
+ mMaxChannels = aMaxChannels;
+ }
+
+ void UpdateState(RTCSctpTransportState aState);
+
+ private:
+ virtual ~RTCSctpTransport() = default;
+
+ RTCSctpTransportState mState;
+ RefPtr<RTCDtlsTransport> mDtlsTransport;
+ double mMaxMessageSize;
+ Nullable<uint16_t> mMaxChannels;
+};
+
+} // namespace mozilla::dom
+#endif // _RTCSctpTransport_h_
diff --git a/dom/media/webrtc/jsapi/RTCStatsIdGenerator.cpp b/dom/media/webrtc/jsapi/RTCStatsIdGenerator.cpp
new file mode 100644
index 0000000000..8b0462e223
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCStatsIdGenerator.cpp
@@ -0,0 +1,90 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+
+#include "RTCStatsIdGenerator.h"
+
+#include <iostream>
+
+#include "mozilla/RandomNum.h"
+#include "RTCStatsReport.h"
+#include "WebrtcGlobal.h"
+
+namespace mozilla {
+
+RTCStatsIdGenerator::RTCStatsIdGenerator()
+ : mSalt(RandomUint64().valueOr(0xa5a5a5a5)), mCounter(0) {}
+
+void RTCStatsIdGenerator::RewriteIds(
+ nsTArray<UniquePtr<dom::RTCStatsCollection>> aFromStats,
+ dom::RTCStatsCollection* aIntoReport) {
+ // Rewrite an Optional id
+ auto rewriteId = [&](dom::Optional<nsString>& id) {
+ if (id.WasPassed()) {
+ id.Value() = Id(id.Value());
+ }
+ };
+
+ auto rewriteIds = [&](auto& aList, auto... aParam) {
+ for (auto& stat : aList) {
+ (rewriteId(stat.*aParam), ...);
+ }
+ };
+
+ // Involves a lot of copying, since webidl dictionaries don't have
+ // move semantics. Oh well.
+
+ // Create a temporary to avoid double-rewriting any stats already in
+ // aIntoReport.
+ auto stats = MakeUnique<dom::RTCStatsCollection>();
+ dom::FlattenStats(std::move(aFromStats), stats.get());
+
+ using S = dom::RTCStats;
+ using ICPS = dom::RTCIceCandidatePairStats;
+ using RSS = dom::RTCRtpStreamStats;
+ using IRSS = dom::RTCInboundRtpStreamStats;
+ using ORSS = dom::RTCOutboundRtpStreamStats;
+ using RIRSS = dom::RTCRemoteInboundRtpStreamStats;
+ using RORSS = dom::RTCRemoteOutboundRtpStreamStats;
+
+ rewriteIds(stats->mIceCandidatePairStats, &S::mId, &ICPS::mLocalCandidateId,
+ &ICPS::mRemoteCandidateId);
+ rewriteIds(stats->mIceCandidateStats, &S::mId);
+ rewriteIds(stats->mInboundRtpStreamStats, &S::mId, &IRSS::mRemoteId,
+ &RSS::mCodecId);
+ rewriteIds(stats->mOutboundRtpStreamStats, &S::mId, &ORSS::mRemoteId,
+ &RSS::mCodecId);
+ rewriteIds(stats->mRemoteInboundRtpStreamStats, &S::mId, &RIRSS::mLocalId,
+ &RSS::mCodecId);
+ rewriteIds(stats->mRemoteOutboundRtpStreamStats, &S::mId, &RORSS::mLocalId,
+ &RSS::mCodecId);
+ rewriteIds(stats->mCodecStats, &S::mId);
+ rewriteIds(stats->mRtpContributingSourceStats, &S::mId);
+ rewriteIds(stats->mTrickledIceCandidateStats, &S::mId);
+ rewriteIds(stats->mDataChannelStats, &S::mId);
+
+ dom::MergeStats(std::move(stats), aIntoReport);
+}
+
+nsString RTCStatsIdGenerator::Id(const nsString& aKey) {
+ if (!aKey.Length()) {
+ MOZ_ASSERT(aKey.Length(), "Stats IDs should never be empty.");
+ return aKey;
+ }
+ if (mAllocated.find(aKey) == mAllocated.end()) {
+ mAllocated[aKey] = Generate();
+ }
+ return mAllocated[aKey];
+}
+
+nsString RTCStatsIdGenerator::Generate() {
+ auto random = RandomUint64().valueOr(0x1a22);
+ auto idNum = static_cast<uint32_t>(mSalt ^ ((mCounter++ << 16) | random));
+ nsString id;
+ id.AppendInt(idNum, 16); // Append as hex
+ return id;
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/jsapi/RTCStatsIdGenerator.h b/dom/media/webrtc/jsapi/RTCStatsIdGenerator.h
new file mode 100644
index 0000000000..1391c029ad
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCStatsIdGenerator.h
@@ -0,0 +1,42 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+
+#ifndef _RTCSTATSIDGENERATOR_H_
+#define _RTCSTATSIDGENERATOR_H_
+
+#include <map>
+
+#include "mozilla/Atomics.h"
+#include "mozilla/UniquePtr.h"
+#include "nsISupportsImpl.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace dom {
+struct RTCStatsCollection;
+} // namespace dom
+
+class RTCStatsIdGenerator {
+ public:
+ RTCStatsIdGenerator();
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RTCStatsIdGenerator);
+
+ void RewriteIds(nsTArray<UniquePtr<dom::RTCStatsCollection>> aFromStats,
+ dom::RTCStatsCollection* aIntoReport);
+
+ private:
+ virtual ~RTCStatsIdGenerator(){};
+ nsString Id(const nsString& aKey);
+ nsString Generate();
+
+ const uint64_t mSalt;
+ uint64_t mCounter;
+ std::map<nsString, nsString> mAllocated;
+};
+
+} // namespace mozilla
+#endif
diff --git a/dom/media/webrtc/jsapi/RTCStatsReport.cpp b/dom/media/webrtc/jsapi/RTCStatsReport.cpp
new file mode 100644
index 0000000000..9f39eb3865
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCStatsReport.cpp
@@ -0,0 +1,213 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "RTCStatsReport.h"
+#include "libwebrtcglue/SystemTime.h"
+#include "mozilla/dom/Performance.h"
+#include "nsRFPService.h"
+#include "WebrtcGlobal.h"
+
+namespace mozilla::dom {
+
+RTCStatsTimestampState::RTCStatsTimestampState()
+ : mRandomTimelineSeed(0),
+ mStartDomRealtime(WebrtcSystemTimeBase()),
+ mStartRealtime(
+ WebrtcSystemTime() -
+ webrtc::TimeDelta::Micros(
+ (TimeStamp::Now() - mStartDomRealtime).ToMicroseconds())),
+ mRTPCallerType(RTPCallerType::Normal),
+ mStartWallClockRaw(
+ PerformanceService::GetOrCreate()->TimeOrigin(mStartDomRealtime)) {}
+
+RTCStatsTimestampState::RTCStatsTimestampState(Performance& aPerformance)
+ : mRandomTimelineSeed(aPerformance.GetRandomTimelineSeed()),
+ mStartDomRealtime(aPerformance.CreationTimeStamp()),
+ mStartRealtime(
+ WebrtcSystemTime() -
+ webrtc::TimeDelta::Micros(
+ (TimeStamp::Now() - mStartDomRealtime).ToMicroseconds())),
+ mRTPCallerType(aPerformance.GetRTPCallerType()),
+ mStartWallClockRaw(
+ PerformanceService::GetOrCreate()->TimeOrigin(mStartDomRealtime)) {}
+
+TimeStamp RTCStatsTimestamp::ToMozTime() const { return mMozTime; }
+
+webrtc::Timestamp RTCStatsTimestamp::ToRealtime() const {
+ return ToDomRealtime() +
+ webrtc::TimeDelta::Micros(mState.mStartRealtime.us());
+}
+
+webrtc::Timestamp RTCStatsTimestamp::To1Jan1970() const {
+ return ToDomRealtime() + webrtc::TimeDelta::Millis(mState.mStartWallClockRaw);
+}
+
+webrtc::Timestamp RTCStatsTimestamp::ToNtp() const {
+ return To1Jan1970() + webrtc::TimeDelta::Seconds(webrtc::kNtpJan1970);
+}
+
+webrtc::Timestamp RTCStatsTimestamp::ToDomRealtime() const {
+ return webrtc::Timestamp::Micros(
+ (mMozTime - mState.mStartDomRealtime).ToMicroseconds());
+}
+
+DOMHighResTimeStamp RTCStatsTimestamp::ToDom() const {
+ // webrtc-pc says to use performance.timeOrigin + performance.now(), but
+ // keeping a Performance object around is difficult because it is
+ // main-thread-only. So, we perform the same calculation here. Note that this
+ // can be very different from the current wall-clock time because of changes
+ // to the wall clock, or monotonic clock drift over long periods of time.
+ // We are very careful to do exactly what Performance does, to avoid timestamp
+ // discrepancies.
+
+ DOMHighResTimeStamp realtime = ToDomRealtime().ms<double>();
+ // mRandomTimelineSeed is not set in the unit-tests.
+ if (mState.mRandomTimelineSeed) {
+ realtime = nsRFPService::ReduceTimePrecisionAsMSecs(
+ realtime, mState.mRandomTimelineSeed, mState.mRTPCallerType);
+ }
+
+ // Ugh. Performance::TimeOrigin is not constant, which means we need to
+ // emulate this weird behavior so our time stamps are consistent with JS
+ // timeOrigin. This is based on the code here:
+ // https://searchfox.org/mozilla-central/rev/
+ // 053826b10f838f77c27507e5efecc96e34718541/dom/performance/Performance.cpp#111-117
+ DOMHighResTimeStamp start = nsRFPService::ReduceTimePrecisionAsMSecs(
+ mState.mStartWallClockRaw, 0, mState.mRTPCallerType);
+
+ return start + realtime;
+}
+
+/* static */ RTCStatsTimestamp RTCStatsTimestamp::FromMozTime(
+ const RTCStatsTimestampMaker& aMaker, TimeStamp aMozTime) {
+ return RTCStatsTimestamp(aMaker.mState, aMozTime);
+}
+
+/* static */ RTCStatsTimestamp RTCStatsTimestamp::FromRealtime(
+ const RTCStatsTimestampMaker& aMaker, webrtc::Timestamp aRealtime) {
+ return FromDomRealtime(
+ aMaker,
+ aRealtime - webrtc::TimeDelta::Micros(aMaker.mState.mStartRealtime.us()));
+}
+
+/* static */ RTCStatsTimestamp RTCStatsTimestamp::From1Jan1970(
+ const RTCStatsTimestampMaker& aMaker, webrtc::Timestamp a1Jan1970) {
+ const auto& state = aMaker.mState;
+ return FromDomRealtime(
+ aMaker, a1Jan1970 - webrtc::TimeDelta::Millis(state.mStartWallClockRaw));
+}
+
+/* static */ RTCStatsTimestamp RTCStatsTimestamp::FromNtp(
+ const RTCStatsTimestampMaker& aMaker, webrtc::Timestamp aNtpTime) {
+ const auto& state = aMaker.mState;
+ const auto domRealtime = aNtpTime -
+ webrtc::TimeDelta::Seconds(webrtc::kNtpJan1970) -
+ webrtc::TimeDelta::Millis(state.mStartWallClockRaw);
+ // Ntp times exposed by libwebrtc to stats are always **rounded** to
+ // milliseconds. That means they can jump up to half a millisecond into the
+ // future. We compensate for that here so that things seem consistent to js.
+ return FromDomRealtime(aMaker, domRealtime - webrtc::TimeDelta::Micros(500));
+}
+
+/* static */ RTCStatsTimestamp RTCStatsTimestamp::FromDomRealtime(
+ const RTCStatsTimestampMaker& aMaker, webrtc::Timestamp aDomRealtime) {
+ return RTCStatsTimestamp(aMaker.mState, aMaker.mState.mStartDomRealtime +
+ TimeDuration::FromMicroseconds(
+ aDomRealtime.us<double>()));
+}
+
+RTCStatsTimestamp::RTCStatsTimestamp(RTCStatsTimestampState aState,
+ TimeStamp aMozTime)
+ : mState(aState), mMozTime(aMozTime) {}
+
+RTCStatsTimestampMaker::RTCStatsTimestampMaker(RTCStatsTimestampState aState)
+ : mState(aState) {}
+
+/* static */
+RTCStatsTimestampMaker RTCStatsTimestampMaker::Create(
+ nsPIDOMWindowInner* aWindow /* = nullptr */) {
+ if (!aWindow) {
+ return RTCStatsTimestampMaker(RTCStatsTimestampState());
+ }
+ if (Performance* p = aWindow->GetPerformance()) {
+ return RTCStatsTimestampMaker(RTCStatsTimestampState(*p));
+ }
+ return RTCStatsTimestampMaker(RTCStatsTimestampState());
+}
+
+RTCStatsTimestamp RTCStatsTimestampMaker::GetNow() const {
+ return RTCStatsTimestamp::FromMozTime(*this, TimeStamp::Now());
+}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(RTCStatsReport, mParent)
+
+RTCStatsReport::RTCStatsReport(nsPIDOMWindowInner* aParent)
+ : mParent(aParent) {}
+
+/*static*/
+already_AddRefed<RTCStatsReport> RTCStatsReport::Constructor(
+ const GlobalObject& aGlobal) {
+ nsCOMPtr<nsPIDOMWindowInner> window(
+ do_QueryInterface(aGlobal.GetAsSupports()));
+ RefPtr<RTCStatsReport> report(new RTCStatsReport(window));
+ return report.forget();
+}
+
+JSObject* RTCStatsReport::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return RTCStatsReport_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void RTCStatsReport::Incorporate(RTCStatsCollection& aStats) {
+ ForAllPublicRTCStatsCollectionMembers(
+ aStats, [&](auto... aMember) { (SetRTCStats(aMember), ...); });
+}
+
+void RTCStatsReport::Set(const nsAString& aKey, JS::Handle<JSObject*> aValue,
+ ErrorResult& aRv) {
+ RTCStatsReport_Binding::MaplikeHelpers::Set(this, aKey, aValue, aRv);
+}
+
+namespace {
+template <size_t I, typename... Ts>
+bool MoveInto(std::tuple<Ts...>& aFrom, std::tuple<Ts*...>& aInto) {
+ return std::get<I>(aInto)->AppendElements(std::move(std::get<I>(aFrom)),
+ fallible);
+}
+
+template <size_t... Is, typename... Ts>
+bool MoveInto(std::tuple<Ts...>&& aFrom, std::tuple<Ts*...>& aInto,
+ std::index_sequence<Is...>) {
+ return (... && MoveInto<Is>(aFrom, aInto));
+}
+
+template <typename... Ts>
+bool MoveInto(std::tuple<Ts...>&& aFrom, std::tuple<Ts*...>& aInto) {
+ return MoveInto(std::move(aFrom), aInto, std::index_sequence_for<Ts...>());
+}
+} // namespace
+
+void MergeStats(UniquePtr<RTCStatsCollection> aFromStats,
+ RTCStatsCollection* aIntoStats) {
+ auto fromTuple = ForAllRTCStatsCollectionMembers(
+ *aFromStats,
+ [&](auto&... aMember) { return std::make_tuple(std::move(aMember)...); });
+ auto intoTuple = ForAllRTCStatsCollectionMembers(
+ *aIntoStats,
+ [&](auto&... aMember) { return std::make_tuple(&aMember...); });
+ if (!MoveInto(std::move(fromTuple), intoTuple)) {
+ mozalloc_handle_oom(0);
+ }
+}
+
+void FlattenStats(nsTArray<UniquePtr<RTCStatsCollection>> aFromStats,
+ RTCStatsCollection* aIntoStats) {
+ for (auto& stats : aFromStats) {
+ MergeStats(std::move(stats), aIntoStats);
+ }
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webrtc/jsapi/RTCStatsReport.h b/dom/media/webrtc/jsapi/RTCStatsReport.h
new file mode 100644
index 0000000000..97bf3daa52
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RTCStatsReport.h
@@ -0,0 +1,205 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef RTCStatsReport_h_
+#define RTCStatsReport_h_
+
+#include "api/units/timestamp.h" // webrtc::Timestamp
+#include "js/RootingAPI.h" // JS::Rooted
+#include "js/Value.h"
+#include "mozilla/dom/AutoEntryScript.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/dom/PerformanceService.h"
+#include "mozilla/dom/RTCStatsReportBinding.h" // RTCStatsCollection
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/UniquePtr.h"
+#include "nsCOMPtr.h"
+#include "nsIGlobalObject.h"
+#include "nsPIDOMWindow.h" // nsPIDOMWindowInner
+#include "nsContentUtils.h"
+#include "nsWrapperCache.h"
+#include "prtime.h" // PR_Now
+
+namespace mozilla {
+
+extern TimeStamp WebrtcSystemTimeBase();
+
+namespace dom {
+
+/**
+ * Keeps the state needed to convert RTCStatsTimestamps.
+ */
+struct RTCStatsTimestampState {
+ RTCStatsTimestampState();
+ explicit RTCStatsTimestampState(Performance& aPerformance);
+
+ RTCStatsTimestampState(const RTCStatsTimestampState&) = default;
+
+ // These members are sampled when a non-copy constructor is called.
+
+ // Performance's random timeline seed.
+ const uint64_t mRandomTimelineSeed;
+ // TimeStamp::Now() when the members were sampled. This is equivalent to time
+ // 0 in DomRealtime.
+ const TimeStamp mStartDomRealtime;
+ // WebrtcSystemTime() when the members were sampled. This represents the same
+ // point in time as mStartDomRealtime, but as a webrtc timestamp.
+ const webrtc::Timestamp mStartRealtime;
+ // Performance's RTPCallerType.
+ const RTPCallerType mRTPCallerType;
+ // Performance.timeOrigin for mStartDomRealtime when the members were sampled.
+ const DOMHighResTimeStamp mStartWallClockRaw;
+};
+
+/**
+ * Classes that facilitate creating timestamps for webrtc stats by mimicking
+ * dom::Performance, as well as getting and converting timestamps for libwebrtc
+ * and our integration with it.
+ *
+ * They use the same clock to avoid drift and inconsistencies, base on
+ * mozilla::TimeStamp, and convert to and from these time bases:
+ * - Moz : Monotonic, unspecified (but constant) and inaccessible epoch,
+ * as implemented by mozilla::TimeStamp
+ * - Realtime : Monotonic, unspecified (but constant) epoch.
+ * - 1Jan1970 : Monotonic, unix epoch (00:00:00 UTC on 1 January 1970).
+ * - Ntp : Monotonic, ntp epoch (00:00:00 UTC on 1 January 1900).
+ * - Dom : Monotonic, milliseconds since unix epoch, as the timestamps
+ * defined by webrtc-pc. Corresponds to Performance.timeOrigin +
+ * Performance.now(). Has reduced precision.
+ * - DomRealtime: Like Dom, but with full precision.
+ * - WallClock : Non-monotonic, unix epoch. Not used here since it is
+ * non-monotonic and cannot be correlated to the other time
+ * bases.
+ */
+class RTCStatsTimestampMaker;
+class RTCStatsTimestamp {
+ public:
+ TimeStamp ToMozTime() const;
+ webrtc::Timestamp ToRealtime() const;
+ webrtc::Timestamp To1Jan1970() const;
+ webrtc::Timestamp ToNtp() const;
+ webrtc::Timestamp ToDomRealtime() const;
+ DOMHighResTimeStamp ToDom() const;
+
+ static RTCStatsTimestamp FromMozTime(const RTCStatsTimestampMaker& aMaker,
+ TimeStamp aMozTime);
+ static RTCStatsTimestamp FromRealtime(const RTCStatsTimestampMaker& aMaker,
+ webrtc::Timestamp aRealtime);
+ static RTCStatsTimestamp From1Jan1970(const RTCStatsTimestampMaker& aMaker,
+ webrtc::Timestamp aRealtime);
+ static RTCStatsTimestamp FromNtp(const RTCStatsTimestampMaker& aMaker,
+ webrtc::Timestamp aRealtime);
+ static RTCStatsTimestamp FromDomRealtime(const RTCStatsTimestampMaker& aMaker,
+ webrtc::Timestamp aDomRealtime);
+ // There is on purpose no conversion functions from DOMHighResTimeStamp
+ // because of the loss in precision of a floating point to integer conversion.
+
+ private:
+ RTCStatsTimestamp(RTCStatsTimestampState aState, TimeStamp aMozTime);
+
+ const RTCStatsTimestampState mState;
+ const TimeStamp mMozTime;
+};
+
+class RTCStatsTimestampMaker {
+ public:
+ static RTCStatsTimestampMaker Create(nsPIDOMWindowInner* aWindow = nullptr);
+
+ RTCStatsTimestamp GetNow() const;
+
+ const RTCStatsTimestampState mState;
+
+ private:
+ explicit RTCStatsTimestampMaker(RTCStatsTimestampState aState);
+};
+
+// TODO(bug 1588303): If we ever get move semantics for webidl dictionaries, we
+// can stop wrapping these in UniquePtr, which will allow us to simplify code
+// in several places.
+typedef MozPromise<UniquePtr<RTCStatsCollection>, nsresult, true>
+ RTCStatsPromise;
+
+typedef MozPromise<UniquePtr<RTCStatsReportInternal>, nsresult, true>
+ RTCStatsReportPromise;
+
+class RTCStatsReport final : public nsWrapperCache {
+ public:
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(RTCStatsReport)
+ NS_DECL_CYCLE_COLLECTION_NATIVE_WRAPPERCACHE_CLASS(RTCStatsReport)
+
+ explicit RTCStatsReport(nsPIDOMWindowInner* aParent);
+
+ // TODO(bug 1586109): Remove this once we no longer have to create empty
+ // RTCStatsReports from JS.
+ static already_AddRefed<RTCStatsReport> Constructor(
+ const GlobalObject& aGlobal);
+
+ void Incorporate(RTCStatsCollection& aStats);
+
+ nsPIDOMWindowInner* GetParentObject() const { return mParent; }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ private:
+ ~RTCStatsReport() = default;
+ void Set(const nsAString& aKey, JS::Handle<JSObject*> aValue,
+ ErrorResult& aRv);
+
+ template <typename T>
+ nsresult SetRTCStats(Sequence<T>& aValues) {
+ for (T& value : aValues) {
+ nsresult rv = SetRTCStats(value);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+ return NS_OK;
+ }
+
+ // We cannot just declare this as SetRTCStats(RTCStats&), because the
+ // conversion function that ToJSValue uses is non-virtual.
+ template <typename T>
+ nsresult SetRTCStats(T& aValue) {
+ static_assert(std::is_base_of<RTCStats, T>::value,
+ "SetRTCStats is for setting RTCStats only");
+
+ if (!aValue.mId.WasPassed()) {
+ return NS_OK;
+ }
+
+ const nsString key(aValue.mId.Value());
+
+ // Cargo-culted from dom::Promise; converts aValue to a JSObject
+ AutoEntryScript aes(mParent->AsGlobal()->GetGlobalJSObject(),
+ "RTCStatsReport::SetRTCStats");
+ JSContext* cx = aes.cx();
+ JS::Rooted<JS::Value> val(cx);
+ if (!ToJSValue(cx, std::forward<T>(aValue), &val)) {
+ return NS_ERROR_FAILURE;
+ }
+ JS::Rooted<JSObject*> jsObject(cx, &val.toObject());
+
+ ErrorResult rv;
+ Set(key, jsObject, rv);
+ return rv.StealNSResult();
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> mParent;
+};
+
+void MergeStats(UniquePtr<dom::RTCStatsCollection> aFromStats,
+ dom::RTCStatsCollection* aIntoStats);
+
+void FlattenStats(nsTArray<UniquePtr<dom::RTCStatsCollection>> aFromStats,
+ dom::RTCStatsCollection* aIntoStats);
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // RTCStatsReport_h_
diff --git a/dom/media/webrtc/jsapi/RemoteTrackSource.cpp b/dom/media/webrtc/jsapi/RemoteTrackSource.cpp
new file mode 100644
index 0000000000..41c679e431
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RemoteTrackSource.cpp
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "RemoteTrackSource.h"
+
+#include "MediaStreamError.h"
+#include "MediaTrackGraph.h"
+#include "RTCRtpReceiver.h"
+
+namespace mozilla {
+
+NS_IMPL_ADDREF_INHERITED(RemoteTrackSource, dom::MediaStreamTrackSource)
+NS_IMPL_RELEASE_INHERITED(RemoteTrackSource, dom::MediaStreamTrackSource)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RemoteTrackSource)
+NS_INTERFACE_MAP_END_INHERITING(dom::MediaStreamTrackSource)
+NS_IMPL_CYCLE_COLLECTION_CLASS(RemoteTrackSource)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(RemoteTrackSource,
+ dom::MediaStreamTrackSource)
+ tmp->Destroy();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mReceiver)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(RemoteTrackSource,
+ dom::MediaStreamTrackSource)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReceiver)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+RemoteTrackSource::RemoteTrackSource(SourceMediaTrack* aStream,
+ dom::RTCRtpReceiver* aReceiver,
+ nsIPrincipal* aPrincipal,
+ const nsString& aLabel,
+ TrackingId aTrackingId)
+ : dom::MediaStreamTrackSource(aPrincipal, aLabel, std::move(aTrackingId)),
+ mStream(aStream),
+ mReceiver(aReceiver) {}
+
+RemoteTrackSource::~RemoteTrackSource() { Destroy(); }
+
+void RemoteTrackSource::Destroy() {
+ if (mStream) {
+ MOZ_ASSERT(!mStream->IsDestroyed());
+ mStream->End();
+ mStream->Destroy();
+ mStream = nullptr;
+
+ GetMainThreadSerialEventTarget()->Dispatch(NewRunnableMethod(
+ "RemoteTrackSource::ForceEnded", this, &RemoteTrackSource::ForceEnded));
+ }
+}
+
+auto RemoteTrackSource::ApplyConstraints(
+ const dom::MediaTrackConstraints& aConstraints, dom::CallerType aCallerType)
+ -> RefPtr<ApplyConstraintsPromise> {
+ return ApplyConstraintsPromise::CreateAndReject(
+ MakeRefPtr<MediaMgrError>(
+ dom::MediaStreamError::Name::OverconstrainedError, ""),
+ __func__);
+}
+
+void RemoteTrackSource::SetPrincipal(nsIPrincipal* aPrincipal) {
+ mPrincipal = aPrincipal;
+ PrincipalChanged();
+}
+
+void RemoteTrackSource::SetMuted(bool aMuted) { MutedChanged(aMuted); }
+
+void RemoteTrackSource::ForceEnded() { OverrideEnded(); }
+
+SourceMediaTrack* RemoteTrackSource::Stream() const { return mStream; }
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/jsapi/RemoteTrackSource.h b/dom/media/webrtc/jsapi/RemoteTrackSource.h
new file mode 100644
index 0000000000..be730f0030
--- /dev/null
+++ b/dom/media/webrtc/jsapi/RemoteTrackSource.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DOM_MEDIA_WEBRTC_JSAPI_REMOTETRACKSOURCE_H_
+#define DOM_MEDIA_WEBRTC_JSAPI_REMOTETRACKSOURCE_H_
+
+#include "MediaStreamTrack.h"
+
+namespace mozilla {
+
+namespace dom {
+class RTCRtpReceiver;
+}
+
+class SourceMediaTrack;
+
+class RemoteTrackSource : public dom::MediaStreamTrackSource {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(RemoteTrackSource,
+ dom::MediaStreamTrackSource)
+
+ RemoteTrackSource(SourceMediaTrack* aStream, dom::RTCRtpReceiver* aReceiver,
+ nsIPrincipal* aPrincipal, const nsString& aLabel,
+ TrackingId aTrackingId);
+
+ void Destroy() override;
+
+ dom::MediaSourceEnum GetMediaSource() const override {
+ return dom::MediaSourceEnum::Other;
+ }
+
+ RefPtr<ApplyConstraintsPromise> ApplyConstraints(
+ const dom::MediaTrackConstraints& aConstraints,
+ dom::CallerType aCallerType) override;
+
+ void Stop() override {
+ // XXX (Bug 1314270): Implement rejection logic if necessary when we have
+ // clarity in the spec.
+ }
+
+ void Disable() override {}
+
+ void Enable() override {}
+
+ void SetPrincipal(nsIPrincipal* aPrincipal);
+ void SetMuted(bool aMuted);
+ void ForceEnded();
+
+ SourceMediaTrack* Stream() const;
+
+ private:
+ virtual ~RemoteTrackSource();
+
+ RefPtr<SourceMediaTrack> mStream;
+ RefPtr<dom::RTCRtpReceiver> mReceiver;
+};
+
+} // namespace mozilla
+
+#endif // DOM_MEDIA_WEBRTC_JSAPI_REMOTETRACKSOURCE_H_
diff --git a/dom/media/webrtc/jsapi/WebrtcGlobalChild.h b/dom/media/webrtc/jsapi/WebrtcGlobalChild.h
new file mode 100644
index 0000000000..147b4b44f0
--- /dev/null
+++ b/dom/media/webrtc/jsapi/WebrtcGlobalChild.h
@@ -0,0 +1,42 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _WEBRTC_GLOBAL_CHILD_H_
+#define _WEBRTC_GLOBAL_CHILD_H_
+
+#include "mozilla/dom/PWebrtcGlobalChild.h"
+
+namespace mozilla::dom {
+
+class WebrtcGlobalChild : public PWebrtcGlobalChild {
+ friend class ContentChild;
+
+ bool mShutdown;
+
+ MOZ_IMPLICIT WebrtcGlobalChild();
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ virtual mozilla::ipc::IPCResult RecvGetStats(
+ const nsAString& aPcIdFilter, GetStatsResolver&& aResolve) override;
+ virtual mozilla::ipc::IPCResult RecvClearStats() override;
+ // MOZ_CAN_RUN_SCRIPT_BOUNDARY because we can't do MOZ_CAN_RUN_SCRIPT in
+ // ipdl-generated things yet.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ virtual mozilla::ipc::IPCResult RecvGetLog(
+ GetLogResolver&& aResolve) override;
+ virtual mozilla::ipc::IPCResult RecvClearLog() override;
+ virtual mozilla::ipc::IPCResult RecvSetAecLogging(
+ const bool& aEnable) override;
+ virtual mozilla::ipc::IPCResult RecvSetDebugMode(const int& aLevel) override;
+
+ static WebrtcGlobalChild* GetOrSet(const Maybe<WebrtcGlobalChild*>& aChild);
+
+ public:
+ virtual ~WebrtcGlobalChild();
+ static WebrtcGlobalChild* Get();
+};
+
+} // namespace mozilla::dom
+
+#endif // _WEBRTC_GLOBAL_CHILD_H_
diff --git a/dom/media/webrtc/jsapi/WebrtcGlobalInformation.cpp b/dom/media/webrtc/jsapi/WebrtcGlobalInformation.cpp
new file mode 100644
index 0000000000..7d0a9e64b1
--- /dev/null
+++ b/dom/media/webrtc/jsapi/WebrtcGlobalInformation.cpp
@@ -0,0 +1,829 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebrtcGlobalInformation.h"
+#include "WebrtcGlobalStatsHistory.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/dom/PWebrtcGlobal.h"
+#include "mozilla/dom/PWebrtcGlobalChild.h"
+#include "mozilla/media/webrtc/WebrtcGlobal.h"
+#include "WebrtcGlobalChild.h"
+#include "WebrtcGlobalParent.h"
+
+#include <algorithm>
+#include <vector>
+#include <type_traits>
+
+#include "mozilla/dom/WebrtcGlobalInformationBinding.h"
+#include "mozilla/dom/RTCStatsReportBinding.h" // for RTCStatsReportInternal
+#include "mozilla/dom/ContentChild.h"
+
+#include "nsISupports.h"
+#include "nsITimer.h"
+#include "nsLiteralString.h"
+#include "nsNetCID.h" // NS_SOCKETTRANSPORTSERVICE_CONTRACTID
+#include "nsServiceManagerUtils.h" // do_GetService
+#include "nsXULAppAPI.h"
+#include "mozilla/ErrorResult.h"
+#include "nsProxyRelease.h" // nsMainThreadPtrHolder
+#include "mozilla/Telemetry.h"
+#include "mozilla/Unused.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ClearOnShutdown.h"
+
+#include "common/browser_logging/WebRtcLog.h"
+#include "nsString.h"
+#include "transport/runnable_utils.h"
+#include "MediaTransportHandler.h"
+#include "PeerConnectionCtx.h"
+#include "PeerConnectionImpl.h"
+
+#ifdef XP_WIN
+# include <process.h>
+#endif
+
+namespace mozilla::dom {
+
+using StatsRequestCallback =
+ nsMainThreadPtrHandle<WebrtcGlobalStatisticsCallback>;
+
+using LogRequestCallback = nsMainThreadPtrHandle<WebrtcGlobalLoggingCallback>;
+
+class WebrtcContentParents {
+ public:
+ static WebrtcGlobalParent* Alloc();
+ static void Dealloc(WebrtcGlobalParent* aParent);
+ static bool Empty() { return sContentParents.empty(); }
+ static const std::vector<RefPtr<WebrtcGlobalParent>>& GetAll() {
+ return sContentParents;
+ }
+
+ WebrtcContentParents() = delete;
+ WebrtcContentParents(const WebrtcContentParents&) = delete;
+ WebrtcContentParents& operator=(const WebrtcContentParents&) = delete;
+
+ private:
+ static std::vector<RefPtr<WebrtcGlobalParent>> sContentParents;
+};
+
+std::vector<RefPtr<WebrtcGlobalParent>> WebrtcContentParents::sContentParents;
+
+WebrtcGlobalParent* WebrtcContentParents::Alloc() {
+ RefPtr<WebrtcGlobalParent> cp = new WebrtcGlobalParent;
+ sContentParents.push_back(cp);
+ return cp.get();
+}
+
+void WebrtcContentParents::Dealloc(WebrtcGlobalParent* aParent) {
+ if (aParent) {
+ aParent->mShutdown = true;
+ auto cp =
+ std::find(sContentParents.begin(), sContentParents.end(), aParent);
+ if (cp != sContentParents.end()) {
+ sContentParents.erase(cp);
+ }
+ }
+}
+
+static PeerConnectionCtx* GetPeerConnectionCtx() {
+ if (PeerConnectionCtx::isActive()) {
+ MOZ_ASSERT(PeerConnectionCtx::GetInstance());
+ return PeerConnectionCtx::GetInstance();
+ }
+ return nullptr;
+}
+
+static nsTArray<dom::RTCStatsReportInternal>& GetWebrtcGlobalStatsStash() {
+ static StaticAutoPtr<nsTArray<dom::RTCStatsReportInternal>> sStash;
+ if (!sStash) {
+ sStash = new nsTArray<dom::RTCStatsReportInternal>();
+ ClearOnShutdown(&sStash);
+ }
+ return *sStash;
+}
+
+static RefPtr<PWebrtcGlobalParent::GetStatsPromise>
+GetStatsPromiseForThisProcess(const nsAString& aPcIdFilter) {
+ nsTArray<RefPtr<dom::RTCStatsReportPromise>> promises;
+
+ std::set<nsString> pcids;
+ if (auto* ctx = GetPeerConnectionCtx()) {
+ // Grab stats for PCs that still exist
+ ctx->ForEachPeerConnection([&](PeerConnectionImpl* aPc) {
+ if (!aPcIdFilter.IsEmpty() &&
+ !aPcIdFilter.EqualsASCII(aPc->GetIdAsAscii().c_str())) {
+ return;
+ }
+ if (!aPc->IsClosed() || !aPc->LongTermStatsIsDisabled()) {
+ nsString id;
+ aPc->GetId(id);
+ pcids.insert(id);
+ promises.AppendElement(aPc->GetStats(nullptr, true));
+ }
+ });
+
+ // Grab previously stashed stats, if they aren't dupes, and ensure they
+ // are marked closed. (In a content process, this should already have
+ // happened, but in the parent process, the stash will contain the last
+ // observed stats from the content processes. From the perspective of the
+ // parent process, these are assumed closed unless we see new stats from the
+ // content process that say otherwise.)
+ for (auto& report : GetWebrtcGlobalStatsStash()) {
+ report.mClosed = true;
+ if ((aPcIdFilter.IsEmpty() || aPcIdFilter == report.mPcid) &&
+ !pcids.count(report.mPcid)) {
+ promises.AppendElement(dom::RTCStatsReportPromise::CreateAndResolve(
+ MakeUnique<dom::RTCStatsReportInternal>(report), __func__));
+ }
+ }
+ }
+
+ auto UnwrapUniquePtrs = [](dom::RTCStatsReportPromise::AllSettledPromiseType::
+ ResolveOrRejectValue&& aResult) {
+ nsTArray<dom::RTCStatsReportInternal> reports;
+ MOZ_RELEASE_ASSERT(aResult.IsResolve(), "AllSettled should never reject!");
+ for (auto& reportResult : aResult.ResolveValue()) {
+ if (reportResult.IsResolve()) {
+ reports.AppendElement(*reportResult.ResolveValue());
+ }
+ }
+ return PWebrtcGlobalParent::GetStatsPromise::CreateAndResolve(
+ std::move(reports), __func__);
+ };
+
+ return dom::RTCStatsReportPromise::AllSettled(
+ GetMainThreadSerialEventTarget(), promises)
+ ->Then(GetMainThreadSerialEventTarget(), __func__,
+ std::move(UnwrapUniquePtrs));
+}
+
+static std::map<int32_t, dom::Sequence<nsString>>& GetWebrtcGlobalLogStash() {
+ static StaticAutoPtr<std::map<int32_t, dom::Sequence<nsString>>> sStash;
+ if (!sStash) {
+ sStash = new std::map<int32_t, dom::Sequence<nsString>>();
+ ClearOnShutdown(&sStash);
+ }
+ return *sStash;
+}
+
+static void ClearLongTermStats() {
+ if (!NS_IsMainThread()) {
+ MOZ_ASSERT(NS_IsMainThread());
+ return;
+ }
+
+ GetWebrtcGlobalStatsStash().Clear();
+ if (XRE_IsParentProcess()) {
+ WebrtcGlobalStatsHistory::Clear();
+ }
+ if (auto* ctx = GetPeerConnectionCtx()) {
+ ctx->ClearClosedStats();
+ }
+}
+
+void WebrtcGlobalInformation::ClearAllStats(const GlobalObject& aGlobal) {
+ if (!NS_IsMainThread()) {
+ return;
+ }
+
+ // Chrome-only API
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ if (!WebrtcContentParents::Empty()) {
+ // Pass on the request to any content process based PeerConnections.
+ for (const auto& cp : WebrtcContentParents::GetAll()) {
+ Unused << cp->SendClearStats();
+ }
+ }
+
+ // Flush the history for the chrome process
+ ClearLongTermStats();
+}
+
+void WebrtcGlobalInformation::GetStatsHistoryPcIds(
+ const GlobalObject& aGlobal,
+ WebrtcGlobalStatisticsHistoryPcIdsCallback& aPcIdsCallback,
+ ErrorResult& aRv) {
+ if (!NS_IsMainThread()) {
+ aRv.Throw(NS_ERROR_NOT_SAME_THREAD);
+ return;
+ }
+
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ IgnoredErrorResult rv;
+ aPcIdsCallback.Call(WebrtcGlobalStatsHistory::PcIds(), rv);
+ aRv = NS_OK;
+}
+
+void WebrtcGlobalInformation::GetStatsHistorySince(
+ const GlobalObject& aGlobal,
+ WebrtcGlobalStatisticsHistoryCallback& aStatsCallback,
+ const nsAString& pcIdFilter, const Optional<DOMHighResTimeStamp>& aAfter,
+ const Optional<DOMHighResTimeStamp>& aSdpAfter, ErrorResult& aRv) {
+ if (!NS_IsMainThread()) {
+ aRv.Throw(NS_ERROR_NOT_SAME_THREAD);
+ return;
+ }
+
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ WebrtcGlobalStatisticsReport history;
+
+ auto statsAfter = aAfter.WasPassed() ? Some(aAfter.Value()) : Nothing();
+ auto sdpAfter = aSdpAfter.WasPassed() ? Some(aSdpAfter.Value()) : Nothing();
+
+ WebrtcGlobalStatsHistory::GetHistory(pcIdFilter).apply([&](auto& hist) {
+ if (!history.mReports.AppendElements(hist->Since(statsAfter), fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ if (!history.mSdpHistories.AppendElement(hist->SdpSince(sdpAfter),
+ fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ });
+
+ IgnoredErrorResult rv;
+ aStatsCallback.Call(history, rv);
+ aRv = NS_OK;
+}
+
+using StatsPromiseArray =
+ nsTArray<RefPtr<PWebrtcGlobalParent::GetStatsPromise>>;
+
+void WebrtcGlobalInformation::GatherHistory() {
+ const nsString emptyFilter;
+ if (!NS_IsMainThread()) {
+ MOZ_ASSERT(NS_IsMainThread());
+ return;
+ }
+
+ MOZ_ASSERT(XRE_IsParentProcess());
+ using StatsPromise = PWebrtcGlobalParent::GetStatsPromise;
+ auto resolveThenAppendStatsHistory = [](RefPtr<StatsPromise>&& promise) {
+ auto AppendStatsHistory = [](StatsPromise::ResolveOrRejectValue&& result) {
+ if (result.IsReject()) {
+ return;
+ }
+ for (const auto& report : result.ResolveValue()) {
+ WebrtcGlobalStatsHistory::Record(
+ MakeUnique<RTCStatsReportInternal>(report));
+ }
+ };
+ promise->Then(GetMainThreadSerialEventTarget(), __func__,
+ std::move(AppendStatsHistory));
+ };
+ for (const auto& cp : WebrtcContentParents::GetAll()) {
+ resolveThenAppendStatsHistory(cp->SendGetStats(emptyFilter));
+ }
+ resolveThenAppendStatsHistory(GetStatsPromiseForThisProcess(emptyFilter));
+}
+
+void WebrtcGlobalInformation::GetAllStats(
+ const GlobalObject& aGlobal, WebrtcGlobalStatisticsCallback& aStatsCallback,
+ const Optional<nsAString>& aPcIdFilter, ErrorResult& aRv) {
+ if (!NS_IsMainThread()) {
+ aRv.Throw(NS_ERROR_NOT_SAME_THREAD);
+ return;
+ }
+
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ StatsPromiseArray statsPromises;
+
+ nsString filter;
+ if (aPcIdFilter.WasPassed()) {
+ filter = aPcIdFilter.Value();
+ }
+
+ for (const auto& cp : WebrtcContentParents::GetAll()) {
+ statsPromises.AppendElement(cp->SendGetStats(filter));
+ }
+
+ // Stats from this (the parent) process. How long do we keep supporting this?
+ statsPromises.AppendElement(GetStatsPromiseForThisProcess(filter));
+
+ // CallbackObject does not support threadsafe refcounting, and must be
+ // used and destroyed on main.
+ StatsRequestCallback callbackHandle(
+ new nsMainThreadPtrHolder<WebrtcGlobalStatisticsCallback>(
+ "WebrtcGlobalStatisticsCallback", &aStatsCallback));
+
+ auto FlattenThenStashThenCallback =
+ [callbackHandle,
+ filter](PWebrtcGlobalParent::GetStatsPromise::AllSettledPromiseType::
+ ResolveOrRejectValue&& aResult) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
+ std::set<nsString> pcids;
+ WebrtcGlobalStatisticsReport flattened;
+ MOZ_RELEASE_ASSERT(aResult.IsResolve(),
+ "AllSettled should never reject!");
+ // Flatten stats from content processes and parent process.
+ // The stats from the parent process (which will come last) might
+ // contain some stale content-process stats, so skip those.
+ for (auto& processResult : aResult.ResolveValue()) {
+ // TODO: Report rejection on individual content processes someday?
+ if (processResult.IsResolve()) {
+ for (auto& pcStats : processResult.ResolveValue()) {
+ if (!pcids.count(pcStats.mPcid)) {
+ pcids.insert(pcStats.mPcid);
+ if (!flattened.mReports.AppendElement(std::move(pcStats),
+ fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ }
+ }
+ }
+ }
+
+ if (filter.IsEmpty()) {
+ // Unfiltered is simple; the flattened result becomes the new stash.
+ GetWebrtcGlobalStatsStash() = flattened.mReports;
+ } else if (!flattened.mReports.IsEmpty()) {
+ // Update our stash with the single result.
+ MOZ_ASSERT(flattened.mReports.Length() == 1);
+ StashStats(flattened.mReports[0]);
+ }
+
+ IgnoredErrorResult rv;
+ callbackHandle->Call(flattened, rv);
+ };
+
+ PWebrtcGlobalParent::GetStatsPromise::AllSettled(
+ GetMainThreadSerialEventTarget(), statsPromises)
+ ->Then(GetMainThreadSerialEventTarget(), __func__,
+ std::move(FlattenThenStashThenCallback));
+
+ aRv = NS_OK;
+}
+
+static RefPtr<PWebrtcGlobalParent::GetLogPromise> GetLogPromise() {
+ PeerConnectionCtx* ctx = GetPeerConnectionCtx();
+ if (!ctx) {
+ // This process has never created a PeerConnection, so no ICE logging.
+ return PWebrtcGlobalParent::GetLogPromise::CreateAndResolve(
+ Sequence<nsString>(), __func__);
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsISerialEventTarget> stsThread =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+
+ if (NS_WARN_IF(NS_FAILED(rv) || !stsThread)) {
+ return PWebrtcGlobalParent::GetLogPromise::CreateAndResolve(
+ Sequence<nsString>(), __func__);
+ }
+
+ RefPtr<MediaTransportHandler> transportHandler = ctx->GetTransportHandler();
+
+ auto AddMarkers =
+ [](MediaTransportHandler::IceLogPromise::ResolveOrRejectValue&& aValue) {
+ nsString pid;
+ pid.AppendInt(getpid());
+ Sequence<nsString> logs;
+ if (aValue.IsResolve() && !aValue.ResolveValue().IsEmpty()) {
+ bool ok = logs.AppendElement(
+ u"+++++++ BEGIN (process id "_ns + pid + u") ++++++++"_ns,
+ fallible);
+ ok &=
+ !!logs.AppendElements(std::move(aValue.ResolveValue()), fallible);
+ ok &= !!logs.AppendElement(
+ u"+++++++ END (process id "_ns + pid + u") ++++++++"_ns,
+ fallible);
+ if (!ok) {
+ mozalloc_handle_oom(0);
+ }
+ }
+ return PWebrtcGlobalParent::GetLogPromise::CreateAndResolve(
+ std::move(logs), __func__);
+ };
+
+ return transportHandler->GetIceLog(nsCString())
+ ->Then(GetMainThreadSerialEventTarget(), __func__, std::move(AddMarkers));
+}
+
+static nsresult RunLogClear() {
+ PeerConnectionCtx* ctx = GetPeerConnectionCtx();
+ if (!ctx) {
+ // This process has never created a PeerConnection, so no ICE logging.
+ return NS_OK;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsISerialEventTarget> stsThread =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (!stsThread) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<MediaTransportHandler> transportHandler = ctx->GetTransportHandler();
+
+ return RUN_ON_THREAD(
+ stsThread,
+ WrapRunnable(transportHandler, &MediaTransportHandler::ClearIceLog),
+ NS_DISPATCH_NORMAL);
+}
+
+void WebrtcGlobalInformation::ClearLogging(const GlobalObject& aGlobal) {
+ if (!NS_IsMainThread()) {
+ return;
+ }
+
+ // Chrome-only API
+ MOZ_ASSERT(XRE_IsParentProcess());
+ GetWebrtcGlobalLogStash().clear();
+
+ if (!WebrtcContentParents::Empty()) {
+ // Clear content process signaling logs
+ for (const auto& cp : WebrtcContentParents::GetAll()) {
+ Unused << cp->SendClearLog();
+ }
+ }
+
+ // Clear chrome process signaling logs
+ Unused << RunLogClear();
+}
+
+static RefPtr<GenericPromise> UpdateLogStash() {
+ nsTArray<RefPtr<GenericPromise>> logPromises;
+ MOZ_ASSERT(XRE_IsParentProcess());
+ for (const auto& cp : WebrtcContentParents::GetAll()) {
+ auto StashLog =
+ [id = cp->Id() * 2 /* Make sure 1 isn't used */](
+ PWebrtcGlobalParent::GetLogPromise::ResolveOrRejectValue&& aValue) {
+ if (aValue.IsResolve() && !aValue.ResolveValue().IsEmpty()) {
+ GetWebrtcGlobalLogStash()[id] = aValue.ResolveValue();
+ }
+ return GenericPromise::CreateAndResolve(true, __func__);
+ };
+ logPromises.AppendElement(cp->SendGetLog()->Then(
+ GetMainThreadSerialEventTarget(), __func__, std::move(StashLog)));
+ }
+
+ // Get ICE logging for this (the parent) process. How long do we support this?
+ logPromises.AppendElement(GetLogPromise()->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [](PWebrtcGlobalParent::GetLogPromise::ResolveOrRejectValue&& aValue) {
+ if (aValue.IsResolve()) {
+ GetWebrtcGlobalLogStash()[1] = aValue.ResolveValue();
+ }
+ return GenericPromise::CreateAndResolve(true, __func__);
+ }));
+
+ return GenericPromise::AllSettled(GetMainThreadSerialEventTarget(),
+ logPromises)
+ ->Then(GetMainThreadSerialEventTarget(), __func__,
+ [](GenericPromise::AllSettledPromiseType::ResolveOrRejectValue&&
+ aValue) {
+ // We don't care about the value, since we're just going to copy
+ // what is in the stash. This ignores failures too, which is what
+ // we want.
+ return GenericPromise::CreateAndResolve(true, __func__);
+ });
+}
+
+void WebrtcGlobalInformation::GetLogging(
+ const GlobalObject& aGlobal, const nsAString& aPattern,
+ WebrtcGlobalLoggingCallback& aLoggingCallback, ErrorResult& aRv) {
+ if (!NS_IsMainThread()) {
+ aRv.Throw(NS_ERROR_NOT_SAME_THREAD);
+ return;
+ }
+
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ nsAutoString pattern(aPattern);
+
+ // CallbackObject does not support threadsafe refcounting, and must be
+ // destroyed on main.
+ LogRequestCallback callbackHandle(
+ new nsMainThreadPtrHolder<WebrtcGlobalLoggingCallback>(
+ "WebrtcGlobalLoggingCallback", &aLoggingCallback));
+
+ auto FilterThenCallback =
+ [pattern, callbackHandle](GenericPromise::ResolveOrRejectValue&& aValue)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY {
+ dom::Sequence<nsString> flattened;
+ for (const auto& [id, log] : GetWebrtcGlobalLogStash()) {
+ (void)id;
+ for (const auto& line : log) {
+ if (pattern.IsEmpty() || (line.Find(pattern) != kNotFound)) {
+ if (!flattened.AppendElement(line, fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ }
+ }
+ }
+ IgnoredErrorResult rv;
+ callbackHandle->Call(flattened, rv);
+ };
+
+ UpdateLogStash()->Then(GetMainThreadSerialEventTarget(), __func__,
+ std::move(FilterThenCallback));
+ aRv = NS_OK;
+}
+
+static int32_t sLastSetLevel = 0;
+static bool sLastAECDebug = false;
+static Maybe<nsCString> sAecDebugLogDir;
+
+void WebrtcGlobalInformation::SetDebugLevel(const GlobalObject& aGlobal,
+ int32_t aLevel) {
+ if (aLevel) {
+ StartWebRtcLog(mozilla::LogLevel(aLevel));
+ } else {
+ StopWebRtcLog();
+ }
+ sLastSetLevel = aLevel;
+
+ for (const auto& cp : WebrtcContentParents::GetAll()) {
+ Unused << cp->SendSetDebugMode(aLevel);
+ }
+}
+
+int32_t WebrtcGlobalInformation::DebugLevel(const GlobalObject& aGlobal) {
+ return sLastSetLevel;
+}
+
+void WebrtcGlobalInformation::SetAecDebug(const GlobalObject& aGlobal,
+ bool aEnable) {
+ if (aEnable) {
+ sAecDebugLogDir = Some(StartAecLog());
+ } else {
+ StopAecLog();
+ }
+
+ sLastAECDebug = aEnable;
+
+ for (const auto& cp : WebrtcContentParents::GetAll()) {
+ Unused << cp->SendSetAecLogging(aEnable);
+ }
+}
+
+bool WebrtcGlobalInformation::AecDebug(const GlobalObject& aGlobal) {
+ return sLastAECDebug;
+}
+
+void WebrtcGlobalInformation::GetAecDebugLogDir(const GlobalObject& aGlobal,
+ nsAString& aDir) {
+ aDir = NS_ConvertASCIItoUTF16(sAecDebugLogDir.valueOr(""_ns));
+}
+
+/*static*/
+void WebrtcGlobalInformation::StashStats(
+ const dom::RTCStatsReportInternal& aReport) {
+ // Remove previous report, if present
+ // TODO: Make this a map instead of an array?
+ for (size_t i = 0; i < GetWebrtcGlobalStatsStash().Length();) {
+ auto& pcStats = GetWebrtcGlobalStatsStash()[i];
+ if (pcStats.mPcid == aReport.mPcid) {
+ GetWebrtcGlobalStatsStash().RemoveElementAt(i);
+ break;
+ }
+ ++i;
+ }
+ // Stash final stats
+ GetWebrtcGlobalStatsStash().AppendElement(aReport);
+}
+
+void WebrtcGlobalInformation::AdjustTimerReferences(
+ PcTrackingUpdate&& aUpdate) {
+ static StaticRefPtr<nsITimer> sHistoryTimer;
+ static StaticAutoPtr<nsTHashSet<nsString>> sPcids;
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ auto HandleAdd = [&](nsString&& aPcid, bool aIsLongTermStatsDisabled) {
+ if (!sPcids) {
+ sPcids = new nsTHashSet<nsString>();
+ ClearOnShutdown(&sPcids);
+ }
+ sPcids->EnsureInserted(aPcid);
+ // Reserve a stats history
+ WebrtcGlobalStatsHistory::InitHistory(nsString(aPcid),
+ aIsLongTermStatsDisabled);
+ if (!sHistoryTimer) {
+ sHistoryTimer = NS_NewTimer(GetMainThreadSerialEventTarget());
+ if (sHistoryTimer) {
+ sHistoryTimer->InitWithNamedFuncCallback(
+ [](nsITimer* aTimer, void* aClosure) {
+ if (WebrtcGlobalStatsHistory::Pref::Enabled()) {
+ WebrtcGlobalInformation::GatherHistory();
+ }
+ },
+ nullptr, WebrtcGlobalStatsHistory::Pref::PollIntervalMs(),
+ nsITimer::TYPE_REPEATING_SLACK,
+ "WebrtcGlobalInformation::GatherHistory");
+ }
+ ClearOnShutdown(&sHistoryTimer);
+ }
+ };
+
+ auto HandleRemove = [&](const nsString& aRemoved) {
+ WebrtcGlobalStatsHistory::CloseHistory(nsString(aRemoved));
+ if (!sPcids || !sPcids->Count()) {
+ return;
+ }
+ if (!sPcids->Contains(aRemoved)) {
+ return;
+ }
+ sPcids->Remove(aRemoved);
+ if (!sPcids->Count() && sHistoryTimer) {
+ sHistoryTimer->Cancel();
+ sHistoryTimer = nullptr;
+ }
+ };
+
+ switch (aUpdate.Type()) {
+ case PcTrackingUpdate::Type::Add: {
+ HandleAdd(std::move(aUpdate.mPcid),
+ aUpdate.mLongTermStatsDisabled.valueOrFrom([&]() {
+ MOZ_ASSERT(aUpdate.mLongTermStatsDisabled.isNothing());
+ return true;
+ }));
+ return;
+ }
+ case PcTrackingUpdate::Type::Remove: {
+ HandleRemove(aUpdate.mPcid);
+ return;
+ }
+ default: {
+ MOZ_ASSERT(false, "Invalid PcCount operation");
+ }
+ }
+}
+
+WebrtcGlobalParent* WebrtcGlobalParent::Alloc() {
+ return WebrtcContentParents::Alloc();
+}
+
+bool WebrtcGlobalParent::Dealloc(WebrtcGlobalParent* aActor) {
+ WebrtcContentParents::Dealloc(aActor);
+ return true;
+}
+
+void WebrtcGlobalParent::ActorDestroy(ActorDestroyReason aWhy) {
+ mShutdown = true;
+ for (const auto& pcId : mPcids) {
+ using Update = WebrtcGlobalInformation::PcTrackingUpdate;
+ auto update = Update::Remove(nsString(pcId));
+ WebrtcGlobalInformation::PeerConnectionTracking(update);
+ }
+}
+
+mozilla::ipc::IPCResult WebrtcGlobalParent::Recv__delete__() {
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebrtcGlobalParent::RecvPeerConnectionCreated(
+ const nsAString& aPcId, const bool& aIsLongTermStatsDisabled) {
+ if (mShutdown) {
+ return IPC_OK();
+ }
+ mPcids.EnsureInserted(aPcId);
+ using Update = WebrtcGlobalInformation::PcTrackingUpdate;
+ auto update = Update::Add(nsString(aPcId), aIsLongTermStatsDisabled);
+ WebrtcGlobalInformation::PeerConnectionTracking(update);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebrtcGlobalParent::RecvPeerConnectionDestroyed(
+ const nsAString& aPcId) {
+ mPcids.EnsureRemoved(aPcId);
+ using Update = WebrtcGlobalInformation::PcTrackingUpdate;
+ auto update = Update::Remove(nsString(aPcId));
+ WebrtcGlobalStatsHistory::CloseHistory(aPcId);
+ WebrtcGlobalInformation::PeerConnectionTracking(update);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebrtcGlobalParent::RecvPeerConnectionFinalStats(
+ const RTCStatsReportInternal& aFinalStats) {
+ auto finalStats = MakeUnique<RTCStatsReportInternal>(aFinalStats);
+ WebrtcGlobalStatsHistory::Record(std::move(finalStats));
+ WebrtcGlobalStatsHistory::CloseHistory(aFinalStats.mPcid);
+ return IPC_OK();
+}
+
+MOZ_IMPLICIT WebrtcGlobalParent::WebrtcGlobalParent() : mShutdown(false) {
+ MOZ_COUNT_CTOR(WebrtcGlobalParent);
+}
+
+MOZ_IMPLICIT WebrtcGlobalParent::~WebrtcGlobalParent() {
+ MOZ_COUNT_DTOR(WebrtcGlobalParent);
+}
+
+mozilla::ipc::IPCResult WebrtcGlobalChild::RecvGetStats(
+ const nsAString& aPcIdFilter, GetStatsResolver&& aResolve) {
+ if (!mShutdown) {
+ GetStatsPromiseForThisProcess(aPcIdFilter)
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [resolve = std::move(aResolve)](
+ nsTArray<dom::RTCStatsReportInternal>&& aReports) {
+ resolve(std::move(aReports));
+ },
+ []() { MOZ_CRASH(); });
+ return IPC_OK();
+ }
+
+ aResolve(nsTArray<RTCStatsReportInternal>());
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebrtcGlobalChild::RecvClearStats() {
+ if (mShutdown) {
+ return IPC_OK();
+ }
+
+ ClearLongTermStats();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebrtcGlobalChild::RecvGetLog(
+ GetLogResolver&& aResolve) {
+ if (mShutdown) {
+ aResolve(Sequence<nsString>());
+ return IPC_OK();
+ }
+
+ GetLogPromise()->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [aResolve = std::move(aResolve)](
+ PWebrtcGlobalParent::GetLogPromise::ResolveOrRejectValue&& aValue) {
+ if (aValue.IsResolve()) {
+ aResolve(aValue.ResolveValue());
+ } else {
+ aResolve(Sequence<nsString>());
+ }
+ });
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebrtcGlobalChild::RecvClearLog() {
+ if (mShutdown) {
+ return IPC_OK();
+ }
+
+ RunLogClear();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebrtcGlobalChild::RecvSetAecLogging(
+ const bool& aEnable) {
+ if (!mShutdown) {
+ if (aEnable) {
+ StartAecLog();
+ } else {
+ StopAecLog();
+ }
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebrtcGlobalChild::RecvSetDebugMode(const int& aLevel) {
+ if (!mShutdown) {
+ if (aLevel) {
+ StartWebRtcLog(mozilla::LogLevel(aLevel));
+ } else {
+ StopWebRtcLog();
+ }
+ }
+ return IPC_OK();
+}
+
+WebrtcGlobalChild* WebrtcGlobalChild::GetOrSet(
+ const Maybe<WebrtcGlobalChild*>& aChild) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(XRE_IsContentProcess());
+ static WebrtcGlobalChild* sChild;
+ if (!sChild && !aChild) {
+ sChild = static_cast<WebrtcGlobalChild*>(
+ ContentChild::GetSingleton()->SendPWebrtcGlobalConstructor());
+ }
+ aChild.apply([](auto* child) { sChild = child; });
+ return sChild;
+}
+
+WebrtcGlobalChild* WebrtcGlobalChild::Get() { return GetOrSet(Nothing()); }
+
+void WebrtcGlobalChild::ActorDestroy(ActorDestroyReason aWhy) {
+ mShutdown = true;
+}
+
+MOZ_IMPLICIT WebrtcGlobalChild::WebrtcGlobalChild() : mShutdown(false) {
+ MOZ_COUNT_CTOR(WebrtcGlobalChild);
+}
+
+MOZ_IMPLICIT WebrtcGlobalChild::~WebrtcGlobalChild() {
+ MOZ_COUNT_DTOR(WebrtcGlobalChild);
+ GetOrSet(Some(nullptr));
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webrtc/jsapi/WebrtcGlobalInformation.h b/dom/media/webrtc/jsapi/WebrtcGlobalInformation.h
new file mode 100644
index 0000000000..d43013bb43
--- /dev/null
+++ b/dom/media/webrtc/jsapi/WebrtcGlobalInformation.h
@@ -0,0 +1,102 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _WEBRTC_GLOBAL_INFORMATION_H_
+#define _WEBRTC_GLOBAL_INFORMATION_H_
+
+#include <tuple>
+#include "mozilla/dom/WebrtcGlobalInformationBinding.h"
+#include "nsString.h"
+#include "mozilla/dom/BindingDeclarations.h" // for Optional
+#include "nsDOMNavigationTiming.h"
+#include "WebrtcGlobalStatsHistory.h"
+
+namespace mozilla {
+class PeerConnectionImpl;
+class ErrorResult;
+
+namespace dom {
+
+class GlobalObject;
+class WebrtcGlobalStatisticsCallback;
+class WebrtcGlobalStatisticsHistoryPcIdsCallback;
+class WebrtcGlobalLoggingCallback;
+struct RTCStatsReportInternal;
+
+class WebrtcGlobalInformation {
+ public:
+ MOZ_CAN_RUN_SCRIPT
+ static void GetAllStats(const GlobalObject& aGlobal,
+ WebrtcGlobalStatisticsCallback& aStatsCallback,
+ const Optional<nsAString>& aPcIdFilter,
+ ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT
+ static void GetStatsHistoryPcIds(
+ const GlobalObject& aGlobal,
+ WebrtcGlobalStatisticsHistoryPcIdsCallback& aPcIdsCallback,
+ ErrorResult& aRv);
+
+ MOZ_CAN_RUN_SCRIPT
+ static void GetStatsHistorySince(
+ const GlobalObject& aGlobal,
+ WebrtcGlobalStatisticsHistoryCallback& aStatsCallback,
+ const nsAString& aPcIdFilter, const Optional<DOMHighResTimeStamp>& aAfter,
+ const Optional<DOMHighResTimeStamp>& aSdpAfter, ErrorResult& aRv);
+
+ static void GatherHistory();
+
+ static void ClearAllStats(const GlobalObject& aGlobal);
+
+ MOZ_CAN_RUN_SCRIPT
+ static void GetLogging(const GlobalObject& aGlobal, const nsAString& aPattern,
+ WebrtcGlobalLoggingCallback& aLoggingCallback,
+ ErrorResult& aRv);
+
+ static void ClearLogging(const GlobalObject& aGlobal);
+
+ static void SetDebugLevel(const GlobalObject& aGlobal, int32_t aLevel);
+ static int32_t DebugLevel(const GlobalObject& aGlobal);
+
+ static void SetAecDebug(const GlobalObject& aGlobal, bool aEnable);
+ static bool AecDebug(const GlobalObject& aGlobal);
+ static void GetAecDebugLogDir(const GlobalObject& aGlobal, nsAString& aDir);
+
+ static void StashStats(const RTCStatsReportInternal& aReport);
+
+ WebrtcGlobalInformation() = delete;
+ WebrtcGlobalInformation(const WebrtcGlobalInformation& aOrig) = delete;
+ WebrtcGlobalInformation& operator=(const WebrtcGlobalInformation& aRhs) =
+ delete;
+
+ struct PcTrackingUpdate {
+ static PcTrackingUpdate Add(const nsString& aPcid,
+ const bool& aLongTermStatsDisabled) {
+ return PcTrackingUpdate{aPcid, Some(aLongTermStatsDisabled)};
+ }
+ static PcTrackingUpdate Remove(const nsString& aPcid) {
+ return PcTrackingUpdate{aPcid, Nothing()};
+ }
+ nsString mPcid;
+ Maybe<bool> mLongTermStatsDisabled;
+ enum class Type {
+ Add,
+ Remove,
+ };
+ Type Type() const {
+ return mLongTermStatsDisabled ? Type::Add : Type::Remove;
+ }
+ };
+ static void PeerConnectionTracking(PcTrackingUpdate& aUpdate) {
+ AdjustTimerReferences(std::move(aUpdate));
+ }
+
+ private:
+ static void AdjustTimerReferences(PcTrackingUpdate&& aUpdate);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // _WEBRTC_GLOBAL_INFORMATION_H_
diff --git a/dom/media/webrtc/jsapi/WebrtcGlobalParent.h b/dom/media/webrtc/jsapi/WebrtcGlobalParent.h
new file mode 100644
index 0000000000..8372c4182f
--- /dev/null
+++ b/dom/media/webrtc/jsapi/WebrtcGlobalParent.h
@@ -0,0 +1,52 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _WEBRTC_GLOBAL_PARENT_H_
+#define _WEBRTC_GLOBAL_PARENT_H_
+
+#include "mozilla/dom/PWebrtcGlobalParent.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla::dom {
+
+class WebrtcParents;
+
+class WebrtcGlobalParent : public PWebrtcGlobalParent {
+ friend class ContentParent;
+ friend class WebrtcGlobalInformation;
+ friend class WebrtcContentParents;
+
+ bool mShutdown;
+ nsTHashSet<nsString> mPcids;
+
+ MOZ_IMPLICIT WebrtcGlobalParent();
+
+ static WebrtcGlobalParent* Alloc();
+ static bool Dealloc(WebrtcGlobalParent* aActor);
+
+ virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+ virtual mozilla::ipc::IPCResult Recv__delete__() override;
+ // Notification that a PeerConnection exists, and stats polling can begin
+ // if it hasn't already begun due to a previously created PeerConnection.
+ virtual mozilla::ipc::IPCResult RecvPeerConnectionCreated(
+ const nsAString& aPcId, const bool& aIsLongTermStatsDisabled) override;
+ // Notification that a PeerConnection no longer exists, and stats polling
+ // can end if there are no other PeerConnections.
+ virtual mozilla::ipc::IPCResult RecvPeerConnectionDestroyed(
+ const nsAString& aPcid) override;
+ // Ditto but we have final stats
+ virtual mozilla::ipc::IPCResult RecvPeerConnectionFinalStats(
+ const RTCStatsReportInternal& aFinalStats) override;
+ virtual ~WebrtcGlobalParent();
+
+ public:
+ NS_INLINE_DECL_REFCOUNTING(WebrtcGlobalParent)
+
+ bool IsActive() { return !mShutdown; }
+};
+
+} // namespace mozilla::dom
+
+#endif // _WEBRTC_GLOBAL_PARENT_H_
diff --git a/dom/media/webrtc/jsapi/WebrtcGlobalStatsHistory.cpp b/dom/media/webrtc/jsapi/WebrtcGlobalStatsHistory.cpp
new file mode 100644
index 0000000000..1d576b5dca
--- /dev/null
+++ b/dom/media/webrtc/jsapi/WebrtcGlobalStatsHistory.cpp
@@ -0,0 +1,282 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebrtcGlobalStatsHistory.h"
+#include <memory>
+
+#include "domstubs.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/RTCStatsReportBinding.h" // for RTCStatsReportInternal
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/StaticPrefs_media.h"
+#include "mozilla/fallible.h"
+#include "mozilla/mozalloc_oom.h"
+#include "nsDOMNavigationTiming.h"
+
+namespace mozilla::dom {
+
+constexpr auto SEC_TO_MS(const DOMHighResTimeStamp sec) -> DOMHighResTimeStamp {
+ return sec * 1000.0;
+}
+
+constexpr auto MIN_TO_MS(const DOMHighResTimeStamp min) -> DOMHighResTimeStamp {
+ return SEC_TO_MS(min * 60.0);
+}
+
+// Prefs
+auto WebrtcGlobalStatsHistory::Pref::Enabled() -> bool {
+ return mozilla::StaticPrefs::media_aboutwebrtc_hist_enabled();
+}
+
+auto WebrtcGlobalStatsHistory::Pref::PollIntervalMs() -> uint32_t {
+ return mozilla::StaticPrefs::media_aboutwebrtc_hist_poll_interval_ms();
+}
+
+auto WebrtcGlobalStatsHistory::Pref::StorageWindowS() -> uint32_t {
+ return mozilla::StaticPrefs::media_aboutwebrtc_hist_storage_window_s();
+}
+
+auto WebrtcGlobalStatsHistory::Pref::PruneAfterM() -> uint32_t {
+ return mozilla::StaticPrefs::media_aboutwebrtc_hist_prune_after_m();
+}
+
+auto WebrtcGlobalStatsHistory::Pref::ClosedStatsToRetain() -> uint32_t {
+ return mozilla::StaticPrefs::media_aboutwebrtc_hist_closed_stats_to_retain();
+}
+
+auto WebrtcGlobalStatsHistory::Get() -> WebrtcGlobalStatsHistory::StatsMap& {
+ static StaticAutoPtr<StatsMap> sHist;
+ if (!sHist) {
+ sHist = new StatsMap();
+ ClearOnShutdown(&sHist);
+ }
+ return *sHist;
+}
+
+auto WebrtcGlobalStatsHistory::Entry::ReportElement::Timestamp() const
+ -> DOMHighResTimeStamp {
+ return report->mTimestamp;
+}
+
+auto WebrtcGlobalStatsHistory::Entry::SdpElement::Timestamp() const
+ -> DOMHighResTimeStamp {
+ return sdp.mTimestamp;
+}
+
+auto WebrtcGlobalStatsHistory::Entry::MakeReportElement(
+ UniquePtr<RTCStatsReportInternal> aReport)
+ -> WebrtcGlobalStatsHistory::Entry::ReportElement* {
+ auto* elem = new ReportElement();
+ elem->report = std::move(aReport);
+ // We don't want to store a copy of the SDP history with each stats entry.
+ // SDP History is stored seperately, see MakeSdpElements.
+ elem->report->mSdpHistory.Clear();
+ return elem;
+}
+
+auto WebrtcGlobalStatsHistory::Entry::MakeSdpElementsSince(
+ Sequence<RTCSdpHistoryEntryInternal>&& aSdpHistory,
+ const Maybe<DOMHighResTimeStamp>& aSdpAfter)
+ -> AutoCleanLinkedList<WebrtcGlobalStatsHistory::Entry::SdpElement> {
+ AutoCleanLinkedList<WebrtcGlobalStatsHistory::Entry::SdpElement> result;
+ for (auto& sdpHist : aSdpHistory) {
+ if (!aSdpAfter || aSdpAfter.value() < sdpHist.mTimestamp) {
+ auto* element = new SdpElement();
+ element->sdp = sdpHist;
+ result.insertBack(element);
+ }
+ }
+ return result;
+}
+
+template <typename T>
+auto FindFirstEntryAfter(const T* first,
+ const Maybe<DOMHighResTimeStamp>& aAfter) -> const T* {
+ const auto* current = first;
+ while (aAfter && current && current->Timestamp() <= aAfter.value()) {
+ current = current->getNext();
+ }
+ return current;
+}
+
+template <typename T>
+auto CountElementsToEndInclusive(const LinkedListElement<T>* elem) -> size_t {
+ size_t count = 0;
+ const auto* cursor = elem;
+ while (cursor && cursor->isInList()) {
+ count++;
+ cursor = cursor->getNext();
+ }
+ return count;
+}
+
+auto WebrtcGlobalStatsHistory::Entry::Since(
+ const Maybe<DOMHighResTimeStamp>& aAfter) const
+ -> nsTArray<RTCStatsReportInternal> {
+ nsTArray<RTCStatsReportInternal> results;
+ const auto* cursor = FindFirstEntryAfter(mReports.getFirst(), aAfter);
+ const auto count = CountElementsToEndInclusive(cursor);
+ if (!results.SetCapacity(count, fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ while (cursor) {
+ results.AppendElement(RTCStatsReportInternal(*cursor->report));
+ cursor = cursor->getNext();
+ }
+ return results;
+}
+
+auto WebrtcGlobalStatsHistory::Entry::SdpSince(
+ const Maybe<DOMHighResTimeStamp>& aAfter) const -> RTCSdpHistoryInternal {
+ RTCSdpHistoryInternal results;
+ results.mPcid = mPcid;
+ // If no timestamp was passed copy the entire history
+ const auto* cursor = FindFirstEntryAfter(mSdp.getFirst(), aAfter);
+ const auto count = CountElementsToEndInclusive(cursor);
+ if (!results.mSdpHistory.SetCapacity(count, fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ while (cursor) {
+ if (!results.mSdpHistory.AppendElement(
+ RTCSdpHistoryEntryInternal(cursor->sdp), fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ cursor = cursor->getNext();
+ }
+ return results;
+}
+
+auto WebrtcGlobalStatsHistory::Entry::Prune(const DOMHighResTimeStamp aBefore)
+ -> void {
+ // Clear everything in the case that we don't keep stats
+ if (mIsLongTermStatsDisabled) {
+ mReports.clear();
+ }
+ // Clear everything before the cutoff
+ for (auto* element = mReports.getFirst();
+ element && element->report->mTimestamp < aBefore;
+ element = mReports.getFirst()) {
+ delete mReports.popFirst();
+ }
+ // I don't think we should prune SDPs but if we did it would look like this:
+ // Note: we always keep the most recent SDP
+}
+
+auto WebrtcGlobalStatsHistory::InitHistory(const nsAString& aPcId,
+ const bool aIsLongTermStatsDisabled)
+ -> void {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ if (WebrtcGlobalStatsHistory::Get().MaybeGet(aPcId)) {
+ return;
+ }
+ WebrtcGlobalStatsHistory::Get().GetOrInsertNew(aPcId, nsString(aPcId),
+ aIsLongTermStatsDisabled);
+};
+
+auto WebrtcGlobalStatsHistory::Record(UniquePtr<RTCStatsReportInternal> aReport)
+ -> void {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ // Use the report timestamp as "now" for determining time depth
+ // based pruning.
+ const auto now = aReport->mTimestamp;
+ const auto earliest = now - SEC_TO_MS(Pref::StorageWindowS());
+
+ // Store closed state before moving the report
+ const auto closed = aReport->mClosed;
+ const auto pcId = aReport->mPcid;
+
+ auto history = WebrtcGlobalStatsHistory::GetHistory(aReport->mPcid);
+ if (history && Pref::Enabled()) {
+ auto entry = history.value();
+ // Remove expired entries
+ entry->Prune(earliest);
+ // Find new SDP entries
+ auto sdpAfter = Maybe<DOMHighResTimeStamp>(Nothing());
+ if (auto* lastSdp = entry->mSdp.getLast(); lastSdp) {
+ sdpAfter = Some(lastSdp->Timestamp());
+ }
+ entry->mSdp.extendBack(
+ Entry::MakeSdpElementsSince(std::move(aReport->mSdpHistory), sdpAfter));
+ // Reports must be in ascending order by mTimestamp
+ const auto* latest = entry->mReports.getLast();
+ // Maintain sorted order
+ if (!latest || latest->report->mTimestamp < aReport->mTimestamp) {
+ entry->mReports.insertBack(Entry::MakeReportElement(std::move(aReport)));
+ }
+ }
+ // Close the history if needed
+ if (closed) {
+ CloseHistory(pcId);
+ }
+}
+
+auto WebrtcGlobalStatsHistory::CloseHistory(const nsAString& aPcId) -> void {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ auto maybeHist = WebrtcGlobalStatsHistory::Get().MaybeGet(aPcId);
+ if (!maybeHist) {
+ return;
+ }
+ {
+ auto&& hist = maybeHist.value();
+ hist->mIsClosed = true;
+
+ if (hist->mIsLongTermStatsDisabled) {
+ WebrtcGlobalStatsHistory::Get().Remove(aPcId);
+ return;
+ }
+ }
+ size_t remainingClosedStatsToRetain =
+ WebrtcGlobalStatsHistory::Pref::ClosedStatsToRetain();
+ WebrtcGlobalStatsHistory::Get().RemoveIf([&](auto& iter) {
+ auto& entry = iter.Data();
+ if (!entry->mIsClosed) {
+ return false;
+ }
+ if (entry->mIsLongTermStatsDisabled) {
+ return true;
+ }
+ if (remainingClosedStatsToRetain > 0) {
+ remainingClosedStatsToRetain -= 1;
+ return false;
+ }
+ return true;
+ });
+}
+
+auto WebrtcGlobalStatsHistory::Clear() -> void {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ WebrtcGlobalStatsHistory::Get().RemoveIf([](auto& aIter) {
+ // First clear all the closed histories.
+ if (aIter.Data()->mIsClosed) {
+ return true;
+ }
+ // For all remaining histories clear their stored reports
+ aIter.Data()->mReports.clear();
+ // As an optimization we don't clear the SDP, because that would
+ // be reconstitued in the very next stats gathering polling period.
+ // Those are potentially large allocations which we can skip.
+ return false;
+ });
+}
+
+auto WebrtcGlobalStatsHistory::PcIds() -> dom::Sequence<nsString> {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ dom::Sequence<nsString> pcIds;
+ for (const auto& pcId : WebrtcGlobalStatsHistory::Get().Keys()) {
+ if (!pcIds.AppendElement(pcId, fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ }
+ return pcIds;
+}
+
+auto WebrtcGlobalStatsHistory::GetHistory(const nsAString& aPcId)
+ -> Maybe<RefPtr<Entry> > {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ const auto pcid = NS_ConvertUTF16toUTF8(aPcId);
+
+ return WebrtcGlobalStatsHistory::Get().MaybeGet(aPcId);
+}
+} // namespace mozilla::dom
diff --git a/dom/media/webrtc/jsapi/WebrtcGlobalStatsHistory.h b/dom/media/webrtc/jsapi/WebrtcGlobalStatsHistory.h
new file mode 100644
index 0000000000..aa820e787d
--- /dev/null
+++ b/dom/media/webrtc/jsapi/WebrtcGlobalStatsHistory.h
@@ -0,0 +1,88 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#pragma once
+
+#include <memory>
+#include "domstubs.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/DOMString.h"
+#include "mozilla/dom/RTCStatsReportBinding.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsHashKeys.h"
+#include "nsTHashMap.h"
+
+namespace mozilla::dom {
+class WebrtcGlobalStatisticsHistoryCallback;
+struct RTCStatsReportInternal;
+
+struct WebrtcGlobalStatsHistory {
+ // History preferences
+ struct Pref {
+ static auto Enabled() -> bool;
+ static auto PollIntervalMs() -> uint32_t;
+ static auto StorageWindowS() -> uint32_t;
+ static auto PruneAfterM() -> uint32_t;
+ static auto ClosedStatsToRetain() -> uint32_t;
+ Pref() = delete;
+ ~Pref() = delete;
+ };
+
+ struct Entry {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Entry)
+ // We need to wrap the report in an element
+ struct ReportElement : public LinkedListElement<ReportElement> {
+ UniquePtr<RTCStatsReportInternal> report;
+ auto Timestamp() const -> DOMHighResTimeStamp;
+ virtual ~ReportElement() = default;
+ };
+ // And likewise for the SDP history
+ struct SdpElement : public LinkedListElement<SdpElement> {
+ RTCSdpHistoryEntryInternal sdp;
+ auto Timestamp() const -> DOMHighResTimeStamp;
+ virtual ~SdpElement() = default;
+ };
+
+ explicit Entry(const nsString& aPcid, const bool aIsLongTermStatsDisabled)
+ : mPcid(aPcid), mIsLongTermStatsDisabled(aIsLongTermStatsDisabled) {}
+
+ nsString mPcid;
+ AutoCleanLinkedList<ReportElement> mReports;
+ AutoCleanLinkedList<SdpElement> mSdp;
+ bool mIsLongTermStatsDisabled;
+ bool mIsClosed = false;
+
+ auto Since(const Maybe<DOMHighResTimeStamp>& aAfter) const
+ -> nsTArray<RTCStatsReportInternal>;
+ auto SdpSince(const Maybe<DOMHighResTimeStamp>& aAfter) const
+ -> RTCSdpHistoryInternal;
+
+ static auto MakeReportElement(UniquePtr<RTCStatsReportInternal> aReport)
+ -> ReportElement*;
+ static auto MakeSdpElementsSince(
+ Sequence<RTCSdpHistoryEntryInternal>&& aSdpHistory,
+ const Maybe<DOMHighResTimeStamp>& aSdpAfter)
+ -> AutoCleanLinkedList<SdpElement>;
+ auto Prune(const DOMHighResTimeStamp aBefore) -> void;
+
+ private:
+ virtual ~Entry() = default;
+ };
+ using StatsMap = nsTHashMap<nsStringHashKey, RefPtr<Entry> >;
+ static auto InitHistory(const nsAString& aPcId,
+ const bool aIsLongTermStatsDisabled) -> void;
+ static auto Record(UniquePtr<RTCStatsReportInternal> aReport) -> void;
+ static auto CloseHistory(const nsAString& aPcId) -> void;
+ static auto GetHistory(const nsAString& aPcId) -> Maybe<RefPtr<Entry> >;
+ static auto Clear() -> void;
+ static auto PcIds() -> dom::Sequence<nsString>;
+
+ WebrtcGlobalStatsHistory() = delete;
+
+ private:
+ static auto Get() -> StatsMap&;
+};
+} // namespace mozilla::dom
diff --git a/dom/media/webrtc/jsapi/moz.build b/dom/media/webrtc/jsapi/moz.build
new file mode 100644
index 0000000000..2c1dbe79f1
--- /dev/null
+++ b/dom/media/webrtc/jsapi/moz.build
@@ -0,0 +1,51 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+include("/dom/media/webrtc/third_party_build/webrtc.mozbuild")
+
+LOCAL_INCLUDES += [
+ "!/ipc/ipdl/_ipdlheaders",
+ "/dom/base",
+ "/dom/media",
+ "/dom/media/webrtc",
+ "/ipc/chromium/src",
+ "/media/webrtc",
+ "/netwerk/dns", # For nsDNSService2.h
+ "/third_party/libsrtp/src/include",
+ "/third_party/libwebrtc",
+ "/third_party/libwebrtc/third_party/abseil-cpp",
+]
+
+UNIFIED_SOURCES += [
+ "MediaTransportHandler.cpp",
+ "MediaTransportHandlerIPC.cpp",
+ "MediaTransportParent.cpp",
+ "PacketDumper.cpp",
+ "PeerConnectionCtx.cpp",
+ "PeerConnectionImpl.cpp",
+ "RemoteTrackSource.cpp",
+ "RTCDtlsTransport.cpp",
+ "RTCDTMFSender.cpp",
+ "RTCRtpReceiver.cpp",
+ "RTCRtpSender.cpp",
+ "RTCRtpTransceiver.cpp",
+ "RTCSctpTransport.cpp",
+ "RTCStatsIdGenerator.cpp",
+ "RTCStatsReport.cpp",
+ "WebrtcGlobalInformation.cpp",
+ "WebrtcGlobalStatsHistory.cpp",
+]
+
+EXPORTS.mozilla.dom += [
+ "RTCDtlsTransport.h",
+ "RTCDTMFSender.h",
+ "RTCRtpReceiver.h",
+ "RTCRtpSender.h",
+ "RTCRtpTransceiver.h",
+ "RTCSctpTransport.h",
+ "RTCStatsReport.h",
+]
+
+FINAL_LIBRARY = "xul"
diff --git a/dom/media/webrtc/jsep/JsepCodecDescription.h b/dom/media/webrtc/jsep/JsepCodecDescription.h
new file mode 100644
index 0000000000..5336182b77
--- /dev/null
+++ b/dom/media/webrtc/jsep/JsepCodecDescription.h
@@ -0,0 +1,1196 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _JSEPCODECDESCRIPTION_H_
+#define _JSEPCODECDESCRIPTION_H_
+
+#include <cmath>
+#include <string>
+#include "sdp/SdpMediaSection.h"
+#include "sdp/SdpHelper.h"
+#include "nsCRT.h"
+#include "nsString.h"
+#include "mozilla/net/DataChannelProtocol.h"
+#include "mozilla/Preferences.h"
+
+namespace mozilla {
+
+#define JSEP_CODEC_CLONE(T) \
+ virtual JsepCodecDescription* Clone() const override { return new T(*this); }
+
+// A single entry in our list of known codecs.
+class JsepCodecDescription {
+ public:
+ JsepCodecDescription(const std::string& defaultPt, const std::string& name,
+ uint32_t clock, uint32_t channels, bool enabled)
+ : mDefaultPt(defaultPt),
+ mName(name),
+ mClock(clock),
+ mChannels(channels),
+ mEnabled(enabled),
+ mStronglyPreferred(false),
+ mDirection(sdp::kSend) {}
+ virtual ~JsepCodecDescription() {}
+
+ virtual SdpMediaSection::MediaType Type() const = 0;
+
+ virtual JsepCodecDescription* Clone() const = 0;
+
+ bool GetPtAsInt(uint16_t* ptOutparam) const {
+ return SdpHelper::GetPtAsInt(mDefaultPt, ptOutparam);
+ }
+
+ // The id used for codec stats, to uniquely identify this codec configuration
+ // within a transport.
+ const nsString& StatsId() const {
+ if (!mStatsId) {
+ mStatsId.emplace();
+ mStatsId->AppendPrintf(
+ "_%s_%s/%s_%u_%u_%s", mDefaultPt.c_str(),
+ Type() == SdpMediaSection::kVideo ? "video" : "audio", mName.c_str(),
+ mClock, mChannels, mSdpFmtpLine ? mSdpFmtpLine->c_str() : "nothing");
+ }
+ return *mStatsId;
+ }
+
+ virtual bool Matches(const std::string& fmt,
+ const SdpMediaSection& remoteMsection) const {
+ // note: fmt here is remote fmt (to go with remoteMsection)
+ if (Type() != remoteMsection.GetMediaType()) {
+ return false;
+ }
+
+ const SdpRtpmapAttributeList::Rtpmap* entry(remoteMsection.FindRtpmap(fmt));
+
+ if (entry) {
+ if (!nsCRT::strcasecmp(mName.c_str(), entry->name.c_str()) &&
+ (mClock == entry->clock) && (mChannels == entry->channels)) {
+ return ParametersMatch(fmt, remoteMsection);
+ }
+ } else if (!fmt.compare("9") && mName == "G722") {
+ return true;
+ } else if (!fmt.compare("0") && mName == "PCMU") {
+ return true;
+ } else if (!fmt.compare("8") && mName == "PCMA") {
+ return true;
+ }
+ return false;
+ }
+
+ virtual bool ParametersMatch(const std::string& fmt,
+ const SdpMediaSection& remoteMsection) const {
+ return true;
+ }
+
+ Maybe<std::string> GetMatchingFormat(
+ const SdpMediaSection& remoteMsection) const {
+ for (const auto& fmt : remoteMsection.GetFormats()) {
+ if (Matches(fmt, remoteMsection)) {
+ return Some(fmt);
+ }
+ }
+ return Nothing();
+ }
+
+ virtual bool Negotiate(const std::string& pt,
+ const SdpMediaSection& remoteMsection,
+ bool remoteIsOffer,
+ Maybe<const SdpMediaSection&> localMsection) {
+ // Configuration might change. Invalidate the stats id.
+ mStatsId = Nothing();
+ if (mDirection == sdp::kSend || remoteIsOffer) {
+ mDefaultPt = pt;
+ }
+ if (localMsection) {
+ // Offer/answer is concluding. Update the sdpFmtpLine.
+ MOZ_ASSERT(mDirection == sdp::kSend || mDirection == sdp::kRecv);
+ const SdpMediaSection& msection =
+ mDirection == sdp::kSend ? remoteMsection : *localMsection;
+ UpdateSdpFmtpLine(ToMaybeRef(msection.FindFmtp(mDefaultPt)));
+ }
+ return true;
+ }
+
+ virtual void ApplyConfigToFmtp(
+ UniquePtr<SdpFmtpAttributeList::Parameters>& aFmtp) const = 0;
+
+ virtual void AddToMediaSection(SdpMediaSection& msection) const {
+ if (mEnabled && msection.GetMediaType() == Type()) {
+ if (mDirection == sdp::kRecv) {
+ msection.AddCodec(mDefaultPt, mName, mClock, mChannels);
+ }
+
+ AddParametersToMSection(msection);
+ }
+ }
+
+ virtual void AddParametersToMSection(SdpMediaSection& msection) const {}
+
+ virtual void EnsureNoDuplicatePayloadTypes(std::set<std::string>& aUsedPts) {
+ mEnabled = EnsurePayloadTypeNotDuplicate(aUsedPts, mDefaultPt);
+ }
+
+ bool EnsurePayloadTypeNotDuplicate(std::set<std::string>& aUsedPts,
+ std::string& aPtToCheck) {
+ if (!mEnabled) {
+ return false;
+ }
+
+ if (!aUsedPts.count(aPtToCheck)) {
+ aUsedPts.insert(aPtToCheck);
+ return true;
+ }
+
+ // |codec| cannot use its current payload type. Try to find another.
+ for (uint16_t freePt = 96; freePt <= 127; ++freePt) {
+ // Not super efficient, but readability is probably more important.
+ std::ostringstream os;
+ os << freePt;
+ std::string freePtAsString = os.str();
+
+ if (!aUsedPts.count(freePtAsString)) {
+ aUsedPts.insert(freePtAsString);
+ aPtToCheck = freePtAsString;
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ // TODO Bug 1751671: Take a verbatim fmtp line (std::string or eq.) instead
+ // of fmtp parameters that have to be (re-)serialized.
+ void UpdateSdpFmtpLine(
+ const Maybe<const SdpFmtpAttributeList::Parameters&> aParams) {
+ mSdpFmtpLine = aParams.map([](const auto& aFmtp) {
+ std::stringstream ss;
+ aFmtp.Serialize(ss);
+ return ss.str();
+ });
+ }
+
+ std::string mDefaultPt;
+ std::string mName;
+ Maybe<std::string> mSdpFmtpLine;
+ mutable Maybe<nsString> mStatsId;
+ uint32_t mClock;
+ uint32_t mChannels;
+ bool mEnabled;
+ bool mStronglyPreferred;
+ sdp::Direction mDirection;
+ // Will hold constraints from both fmtp and rid
+ EncodingConstraints mConstraints;
+};
+
+class JsepAudioCodecDescription : public JsepCodecDescription {
+ public:
+ JsepAudioCodecDescription(const std::string& defaultPt,
+ const std::string& name, uint32_t clock,
+ uint32_t channels, bool enabled = true)
+ : JsepCodecDescription(defaultPt, name, clock, channels, enabled),
+ mMaxPlaybackRate(0),
+ mForceMono(false),
+ mFECEnabled(false),
+ mDtmfEnabled(false),
+ mMaxAverageBitrate(0),
+ mDTXEnabled(false),
+ mFrameSizeMs(0),
+ mMinFrameSizeMs(0),
+ mMaxFrameSizeMs(0),
+ mCbrEnabled(false) {}
+
+ static constexpr SdpMediaSection::MediaType type = SdpMediaSection::kAudio;
+
+ SdpMediaSection::MediaType Type() const override { return type; }
+
+ JSEP_CODEC_CLONE(JsepAudioCodecDescription)
+
+ static UniquePtr<JsepAudioCodecDescription> CreateDefaultOpus() {
+ // Per jmspeex on IRC:
+ // For 32KHz sampling, 28 is ok, 32 is good, 40 should be really good
+ // quality. Note that 1-2Kbps will be wasted on a stereo Opus channel
+ // with mono input compared to configuring it for mono.
+ // If we reduce bitrate enough Opus will low-pass us; 16000 will kill a
+ // 9KHz tone. This should be adaptive when we're at the low-end of video
+ // bandwidth (say <100Kbps), and if we're audio-only, down to 8 or
+ // 12Kbps.
+ return MakeUnique<JsepAudioCodecDescription>("109", "opus", 48000, 2);
+ }
+
+ static UniquePtr<JsepAudioCodecDescription> CreateDefaultG722() {
+ return MakeUnique<JsepAudioCodecDescription>("9", "G722", 8000, 1);
+ }
+
+ static UniquePtr<JsepAudioCodecDescription> CreateDefaultPCMU() {
+ return MakeUnique<JsepAudioCodecDescription>("0", "PCMU", 8000, 1);
+ }
+
+ static UniquePtr<JsepAudioCodecDescription> CreateDefaultPCMA() {
+ return MakeUnique<JsepAudioCodecDescription>("8", "PCMA", 8000, 1);
+ }
+
+ static UniquePtr<JsepAudioCodecDescription> CreateDefaultTelephoneEvent() {
+ return MakeUnique<JsepAudioCodecDescription>("101", "telephone-event", 8000,
+ 1);
+ }
+
+ SdpFmtpAttributeList::OpusParameters GetOpusParameters(
+ const std::string& pt, const SdpMediaSection& msection) const {
+ // Will contain defaults if nothing else
+ SdpFmtpAttributeList::OpusParameters result;
+ auto* params = msection.FindFmtp(pt);
+
+ if (params && params->codec_type == SdpRtpmapAttributeList::kOpus) {
+ result =
+ static_cast<const SdpFmtpAttributeList::OpusParameters&>(*params);
+ }
+
+ return result;
+ }
+
+ SdpFmtpAttributeList::TelephoneEventParameters GetTelephoneEventParameters(
+ const std::string& pt, const SdpMediaSection& msection) const {
+ // Will contain defaults if nothing else
+ SdpFmtpAttributeList::TelephoneEventParameters result;
+ auto* params = msection.FindFmtp(pt);
+
+ if (params &&
+ params->codec_type == SdpRtpmapAttributeList::kTelephoneEvent) {
+ result =
+ static_cast<const SdpFmtpAttributeList::TelephoneEventParameters&>(
+ *params);
+ }
+
+ return result;
+ }
+
+ void AddParametersToMSection(SdpMediaSection& msection) const override {
+ if (mDirection == sdp::kSend) {
+ return;
+ }
+
+ if (mName == "opus") {
+ UniquePtr<SdpFmtpAttributeList::Parameters> opusParams =
+ MakeUnique<SdpFmtpAttributeList::OpusParameters>(
+ GetOpusParameters(mDefaultPt, msection));
+
+ ApplyConfigToFmtp(opusParams);
+
+ msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, *opusParams));
+ } else if (mName == "telephone-event") {
+ // add the default dtmf tones
+ SdpFmtpAttributeList::TelephoneEventParameters teParams(
+ GetTelephoneEventParameters(mDefaultPt, msection));
+ msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, teParams));
+ }
+ }
+
+ bool Negotiate(const std::string& pt, const SdpMediaSection& remoteMsection,
+ bool remoteIsOffer,
+ Maybe<const SdpMediaSection&> localMsection) override {
+ JsepCodecDescription::Negotiate(pt, remoteMsection, remoteIsOffer,
+ localMsection);
+ if (mName == "opus" && mDirection == sdp::kSend) {
+ SdpFmtpAttributeList::OpusParameters opusParams(
+ GetOpusParameters(mDefaultPt, remoteMsection));
+
+ mMaxPlaybackRate = opusParams.maxplaybackrate;
+ mForceMono = !opusParams.stereo;
+ // draft-ietf-rtcweb-fec-03.txt section 4.2 says support for FEC
+ // at the received side is declarative and can be negotiated
+ // separately for either media direction.
+ mFECEnabled = opusParams.useInBandFec;
+ if ((opusParams.maxAverageBitrate >= 6000) &&
+ (opusParams.maxAverageBitrate <= 510000)) {
+ mMaxAverageBitrate = opusParams.maxAverageBitrate;
+ }
+ mDTXEnabled = opusParams.useDTX;
+ if (remoteMsection.GetAttributeList().HasAttribute(
+ SdpAttribute::kPtimeAttribute)) {
+ mFrameSizeMs = remoteMsection.GetAttributeList().GetPtime();
+ } else {
+ mFrameSizeMs = opusParams.frameSizeMs;
+ }
+ mMinFrameSizeMs = opusParams.minFrameSizeMs;
+ if (remoteMsection.GetAttributeList().HasAttribute(
+ SdpAttribute::kMaxptimeAttribute)) {
+ mMaxFrameSizeMs = remoteMsection.GetAttributeList().GetMaxptime();
+ } else {
+ mMaxFrameSizeMs = opusParams.maxFrameSizeMs;
+ }
+ mCbrEnabled = opusParams.useCbr;
+ }
+
+ return true;
+ }
+
+ void ApplyConfigToFmtp(
+ UniquePtr<SdpFmtpAttributeList::Parameters>& aFmtp) const override {
+ if (mName == "opus") {
+ SdpFmtpAttributeList::OpusParameters opusParams;
+ if (aFmtp) {
+ MOZ_RELEASE_ASSERT(aFmtp->codec_type == SdpRtpmapAttributeList::kOpus);
+ opusParams =
+ static_cast<const SdpFmtpAttributeList::OpusParameters&>(*aFmtp);
+ opusParams.useInBandFec = mFECEnabled ? 1 : 0;
+ } else {
+ // If we weren't passed a fmtp to use then show we can do in band FEC
+ // for getCapabilities queries.
+ opusParams.useInBandFec = 1;
+ }
+ if (mMaxPlaybackRate) {
+ opusParams.maxplaybackrate = mMaxPlaybackRate;
+ }
+ opusParams.maxAverageBitrate = mMaxAverageBitrate;
+
+ if (mChannels == 2 &&
+ !Preferences::GetBool("media.peerconnection.sdp.disable_stereo_fmtp",
+ false) &&
+ !mForceMono) {
+ // We prefer to receive stereo, if available.
+ opusParams.stereo = 1;
+ }
+ opusParams.useDTX = mDTXEnabled;
+ opusParams.frameSizeMs = mFrameSizeMs;
+ opusParams.minFrameSizeMs = mMinFrameSizeMs;
+ opusParams.maxFrameSizeMs = mMaxFrameSizeMs;
+ opusParams.useCbr = mCbrEnabled;
+ aFmtp.reset(opusParams.Clone());
+ }
+ };
+
+ uint32_t mMaxPlaybackRate;
+ bool mForceMono;
+ bool mFECEnabled;
+ bool mDtmfEnabled;
+ uint32_t mMaxAverageBitrate;
+ bool mDTXEnabled;
+ uint32_t mFrameSizeMs;
+ uint32_t mMinFrameSizeMs;
+ uint32_t mMaxFrameSizeMs;
+ bool mCbrEnabled;
+};
+
+class JsepVideoCodecDescription : public JsepCodecDescription {
+ public:
+ JsepVideoCodecDescription(const std::string& defaultPt,
+ const std::string& name, uint32_t clock,
+ bool enabled = true)
+ : JsepCodecDescription(defaultPt, name, clock, 0, enabled),
+ mTmmbrEnabled(false),
+ mRembEnabled(false),
+ mFECEnabled(false),
+ mTransportCCEnabled(false),
+ mRtxEnabled(false),
+ mProfileLevelId(0),
+ mPacketizationMode(0) {
+ // Add supported rtcp-fb types
+ mNackFbTypes.push_back("");
+ mNackFbTypes.push_back(SdpRtcpFbAttributeList::pli);
+ mCcmFbTypes.push_back(SdpRtcpFbAttributeList::fir);
+ }
+
+ static constexpr SdpMediaSection::MediaType type = SdpMediaSection::kVideo;
+
+ SdpMediaSection::MediaType Type() const override { return type; }
+
+ static UniquePtr<JsepVideoCodecDescription> CreateDefaultVP8(bool aUseRtx) {
+ auto codec = MakeUnique<JsepVideoCodecDescription>("120", "VP8", 90000);
+ // Defaults for mandatory params
+ codec->mConstraints.maxFs = 12288; // Enough for 2048x1536
+ codec->mConstraints.maxFps = Some(60);
+ if (aUseRtx) {
+ codec->EnableRtx("124");
+ }
+ return codec;
+ }
+
+ static UniquePtr<JsepVideoCodecDescription> CreateDefaultVP9(bool aUseRtx) {
+ auto codec = MakeUnique<JsepVideoCodecDescription>("121", "VP9", 90000);
+ // Defaults for mandatory params
+ codec->mConstraints.maxFs = 12288; // Enough for 2048x1536
+ codec->mConstraints.maxFps = Some(60);
+ if (aUseRtx) {
+ codec->EnableRtx("125");
+ }
+ return codec;
+ }
+
+ static UniquePtr<JsepVideoCodecDescription> CreateDefaultH264_0(
+ bool aUseRtx) {
+ auto codec = MakeUnique<JsepVideoCodecDescription>("97", "H264", 90000);
+ codec->mPacketizationMode = 0;
+ // Defaults for mandatory params
+ codec->mProfileLevelId = 0x42E00D;
+ if (aUseRtx) {
+ codec->EnableRtx("98");
+ }
+ return codec;
+ }
+
+ static UniquePtr<JsepVideoCodecDescription> CreateDefaultH264_1(
+ bool aUseRtx) {
+ auto codec = MakeUnique<JsepVideoCodecDescription>("126", "H264", 90000);
+ codec->mPacketizationMode = 1;
+ // Defaults for mandatory params
+ codec->mProfileLevelId = 0x42E00D;
+ if (aUseRtx) {
+ codec->EnableRtx("127");
+ }
+ return codec;
+ }
+
+ static UniquePtr<JsepVideoCodecDescription> CreateDefaultUlpFec() {
+ return MakeUnique<JsepVideoCodecDescription>(
+ "123", // payload type
+ "ulpfec", // codec name
+ 90000 // clock rate (match other video codecs)
+ );
+ }
+
+ static UniquePtr<JsepVideoCodecDescription> CreateDefaultRed() {
+ return MakeUnique<JsepVideoCodecDescription>(
+ "122", // payload type
+ "red", // codec name
+ 90000 // clock rate (match other video codecs)
+ );
+ }
+
+ void ApplyConfigToFmtp(
+ UniquePtr<SdpFmtpAttributeList::Parameters>& aFmtp) const override {
+ if (mName == "H264") {
+ SdpFmtpAttributeList::H264Parameters h264Params;
+ if (aFmtp) {
+ MOZ_RELEASE_ASSERT(aFmtp->codec_type == SdpRtpmapAttributeList::kH264);
+ h264Params =
+ static_cast<const SdpFmtpAttributeList::H264Parameters&>(*aFmtp);
+ }
+
+ if (mDirection == sdp::kSend) {
+ if (!h264Params.level_asymmetry_allowed) {
+ // First time the fmtp has been set; set just in case this is for a
+ // sendonly m-line, since even though we aren't receiving the level
+ // negotiation still needs to happen (sigh).
+ h264Params.profile_level_id = mProfileLevelId;
+ }
+ } else {
+ // Parameters that only apply to what we receive
+ h264Params.max_mbps = mConstraints.maxMbps;
+ h264Params.max_fs = mConstraints.maxFs;
+ h264Params.max_cpb = mConstraints.maxCpb;
+ h264Params.max_dpb = mConstraints.maxDpb;
+ h264Params.max_br = mConstraints.maxBr;
+ strncpy(h264Params.sprop_parameter_sets, mSpropParameterSets.c_str(),
+ sizeof(h264Params.sprop_parameter_sets) - 1);
+ h264Params.profile_level_id = mProfileLevelId;
+ }
+
+ // Parameters that apply to both the send and recv directions
+ h264Params.packetization_mode = mPacketizationMode;
+ // Hard-coded, may need to change someday?
+ h264Params.level_asymmetry_allowed = true;
+
+ // Parameters that apply to both the send and recv directions
+ h264Params.packetization_mode = mPacketizationMode;
+ // Hard-coded, may need to change someday?
+ h264Params.level_asymmetry_allowed = true;
+ aFmtp.reset(h264Params.Clone());
+ } else if (mName == "VP8" || mName == "VP9") {
+ SdpRtpmapAttributeList::CodecType type =
+ mName == "VP8" ? SdpRtpmapAttributeList::CodecType::kVP8
+ : SdpRtpmapAttributeList::CodecType::kVP9;
+ auto vp8Params = SdpFmtpAttributeList::VP8Parameters(type);
+
+ if (aFmtp) {
+ MOZ_RELEASE_ASSERT(aFmtp->codec_type == type);
+ vp8Params =
+ static_cast<const SdpFmtpAttributeList::VP8Parameters&>(*aFmtp);
+ }
+ // VP8 and VP9 share the same SDP parameters thus far
+ vp8Params.max_fs = mConstraints.maxFs;
+ if (mConstraints.maxFps.isSome()) {
+ vp8Params.max_fr =
+ static_cast<unsigned int>(std::round(*mConstraints.maxFps));
+ } else {
+ vp8Params.max_fr = 60;
+ }
+ aFmtp.reset(vp8Params.Clone());
+ }
+ }
+
+ virtual void EnableTmmbr() {
+ // EnableTmmbr can be called multiple times due to multiple calls to
+ // PeerConnectionImpl::ConfigureJsepSessionCodecs
+ if (!mTmmbrEnabled) {
+ mTmmbrEnabled = true;
+ mCcmFbTypes.push_back(SdpRtcpFbAttributeList::tmmbr);
+ }
+ }
+
+ virtual void EnableRemb() {
+ // EnableRemb can be called multiple times due to multiple calls to
+ // PeerConnectionImpl::ConfigureJsepSessionCodecs
+ if (!mRembEnabled) {
+ mRembEnabled = true;
+ mOtherFbTypes.push_back({"", SdpRtcpFbAttributeList::kRemb, "", ""});
+ }
+ }
+
+ virtual void EnableFec(std::string redPayloadType,
+ std::string ulpfecPayloadType) {
+ // Enabling FEC for video works a little differently than enabling
+ // REMB or TMMBR. Support for FEC is indicated by the presence of
+ // particular codes (red and ulpfec) instead of using rtcpfb
+ // attributes on a given codec. There is no rtcpfb to push for FEC
+ // as can be seen above when REMB or TMMBR are enabled.
+
+ // Ensure we have valid payload types. This returns zero on failure, which
+ // is a valid payload type.
+ uint16_t redPt, ulpfecPt;
+ if (!SdpHelper::GetPtAsInt(redPayloadType, &redPt) ||
+ !SdpHelper::GetPtAsInt(ulpfecPayloadType, &ulpfecPt)) {
+ return;
+ }
+
+ mFECEnabled = true;
+ mREDPayloadType = redPayloadType;
+ mULPFECPayloadType = ulpfecPayloadType;
+ }
+
+ virtual void EnableTransportCC() {
+ if (!mTransportCCEnabled) {
+ mTransportCCEnabled = true;
+ mOtherFbTypes.push_back(
+ {"", SdpRtcpFbAttributeList::kTransportCC, "", ""});
+ }
+ }
+
+ void EnableRtx(const std::string& rtxPayloadType) {
+ mRtxEnabled = true;
+ mRtxPayloadType = rtxPayloadType;
+ }
+
+ void AddParametersToMSection(SdpMediaSection& msection) const override {
+ AddFmtpsToMSection(msection);
+ AddRtcpFbsToMSection(msection);
+ }
+
+ void AddFmtpsToMSection(SdpMediaSection& msection) const {
+ if (mName == "H264") {
+ UniquePtr<SdpFmtpAttributeList::Parameters> h264Params =
+ MakeUnique<SdpFmtpAttributeList::H264Parameters>(
+ GetH264Parameters(mDefaultPt, msection));
+
+ ApplyConfigToFmtp(h264Params);
+
+ msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, *h264Params));
+ } else if (mName == "red" && !mRedundantEncodings.empty()) {
+ SdpFmtpAttributeList::RedParameters redParams(
+ GetRedParameters(mDefaultPt, msection));
+ redParams.encodings = mRedundantEncodings;
+ msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, redParams));
+ } else if (mName == "VP8" || mName == "VP9") {
+ if (mDirection == sdp::kRecv) {
+ // VP8 and VP9 share the same SDP parameters thus far
+ UniquePtr<SdpFmtpAttributeList::Parameters> vp8Params =
+ MakeUnique<SdpFmtpAttributeList::VP8Parameters>(
+ GetVP8Parameters(mDefaultPt, msection));
+ ApplyConfigToFmtp(vp8Params);
+ msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mDefaultPt, *vp8Params));
+ }
+ }
+
+ if (mRtxEnabled && mDirection == sdp::kRecv) {
+ SdpFmtpAttributeList::RtxParameters params(
+ GetRtxParameters(mDefaultPt, msection));
+ uint16_t apt;
+ if (SdpHelper::GetPtAsInt(mDefaultPt, &apt)) {
+ if (apt <= 127) {
+ msection.AddCodec(mRtxPayloadType, "rtx", mClock, mChannels);
+
+ params.apt = apt;
+ msection.SetFmtp(SdpFmtpAttributeList::Fmtp(mRtxPayloadType, params));
+ }
+ }
+ }
+ }
+
+ void AddRtcpFbsToMSection(SdpMediaSection& msection) const {
+ SdpRtcpFbAttributeList rtcpfbs(msection.GetRtcpFbs());
+ for (const auto& rtcpfb : rtcpfbs.mFeedbacks) {
+ if (rtcpfb.pt == mDefaultPt) {
+ // Already set by the codec for the other direction.
+ return;
+ }
+ }
+
+ for (const std::string& type : mAckFbTypes) {
+ rtcpfbs.PushEntry(mDefaultPt, SdpRtcpFbAttributeList::kAck, type);
+ }
+ for (const std::string& type : mNackFbTypes) {
+ rtcpfbs.PushEntry(mDefaultPt, SdpRtcpFbAttributeList::kNack, type);
+ }
+ for (const std::string& type : mCcmFbTypes) {
+ rtcpfbs.PushEntry(mDefaultPt, SdpRtcpFbAttributeList::kCcm, type);
+ }
+ for (const auto& fb : mOtherFbTypes) {
+ rtcpfbs.PushEntry(mDefaultPt, fb.type, fb.parameter, fb.extra);
+ }
+
+ msection.SetRtcpFbs(rtcpfbs);
+ }
+
+ SdpFmtpAttributeList::H264Parameters GetH264Parameters(
+ const std::string& pt, const SdpMediaSection& msection) const {
+ // Will contain defaults if nothing else
+ SdpFmtpAttributeList::H264Parameters result;
+ auto* params = msection.FindFmtp(pt);
+
+ if (params && params->codec_type == SdpRtpmapAttributeList::kH264) {
+ result =
+ static_cast<const SdpFmtpAttributeList::H264Parameters&>(*params);
+ }
+
+ return result;
+ }
+
+ SdpFmtpAttributeList::RedParameters GetRedParameters(
+ const std::string& pt, const SdpMediaSection& msection) const {
+ SdpFmtpAttributeList::RedParameters result;
+ auto* params = msection.FindFmtp(pt);
+
+ if (params && params->codec_type == SdpRtpmapAttributeList::kRed) {
+ result = static_cast<const SdpFmtpAttributeList::RedParameters&>(*params);
+ }
+
+ return result;
+ }
+
+ SdpFmtpAttributeList::RtxParameters GetRtxParameters(
+ const std::string& pt, const SdpMediaSection& msection) const {
+ SdpFmtpAttributeList::RtxParameters result;
+ const auto* params = msection.FindFmtp(pt);
+
+ if (params && params->codec_type == SdpRtpmapAttributeList::kRtx) {
+ result = static_cast<const SdpFmtpAttributeList::RtxParameters&>(*params);
+ }
+
+ return result;
+ }
+
+ Maybe<std::string> GetRtxPtByApt(const std::string& apt,
+ const SdpMediaSection& msection) const {
+ Maybe<std::string> result;
+ uint16_t aptAsInt;
+ if (!SdpHelper::GetPtAsInt(apt, &aptAsInt)) {
+ return result;
+ }
+
+ const SdpAttributeList& attrs = msection.GetAttributeList();
+ if (attrs.HasAttribute(SdpAttribute::kFmtpAttribute)) {
+ for (const auto& fmtpAttr : attrs.GetFmtp().mFmtps) {
+ if (fmtpAttr.parameters) {
+ auto* params = fmtpAttr.parameters.get();
+ if (params && params->codec_type == SdpRtpmapAttributeList::kRtx) {
+ const SdpFmtpAttributeList::RtxParameters* rtxParams =
+ static_cast<const SdpFmtpAttributeList::RtxParameters*>(params);
+ if (rtxParams->apt == aptAsInt) {
+ result = Some(fmtpAttr.format);
+ break;
+ }
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ SdpFmtpAttributeList::VP8Parameters GetVP8Parameters(
+ const std::string& pt, const SdpMediaSection& msection) const {
+ SdpRtpmapAttributeList::CodecType expectedType(
+ mName == "VP8" ? SdpRtpmapAttributeList::kVP8
+ : SdpRtpmapAttributeList::kVP9);
+
+ // Will contain defaults if nothing else
+ SdpFmtpAttributeList::VP8Parameters result(expectedType);
+ auto* params = msection.FindFmtp(pt);
+
+ if (params && params->codec_type == expectedType) {
+ result = static_cast<const SdpFmtpAttributeList::VP8Parameters&>(*params);
+ }
+
+ return result;
+ }
+
+ void NegotiateRtcpFb(const SdpMediaSection& remoteMsection,
+ SdpRtcpFbAttributeList::Type type,
+ std::vector<std::string>* supportedTypes) {
+ Maybe<std::string> remoteFmt = GetMatchingFormat(remoteMsection);
+ if (!remoteFmt) {
+ return;
+ }
+ std::vector<std::string> temp;
+ for (auto& subType : *supportedTypes) {
+ if (remoteMsection.HasRtcpFb(*remoteFmt, type, subType)) {
+ temp.push_back(subType);
+ }
+ }
+ *supportedTypes = temp;
+ }
+
+ void NegotiateRtcpFb(
+ const SdpMediaSection& remoteMsection,
+ std::vector<SdpRtcpFbAttributeList::Feedback>* supportedFbs) {
+ Maybe<std::string> remoteFmt = GetMatchingFormat(remoteMsection);
+ if (!remoteFmt) {
+ return;
+ }
+ std::vector<SdpRtcpFbAttributeList::Feedback> temp;
+ for (auto& fb : *supportedFbs) {
+ if (remoteMsection.HasRtcpFb(*remoteFmt, fb.type, fb.parameter)) {
+ temp.push_back(fb);
+ }
+ }
+ *supportedFbs = temp;
+ }
+
+ void NegotiateRtcpFb(const SdpMediaSection& remote) {
+ // Removes rtcp-fb types that the other side doesn't support
+ NegotiateRtcpFb(remote, SdpRtcpFbAttributeList::kAck, &mAckFbTypes);
+ NegotiateRtcpFb(remote, SdpRtcpFbAttributeList::kNack, &mNackFbTypes);
+ NegotiateRtcpFb(remote, SdpRtcpFbAttributeList::kCcm, &mCcmFbTypes);
+ NegotiateRtcpFb(remote, &mOtherFbTypes);
+ }
+
+ virtual bool Negotiate(const std::string& pt,
+ const SdpMediaSection& remoteMsection,
+ bool remoteIsOffer,
+ Maybe<const SdpMediaSection&> localMsection) override {
+ JsepCodecDescription::Negotiate(pt, remoteMsection, remoteIsOffer,
+ localMsection);
+ if (mName == "H264") {
+ SdpFmtpAttributeList::H264Parameters h264Params(
+ GetH264Parameters(mDefaultPt, remoteMsection));
+
+ // Level is negotiated symmetrically if level asymmetry is disallowed
+ if (!h264Params.level_asymmetry_allowed) {
+ SetSaneH264Level(std::min(GetSaneH264Level(h264Params.profile_level_id),
+ GetSaneH264Level(mProfileLevelId)),
+ &mProfileLevelId);
+ }
+
+ if (mDirection == sdp::kSend) {
+ // Remote values of these apply only to the send codec.
+ mConstraints.maxFs = h264Params.max_fs;
+ mConstraints.maxMbps = h264Params.max_mbps;
+ mConstraints.maxCpb = h264Params.max_cpb;
+ mConstraints.maxDpb = h264Params.max_dpb;
+ mConstraints.maxBr = h264Params.max_br;
+ mSpropParameterSets = h264Params.sprop_parameter_sets;
+ // Only do this if we didn't symmetrically negotiate above
+ if (h264Params.level_asymmetry_allowed) {
+ SetSaneH264Level(GetSaneH264Level(h264Params.profile_level_id),
+ &mProfileLevelId);
+ }
+ } else {
+ // TODO(bug 1143709): max-recv-level support
+ }
+ } else if (mName == "red") {
+ SdpFmtpAttributeList::RedParameters redParams(
+ GetRedParameters(mDefaultPt, remoteMsection));
+ mRedundantEncodings = redParams.encodings;
+ } else if (mName == "VP8" || mName == "VP9") {
+ if (mDirection == sdp::kSend) {
+ SdpFmtpAttributeList::VP8Parameters vp8Params(
+ GetVP8Parameters(mDefaultPt, remoteMsection));
+
+ mConstraints.maxFs = vp8Params.max_fs;
+ // Right now, we treat max-fr=0 (or the absence of max-fr) as no limit.
+ // We will eventually want to stop doing this (bug 1762600).
+ if (vp8Params.max_fr) {
+ mConstraints.maxFps = Some(vp8Params.max_fr);
+ }
+ }
+ }
+
+ if (mRtxEnabled && (mDirection == sdp::kSend || remoteIsOffer)) {
+ Maybe<std::string> rtxPt = GetRtxPtByApt(mDefaultPt, remoteMsection);
+ if (rtxPt.isSome()) {
+ EnableRtx(*rtxPt);
+ } else {
+ mRtxEnabled = false;
+ mRtxPayloadType = "";
+ }
+ }
+
+ NegotiateRtcpFb(remoteMsection);
+ return true;
+ }
+
+ // Maps the not-so-sane encoding of H264 level into something that is
+ // ordered in the way one would expect
+ // 1b is 0xAB, everything else is the level left-shifted one half-byte
+ // (eg; 1.0 is 0xA0, 1.1 is 0xB0, 3.1 is 0x1F0)
+ static uint32_t GetSaneH264Level(uint32_t profileLevelId) {
+ uint32_t profileIdc = (profileLevelId >> 16);
+
+ if (profileIdc == 0x42 || profileIdc == 0x4D || profileIdc == 0x58) {
+ if ((profileLevelId & 0x10FF) == 0x100B) {
+ // Level 1b
+ return 0xAB;
+ }
+ }
+
+ uint32_t level = profileLevelId & 0xFF;
+
+ if (level == 0x09) {
+ // Another way to encode level 1b
+ return 0xAB;
+ }
+
+ return level << 4;
+ }
+
+ static void SetSaneH264Level(uint32_t level, uint32_t* profileLevelId) {
+ uint32_t profileIdc = (*profileLevelId >> 16);
+ uint32_t levelMask = 0xFF;
+
+ if (profileIdc == 0x42 || profileIdc == 0x4d || profileIdc == 0x58) {
+ levelMask = 0x10FF;
+ if (level == 0xAB) {
+ // Level 1b
+ level = 0x100B;
+ } else {
+ // Not 1b, just shift
+ level = level >> 4;
+ }
+ } else if (level == 0xAB) {
+ // Another way to encode 1b
+ level = 0x09;
+ } else {
+ // Not 1b, just shift
+ level = level >> 4;
+ }
+
+ *profileLevelId = (*profileLevelId & ~levelMask) | level;
+ }
+
+ enum Subprofile {
+ kH264ConstrainedBaseline,
+ kH264Baseline,
+ kH264Main,
+ kH264Extended,
+ kH264High,
+ kH264High10,
+ kH264High42,
+ kH264High44,
+ kH264High10I,
+ kH264High42I,
+ kH264High44I,
+ kH264CALVC44,
+ kH264UnknownSubprofile
+ };
+
+ static Subprofile GetSubprofile(uint32_t profileLevelId) {
+ // Based on Table 5 from RFC 6184:
+ // Profile profile_idc profile-iop
+ // (hexadecimal) (binary)
+
+ // CB 42 (B) x1xx0000
+ // same as: 4D (M) 1xxx0000
+ // same as: 58 (E) 11xx0000
+ // B 42 (B) x0xx0000
+ // same as: 58 (E) 10xx0000
+ // M 4D (M) 0x0x0000
+ // E 58 00xx0000
+ // H 64 00000000
+ // H10 6E 00000000
+ // H42 7A 00000000
+ // H44 F4 00000000
+ // H10I 6E 00010000
+ // H42I 7A 00010000
+ // H44I F4 00010000
+ // C44I 2C 00010000
+
+ if ((profileLevelId & 0xFF4F00) == 0x424000) {
+ // 01001111 (mask, 0x4F)
+ // x1xx0000 (from table)
+ // 01000000 (expected value, 0x40)
+ return kH264ConstrainedBaseline;
+ }
+
+ if ((profileLevelId & 0xFF8F00) == 0x4D8000) {
+ // 10001111 (mask, 0x8F)
+ // 1xxx0000 (from table)
+ // 10000000 (expected value, 0x80)
+ return kH264ConstrainedBaseline;
+ }
+
+ if ((profileLevelId & 0xFFCF00) == 0x58C000) {
+ // 11001111 (mask, 0xCF)
+ // 11xx0000 (from table)
+ // 11000000 (expected value, 0xC0)
+ return kH264ConstrainedBaseline;
+ }
+
+ if ((profileLevelId & 0xFF4F00) == 0x420000) {
+ // 01001111 (mask, 0x4F)
+ // x0xx0000 (from table)
+ // 00000000 (expected value)
+ return kH264Baseline;
+ }
+
+ if ((profileLevelId & 0xFFCF00) == 0x588000) {
+ // 11001111 (mask, 0xCF)
+ // 10xx0000 (from table)
+ // 10000000 (expected value, 0x80)
+ return kH264Baseline;
+ }
+
+ if ((profileLevelId & 0xFFAF00) == 0x4D0000) {
+ // 10101111 (mask, 0xAF)
+ // 0x0x0000 (from table)
+ // 00000000 (expected value)
+ return kH264Main;
+ }
+
+ if ((profileLevelId & 0xFF0000) == 0x580000) {
+ // 11001111 (mask, 0xCF)
+ // 00xx0000 (from table)
+ // 00000000 (expected value)
+ return kH264Extended;
+ }
+
+ if ((profileLevelId & 0xFFFF00) == 0x640000) {
+ return kH264High;
+ }
+
+ if ((profileLevelId & 0xFFFF00) == 0x6E0000) {
+ return kH264High10;
+ }
+
+ if ((profileLevelId & 0xFFFF00) == 0x7A0000) {
+ return kH264High42;
+ }
+
+ if ((profileLevelId & 0xFFFF00) == 0xF40000) {
+ return kH264High44;
+ }
+
+ if ((profileLevelId & 0xFFFF00) == 0x6E1000) {
+ return kH264High10I;
+ }
+
+ if ((profileLevelId & 0xFFFF00) == 0x7A1000) {
+ return kH264High42I;
+ }
+
+ if ((profileLevelId & 0xFFFF00) == 0xF41000) {
+ return kH264High44I;
+ }
+
+ if ((profileLevelId & 0xFFFF00) == 0x2C1000) {
+ return kH264CALVC44;
+ }
+
+ return kH264UnknownSubprofile;
+ }
+
+ virtual bool ParametersMatch(
+ const std::string& fmt,
+ const SdpMediaSection& remoteMsection) const override {
+ if (mName == "H264") {
+ SdpFmtpAttributeList::H264Parameters h264Params(
+ GetH264Parameters(fmt, remoteMsection));
+
+ if (h264Params.packetization_mode != mPacketizationMode) {
+ return false;
+ }
+
+ if (GetSubprofile(h264Params.profile_level_id) !=
+ GetSubprofile(mProfileLevelId)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ virtual bool RtcpFbRembIsSet() const {
+ for (const auto& fb : mOtherFbTypes) {
+ if (fb.type == SdpRtcpFbAttributeList::kRemb) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ virtual bool RtcpFbTransportCCIsSet() const {
+ for (const auto& fb : mOtherFbTypes) {
+ if (fb.type == SdpRtcpFbAttributeList::kTransportCC) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ virtual void UpdateRedundantEncodings(
+ const std::vector<UniquePtr<JsepCodecDescription>>& codecs) {
+ for (const auto& codec : codecs) {
+ if (codec->Type() == type && codec->mEnabled && codec->mName != "red") {
+ uint16_t pt;
+ if (!SdpHelper::GetPtAsInt(codec->mDefaultPt, &pt)) {
+ continue;
+ }
+ mRedundantEncodings.push_back(pt);
+ }
+ }
+ }
+
+ void EnsureNoDuplicatePayloadTypes(std::set<std::string>& aUsedPts) override {
+ JsepCodecDescription::EnsureNoDuplicatePayloadTypes(aUsedPts);
+ if (mFECEnabled) {
+ mFECEnabled = EnsurePayloadTypeNotDuplicate(aUsedPts, mREDPayloadType) &&
+ EnsurePayloadTypeNotDuplicate(aUsedPts, mULPFECPayloadType);
+ }
+ if (mRtxEnabled) {
+ mRtxEnabled = EnsurePayloadTypeNotDuplicate(aUsedPts, mRtxPayloadType);
+ }
+ }
+
+ JSEP_CODEC_CLONE(JsepVideoCodecDescription)
+
+ std::vector<std::string> mAckFbTypes;
+ std::vector<std::string> mNackFbTypes;
+ std::vector<std::string> mCcmFbTypes;
+ std::vector<SdpRtcpFbAttributeList::Feedback> mOtherFbTypes;
+ bool mTmmbrEnabled;
+ bool mRembEnabled;
+ bool mFECEnabled;
+ bool mTransportCCEnabled;
+ bool mRtxEnabled;
+ std::string mREDPayloadType;
+ std::string mULPFECPayloadType;
+ std::string mRtxPayloadType;
+ std::vector<uint8_t> mRedundantEncodings;
+
+ // H264-specific stuff
+ uint32_t mProfileLevelId;
+ uint32_t mPacketizationMode;
+ std::string mSpropParameterSets;
+};
+
+class JsepApplicationCodecDescription : public JsepCodecDescription {
+ // This is the new draft-21 implementation
+ public:
+ JsepApplicationCodecDescription(const std::string& name, uint16_t channels,
+ uint16_t localPort,
+ uint32_t localMaxMessageSize,
+ bool enabled = true)
+ : JsepCodecDescription("", name, 0, channels, enabled),
+ mLocalPort(localPort),
+ mLocalMaxMessageSize(localMaxMessageSize),
+ mRemotePort(0),
+ mRemoteMaxMessageSize(0),
+ mRemoteMMSSet(false) {}
+
+ static constexpr SdpMediaSection::MediaType type =
+ SdpMediaSection::kApplication;
+
+ SdpMediaSection::MediaType Type() const override { return type; }
+
+ JSEP_CODEC_CLONE(JsepApplicationCodecDescription)
+
+ static UniquePtr<JsepApplicationCodecDescription> CreateDefault() {
+ return MakeUnique<JsepApplicationCodecDescription>(
+ "webrtc-datachannel", WEBRTC_DATACHANNEL_STREAMS_DEFAULT,
+ WEBRTC_DATACHANNEL_PORT_DEFAULT,
+ WEBRTC_DATACHANNEL_MAX_MESSAGE_SIZE_LOCAL);
+ }
+
+ // Override, uses sctpport or sctpmap instead of rtpmap
+ virtual bool Matches(const std::string& fmt,
+ const SdpMediaSection& remoteMsection) const override {
+ if (type != remoteMsection.GetMediaType()) {
+ return false;
+ }
+
+ int sctp_port = remoteMsection.GetSctpPort();
+ bool fmt_matches =
+ nsCRT::strcasecmp(mName.c_str(),
+ remoteMsection.GetFormats()[0].c_str()) == 0;
+ if (sctp_port && fmt_matches) {
+ // New sctp draft 21 format
+ return true;
+ }
+
+ const SdpSctpmapAttributeList::Sctpmap* sctp_map(
+ remoteMsection.GetSctpmap());
+ if (sctp_map) {
+ // Old sctp draft 05 format
+ return nsCRT::strcasecmp(mName.c_str(), sctp_map->name.c_str()) == 0;
+ }
+
+ return false;
+ }
+
+ virtual void AddToMediaSection(SdpMediaSection& msection) const override {
+ if (mEnabled && msection.GetMediaType() == type) {
+ if (mDirection == sdp::kRecv) {
+ msection.AddDataChannel(mName, mLocalPort, mChannels,
+ mLocalMaxMessageSize);
+ }
+
+ AddParametersToMSection(msection);
+ }
+ }
+
+ bool Negotiate(const std::string& pt, const SdpMediaSection& remoteMsection,
+ bool remoteIsOffer,
+ Maybe<const SdpMediaSection&> localMsection) override {
+ JsepCodecDescription::Negotiate(pt, remoteMsection, remoteIsOffer,
+ localMsection);
+
+ uint32_t message_size;
+ mRemoteMMSSet = remoteMsection.GetMaxMessageSize(&message_size);
+ if (mRemoteMMSSet) {
+ mRemoteMaxMessageSize = message_size;
+ } else {
+ mRemoteMaxMessageSize =
+ WEBRTC_DATACHANNEL_MAX_MESSAGE_SIZE_REMOTE_DEFAULT;
+ }
+
+ int sctp_port = remoteMsection.GetSctpPort();
+ if (sctp_port) {
+ mRemotePort = sctp_port;
+ return true;
+ }
+
+ const SdpSctpmapAttributeList::Sctpmap* sctp_map(
+ remoteMsection.GetSctpmap());
+ if (sctp_map) {
+ mRemotePort = std::stoi(sctp_map->pt);
+ return true;
+ }
+
+ return false;
+ }
+
+ // We only support one datachannel per m-section
+ void EnsureNoDuplicatePayloadTypes(std::set<std::string>& aUsedPts) override {
+ }
+
+ void ApplyConfigToFmtp(
+ UniquePtr<SdpFmtpAttributeList::Parameters>& aFmtp) const override{};
+
+ uint16_t mLocalPort;
+ uint32_t mLocalMaxMessageSize;
+ uint16_t mRemotePort;
+ uint32_t mRemoteMaxMessageSize;
+ bool mRemoteMMSSet;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/jsep/JsepSession.h b/dom/media/webrtc/jsep/JsepSession.h
new file mode 100644
index 0000000000..bf68da900b
--- /dev/null
+++ b/dom/media/webrtc/jsep/JsepSession.h
@@ -0,0 +1,287 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _JSEPSESSION_H_
+#define _JSEPSESSION_H_
+
+#include <map>
+#include <string>
+#include <vector>
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "nsError.h"
+
+#include "jsep/JsepTransport.h"
+#include "sdp/Sdp.h"
+
+#include "jsep/JsepTransceiver.h"
+
+#include "mozilla/dom/PeerConnectionObserverEnumsBinding.h"
+
+namespace mozilla {
+
+// Forward declarations
+class JsepCodecDescription;
+
+enum JsepSignalingState {
+ kJsepStateStable,
+ kJsepStateHaveLocalOffer,
+ kJsepStateHaveRemoteOffer,
+ kJsepStateHaveLocalPranswer,
+ kJsepStateHaveRemotePranswer,
+ kJsepStateClosed
+};
+
+enum JsepSdpType {
+ kJsepSdpOffer,
+ kJsepSdpAnswer,
+ kJsepSdpPranswer,
+ kJsepSdpRollback
+};
+
+enum JsepDescriptionPendingOrCurrent {
+ kJsepDescriptionCurrent,
+ kJsepDescriptionPending,
+ kJsepDescriptionPendingOrCurrent
+};
+
+struct JsepOAOptions {};
+struct JsepOfferOptions : public JsepOAOptions {
+ Maybe<size_t> mOfferToReceiveAudio;
+ Maybe<size_t> mOfferToReceiveVideo;
+ Maybe<bool> mIceRestart; // currently ignored by JsepSession
+};
+struct JsepAnswerOptions : public JsepOAOptions {};
+
+enum JsepBundlePolicy { kBundleBalanced, kBundleMaxCompat, kBundleMaxBundle };
+
+enum JsepMediaType { kNone = 0, kAudio, kVideo, kAudioVideo };
+
+struct JsepExtmapMediaType {
+ JsepMediaType mMediaType;
+ SdpExtmapAttributeList::Extmap mExtmap;
+};
+
+class JsepSession {
+ public:
+ explicit JsepSession(const std::string& name)
+ : mName(name), mState(kJsepStateStable), mNegotiations(0) {}
+ virtual ~JsepSession() {}
+
+ virtual JsepSession* Clone() const = 0;
+
+ virtual nsresult Init() = 0;
+
+ // Accessors for basic properties.
+ virtual const std::string& GetName() const { return mName; }
+ virtual JsepSignalingState GetState() const { return mState; }
+ virtual uint32_t GetNegotiations() const { return mNegotiations; }
+
+ // Set up the ICE And DTLS data.
+ virtual nsresult SetBundlePolicy(JsepBundlePolicy policy) = 0;
+ virtual bool RemoteIsIceLite() const = 0;
+ virtual std::vector<std::string> GetIceOptions() const = 0;
+
+ virtual nsresult AddDtlsFingerprint(const std::string& algorithm,
+ const std::vector<uint8_t>& value) = 0;
+
+ virtual nsresult AddRtpExtension(
+ JsepMediaType mediaType, const std::string& extensionName,
+ SdpDirectionAttribute::Direction direction) = 0;
+ virtual nsresult AddAudioRtpExtension(
+ const std::string& extensionName,
+ SdpDirectionAttribute::Direction direction) = 0;
+ virtual nsresult AddVideoRtpExtension(
+ const std::string& extensionName,
+ SdpDirectionAttribute::Direction direction) = 0;
+ virtual nsresult AddAudioVideoRtpExtension(
+ const std::string& extensionName,
+ SdpDirectionAttribute::Direction direction) = 0;
+
+ // Kinda gross to be locking down the data structure type like this, but
+ // returning by value is problematic due to the lack of stl move semantics in
+ // our build config, since we can't use UniquePtr in the container. The
+ // alternative is writing a raft of accessor functions that allow arbitrary
+ // manipulation (which will be unwieldy), or allowing functors to be injected
+ // that manipulate the data structure (still pretty unwieldy).
+ virtual std::vector<UniquePtr<JsepCodecDescription>>& Codecs() = 0;
+
+ template <class UnaryFunction>
+ void ForEachCodec(UnaryFunction& function) {
+ std::for_each(Codecs().begin(), Codecs().end(), function);
+ for (auto& transceiver : GetTransceivers()) {
+ transceiver.mSendTrack.ForEachCodec(function);
+ transceiver.mRecvTrack.ForEachCodec(function);
+ }
+ }
+
+ template <class BinaryPredicate>
+ void SortCodecs(BinaryPredicate& sorter) {
+ std::stable_sort(Codecs().begin(), Codecs().end(), sorter);
+ for (auto& transceiver : GetTransceivers()) {
+ transceiver.mSendTrack.SortCodecs(sorter);
+ transceiver.mRecvTrack.SortCodecs(sorter);
+ }
+ }
+
+ // Would be nice to have this return a Maybe containing the return of
+ // |aFunction|, but Maybe cannot contain a void.
+ template <typename UnaryFunction>
+ bool ApplyToTransceiver(const std::string& aId, UnaryFunction&& aFunction) {
+ for (auto& transceiver : GetTransceivers()) {
+ if (transceiver.GetUuid() == aId) {
+ std::forward<UnaryFunction>(aFunction)(transceiver);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ template <typename UnaryFunction>
+ void ForEachTransceiver(UnaryFunction&& aFunction) {
+ for (auto& transceiver : GetTransceivers()) {
+ std::forward<UnaryFunction>(aFunction)(transceiver);
+ }
+ }
+
+ template <typename UnaryFunction>
+ void ForEachTransceiver(UnaryFunction&& aFunction) const {
+ for (const auto& transceiver : GetTransceivers()) {
+ std::forward<UnaryFunction>(aFunction)(transceiver);
+ }
+ }
+
+ Maybe<const JsepTransceiver> GetTransceiver(const std::string& aId) const {
+ for (const auto& transceiver : GetTransceivers()) {
+ if (transceiver.GetUuid() == aId) {
+ return Some(transceiver);
+ }
+ }
+ return Nothing();
+ }
+
+ template <typename MatchFunction>
+ Maybe<const JsepTransceiver> FindTransceiver(MatchFunction&& aFunc) const {
+ for (const auto& transceiver : GetTransceivers()) {
+ if (std::forward<MatchFunction>(aFunc)(transceiver)) {
+ return Some(transceiver);
+ }
+ }
+ return Nothing();
+ }
+
+ bool SetTransceiver(const JsepTransceiver& aNew) {
+ return ApplyToTransceiver(aNew.GetUuid(),
+ [aNew](JsepTransceiver& aOld) { aOld = aNew; });
+ }
+
+ virtual void AddTransceiver(const JsepTransceiver& transceiver) = 0;
+
+ class Result {
+ public:
+ Result() = default;
+ MOZ_IMPLICIT Result(dom::PCError aError) : mError(Some(aError)) {}
+ // TODO(bug 1527916): Need c'tor and members for handling RTCError.
+ Maybe<dom::PCError> mError;
+ };
+
+ // Basic JSEP operations.
+ virtual Result CreateOffer(const JsepOfferOptions& options,
+ std::string* offer) = 0;
+ virtual Result CreateAnswer(const JsepAnswerOptions& options,
+ std::string* answer) = 0;
+ virtual std::string GetLocalDescription(
+ JsepDescriptionPendingOrCurrent type) const = 0;
+ virtual std::string GetRemoteDescription(
+ JsepDescriptionPendingOrCurrent type) const = 0;
+ virtual Result SetLocalDescription(JsepSdpType type,
+ const std::string& sdp) = 0;
+ virtual Result SetRemoteDescription(JsepSdpType type,
+ const std::string& sdp) = 0;
+ virtual Result AddRemoteIceCandidate(const std::string& candidate,
+ const std::string& mid,
+ const Maybe<uint16_t>& level,
+ const std::string& ufrag,
+ std::string* transportId) = 0;
+ virtual nsresult AddLocalIceCandidate(const std::string& candidate,
+ const std::string& transportId,
+ const std::string& ufrag,
+ uint16_t* level, std::string* mid,
+ bool* skipped) = 0;
+ virtual nsresult UpdateDefaultCandidate(
+ const std::string& defaultCandidateAddr, uint16_t defaultCandidatePort,
+ const std::string& defaultRtcpCandidateAddr,
+ uint16_t defaultRtcpCandidatePort, const std::string& transportId) = 0;
+ virtual nsresult Close() = 0;
+
+ // ICE controlling or controlled
+ virtual bool IsIceControlling() const = 0;
+ virtual Maybe<bool> IsPendingOfferer() const = 0;
+ virtual Maybe<bool> IsCurrentOfferer() const = 0;
+ virtual bool IsIceRestarting() const = 0;
+ virtual std::set<std::pair<std::string, std::string>> GetLocalIceCredentials()
+ const = 0;
+
+ virtual const std::string GetLastError() const { return "Error"; }
+
+ virtual const std::vector<std::pair<size_t, std::string>>&
+ GetLastSdpParsingErrors() const = 0;
+
+ static const char* GetStateStr(JsepSignalingState state) {
+ static const char* states[] = {"stable",
+ "have-local-offer",
+ "have-remote-offer",
+ "have-local-pranswer",
+ "have-remote-pranswer",
+ "closed"};
+
+ return states[state];
+ }
+
+ virtual bool CheckNegotiationNeeded() const = 0;
+
+ void CountTracksAndDatachannels(
+ uint16_t (&receiving)[SdpMediaSection::kMediaTypes],
+ uint16_t (&sending)[SdpMediaSection::kMediaTypes]) const {
+ memset(receiving, 0, sizeof(receiving));
+ memset(sending, 0, sizeof(sending));
+
+ for (const auto& transceiver : GetTransceivers()) {
+ if (transceiver.mRecvTrack.GetActive() ||
+ transceiver.GetMediaType() == SdpMediaSection::kApplication) {
+ receiving[transceiver.mRecvTrack.GetMediaType()]++;
+ }
+
+ if (transceiver.mSendTrack.GetActive() ||
+ transceiver.GetMediaType() == SdpMediaSection::kApplication) {
+ sending[transceiver.mSendTrack.GetMediaType()]++;
+ }
+ }
+ }
+
+ virtual void SetDefaultCodecs(
+ const std::vector<UniquePtr<JsepCodecDescription>>& aPreferredCodecs) = 0;
+
+ // See Bug 1642419, this can be removed when all sites are working with RTX.
+ void SetRtxIsAllowed(bool aRtxIsAllowed) { mRtxIsAllowed = aRtxIsAllowed; }
+
+ protected:
+ friend class JsepSessionTest;
+ // Returns transceivers in the order they were added.
+ virtual std::vector<JsepTransceiver>& GetTransceivers() = 0;
+ virtual const std::vector<JsepTransceiver>& GetTransceivers() const = 0;
+
+ const std::string mName;
+ JsepSignalingState mState;
+ uint32_t mNegotiations;
+
+ // See Bug 1642419, this can be removed when all sites are working with RTX.
+ bool mRtxIsAllowed = true;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/jsep/JsepSessionImpl.cpp b/dom/media/webrtc/jsep/JsepSessionImpl.cpp
new file mode 100644
index 0000000000..bb792e7764
--- /dev/null
+++ b/dom/media/webrtc/jsep/JsepSessionImpl.cpp
@@ -0,0 +1,2457 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "jsep/JsepSessionImpl.h"
+
+#include <stdlib.h>
+
+#include <bitset>
+#include <iterator>
+#include <set>
+#include <string>
+#include <utility>
+
+#include "transport/logging.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/net/DataChannelProtocol.h"
+#include "nsDebug.h"
+#include "nspr.h"
+#include "nss.h"
+#include "pk11pub.h"
+
+#include "api/rtp_parameters.h"
+
+#include "jsep/JsepTrack.h"
+#include "jsep/JsepTransport.h"
+#include "sdp/HybridSdpParser.h"
+#include "sdp/SipccSdp.h"
+
+namespace mozilla {
+
+MOZ_MTLOG_MODULE("jsep")
+
+#define JSEP_SET_ERROR(error) \
+ do { \
+ std::ostringstream os; \
+ os << error; \
+ mLastError = os.str(); \
+ MOZ_MTLOG(ML_ERROR, "[" << mName << "]: " << mLastError); \
+ } while (0);
+
+static std::bitset<128> GetForbiddenSdpPayloadTypes() {
+ std::bitset<128> forbidden(0);
+ forbidden[1] = true;
+ forbidden[2] = true;
+ forbidden[19] = true;
+ for (uint16_t i = 64; i < 96; ++i) {
+ forbidden[i] = true;
+ }
+ return forbidden;
+}
+
+static std::string GetRandomHex(size_t words) {
+ std::ostringstream os;
+
+ for (size_t i = 0; i < words; ++i) {
+ uint32_t rand;
+ SECStatus rv = PK11_GenerateRandom(reinterpret_cast<unsigned char*>(&rand),
+ sizeof(rand));
+ if (rv != SECSuccess) {
+ MOZ_CRASH();
+ return "";
+ }
+
+ os << std::hex << std::setfill('0') << std::setw(8) << rand;
+ }
+ return os.str();
+}
+
+JsepSessionImpl::JsepSessionImpl(const JsepSessionImpl& aOrig)
+ : JsepSession(aOrig),
+ JsepSessionCopyableStuff(aOrig),
+ mUuidGen(aOrig.mUuidGen->Clone()),
+ mGeneratedOffer(aOrig.mGeneratedOffer ? aOrig.mGeneratedOffer->Clone()
+ : nullptr),
+ mGeneratedAnswer(aOrig.mGeneratedAnswer ? aOrig.mGeneratedAnswer->Clone()
+ : nullptr),
+ mCurrentLocalDescription(aOrig.mCurrentLocalDescription
+ ? aOrig.mCurrentLocalDescription->Clone()
+ : nullptr),
+ mCurrentRemoteDescription(aOrig.mCurrentRemoteDescription
+ ? aOrig.mCurrentRemoteDescription->Clone()
+ : nullptr),
+ mPendingLocalDescription(aOrig.mPendingLocalDescription
+ ? aOrig.mPendingLocalDescription->Clone()
+ : nullptr),
+ mPendingRemoteDescription(aOrig.mPendingRemoteDescription
+ ? aOrig.mPendingRemoteDescription->Clone()
+ : nullptr),
+ mSdpHelper(&mLastError),
+ mParser(new HybridSdpParser()) {
+ for (const auto& codec : aOrig.mSupportedCodecs) {
+ mSupportedCodecs.emplace_back(codec->Clone());
+ }
+}
+
+nsresult JsepSessionImpl::Init() {
+ mLastError.clear();
+
+ MOZ_ASSERT(!mSessionId, "Init called more than once");
+
+ nsresult rv = SetupIds();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mEncodeTrackId =
+ Preferences::GetBool("media.peerconnection.sdp.encode_track_id", true);
+
+ mIceUfrag = GetRandomHex(1);
+ mIcePwd = GetRandomHex(4);
+ return NS_OK;
+}
+
+static void GetIceCredentials(
+ const Sdp& aSdp,
+ std::set<std::pair<std::string, std::string>>* aCredentials) {
+ for (size_t i = 0; i < aSdp.GetMediaSectionCount(); ++i) {
+ const SdpAttributeList& attrs = aSdp.GetMediaSection(i).GetAttributeList();
+ if (attrs.HasAttribute(SdpAttribute::kIceUfragAttribute) &&
+ attrs.HasAttribute(SdpAttribute::kIcePwdAttribute)) {
+ aCredentials->insert(
+ std::make_pair(attrs.GetIceUfrag(), attrs.GetIcePwd()));
+ }
+ }
+}
+
+std::set<std::pair<std::string, std::string>>
+JsepSessionImpl::GetLocalIceCredentials() const {
+ std::set<std::pair<std::string, std::string>> result;
+ if (mCurrentLocalDescription) {
+ GetIceCredentials(*mCurrentLocalDescription, &result);
+ }
+ if (mPendingLocalDescription) {
+ GetIceCredentials(*mPendingLocalDescription, &result);
+ }
+ return result;
+}
+
+void JsepSessionImpl::AddTransceiver(const JsepTransceiver& aTransceiver) {
+ mLastError.clear();
+ MOZ_MTLOG(ML_DEBUG,
+ "[" << mName << "]: Adding transceiver " << aTransceiver.GetUuid());
+#ifdef DEBUG
+ if (aTransceiver.GetMediaType() == SdpMediaSection::kApplication) {
+ // Make sure we don't add more than one DataChannel transceiver
+ for (const auto& transceiver : mTransceivers) {
+ MOZ_ASSERT(transceiver.GetMediaType() != SdpMediaSection::kApplication);
+ }
+ }
+#endif
+ mTransceivers.push_back(aTransceiver);
+ InitTransceiver(mTransceivers.back());
+}
+
+void JsepSessionImpl::InitTransceiver(JsepTransceiver& aTransceiver) {
+ mLastError.clear();
+
+ if (aTransceiver.GetMediaType() != SdpMediaSection::kApplication) {
+ // Make sure we have an ssrc. Might already be set.
+ aTransceiver.mSendTrack.EnsureSsrcs(mSsrcGenerator, 1U);
+ aTransceiver.mSendTrack.SetCNAME(mCNAME);
+
+ // Make sure we have identifiers for send track, just in case.
+ // (man I hate this)
+ if (mEncodeTrackId) {
+ aTransceiver.mSendTrack.SetTrackId(aTransceiver.GetUuid());
+ }
+ } else {
+ // Datachannel transceivers should always be sendrecv. Just set it instead
+ // of asserting.
+ aTransceiver.mJsDirection = SdpDirectionAttribute::kSendrecv;
+ }
+
+ aTransceiver.mSendTrack.PopulateCodecs(mSupportedCodecs);
+ aTransceiver.mRecvTrack.PopulateCodecs(mSupportedCodecs);
+ // We do not set mLevel yet, we do that either on createOffer, or setRemote
+}
+
+nsresult JsepSessionImpl::SetBundlePolicy(JsepBundlePolicy policy) {
+ mLastError.clear();
+
+ if (mBundlePolicy == policy) {
+ return NS_OK;
+ }
+
+ if (mCurrentLocalDescription) {
+ JSEP_SET_ERROR(
+ "Changing the bundle policy is only supported before the "
+ "first SetLocalDescription.");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mBundlePolicy = policy;
+ return NS_OK;
+}
+
+nsresult JsepSessionImpl::AddDtlsFingerprint(
+ const std::string& algorithm, const std::vector<uint8_t>& value) {
+ mLastError.clear();
+ JsepDtlsFingerprint fp;
+
+ fp.mAlgorithm = algorithm;
+ fp.mValue = value;
+
+ mDtlsFingerprints.push_back(fp);
+
+ return NS_OK;
+}
+
+nsresult JsepSessionImpl::AddRtpExtension(
+ JsepMediaType mediaType, const std::string& extensionName,
+ SdpDirectionAttribute::Direction direction) {
+ mLastError.clear();
+
+ for (auto& ext : mRtpExtensions) {
+ if (ext.mExtmap.direction == direction &&
+ ext.mExtmap.extensionname == extensionName) {
+ if (ext.mMediaType != mediaType) {
+ ext.mMediaType = JsepMediaType::kAudioVideo;
+ }
+ return NS_OK;
+ }
+ }
+
+ uint16_t freeEntry = GetNeverUsedExtmapEntry();
+
+ if (freeEntry == 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ JsepExtmapMediaType extMediaType = {
+ mediaType,
+ {freeEntry, direction,
+ // do we want to specify direction?
+ direction != SdpDirectionAttribute::kSendrecv, extensionName, ""}};
+
+ mRtpExtensions.push_back(extMediaType);
+ return NS_OK;
+}
+
+nsresult JsepSessionImpl::AddAudioRtpExtension(
+ const std::string& extensionName,
+ SdpDirectionAttribute::Direction direction) {
+ return AddRtpExtension(JsepMediaType::kAudio, extensionName, direction);
+}
+
+nsresult JsepSessionImpl::AddVideoRtpExtension(
+ const std::string& extensionName,
+ SdpDirectionAttribute::Direction direction) {
+ return AddRtpExtension(JsepMediaType::kVideo, extensionName, direction);
+}
+
+nsresult JsepSessionImpl::AddAudioVideoRtpExtension(
+ const std::string& extensionName,
+ SdpDirectionAttribute::Direction direction) {
+ return AddRtpExtension(JsepMediaType::kAudioVideo, extensionName, direction);
+}
+
+nsresult JsepSessionImpl::CreateOfferMsection(const JsepOfferOptions& options,
+ JsepTransceiver& transceiver,
+ Sdp* local) {
+ SdpMediaSection::Protocol protocol(
+ SdpHelper::GetProtocolForMediaType(transceiver.GetMediaType()));
+
+ const Sdp* answer(GetAnswer());
+ const SdpMediaSection* lastAnswerMsection = nullptr;
+
+ if (answer &&
+ (local->GetMediaSectionCount() < answer->GetMediaSectionCount())) {
+ lastAnswerMsection =
+ &answer->GetMediaSection(local->GetMediaSectionCount());
+ // Use the protocol the answer used, even if it is not what we would have
+ // used.
+ protocol = lastAnswerMsection->GetProtocol();
+ }
+
+ SdpMediaSection* msection = &local->AddMediaSection(
+ transceiver.GetMediaType(), transceiver.mJsDirection, 0, protocol,
+ sdp::kIPv4, "0.0.0.0");
+
+ // Some of this stuff (eg; mid) sticks around even if disabled
+ if (lastAnswerMsection) {
+ MOZ_ASSERT(lastAnswerMsection->GetMediaType() ==
+ transceiver.GetMediaType());
+ nsresult rv = mSdpHelper.CopyStickyParams(*lastAnswerMsection, msection);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (transceiver.IsStopped()) {
+ SdpHelper::DisableMsection(local, msection);
+ return NS_OK;
+ }
+
+ msection->SetPort(9);
+
+ // We don't do this in AddTransportAttributes because that is also used for
+ // making answers, and we don't want to unconditionally set rtcp-mux or
+ // rtcp-rsize there.
+ if (mSdpHelper.HasRtcp(msection->GetProtocol())) {
+ // Set RTCP-MUX.
+ msection->GetAttributeList().SetAttribute(
+ new SdpFlagAttribute(SdpAttribute::kRtcpMuxAttribute));
+ // Set RTCP-RSIZE
+ if (msection->GetMediaType() == SdpMediaSection::MediaType::kVideo &&
+ Preferences::GetBool("media.navigator.video.offer_rtcp_rsize", false)) {
+ msection->GetAttributeList().SetAttribute(
+ new SdpFlagAttribute(SdpAttribute::kRtcpRsizeAttribute));
+ }
+ }
+
+ nsresult rv = AddTransportAttributes(msection, SdpSetupAttribute::kActpass);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ transceiver.mSendTrack.AddToOffer(mSsrcGenerator, msection);
+ transceiver.mRecvTrack.AddToOffer(mSsrcGenerator, msection);
+
+ AddExtmap(msection);
+
+ std::string mid;
+ // We do not set the mid on the transceiver, that happens when a description
+ // is set.
+ if (transceiver.IsAssociated()) {
+ mid = transceiver.GetMid();
+ } else {
+ mid = GetNewMid();
+ }
+
+ msection->GetAttributeList().SetAttribute(
+ new SdpStringAttribute(SdpAttribute::kMidAttribute, mid));
+
+ return NS_OK;
+}
+
+void JsepSessionImpl::SetupBundle(Sdp* sdp) const {
+ std::vector<std::string> mids;
+ std::set<SdpMediaSection::MediaType> observedTypes;
+
+ // This has the effect of changing the bundle level if the first m-section
+ // goes from disabled to enabled. This is kinda inefficient.
+
+ for (size_t i = 0; i < sdp->GetMediaSectionCount(); ++i) {
+ auto& attrs = sdp->GetMediaSection(i).GetAttributeList();
+ if ((sdp->GetMediaSection(i).GetPort() != 0) &&
+ attrs.HasAttribute(SdpAttribute::kMidAttribute)) {
+ bool useBundleOnly = false;
+ switch (mBundlePolicy) {
+ case kBundleMaxCompat:
+ // We don't use bundle-only for max-compat
+ break;
+ case kBundleBalanced:
+ // balanced means we use bundle-only on everything but the first
+ // m-section of a given type
+ if (observedTypes.count(sdp->GetMediaSection(i).GetMediaType())) {
+ useBundleOnly = true;
+ }
+ observedTypes.insert(sdp->GetMediaSection(i).GetMediaType());
+ break;
+ case kBundleMaxBundle:
+ // max-bundle means we use bundle-only on everything but the first
+ // m-section
+ useBundleOnly = !mids.empty();
+ break;
+ }
+
+ if (useBundleOnly) {
+ attrs.SetAttribute(
+ new SdpFlagAttribute(SdpAttribute::kBundleOnlyAttribute));
+ // Set port to 0 for sections with bundle-only attribute. (mjf)
+ sdp->GetMediaSection(i).SetPort(0);
+ }
+
+ mids.push_back(attrs.GetMid());
+ }
+ }
+
+ if (!mids.empty()) {
+ UniquePtr<SdpGroupAttributeList> groupAttr(new SdpGroupAttributeList);
+ groupAttr->PushEntry(SdpGroupAttributeList::kBundle, mids);
+ sdp->GetAttributeList().SetAttribute(groupAttr.release());
+ }
+}
+
+JsepSession::Result JsepSessionImpl::CreateOffer(
+ const JsepOfferOptions& options, std::string* offer) {
+ mLastError.clear();
+
+ if (mState != kJsepStateStable && mState != kJsepStateHaveLocalOffer) {
+ JSEP_SET_ERROR("Cannot create offer in state " << GetStateStr(mState));
+ // Spec doesn't seem to say this is an error. It probably should.
+ return dom::PCError::InvalidStateError;
+ }
+
+ // This is one of those places where CreateOffer sets some state.
+ SetIceRestarting(options.mIceRestart.isSome() && *(options.mIceRestart));
+
+ UniquePtr<Sdp> sdp;
+
+ // Make the basic SDP that is common to offer/answer.
+ nsresult rv = CreateGenericSDP(&sdp);
+ NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
+
+ for (size_t level = 0;
+ Maybe<JsepTransceiver> transceiver = GetTransceiverForLocal(level);
+ ++level) {
+ rv = CreateOfferMsection(options, *transceiver, sdp.get());
+ NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
+ SetTransceiver(*transceiver);
+ }
+
+ SetupBundle(sdp.get());
+
+ if (mCurrentLocalDescription) {
+ rv = CopyPreviousTransportParams(*GetAnswer(), *mCurrentLocalDescription,
+ *sdp, sdp.get());
+ NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
+ }
+
+ *offer = sdp->ToString();
+ mGeneratedOffer = std::move(sdp);
+ ++mSessionVersion;
+ MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: CreateOffer \nSDP=\n" << *offer);
+
+ return Result();
+}
+
+std::string JsepSessionImpl::GetLocalDescription(
+ JsepDescriptionPendingOrCurrent type) const {
+ std::ostringstream os;
+ mozilla::Sdp* sdp = GetParsedLocalDescription(type);
+ if (sdp) {
+ sdp->Serialize(os);
+ }
+ return os.str();
+}
+
+std::string JsepSessionImpl::GetRemoteDescription(
+ JsepDescriptionPendingOrCurrent type) const {
+ std::ostringstream os;
+ mozilla::Sdp* sdp = GetParsedRemoteDescription(type);
+ if (sdp) {
+ sdp->Serialize(os);
+ }
+ return os.str();
+}
+
+void JsepSessionImpl::AddExtmap(SdpMediaSection* msection) {
+ auto extensions = GetRtpExtensions(*msection);
+
+ if (!extensions.empty()) {
+ SdpExtmapAttributeList* extmap = new SdpExtmapAttributeList;
+ extmap->mExtmaps = extensions;
+ msection->GetAttributeList().SetAttribute(extmap);
+ }
+}
+
+std::vector<SdpExtmapAttributeList::Extmap> JsepSessionImpl::GetRtpExtensions(
+ const SdpMediaSection& msection) {
+ std::vector<SdpExtmapAttributeList::Extmap> result;
+ JsepMediaType mediaType = JsepMediaType::kNone;
+ switch (msection.GetMediaType()) {
+ case SdpMediaSection::kAudio:
+ mediaType = JsepMediaType::kAudio;
+ break;
+ case SdpMediaSection::kVideo:
+ mediaType = JsepMediaType::kVideo;
+ if (msection.GetAttributeList().HasAttribute(
+ SdpAttribute::kRidAttribute)) {
+ // We need RID support
+ // TODO: Would it be worth checking that the direction is sane?
+ AddVideoRtpExtension(webrtc::RtpExtension::kRidUri,
+ SdpDirectionAttribute::kSendonly);
+
+ if (mRtxIsAllowed &&
+ Preferences::GetBool("media.peerconnection.video.use_rtx", false)) {
+ AddVideoRtpExtension(webrtc::RtpExtension::kRepairedRidUri,
+ SdpDirectionAttribute::kSendonly);
+ }
+ }
+ break;
+ default:;
+ }
+ if (mediaType != JsepMediaType::kNone) {
+ for (auto ext = mRtpExtensions.begin(); ext != mRtpExtensions.end();
+ ++ext) {
+ if (ext->mMediaType == mediaType ||
+ ext->mMediaType == JsepMediaType::kAudioVideo) {
+ result.push_back(ext->mExtmap);
+ }
+ }
+ }
+ return result;
+}
+
+std::string JsepSessionImpl::GetNewMid() {
+ std::string mid;
+
+ do {
+ std::ostringstream osMid;
+ osMid << mMidCounter++;
+ mid = osMid.str();
+ } while (mUsedMids.count(mid));
+
+ mUsedMids.insert(mid);
+ return mid;
+}
+
+void JsepSessionImpl::AddCommonExtmaps(const SdpMediaSection& remoteMsection,
+ SdpMediaSection* msection) {
+ auto negotiatedRtpExtensions = GetRtpExtensions(*msection);
+ mSdpHelper.NegotiateAndAddExtmaps(remoteMsection, negotiatedRtpExtensions,
+ msection);
+}
+
+uint16_t JsepSessionImpl::GetNeverUsedExtmapEntry() {
+ uint16_t result = 1;
+
+ // Walk the set in order, and return the first "hole" we find
+ for (const auto used : mExtmapEntriesEverUsed) {
+ if (result != used) {
+ MOZ_ASSERT(result < used);
+ break;
+ }
+
+ // RFC 5285 says entries >= 4096 are used in offers to force the answerer
+ // to pick, so we do not want to actually use these
+ if (used == 4095) {
+ JSEP_SET_ERROR(
+ "Too many rtp extensions have been added. "
+ "That's 4095. Who _does_ that?");
+ return 0;
+ }
+
+ result = used + 1;
+ }
+
+ mExtmapEntriesEverUsed.insert(result);
+ return result;
+}
+
+JsepSession::Result JsepSessionImpl::CreateAnswer(
+ const JsepAnswerOptions& options, std::string* answer) {
+ mLastError.clear();
+
+ if (mState != kJsepStateHaveRemoteOffer) {
+ JSEP_SET_ERROR("Cannot create answer in state " << GetStateStr(mState));
+ return dom::PCError::InvalidStateError;
+ }
+
+ UniquePtr<Sdp> sdp;
+
+ // Make the basic SDP that is common to offer/answer.
+ nsresult rv = CreateGenericSDP(&sdp);
+ NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
+
+ const Sdp& offer = *mPendingRemoteDescription;
+
+ // Copy the bundle groups into our answer
+ UniquePtr<SdpGroupAttributeList> groupAttr(new SdpGroupAttributeList);
+ mSdpHelper.GetBundleGroups(offer, &groupAttr->mGroups);
+ sdp->GetAttributeList().SetAttribute(groupAttr.release());
+
+ for (size_t i = 0; i < offer.GetMediaSectionCount(); ++i) {
+ // The transceivers are already in place, due to setRemote
+ Maybe<JsepTransceiver> transceiver(GetTransceiverForLevel(i));
+ if (!transceiver) {
+ JSEP_SET_ERROR("No transceiver for level " << i);
+ MOZ_ASSERT(false);
+ return dom::PCError::OperationError;
+ }
+ rv = CreateAnswerMsection(options, *transceiver, offer.GetMediaSection(i),
+ sdp.get());
+ NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
+ SetTransceiver(*transceiver);
+ }
+
+ // Ensure that each bundle-group starts with a mid that has a transport, in
+ // case we've disabled what the offerer wanted to use. If the group doesn't
+ // contain anything that has a transport, remove it.
+ groupAttr.reset(new SdpGroupAttributeList);
+ std::vector<SdpGroupAttributeList::Group> bundleGroups;
+ mSdpHelper.GetBundleGroups(*sdp, &bundleGroups);
+ for (auto& group : bundleGroups) {
+ for (auto& mid : group.tags) {
+ const SdpMediaSection* msection =
+ mSdpHelper.FindMsectionByMid(offer, mid);
+
+ if (msection && !msection->GetAttributeList().HasAttribute(
+ SdpAttribute::kBundleOnlyAttribute)) {
+ std::swap(group.tags[0], mid);
+ groupAttr->mGroups.push_back(group);
+ break;
+ }
+ }
+ }
+ sdp->GetAttributeList().SetAttribute(groupAttr.release());
+
+ if (mCurrentLocalDescription) {
+ // per discussion with bwc, 3rd parm here should be offer, not *sdp. (mjf)
+ rv = CopyPreviousTransportParams(*GetAnswer(), *mCurrentRemoteDescription,
+ offer, sdp.get());
+ NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
+ }
+
+ *answer = sdp->ToString();
+ mGeneratedAnswer = std::move(sdp);
+ ++mSessionVersion;
+ MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: CreateAnswer \nSDP=\n" << *answer);
+
+ return Result();
+}
+
+nsresult JsepSessionImpl::CreateAnswerMsection(
+ const JsepAnswerOptions& options, JsepTransceiver& transceiver,
+ const SdpMediaSection& remoteMsection, Sdp* sdp) {
+ MOZ_ASSERT(transceiver.GetMediaType() == remoteMsection.GetMediaType());
+ SdpDirectionAttribute::Direction direction =
+ reverse(remoteMsection.GetDirection()) & transceiver.mJsDirection;
+ SdpMediaSection& msection =
+ sdp->AddMediaSection(remoteMsection.GetMediaType(), direction, 9,
+ remoteMsection.GetProtocol(), sdp::kIPv4, "0.0.0.0");
+
+ nsresult rv = mSdpHelper.CopyStickyParams(remoteMsection, &msection);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mSdpHelper.MsectionIsDisabled(remoteMsection) ||
+ // JS might have stopped this
+ transceiver.IsStopped()) {
+ SdpHelper::DisableMsection(sdp, &msection);
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(transceiver.IsAssociated());
+ if (msection.GetAttributeList().GetMid().empty()) {
+ msection.GetAttributeList().SetAttribute(new SdpStringAttribute(
+ SdpAttribute::kMidAttribute, transceiver.GetMid()));
+ }
+
+ MOZ_ASSERT(transceiver.GetMid() == msection.GetAttributeList().GetMid());
+
+ SdpSetupAttribute::Role role;
+ if (transceiver.mTransport.mDtls && !IsIceRestarting()) {
+ role = (transceiver.mTransport.mDtls->mRole ==
+ JsepDtlsTransport::kJsepDtlsClient)
+ ? SdpSetupAttribute::kActive
+ : SdpSetupAttribute::kPassive;
+ } else {
+ rv = DetermineAnswererSetupRole(remoteMsection, &role);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = AddTransportAttributes(&msection, role);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ transceiver.mSendTrack.AddToAnswer(remoteMsection, mSsrcGenerator, &msection);
+ transceiver.mRecvTrack.AddToAnswer(remoteMsection, mSsrcGenerator, &msection);
+
+ // Add extmap attributes. This logic will probably be moved to the track,
+ // since it can be specified on a per-sender basis in JS.
+ // We will need some validation to ensure that the ids are identical for
+ // RTP streams that are bundled together, though (bug 1406529).
+ AddCommonExtmaps(remoteMsection, &msection);
+
+ if (msection.GetFormats().empty()) {
+ // Could not negotiate anything. Disable m-section.
+ SdpHelper::DisableMsection(sdp, &msection);
+ }
+
+ return NS_OK;
+}
+
+nsresult JsepSessionImpl::DetermineAnswererSetupRole(
+ const SdpMediaSection& remoteMsection, SdpSetupAttribute::Role* rolep) {
+ // Determine the role.
+ // RFC 5763 says:
+ //
+ // The endpoint MUST use the setup attribute defined in [RFC4145].
+ // The endpoint that is the offerer MUST use the setup attribute
+ // value of setup:actpass and be prepared to receive a client_hello
+ // before it receives the answer. The answerer MUST use either a
+ // setup attribute value of setup:active or setup:passive. Note that
+ // if the answerer uses setup:passive, then the DTLS handshake will
+ // not begin until the answerer is received, which adds additional
+ // latency. setup:active allows the answer and the DTLS handshake to
+ // occur in parallel. Thus, setup:active is RECOMMENDED. Whichever
+ // party is active MUST initiate a DTLS handshake by sending a
+ // ClientHello over each flow (host/port quartet).
+ //
+ // We default to assuming that the offerer is passive and we are active.
+ SdpSetupAttribute::Role role = SdpSetupAttribute::kActive;
+
+ if (remoteMsection.GetAttributeList().HasAttribute(
+ SdpAttribute::kSetupAttribute)) {
+ switch (remoteMsection.GetAttributeList().GetSetup().mRole) {
+ case SdpSetupAttribute::kActive:
+ role = SdpSetupAttribute::kPassive;
+ break;
+ case SdpSetupAttribute::kPassive:
+ case SdpSetupAttribute::kActpass:
+ role = SdpSetupAttribute::kActive;
+ break;
+ case SdpSetupAttribute::kHoldconn:
+ // This should have been caught by ParseSdp
+ MOZ_ASSERT(false);
+ JSEP_SET_ERROR(
+ "The other side used an illegal setup attribute"
+ " (\"holdconn\").");
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ *rolep = role;
+ return NS_OK;
+}
+
+JsepSession::Result JsepSessionImpl::SetLocalDescription(
+ JsepSdpType type, const std::string& constSdp) {
+ mLastError.clear();
+ std::string sdp = constSdp;
+
+ MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: SetLocalDescription type=" << type
+ << "\nSDP=\n"
+ << sdp);
+
+ switch (type) {
+ case kJsepSdpOffer:
+ if (!mGeneratedOffer) {
+ JSEP_SET_ERROR(
+ "Cannot set local offer when createOffer has not been called.");
+ return dom::PCError::InvalidModificationError;
+ }
+ if (sdp.empty()) {
+ sdp = mGeneratedOffer->ToString();
+ }
+ if (mState == kJsepStateHaveLocalOffer) {
+ // Rollback previous offer before applying the new one.
+ SetLocalDescription(kJsepSdpRollback, "");
+ MOZ_ASSERT(mState == kJsepStateStable);
+ }
+ break;
+ case kJsepSdpAnswer:
+ case kJsepSdpPranswer:
+ if (!mGeneratedAnswer) {
+ JSEP_SET_ERROR(
+ "Cannot set local answer when createAnswer has not been called.");
+ return dom::PCError::InvalidModificationError;
+ }
+ if (sdp.empty()) {
+ sdp = mGeneratedAnswer->ToString();
+ }
+ break;
+ case kJsepSdpRollback:
+ if (mState != kJsepStateHaveLocalOffer) {
+ JSEP_SET_ERROR("Cannot rollback local description in "
+ << GetStateStr(mState));
+ // Currently, spec allows this in any state except stable, and
+ // sRD(rollback) and sLD(rollback) do exactly the same thing.
+ return dom::PCError::InvalidStateError;
+ }
+
+ mPendingLocalDescription.reset();
+ SetState(kJsepStateStable);
+ RollbackLocalOffer();
+ return Result();
+ }
+
+ switch (mState) {
+ case kJsepStateStable:
+ if (type != kJsepSdpOffer) {
+ JSEP_SET_ERROR("Cannot set local answer in state "
+ << GetStateStr(mState));
+ return dom::PCError::InvalidStateError;
+ }
+ break;
+ case kJsepStateHaveRemoteOffer:
+ if (type != kJsepSdpAnswer && type != kJsepSdpPranswer) {
+ JSEP_SET_ERROR("Cannot set local offer in state "
+ << GetStateStr(mState));
+ return dom::PCError::InvalidStateError;
+ }
+ break;
+ default:
+ JSEP_SET_ERROR("Cannot set local offer or answer in state "
+ << GetStateStr(mState));
+ return dom::PCError::InvalidStateError;
+ }
+
+ UniquePtr<Sdp> parsed;
+ nsresult rv = ParseSdp(sdp, &parsed);
+ // Needs to be RTCError with sdp-syntax-error
+ NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
+
+ // Check that content hasn't done anything unsupported with the SDP
+ rv = ValidateLocalDescription(*parsed, type);
+ NS_ENSURE_SUCCESS(rv, dom::PCError::InvalidModificationError);
+
+ switch (type) {
+ case kJsepSdpOffer:
+ rv = ValidateOffer(*parsed);
+ break;
+ case kJsepSdpAnswer:
+ case kJsepSdpPranswer:
+ rv = ValidateAnswer(*mPendingRemoteDescription, *parsed);
+ break;
+ case kJsepSdpRollback:
+ MOZ_CRASH(); // Handled above
+ }
+ NS_ENSURE_SUCCESS(rv, dom::PCError::InvalidAccessError);
+
+ if (type == kJsepSdpOffer) {
+ // Save in case we need to rollback
+ mOldTransceivers = mTransceivers;
+ }
+
+ SdpHelper::BundledMids bundledMids;
+ rv = mSdpHelper.GetBundledMids(*parsed, &bundledMids);
+ NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
+
+ SdpHelper::BundledMids remoteBundledMids;
+ if (type != kJsepSdpOffer) {
+ rv = mSdpHelper.GetBundledMids(*mPendingRemoteDescription,
+ &remoteBundledMids);
+ NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
+ }
+
+ for (size_t i = 0; i < parsed->GetMediaSectionCount(); ++i) {
+ Maybe<JsepTransceiver> transceiver(GetTransceiverForLevel(i));
+ if (!transceiver) {
+ MOZ_ASSERT(false);
+ JSEP_SET_ERROR("No transceiver for level " << i);
+ return dom::PCError::OperationError;
+ }
+
+ const auto& msection = parsed->GetMediaSection(i);
+ transceiver->Associate(msection.GetAttributeList().GetMid());
+ transceiver->mRecvTrack.RecvTrackSetLocal(msection);
+
+ if (mSdpHelper.MsectionIsDisabled(msection)) {
+ transceiver->mTransport.Close();
+ SetTransceiver(*transceiver);
+ continue;
+ }
+
+ bool hasOwnTransport = mSdpHelper.OwnsTransport(
+ msection, bundledMids,
+ (type == kJsepSdpOffer) ? sdp::kOffer : sdp::kAnswer);
+ if (type != kJsepSdpOffer) {
+ const auto& remoteMsection =
+ mPendingRemoteDescription->GetMediaSection(i);
+ // Don't allow the answer to override what the offer allowed for
+ hasOwnTransport &= mSdpHelper.OwnsTransport(
+ remoteMsection, remoteBundledMids, sdp::kOffer);
+ }
+
+ if (hasOwnTransport) {
+ EnsureHasOwnTransport(parsed->GetMediaSection(i), *transceiver);
+ }
+
+ if (type == kJsepSdpOffer) {
+ if (!hasOwnTransport) {
+ auto it = bundledMids.find(transceiver->GetMid());
+ if (it != bundledMids.end()) {
+ transceiver->SetBundleLevel(it->second->GetLevel());
+ }
+ }
+ } else {
+ auto it = remoteBundledMids.find(transceiver->GetMid());
+ if (it != remoteBundledMids.end()) {
+ transceiver->SetBundleLevel(it->second->GetLevel());
+ }
+ }
+ SetTransceiver(*transceiver);
+ }
+
+ CopyBundleTransports();
+
+ switch (type) {
+ case kJsepSdpOffer:
+ rv = SetLocalDescriptionOffer(std::move(parsed));
+ break;
+ case kJsepSdpAnswer:
+ case kJsepSdpPranswer:
+ rv = SetLocalDescriptionAnswer(type, std::move(parsed));
+ break;
+ case kJsepSdpRollback:
+ MOZ_CRASH(); // Handled above
+ }
+
+ NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
+ return Result();
+}
+
+nsresult JsepSessionImpl::SetLocalDescriptionOffer(UniquePtr<Sdp> offer) {
+ MOZ_ASSERT(mState == kJsepStateStable);
+ mPendingLocalDescription = std::move(offer);
+ mIsPendingOfferer = Some(true);
+ SetState(kJsepStateHaveLocalOffer);
+ return NS_OK;
+}
+
+nsresult JsepSessionImpl::SetLocalDescriptionAnswer(JsepSdpType type,
+ UniquePtr<Sdp> answer) {
+ MOZ_ASSERT(mState == kJsepStateHaveRemoteOffer);
+ mPendingLocalDescription = std::move(answer);
+
+ nsresult rv = HandleNegotiatedSession(mPendingLocalDescription,
+ mPendingRemoteDescription);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mCurrentRemoteDescription = std::move(mPendingRemoteDescription);
+ mCurrentLocalDescription = std::move(mPendingLocalDescription);
+ MOZ_ASSERT(mIsPendingOfferer.isSome() && !*mIsPendingOfferer);
+ mIsPendingOfferer.reset();
+ mIsCurrentOfferer = Some(false);
+
+ SetState(kJsepStateStable);
+ return NS_OK;
+}
+
+JsepSession::Result JsepSessionImpl::SetRemoteDescription(
+ JsepSdpType type, const std::string& sdp) {
+ mLastError.clear();
+
+ MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: SetRemoteDescription type=" << type
+ << "\nSDP=\n"
+ << sdp);
+
+ if (mState == kJsepStateHaveRemoteOffer && type == kJsepSdpOffer) {
+ // Rollback previous offer before applying the new one.
+ SetRemoteDescription(kJsepSdpRollback, "");
+ MOZ_ASSERT(mState == kJsepStateStable);
+ }
+
+ if (type == kJsepSdpRollback) {
+ if (mState != kJsepStateHaveRemoteOffer) {
+ JSEP_SET_ERROR("Cannot rollback remote description in "
+ << GetStateStr(mState));
+ return dom::PCError::InvalidStateError;
+ }
+
+ mPendingRemoteDescription.reset();
+ SetState(kJsepStateStable);
+ RollbackRemoteOffer();
+
+ return Result();
+ }
+
+ switch (mState) {
+ case kJsepStateStable:
+ if (type != kJsepSdpOffer) {
+ JSEP_SET_ERROR("Cannot set remote answer in state "
+ << GetStateStr(mState));
+ return dom::PCError::InvalidStateError;
+ }
+ break;
+ case kJsepStateHaveLocalOffer:
+ case kJsepStateHaveRemotePranswer:
+ if (type != kJsepSdpAnswer && type != kJsepSdpPranswer) {
+ JSEP_SET_ERROR("Cannot set remote offer in state "
+ << GetStateStr(mState));
+ return dom::PCError::InvalidStateError;
+ }
+ break;
+ default:
+ JSEP_SET_ERROR("Cannot set remote offer or answer in current state "
+ << GetStateStr(mState));
+ return dom::PCError::InvalidStateError;
+ }
+
+ // Parse.
+ UniquePtr<Sdp> parsed;
+ nsresult rv = ParseSdp(sdp, &parsed);
+ // Needs to be RTCError with sdp-syntax-error
+ NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
+
+ rv = ValidateRemoteDescription(*parsed);
+ NS_ENSURE_SUCCESS(rv, dom::PCError::InvalidAccessError);
+
+ switch (type) {
+ case kJsepSdpOffer:
+ rv = ValidateOffer(*parsed);
+ break;
+ case kJsepSdpAnswer:
+ case kJsepSdpPranswer:
+ rv = ValidateAnswer(*mPendingLocalDescription, *parsed);
+ break;
+ case kJsepSdpRollback:
+ MOZ_CRASH(); // Handled above
+ }
+ NS_ENSURE_SUCCESS(rv, dom::PCError::InvalidAccessError);
+
+ bool iceLite =
+ parsed->GetAttributeList().HasAttribute(SdpAttribute::kIceLiteAttribute);
+
+ // check for mismatch ufrag/pwd indicating ice restart
+ // can't just check the first one because it might be disabled
+ bool iceRestarting = false;
+ if (mCurrentRemoteDescription.get()) {
+ for (size_t i = 0; !iceRestarting &&
+ i < mCurrentRemoteDescription->GetMediaSectionCount();
+ ++i) {
+ const SdpMediaSection& newMsection = parsed->GetMediaSection(i);
+ const SdpMediaSection& oldMsection =
+ mCurrentRemoteDescription->GetMediaSection(i);
+
+ if (mSdpHelper.MsectionIsDisabled(newMsection) ||
+ mSdpHelper.MsectionIsDisabled(oldMsection)) {
+ continue;
+ }
+
+ iceRestarting = mSdpHelper.IceCredentialsDiffer(newMsection, oldMsection);
+ }
+ }
+
+ std::vector<std::string> iceOptions;
+ if (parsed->GetAttributeList().HasAttribute(
+ SdpAttribute::kIceOptionsAttribute)) {
+ iceOptions = parsed->GetAttributeList().GetIceOptions().mValues;
+ }
+
+ if (type == kJsepSdpOffer) {
+ // Save in case we need to rollback.
+ mOldTransceivers = mTransceivers;
+ for (auto& transceiver : mTransceivers) {
+ if (!transceiver.IsNegotiated()) {
+ // We chose a level for this transceiver, but never negotiated it.
+ // Discard this state.
+ transceiver.ClearLevel();
+ }
+ }
+ }
+
+ // TODO(bug 1095780): Note that we create remote tracks even when
+ // They contain only codecs we can't negotiate or other craziness.
+ rv = UpdateTransceiversFromRemoteDescription(*parsed);
+ NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
+
+ for (size_t i = 0; i < parsed->GetMediaSectionCount(); ++i) {
+ MOZ_ASSERT(GetTransceiverForLevel(i));
+ }
+
+ switch (type) {
+ case kJsepSdpOffer:
+ rv = SetRemoteDescriptionOffer(std::move(parsed));
+ break;
+ case kJsepSdpAnswer:
+ case kJsepSdpPranswer:
+ rv = SetRemoteDescriptionAnswer(type, std::move(parsed));
+ break;
+ case kJsepSdpRollback:
+ MOZ_CRASH(); // Handled above
+ }
+
+ NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
+
+ mRemoteIsIceLite = iceLite;
+ mIceOptions = iceOptions;
+ SetIceRestarting(iceRestarting);
+ return Result();
+}
+
+nsresult JsepSessionImpl::HandleNegotiatedSession(
+ const UniquePtr<Sdp>& local, const UniquePtr<Sdp>& remote) {
+ // local ufrag/pwd has been negotiated; we will never go back to the old ones
+ mOldIceUfrag.clear();
+ mOldIcePwd.clear();
+
+ bool remoteIceLite =
+ remote->GetAttributeList().HasAttribute(SdpAttribute::kIceLiteAttribute);
+
+ mIceControlling = remoteIceLite || *mIsPendingOfferer;
+
+ const Sdp& answer = *mIsPendingOfferer ? *remote : *local;
+
+ SdpHelper::BundledMids bundledMids;
+ nsresult rv = mSdpHelper.GetBundledMids(answer, &bundledMids);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // First, set the bundle level on the transceivers
+ for (auto& [mid, transportOwner] : bundledMids) {
+ Maybe<JsepTransceiver> bundledTransceiver = GetTransceiverForMid(mid);
+ if (!bundledTransceiver) {
+ JSEP_SET_ERROR("No transceiver for bundled mid " << mid);
+ return NS_ERROR_INVALID_ARG;
+ }
+ bundledTransceiver->SetBundleLevel(transportOwner->GetLevel());
+ SetTransceiver(*bundledTransceiver);
+ }
+
+ // Now walk through the m-sections, perform negotiation, and update the
+ // transceivers.
+ for (size_t i = 0; i < local->GetMediaSectionCount(); ++i) {
+ Maybe<JsepTransceiver> transceiver(GetTransceiverForLevel(i));
+ if (!transceiver) {
+ MOZ_ASSERT(false);
+ JSEP_SET_ERROR("No transceiver for level " << i);
+ return NS_ERROR_FAILURE;
+ }
+
+ // Skip disabled m-sections.
+ if (answer.GetMediaSection(i).GetPort() == 0) {
+ transceiver->mTransport.Close();
+ transceiver->Stop();
+ transceiver->Disassociate();
+ transceiver->ClearBundleLevel();
+ transceiver->mSendTrack.SetActive(false);
+ transceiver->mRecvTrack.SetActive(false);
+ transceiver->SetCanRecycle();
+ SetTransceiver(*transceiver);
+ // Do not clear mLevel yet! That will happen on the next negotiation.
+ continue;
+ }
+
+ rv = MakeNegotiatedTransceiver(remote->GetMediaSection(i),
+ local->GetMediaSection(i), *transceiver);
+ NS_ENSURE_SUCCESS(rv, rv);
+ SetTransceiver(*transceiver);
+ }
+
+ CopyBundleTransports();
+
+ std::vector<JsepTrack*> remoteTracks;
+ for (auto& transceiver : mTransceivers) {
+ remoteTracks.push_back(&transceiver.mRecvTrack);
+ }
+ JsepTrack::SetUniquePayloadTypes(remoteTracks);
+
+ mNegotiations++;
+
+ mGeneratedAnswer.reset();
+ mGeneratedOffer.reset();
+
+ return NS_OK;
+}
+
+nsresult JsepSessionImpl::MakeNegotiatedTransceiver(
+ const SdpMediaSection& remote, const SdpMediaSection& local,
+ JsepTransceiver& transceiver) {
+ const SdpMediaSection& answer = *mIsPendingOfferer ? remote : local;
+
+ bool sending = false;
+ bool receiving = false;
+
+ // JS could stop the transceiver after the answer was created.
+ if (!transceiver.IsStopped()) {
+ if (*mIsPendingOfferer) {
+ receiving = answer.IsSending();
+ sending = answer.IsReceiving();
+ } else {
+ sending = answer.IsSending();
+ receiving = answer.IsReceiving();
+ }
+ }
+
+ MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: Negotiated m= line"
+ << " index=" << local.GetLevel() << " type="
+ << local.GetMediaType() << " sending=" << sending
+ << " receiving=" << receiving);
+
+ transceiver.SetNegotiated();
+
+ // Ensure that this is finalized in case we need to copy it below
+ nsresult rv =
+ FinalizeTransport(remote.GetAttributeList(), answer.GetAttributeList(),
+ &transceiver.mTransport);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ transceiver.mSendTrack.SetActive(sending);
+ rv = transceiver.mSendTrack.Negotiate(answer, remote, local);
+ if (NS_FAILED(rv)) {
+ JSEP_SET_ERROR("Answer had no codecs in common with offer in m-section "
+ << local.GetLevel());
+ return rv;
+ }
+
+ JsepTrack& recvTrack = transceiver.mRecvTrack;
+ recvTrack.SetActive(receiving);
+ rv = recvTrack.Negotiate(answer, remote, local);
+ if (NS_FAILED(rv)) {
+ JSEP_SET_ERROR("Answer had no codecs in common with offer in m-section "
+ << local.GetLevel());
+ return rv;
+ }
+
+ if (transceiver.HasBundleLevel() && recvTrack.GetSsrcs().empty() &&
+ recvTrack.GetMediaType() != SdpMediaSection::kApplication) {
+ // TODO(bug 1105005): Once we have urn:ietf:params:rtp-hdrext:sdes:mid
+ // support, we should only fire this warning if that extension was not
+ // negotiated.
+ MOZ_MTLOG(ML_ERROR, "[" << mName
+ << "]: Bundled m-section has no ssrc "
+ "attributes. This may cause media packets to be "
+ "dropped.");
+ }
+
+ if (transceiver.mTransport.mComponents == 2) {
+ // RTCP MUX or not.
+ // TODO(bug 1095743): verify that the PTs are consistent with mux.
+ MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: RTCP-MUX is off");
+ }
+
+ if (answer.GetAttributeList().HasAttribute(SdpAttribute::kExtmapAttribute)) {
+ const auto extmaps = answer.GetAttributeList().GetExtmap().mExtmaps;
+ for (const auto& negotiatedExtension : extmaps) {
+ if (negotiatedExtension.entry == 0) {
+ MOZ_ASSERT(false, "This should have been caught sooner");
+ continue;
+ }
+
+ mExtmapEntriesEverNegotiated[negotiatedExtension.entry] =
+ negotiatedExtension.extensionname;
+
+ for (auto& originalExtension : mRtpExtensions) {
+ if (negotiatedExtension.extensionname ==
+ originalExtension.mExtmap.extensionname) {
+ // Update extmap to match what was negotiated
+ originalExtension.mExtmap.entry = negotiatedExtension.entry;
+ mExtmapEntriesEverUsed.insert(negotiatedExtension.entry);
+ } else if (originalExtension.mExtmap.entry ==
+ negotiatedExtension.entry) {
+ // If this extmap entry was claimed for a different extension, update
+ // it to a new value so we don't end up with a duplicate.
+ originalExtension.mExtmap.entry = GetNeverUsedExtmapEntry();
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+void JsepSessionImpl::EnsureHasOwnTransport(const SdpMediaSection& msection,
+ JsepTransceiver& transceiver) {
+ JsepTransport& transport = transceiver.mTransport;
+
+ if (!transceiver.HasOwnTransport()) {
+ // Transceiver didn't own this transport last time, it won't now either
+ transport.Close();
+ }
+
+ transport.mLocalUfrag = msection.GetAttributeList().GetIceUfrag();
+ transport.mLocalPwd = msection.GetAttributeList().GetIcePwd();
+
+ transceiver.ClearBundleLevel();
+
+ if (!transport.mComponents) {
+ if (mSdpHelper.HasRtcp(msection.GetProtocol())) {
+ transport.mComponents = 2;
+ } else {
+ transport.mComponents = 1;
+ }
+ }
+
+ if (transport.mTransportId.empty()) {
+ // TODO: Once we use different ICE ufrag/pass for each m-section, we can
+ // use that here.
+ std::ostringstream os;
+ os << "transport_" << mTransportIdCounter++;
+ transport.mTransportId = os.str();
+ }
+}
+
+void JsepSessionImpl::CopyBundleTransports() {
+ for (auto& transceiver : mTransceivers) {
+ if (transceiver.HasBundleLevel()) {
+ MOZ_MTLOG(ML_DEBUG,
+ "[" << mName << "] Transceiver " << transceiver.GetLevel()
+ << " is in a bundle; transceiver "
+ << transceiver.BundleLevel() << " owns the transport.");
+ Maybe<const JsepTransceiver> transportOwner =
+ GetTransceiverForLevel(transceiver.BundleLevel());
+ MOZ_ASSERT(transportOwner);
+ if (transportOwner) {
+ transceiver.mTransport = transportOwner->mTransport;
+ }
+ } else if (transceiver.HasLevel()) {
+ MOZ_MTLOG(ML_DEBUG, "[" << mName << "] Transceiver "
+ << transceiver.GetLevel()
+ << " is not necessarily in a bundle.");
+ }
+ if (transceiver.HasLevel()) {
+ MOZ_MTLOG(ML_DEBUG,
+ "[" << mName << "] Transceiver " << transceiver.GetLevel()
+ << " transport-id: " << transceiver.mTransport.mTransportId
+ << " components: " << transceiver.mTransport.mComponents);
+ }
+ }
+}
+
+nsresult JsepSessionImpl::FinalizeTransport(const SdpAttributeList& remote,
+ const SdpAttributeList& answer,
+ JsepTransport* transport) const {
+ if (!transport->mComponents) {
+ return NS_OK;
+ }
+
+ if (!transport->mIce || transport->mIce->mUfrag != remote.GetIceUfrag() ||
+ transport->mIce->mPwd != remote.GetIcePwd()) {
+ UniquePtr<JsepIceTransport> ice = MakeUnique<JsepIceTransport>();
+ transport->mDtls = nullptr;
+
+ // We do sanity-checking for these in ParseSdp
+ ice->mUfrag = remote.GetIceUfrag();
+ ice->mPwd = remote.GetIcePwd();
+ transport->mIce = std::move(ice);
+ }
+
+ if (remote.HasAttribute(SdpAttribute::kCandidateAttribute)) {
+ transport->mIce->mCandidates = remote.GetCandidate();
+ }
+
+ if (!transport->mDtls) {
+ // RFC 5763 says:
+ //
+ // The endpoint MUST use the setup attribute defined in [RFC4145].
+ // The endpoint that is the offerer MUST use the setup attribute
+ // value of setup:actpass and be prepared to receive a client_hello
+ // before it receives the answer. The answerer MUST use either a
+ // setup attribute value of setup:active or setup:passive. Note that
+ // if the answerer uses setup:passive, then the DTLS handshake will
+ // not begin until the answerer is received, which adds additional
+ // latency. setup:active allows the answer and the DTLS handshake to
+ // occur in parallel. Thus, setup:active is RECOMMENDED. Whichever
+ // party is active MUST initiate a DTLS handshake by sending a
+ // ClientHello over each flow (host/port quartet).
+ UniquePtr<JsepDtlsTransport> dtls = MakeUnique<JsepDtlsTransport>();
+ dtls->mFingerprints = remote.GetFingerprint();
+ if (!answer.HasAttribute(mozilla::SdpAttribute::kSetupAttribute)) {
+ dtls->mRole = *mIsPendingOfferer ? JsepDtlsTransport::kJsepDtlsServer
+ : JsepDtlsTransport::kJsepDtlsClient;
+ } else {
+ if (*mIsPendingOfferer) {
+ dtls->mRole = (answer.GetSetup().mRole == SdpSetupAttribute::kActive)
+ ? JsepDtlsTransport::kJsepDtlsServer
+ : JsepDtlsTransport::kJsepDtlsClient;
+ } else {
+ dtls->mRole = (answer.GetSetup().mRole == SdpSetupAttribute::kActive)
+ ? JsepDtlsTransport::kJsepDtlsClient
+ : JsepDtlsTransport::kJsepDtlsServer;
+ }
+ }
+
+ transport->mDtls = std::move(dtls);
+ }
+
+ if (answer.HasAttribute(SdpAttribute::kRtcpMuxAttribute)) {
+ transport->mComponents = 1;
+ }
+
+ return NS_OK;
+}
+
+nsresult JsepSessionImpl::AddTransportAttributes(
+ SdpMediaSection* msection, SdpSetupAttribute::Role dtlsRole) {
+ if (mIceUfrag.empty() || mIcePwd.empty()) {
+ JSEP_SET_ERROR("Missing ICE ufrag or password");
+ return NS_ERROR_FAILURE;
+ }
+
+ SdpAttributeList& attrList = msection->GetAttributeList();
+ attrList.SetAttribute(
+ new SdpStringAttribute(SdpAttribute::kIceUfragAttribute, mIceUfrag));
+ attrList.SetAttribute(
+ new SdpStringAttribute(SdpAttribute::kIcePwdAttribute, mIcePwd));
+
+ msection->GetAttributeList().SetAttribute(new SdpSetupAttribute(dtlsRole));
+
+ return NS_OK;
+}
+
+nsresult JsepSessionImpl::CopyPreviousTransportParams(
+ const Sdp& oldAnswer, const Sdp& offerersPreviousSdp, const Sdp& newOffer,
+ Sdp* newLocal) {
+ for (size_t i = 0; i < oldAnswer.GetMediaSectionCount(); ++i) {
+ if (!mSdpHelper.MsectionIsDisabled(newLocal->GetMediaSection(i)) &&
+ mSdpHelper.AreOldTransportParamsValid(oldAnswer, offerersPreviousSdp,
+ newOffer, i)) {
+ // If newLocal is an offer, this will be the number of components we used
+ // last time, and if it is an answer, this will be the number of
+ // components we've decided we're using now.
+ Maybe<const JsepTransceiver> transceiver(GetTransceiverForLevel(i));
+ if (!transceiver) {
+ MOZ_ASSERT(false);
+ JSEP_SET_ERROR("No transceiver for level " << i);
+ return NS_ERROR_FAILURE;
+ }
+ size_t numComponents = transceiver->mTransport.mComponents;
+ nsresult rv = mSdpHelper.CopyTransportParams(
+ numComponents, mCurrentLocalDescription->GetMediaSection(i),
+ &newLocal->GetMediaSection(i));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult JsepSessionImpl::ParseSdp(const std::string& sdp,
+ UniquePtr<Sdp>* parsedp) {
+ auto results = mParser->Parse(sdp);
+ auto parsed = std::move(results->Sdp());
+ mLastSdpParsingErrors = results->Errors();
+ if (!parsed) {
+ std::string error = results->ParserName() + " Failed to parse SDP: ";
+ mSdpHelper.AppendSdpParseErrors(mLastSdpParsingErrors, &error);
+ JSEP_SET_ERROR(error);
+ return NS_ERROR_INVALID_ARG;
+ }
+ // Verify that the JSEP rules for all SDP are followed
+ for (size_t i = 0; i < parsed->GetMediaSectionCount(); ++i) {
+ if (mSdpHelper.MsectionIsDisabled(parsed->GetMediaSection(i))) {
+ // Disabled, let this stuff slide.
+ continue;
+ }
+
+ const SdpMediaSection& msection(parsed->GetMediaSection(i));
+ auto& mediaAttrs = msection.GetAttributeList();
+
+ if (mediaAttrs.HasAttribute(SdpAttribute::kMidAttribute) &&
+ mediaAttrs.GetMid().length() > 16) {
+ JSEP_SET_ERROR(
+ "Invalid description, mid length greater than 16 "
+ "unsupported until 2-byte rtp header extensions are "
+ "supported in webrtc.org");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (mediaAttrs.HasAttribute(SdpAttribute::kExtmapAttribute)) {
+ std::set<uint16_t> extIds;
+ for (const auto& ext : mediaAttrs.GetExtmap().mExtmaps) {
+ uint16_t id = ext.entry;
+
+ if (id < 1 || id > 14) {
+ JSEP_SET_ERROR("Description contains invalid extension id "
+ << id << " on level " << i
+ << " which is unsupported until 2-byte rtp"
+ " header extensions are supported in webrtc.org");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (extIds.find(id) != extIds.end()) {
+ JSEP_SET_ERROR("Description contains duplicate extension id "
+ << id << " on level " << i);
+ return NS_ERROR_INVALID_ARG;
+ }
+ extIds.insert(id);
+ }
+ }
+
+ static const std::bitset<128> forbidden = GetForbiddenSdpPayloadTypes();
+ if (msection.GetMediaType() == SdpMediaSection::kAudio ||
+ msection.GetMediaType() == SdpMediaSection::kVideo) {
+ // Sanity-check that payload type can work with RTP
+ for (const std::string& fmt : msection.GetFormats()) {
+ uint16_t payloadType;
+ if (!SdpHelper::GetPtAsInt(fmt, &payloadType)) {
+ JSEP_SET_ERROR("Payload type \""
+ << fmt << "\" is not a 16-bit unsigned int at level "
+ << i);
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (payloadType > 127) {
+ JSEP_SET_ERROR("audio/video payload type \""
+ << fmt << "\" is too large at level " << i);
+ return NS_ERROR_INVALID_ARG;
+ }
+ if (forbidden.test(payloadType)) {
+ JSEP_SET_ERROR("Illegal audio/video payload type \""
+ << fmt << "\" at level " << i);
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+ }
+ }
+
+ *parsedp = std::move(parsed);
+ return NS_OK;
+}
+
+nsresult JsepSessionImpl::SetRemoteDescriptionOffer(UniquePtr<Sdp> offer) {
+ MOZ_ASSERT(mState == kJsepStateStable);
+
+ mPendingRemoteDescription = std::move(offer);
+ mIsPendingOfferer = Some(false);
+
+ SetState(kJsepStateHaveRemoteOffer);
+ return NS_OK;
+}
+
+nsresult JsepSessionImpl::SetRemoteDescriptionAnswer(JsepSdpType type,
+ UniquePtr<Sdp> answer) {
+ MOZ_ASSERT(mState == kJsepStateHaveLocalOffer ||
+ mState == kJsepStateHaveRemotePranswer);
+
+ mPendingRemoteDescription = std::move(answer);
+
+ nsresult rv = HandleNegotiatedSession(mPendingLocalDescription,
+ mPendingRemoteDescription);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mCurrentRemoteDescription = std::move(mPendingRemoteDescription);
+ mCurrentLocalDescription = std::move(mPendingLocalDescription);
+ MOZ_ASSERT(mIsPendingOfferer.isSome() && *mIsPendingOfferer);
+ mIsPendingOfferer.reset();
+ mIsCurrentOfferer = Some(true);
+
+ SetState(kJsepStateStable);
+ return NS_OK;
+}
+
+Maybe<JsepTransceiver> JsepSessionImpl::GetTransceiverForLevel(
+ size_t level) const {
+ return FindTransceiver([level](const JsepTransceiver& transceiver) {
+ return transceiver.HasLevel() && (transceiver.GetLevel() == level);
+ });
+}
+
+Maybe<JsepTransceiver> JsepSessionImpl::GetTransceiverForMid(
+ const std::string& mid) const {
+ return FindTransceiver([mid](const JsepTransceiver& transceiver) {
+ return transceiver.IsAssociated() && (transceiver.GetMid() == mid);
+ });
+}
+
+Maybe<JsepTransceiver> JsepSessionImpl::GetTransceiverForLocal(size_t level) {
+ if (Maybe<JsepTransceiver> transceiver = GetTransceiverForLevel(level)) {
+ if (transceiver->CanRecycle() &&
+ transceiver->GetMediaType() != SdpMediaSection::kApplication) {
+ // Attempt to recycle. If this fails, the old transceiver stays put.
+ transceiver->Disassociate();
+ Maybe<JsepTransceiver> newTransceiver =
+ FindUnassociatedTransceiver(transceiver->GetMediaType(), false);
+ if (newTransceiver) {
+ newTransceiver->SetLevel(level);
+ transceiver->ClearLevel();
+ transceiver->mSendTrack.ClearRids();
+ SetTransceiver(*newTransceiver);
+ SetTransceiver(*transceiver);
+ return newTransceiver;
+ }
+ }
+
+ SetTransceiver(*transceiver);
+ return transceiver;
+ }
+
+ // There is no transceiver for |level| right now.
+
+ // Look for an RTP transceiver
+ for (auto& transceiver : mTransceivers) {
+ if (transceiver.GetMediaType() != SdpMediaSection::kApplication &&
+ !transceiver.IsStopped() && !transceiver.HasLevel()) {
+ transceiver.SetLevel(level);
+ return Some(transceiver);
+ }
+ }
+
+ // Ok, look for a datachannel
+ for (auto& transceiver : mTransceivers) {
+ if (!transceiver.IsStopped() && !transceiver.HasLevel()) {
+ transceiver.SetLevel(level);
+ return Some(transceiver);
+ }
+ }
+
+ return Nothing();
+}
+
+Maybe<JsepTransceiver> JsepSessionImpl::GetTransceiverForRemote(
+ const SdpMediaSection& msection) {
+ size_t level = msection.GetLevel();
+ Maybe<JsepTransceiver> transceiver = GetTransceiverForLevel(level);
+ if (transceiver) {
+ if (!transceiver->CanRecycle()) {
+ return transceiver;
+ }
+ transceiver->Disassociate();
+ transceiver->ClearLevel();
+ transceiver->mSendTrack.ClearRids();
+ SetTransceiver(*transceiver);
+ }
+
+ // No transceiver for |level|
+ transceiver = FindUnassociatedTransceiver(msection.GetMediaType(), true);
+ if (transceiver) {
+ transceiver->SetLevel(level);
+ SetTransceiver(*transceiver);
+ return transceiver;
+ }
+
+ // Make a new transceiver
+ JsepTransceiver newTransceiver(msection.GetMediaType(), *mUuidGen,
+ SdpDirectionAttribute::kRecvonly);
+ newTransceiver.SetLevel(level);
+ newTransceiver.SetOnlyExistsBecauseOfSetRemote(true);
+ AddTransceiver(newTransceiver);
+ return Some(mTransceivers.back());
+}
+
+Maybe<JsepTransceiver> JsepSessionImpl::GetTransceiverWithTransport(
+ const std::string& transportId) const {
+ for (const auto& transceiver : mTransceivers) {
+ if (transceiver.HasOwnTransport() &&
+ (transceiver.mTransport.mTransportId == transportId)) {
+ MOZ_ASSERT(transceiver.HasLevel(),
+ "Transceiver has a transport, but no level!");
+ return Some(transceiver);
+ }
+ }
+
+ return Nothing();
+}
+
+nsresult JsepSessionImpl::UpdateTransceiversFromRemoteDescription(
+ const Sdp& remote) {
+ // Iterate over the sdp, updating remote tracks as we go
+ for (size_t i = 0; i < remote.GetMediaSectionCount(); ++i) {
+ const SdpMediaSection& msection = remote.GetMediaSection(i);
+
+ Maybe<JsepTransceiver> transceiver(GetTransceiverForRemote(msection));
+ if (!transceiver) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mSdpHelper.MsectionIsDisabled(msection)) {
+ if (msection.GetAttributeList().HasAttribute(
+ SdpAttribute::kMidAttribute)) {
+ transceiver->Associate(msection.GetAttributeList().GetMid());
+ }
+ if (!transceiver->IsAssociated()) {
+ transceiver->Associate(GetNewMid());
+ } else {
+ mUsedMids.insert(transceiver->GetMid());
+ }
+ } else {
+ transceiver->mTransport.Close();
+ transceiver->Disassociate();
+ // This cannot be rolled back.
+ transceiver->Stop();
+ SetTransceiver(*transceiver);
+ continue;
+ }
+
+ if (msection.GetMediaType() == SdpMediaSection::MediaType::kApplication) {
+ SetTransceiver(*transceiver);
+ continue;
+ }
+
+ transceiver->mSendTrack.SendTrackSetRemote(mSsrcGenerator, msection);
+
+ // Interop workaround for endpoints that don't support msid.
+ // Ensures that there is a default stream id set, provided the remote is
+ // sending.
+ // TODO(bug 1426005): Remove this, or at least move it to JsepTrack.
+ transceiver->mRecvTrack.UpdateStreamIds({mDefaultRemoteStreamId});
+
+ // This will process a=msid if present, or clear the stream ids if the
+ // msection is not sending. If the msection is sending, and there are no
+ // a=msid, the previously set default will stay.
+ transceiver->mRecvTrack.RecvTrackSetRemote(remote, msection);
+ SetTransceiver(*transceiver);
+ }
+
+ return NS_OK;
+}
+
+Maybe<JsepTransceiver> JsepSessionImpl::FindUnassociatedTransceiver(
+ SdpMediaSection::MediaType type, bool magic) {
+ // Look through transceivers that are not mapped to an m-section
+ for (auto& transceiver : mTransceivers) {
+ if (type == SdpMediaSection::kApplication &&
+ type == transceiver.GetMediaType()) {
+ transceiver.RestartDatachannelTransceiver();
+ return Some(transceiver);
+ }
+ if (!transceiver.IsStopped() && !transceiver.HasLevel() &&
+ (!magic || transceiver.HasAddTrackMagic()) &&
+ (transceiver.GetMediaType() == type)) {
+ return Some(transceiver);
+ }
+ }
+
+ return Nothing();
+}
+
+void JsepSessionImpl::RollbackLocalOffer() {
+ for (size_t i = 0; i < mTransceivers.size(); ++i) {
+ auto& transceiver = mTransceivers[i];
+ if (mOldTransceivers.size() > i) {
+ transceiver.Rollback(mOldTransceivers[i], false);
+ mOldTransceivers[i] = transceiver;
+ continue;
+ }
+
+ JsepTransceiver temp(transceiver.GetMediaType(), *mUuidGen);
+ InitTransceiver(temp);
+ transceiver.Rollback(temp, false);
+ mOldTransceivers.push_back(transceiver);
+ }
+
+ mTransceivers = std::move(mOldTransceivers);
+}
+
+void JsepSessionImpl::RollbackRemoteOffer() {
+ for (size_t i = 0; i < mTransceivers.size(); ++i) {
+ auto& transceiver = mTransceivers[i];
+ if (mOldTransceivers.size() > i) {
+ // Some stuff cannot be rolled back. Save this information.
+ transceiver.Rollback(mOldTransceivers[i], true);
+ mOldTransceivers[i] = transceiver;
+ continue;
+ }
+
+ // New transceiver!
+ // We rollback even for transceivers we will remove, just to ensure we end
+ // up at the starting state.
+ JsepTransceiver temp(transceiver.GetMediaType(), *mUuidGen);
+ InitTransceiver(temp);
+ transceiver.Rollback(temp, true);
+
+ if (transceiver.OnlyExistsBecauseOfSetRemote()) {
+ transceiver.Stop();
+ transceiver.SetRemoved();
+ }
+ mOldTransceivers.push_back(transceiver);
+ }
+
+ mTransceivers = std::move(mOldTransceivers);
+}
+
+nsresult JsepSessionImpl::ValidateLocalDescription(const Sdp& description,
+ JsepSdpType type) {
+ Sdp* generated = nullptr;
+ // TODO(bug 1095226): Better checking.
+ if (type == kJsepSdpOffer) {
+ generated = mGeneratedOffer.get();
+ } else {
+ generated = mGeneratedAnswer.get();
+ }
+
+ if (!generated) {
+ JSEP_SET_ERROR(
+ "Calling SetLocal without first calling CreateOffer/Answer"
+ " is not supported.");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (description.GetMediaSectionCount() != generated->GetMediaSectionCount()) {
+ JSEP_SET_ERROR("Changing the number of m-sections is not allowed");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ for (size_t i = 0; i < description.GetMediaSectionCount(); ++i) {
+ auto& origMsection = generated->GetMediaSection(i);
+ auto& finalMsection = description.GetMediaSection(i);
+ if (origMsection.GetMediaType() != finalMsection.GetMediaType()) {
+ JSEP_SET_ERROR("Changing the media-type of m-sections is not allowed");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // These will be present in reoffer
+ if (!mCurrentLocalDescription) {
+ if (finalMsection.GetAttributeList().HasAttribute(
+ SdpAttribute::kCandidateAttribute)) {
+ JSEP_SET_ERROR("Adding your own candidate attributes is not supported");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (finalMsection.GetAttributeList().HasAttribute(
+ SdpAttribute::kEndOfCandidatesAttribute)) {
+ JSEP_SET_ERROR("Why are you trying to set a=end-of-candidates?");
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ if (mSdpHelper.MsectionIsDisabled(finalMsection)) {
+ continue;
+ }
+
+ if (!finalMsection.GetAttributeList().HasAttribute(
+ SdpAttribute::kMidAttribute)) {
+ JSEP_SET_ERROR("Local descriptions must have a=mid attributes.");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (finalMsection.GetAttributeList().GetMid() !=
+ origMsection.GetAttributeList().GetMid()) {
+ JSEP_SET_ERROR("Changing the mid of m-sections is not allowed.");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // TODO(bug 1095218): Check msid
+ // TODO(bug 1095226): Check ice-ufrag and ice-pwd
+ // TODO(bug 1095226): Check fingerprints
+ // TODO(bug 1095226): Check payload types (at least ensure that payload
+ // types we don't actually support weren't added)
+ // TODO(bug 1095226): Check ice-options?
+ }
+
+ if (description.GetAttributeList().HasAttribute(
+ SdpAttribute::kIceLiteAttribute)) {
+ JSEP_SET_ERROR("Running ICE in lite mode is unsupported");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return NS_OK;
+}
+
+nsresult JsepSessionImpl::ValidateRemoteDescription(const Sdp& description) {
+ if (!mCurrentLocalDescription) {
+ // Initial offer; nothing to validate besides the stuff in ParseSdp
+ return NS_OK;
+ }
+
+ if (mCurrentLocalDescription->GetMediaSectionCount() >
+ description.GetMediaSectionCount()) {
+ JSEP_SET_ERROR(
+ "New remote description has fewer m-sections than the "
+ "previous remote description.");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ for (size_t i = 0; i < description.GetMediaSectionCount(); ++i) {
+ const SdpAttributeList& attrs =
+ description.GetMediaSection(i).GetAttributeList();
+
+ if (attrs.HasAttribute(SdpAttribute::kExtmapAttribute)) {
+ for (const auto& ext : attrs.GetExtmap().mExtmaps) {
+ if (mExtmapEntriesEverNegotiated.count(ext.entry) &&
+ mExtmapEntriesEverNegotiated[ext.entry] != ext.extensionname) {
+ JSEP_SET_ERROR(
+ "Remote description attempted to remap RTP extension id "
+ << ext.entry << " from "
+ << mExtmapEntriesEverNegotiated[ext.entry] << " to "
+ << ext.extensionname);
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+ }
+ }
+
+ if (!mCurrentRemoteDescription) {
+ // No further checking for initial answers
+ return NS_OK;
+ }
+
+ // These are solely to check that bundle is valid
+ SdpHelper::BundledMids bundledMids;
+ nsresult rv = GetNegotiatedBundledMids(&bundledMids);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ SdpHelper::BundledMids newBundledMids;
+ rv = mSdpHelper.GetBundledMids(description, &newBundledMids);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // check for partial ice restart, which is not supported
+ Maybe<bool> iceCredsDiffer;
+ for (size_t i = 0; i < mCurrentRemoteDescription->GetMediaSectionCount();
+ ++i) {
+ const SdpMediaSection& newMsection = description.GetMediaSection(i);
+ const SdpMediaSection& oldMsection =
+ mCurrentRemoteDescription->GetMediaSection(i);
+
+ if (mSdpHelper.MsectionIsDisabled(newMsection) ||
+ mSdpHelper.MsectionIsDisabled(oldMsection)) {
+ continue;
+ }
+
+ if (oldMsection.GetMediaType() != newMsection.GetMediaType()) {
+ JSEP_SET_ERROR("Remote description changes the media type of m-line "
+ << i);
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ bool differ = mSdpHelper.IceCredentialsDiffer(newMsection, oldMsection);
+
+ if (mIsPendingOfferer.isSome() && *mIsPendingOfferer && differ &&
+ !IsIceRestarting()) {
+ JSEP_SET_ERROR(
+ "Remote description indicates ICE restart but offer did not "
+ "request ICE restart (new remote description changes either "
+ "the ice-ufrag or ice-pwd)");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Detect whether all the creds are the same or all are different
+ if (!iceCredsDiffer.isSome()) {
+ // for the first msection capture whether creds are different or same
+ iceCredsDiffer = mozilla::Some(differ);
+ } else if (iceCredsDiffer.isSome() && *iceCredsDiffer != differ) {
+ // subsequent msections must match the first sections
+ JSEP_SET_ERROR(
+ "Partial ICE restart is unsupported at this time "
+ "(new remote description changes either the ice-ufrag "
+ "or ice-pwd on fewer than all msections)");
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult JsepSessionImpl::ValidateOffer(const Sdp& offer) {
+ return mSdpHelper.ValidateTransportAttributes(offer, sdp::kOffer);
+}
+
+nsresult JsepSessionImpl::ValidateAnswer(const Sdp& offer, const Sdp& answer) {
+ if (offer.GetMediaSectionCount() != answer.GetMediaSectionCount()) {
+ JSEP_SET_ERROR("Offer and answer have different number of m-lines "
+ << "(" << offer.GetMediaSectionCount() << " vs "
+ << answer.GetMediaSectionCount() << ")");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsresult rv = mSdpHelper.ValidateTransportAttributes(answer, sdp::kAnswer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (size_t i = 0; i < offer.GetMediaSectionCount(); ++i) {
+ const SdpMediaSection& offerMsection = offer.GetMediaSection(i);
+ const SdpMediaSection& answerMsection = answer.GetMediaSection(i);
+
+ if (offerMsection.GetMediaType() != answerMsection.GetMediaType()) {
+ JSEP_SET_ERROR("Answer and offer have different media types at m-line "
+ << i);
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (mSdpHelper.MsectionIsDisabled(answerMsection)) {
+ continue;
+ }
+
+ if (mSdpHelper.MsectionIsDisabled(offerMsection)) {
+ JSEP_SET_ERROR(
+ "Answer tried to enable an m-section that was disabled in the offer");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (!offerMsection.IsSending() && answerMsection.IsReceiving()) {
+ JSEP_SET_ERROR("Answer tried to set recv when offer did not set send");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (!offerMsection.IsReceiving() && answerMsection.IsSending()) {
+ JSEP_SET_ERROR("Answer tried to set send when offer did not set recv");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ const SdpAttributeList& answerAttrs(answerMsection.GetAttributeList());
+ const SdpAttributeList& offerAttrs(offerMsection.GetAttributeList());
+ if (answerAttrs.HasAttribute(SdpAttribute::kMidAttribute) &&
+ offerAttrs.HasAttribute(SdpAttribute::kMidAttribute) &&
+ offerAttrs.GetMid() != answerAttrs.GetMid()) {
+ JSEP_SET_ERROR("Answer changes mid for level, was \'"
+ << offerMsection.GetAttributeList().GetMid()
+ << "\', now \'"
+ << answerMsection.GetAttributeList().GetMid() << "\'");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Sanity check extmap
+ if (answerAttrs.HasAttribute(SdpAttribute::kExtmapAttribute)) {
+ if (!offerAttrs.HasAttribute(SdpAttribute::kExtmapAttribute)) {
+ JSEP_SET_ERROR("Answer adds extmap attributes to level " << i);
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ for (const auto& ansExt : answerAttrs.GetExtmap().mExtmaps) {
+ bool found = false;
+ for (const auto& offExt : offerAttrs.GetExtmap().mExtmaps) {
+ if (ansExt.extensionname == offExt.extensionname) {
+ if ((ansExt.direction & reverse(offExt.direction)) !=
+ ansExt.direction) {
+ // FIXME we do not return an error here, because Chrome up to
+ // version 57 is actually tripping over this if they are the
+ // answerer. See bug 1355010 for details.
+ MOZ_MTLOG(ML_WARNING,
+ "[" << mName
+ << "]: Answer has inconsistent"
+ " direction on extmap attribute at level "
+ << i << " (" << ansExt.extensionname
+ << "). Offer had " << offExt.direction
+ << ", answer had " << ansExt.direction << ".");
+ // return NS_ERROR_INVALID_ARG;
+ }
+
+ if (offExt.entry < 4096 && (offExt.entry != ansExt.entry)) {
+ JSEP_SET_ERROR("Answer changed id for extmap attribute at level "
+ << i << " (" << offExt.extensionname << ") from "
+ << offExt.entry << " to " << ansExt.entry << ".");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (ansExt.entry >= 4096) {
+ JSEP_SET_ERROR("Answer used an invalid id ("
+ << ansExt.entry
+ << ") for extmap attribute at level " << i << " ("
+ << ansExt.extensionname << ").");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ JSEP_SET_ERROR("Answer has extmap "
+ << ansExt.extensionname
+ << " at "
+ "level "
+ << i << " that was not present in offer.");
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult JsepSessionImpl::CreateGenericSDP(UniquePtr<Sdp>* sdpp) {
+ // draft-ietf-rtcweb-jsep-08 Section 5.2.1:
+ // o The second SDP line MUST be an "o=" line, as specified in
+ // [RFC4566], Section 5.2. The value of the <username> field SHOULD
+ // be "-". The value of the <sess-id> field SHOULD be a
+ // cryptographically random number. To ensure uniqueness, this
+ // number SHOULD be at least 64 bits long. The value of the <sess-
+ // version> field SHOULD be zero. The value of the <nettype>
+ // <addrtype> <unicast-address> tuple SHOULD be set to a non-
+ // meaningful address, such as IN IP4 0.0.0.0, to prevent leaking the
+ // local address in this field. As mentioned in [RFC4566], the
+ // entire o= line needs to be unique, but selecting a random number
+ // for <sess-id> is sufficient to accomplish this.
+ //
+ // Historical note: we used to report the actual version number here, after
+ // "SDPARTA-", but that becomes a problem starting with version 100, since
+ // some services parse 100 as "10" and give us legacy/broken behavior. So
+ // we're freezing the version number at 99.0 in this string.
+ auto origin = SdpOrigin("mozilla...THIS_IS_SDPARTA-99.0", mSessionId,
+ mSessionVersion, sdp::kIPv4, "0.0.0.0");
+
+ UniquePtr<Sdp> sdp = MakeUnique<SipccSdp>(origin);
+
+ if (mDtlsFingerprints.empty()) {
+ JSEP_SET_ERROR("Missing DTLS fingerprint");
+ return NS_ERROR_FAILURE;
+ }
+
+ UniquePtr<SdpFingerprintAttributeList> fpl =
+ MakeUnique<SdpFingerprintAttributeList>();
+ for (auto& dtlsFingerprint : mDtlsFingerprints) {
+ fpl->PushEntry(dtlsFingerprint.mAlgorithm, dtlsFingerprint.mValue);
+ }
+ sdp->GetAttributeList().SetAttribute(fpl.release());
+
+ auto* iceOpts = new SdpOptionsAttribute(SdpAttribute::kIceOptionsAttribute);
+ iceOpts->PushEntry("trickle");
+ sdp->GetAttributeList().SetAttribute(iceOpts);
+
+ // This assumes content doesn't add a bunch of msid attributes with a
+ // different semantic in mind.
+ std::vector<std::string> msids;
+ msids.push_back("*");
+ mSdpHelper.SetupMsidSemantic(msids, sdp.get());
+
+ *sdpp = std::move(sdp);
+ return NS_OK;
+}
+
+nsresult JsepSessionImpl::SetupIds() {
+ SECStatus rv = PK11_GenerateRandom(
+ reinterpret_cast<unsigned char*>(&mSessionId), sizeof(mSessionId));
+ // RFC 3264 says that session-ids MUST be representable as a _signed_
+ // 64 bit number, meaning the MSB cannot be set.
+ mSessionId = mSessionId >> 1;
+ if (rv != SECSuccess) {
+ JSEP_SET_ERROR("Failed to generate session id: " << rv);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mUuidGen->Generate(&mDefaultRemoteStreamId)) {
+ JSEP_SET_ERROR("Failed to generate default uuid for streams");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mUuidGen->Generate(&mCNAME)) {
+ JSEP_SET_ERROR("Failed to generate CNAME");
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+void JsepSessionImpl::SetDefaultCodecs(
+ const std::vector<UniquePtr<JsepCodecDescription>>& aPreferredCodecs) {
+ mSupportedCodecs.clear();
+
+ for (const auto& codec : aPreferredCodecs) {
+ mSupportedCodecs.emplace_back(codec->Clone());
+ }
+}
+
+void JsepSessionImpl::SetState(JsepSignalingState state) {
+ if (state == mState) return;
+
+ MOZ_MTLOG(ML_NOTICE, "[" << mName << "]: " << GetStateStr(mState) << " -> "
+ << GetStateStr(state));
+ mState = state;
+}
+
+JsepSession::Result JsepSessionImpl::AddRemoteIceCandidate(
+ const std::string& candidate, const std::string& mid,
+ const Maybe<uint16_t>& level, const std::string& ufrag,
+ std::string* transportId) {
+ mLastError.clear();
+ if (!mCurrentRemoteDescription && !mPendingRemoteDescription) {
+ JSEP_SET_ERROR("Cannot add ICE candidate when there is no remote SDP");
+ return dom::PCError::InvalidStateError;
+ }
+
+ if (mid.empty() && !level.isSome() && candidate.empty()) {
+ // Set end-of-candidates on SDP
+ if (mCurrentRemoteDescription) {
+ nsresult rv = mSdpHelper.SetIceGatheringComplete(
+ mCurrentRemoteDescription.get(), ufrag);
+ NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
+ }
+
+ if (mPendingRemoteDescription) {
+ // If we had an error when adding the candidate to the current
+ // description, we stomp them here. This is deliberate.
+ nsresult rv = mSdpHelper.SetIceGatheringComplete(
+ mPendingRemoteDescription.get(), ufrag);
+ NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
+ }
+ return Result();
+ }
+
+ Maybe<JsepTransceiver> transceiver;
+ if (!mid.empty()) {
+ transceiver = GetTransceiverForMid(mid);
+ } else if (level.isSome()) {
+ transceiver = GetTransceiverForLevel(level.value());
+ }
+
+ if (!transceiver) {
+ JSEP_SET_ERROR("Cannot set ICE candidate for level="
+ << level << " mid=" << mid << ": No such transceiver.");
+ return dom::PCError::OperationError;
+ }
+
+ if (level.isSome() && transceiver->GetLevel() != level.value()) {
+ MOZ_MTLOG(ML_WARNING, "Mismatch between mid and level - \""
+ << mid << "\" is not the mid for level "
+ << level);
+ }
+
+ *transportId = transceiver->mTransport.mTransportId;
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ if (mCurrentRemoteDescription) {
+ rv =
+ mSdpHelper.AddCandidateToSdp(mCurrentRemoteDescription.get(), candidate,
+ transceiver->GetLevel(), ufrag);
+ }
+
+ if (mPendingRemoteDescription) {
+ // If we had an error when adding the candidate to the current description,
+ // we stomp them here. This is deliberate.
+ rv =
+ mSdpHelper.AddCandidateToSdp(mPendingRemoteDescription.get(), candidate,
+ transceiver->GetLevel(), ufrag);
+ }
+
+ NS_ENSURE_SUCCESS(rv, dom::PCError::OperationError);
+ return Result();
+}
+
+nsresult JsepSessionImpl::AddLocalIceCandidate(const std::string& candidate,
+ const std::string& transportId,
+ const std::string& ufrag,
+ uint16_t* level,
+ std::string* mid,
+ bool* skipped) {
+ mLastError.clear();
+ *skipped = true;
+ if (!mCurrentLocalDescription && !mPendingLocalDescription) {
+ JSEP_SET_ERROR("Cannot add ICE candidate when there is no local SDP");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ Maybe<const JsepTransceiver> transceiver =
+ GetTransceiverWithTransport(transportId);
+ if (!transceiver || !transceiver->IsAssociated()) {
+ // mainly here to make some testing less complicated, but also just in case
+ return NS_OK;
+ }
+
+ *level = transceiver->GetLevel();
+ *mid = transceiver->GetMid();
+
+ nsresult rv = NS_ERROR_INVALID_ARG;
+ if (mCurrentLocalDescription) {
+ rv = mSdpHelper.AddCandidateToSdp(mCurrentLocalDescription.get(), candidate,
+ *level, ufrag);
+ }
+
+ if (mPendingLocalDescription) {
+ // If we had an error when adding the candidate to the current description,
+ // we stomp them here. This is deliberate.
+ rv = mSdpHelper.AddCandidateToSdp(mPendingLocalDescription.get(), candidate,
+ *level, ufrag);
+ }
+
+ *skipped = false;
+ return rv;
+}
+
+nsresult JsepSessionImpl::UpdateDefaultCandidate(
+ const std::string& defaultCandidateAddr, uint16_t defaultCandidatePort,
+ const std::string& defaultRtcpCandidateAddr,
+ uint16_t defaultRtcpCandidatePort, const std::string& transportId) {
+ mLastError.clear();
+
+ mozilla::Sdp* sdp =
+ GetParsedLocalDescription(kJsepDescriptionPendingOrCurrent);
+
+ if (!sdp) {
+ JSEP_SET_ERROR("Cannot add ICE candidate in state " << GetStateStr(mState));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ for (const auto& transceiver : mTransceivers) {
+ // We set the default address for bundled m-sections, but not candidate
+ // attributes. Ugh.
+ if (transceiver.mTransport.mTransportId == transportId) {
+ MOZ_ASSERT(transceiver.HasLevel(),
+ "Transceiver has a transport, but no level! "
+ "This should never happen.");
+ std::string defaultRtcpCandidateAddrCopy(defaultRtcpCandidateAddr);
+ if (mState == kJsepStateStable) {
+ if (transceiver.mTransport.mComponents == 1) {
+ // We know we're doing rtcp-mux by now. Don't create an rtcp attr.
+ defaultRtcpCandidateAddrCopy = "";
+ defaultRtcpCandidatePort = 0;
+ }
+ }
+
+ size_t level = transceiver.GetLevel();
+ if (level >= sdp->GetMediaSectionCount()) {
+ MOZ_ASSERT(false, "Transceiver's level is too large!");
+ JSEP_SET_ERROR("Transceiver's level is too large!");
+ return NS_ERROR_FAILURE;
+ }
+
+ auto& msection = sdp->GetMediaSection(level);
+
+ // Do not add default candidate to a bundle-only m-section, sinice that
+ // might confuse endpoints that do not support bundle-only.
+ if (!msection.GetAttributeList().HasAttribute(
+ SdpAttribute::kBundleOnlyAttribute)) {
+ mSdpHelper.SetDefaultAddresses(
+ defaultCandidateAddr, defaultCandidatePort,
+ defaultRtcpCandidateAddrCopy, defaultRtcpCandidatePort, &msection);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult JsepSessionImpl::GetNegotiatedBundledMids(
+ SdpHelper::BundledMids* bundledMids) {
+ const Sdp* answerSdp = GetAnswer();
+
+ if (!answerSdp) {
+ return NS_OK;
+ }
+
+ return mSdpHelper.GetBundledMids(*answerSdp, bundledMids);
+}
+
+mozilla::Sdp* JsepSessionImpl::GetParsedLocalDescription(
+ JsepDescriptionPendingOrCurrent type) const {
+ if (type == kJsepDescriptionPending) {
+ return mPendingLocalDescription.get();
+ } else if (mPendingLocalDescription &&
+ type == kJsepDescriptionPendingOrCurrent) {
+ return mPendingLocalDescription.get();
+ }
+ return mCurrentLocalDescription.get();
+}
+
+mozilla::Sdp* JsepSessionImpl::GetParsedRemoteDescription(
+ JsepDescriptionPendingOrCurrent type) const {
+ if (type == kJsepDescriptionPending) {
+ return mPendingRemoteDescription.get();
+ } else if (mPendingRemoteDescription &&
+ type == kJsepDescriptionPendingOrCurrent) {
+ return mPendingRemoteDescription.get();
+ }
+ return mCurrentRemoteDescription.get();
+}
+
+const Sdp* JsepSessionImpl::GetAnswer() const {
+ return (mIsCurrentOfferer.isSome() && *mIsCurrentOfferer)
+ ? mCurrentRemoteDescription.get()
+ : mCurrentLocalDescription.get();
+}
+
+void JsepSessionImpl::SetIceRestarting(bool restarting) {
+ if (restarting) {
+ // not restarting -> restarting
+ if (!IsIceRestarting()) {
+ // We don't set this more than once, so the old ufrag/pwd is preserved
+ // even if we CreateOffer({iceRestart:true}) multiple times in a row.
+ mOldIceUfrag = mIceUfrag;
+ mOldIcePwd = mIcePwd;
+ }
+ mIceUfrag = GetRandomHex(1);
+ mIcePwd = GetRandomHex(4);
+ } else if (IsIceRestarting()) {
+ // restarting -> not restarting, restore old ufrag/pwd
+ mIceUfrag = mOldIceUfrag;
+ mIcePwd = mOldIcePwd;
+ mOldIceUfrag.clear();
+ mOldIcePwd.clear();
+ }
+}
+
+nsresult JsepSessionImpl::Close() {
+ mLastError.clear();
+ SetState(kJsepStateClosed);
+ return NS_OK;
+}
+
+const std::string JsepSessionImpl::GetLastError() const { return mLastError; }
+
+const std::vector<std::pair<size_t, std::string>>&
+JsepSessionImpl::GetLastSdpParsingErrors() const {
+ return mLastSdpParsingErrors;
+}
+
+bool JsepSessionImpl::CheckNegotiationNeeded() const {
+ MOZ_ASSERT(mState == kJsepStateStable);
+
+ for (const auto& transceiver : mTransceivers) {
+ if (transceiver.IsStopped()) {
+ if (transceiver.IsAssociated()) {
+ MOZ_MTLOG(ML_DEBUG, "[" << mName
+ << "]: Negotiation needed because of "
+ "stopped transceiver that still has a mid.");
+ return true;
+ }
+ continue;
+ }
+
+ if (!transceiver.IsAssociated()) {
+ MOZ_MTLOG(ML_DEBUG, "[" << mName
+ << "]: Negotiation needed because of "
+ "unassociated (but not stopped) transceiver.");
+ return true;
+ }
+
+ if (!mCurrentLocalDescription || !mCurrentRemoteDescription) {
+ MOZ_CRASH(
+ "Transceivers should not be associated if we're in stable "
+ "before the first negotiation.");
+ continue;
+ }
+
+ if (!transceiver.HasLevel()) {
+ MOZ_CRASH("Associated transceivers should always have a level.");
+ continue;
+ }
+
+ if (transceiver.GetMediaType() == SdpMediaSection::kApplication) {
+ continue;
+ }
+
+ size_t level = transceiver.GetLevel();
+ if (NS_WARN_IF(mCurrentLocalDescription->GetMediaSectionCount() <= level) ||
+ NS_WARN_IF(mCurrentRemoteDescription->GetMediaSectionCount() <=
+ level)) {
+ MOZ_ASSERT(false);
+ continue;
+ }
+
+ const SdpMediaSection& local =
+ mCurrentLocalDescription->GetMediaSection(level);
+ const SdpMediaSection& remote =
+ mCurrentRemoteDescription->GetMediaSection(level);
+
+ if (transceiver.mJsDirection & sdp::kSend) {
+ std::vector<std::string> sdpMsids;
+ if (local.GetAttributeList().HasAttribute(SdpAttribute::kMsidAttribute)) {
+ for (const auto& msidAttr : local.GetAttributeList().GetMsid().mMsids) {
+ if (msidAttr.identifier != "-") {
+ sdpMsids.push_back(msidAttr.identifier);
+ }
+ }
+ }
+ std::sort(sdpMsids.begin(), sdpMsids.end());
+
+ std::vector<std::string> jsepMsids;
+ for (const auto& jsepMsid : transceiver.mSendTrack.GetStreamIds()) {
+ jsepMsids.push_back(jsepMsid);
+ }
+ std::sort(jsepMsids.begin(), jsepMsids.end());
+
+ if (!std::equal(sdpMsids.begin(), sdpMsids.end(), jsepMsids.begin(),
+ jsepMsids.end())) {
+ MOZ_MTLOG(ML_DEBUG,
+ "[" << mName
+ << "]: Negotiation needed because transceiver "
+ "is sending, and the local SDP has different "
+ "msids than the send track");
+ MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: SDP msids = [");
+ for (const auto& msid : sdpMsids) {
+ MOZ_MTLOG(ML_DEBUG, msid << ", ");
+ }
+ MOZ_MTLOG(ML_DEBUG, "]");
+ MOZ_MTLOG(ML_DEBUG, "[" << mName << "]: JSEP msids = [");
+ for (const auto& msid : jsepMsids) {
+ MOZ_MTLOG(ML_DEBUG, msid << ", ");
+ }
+ MOZ_MTLOG(ML_DEBUG, "]");
+ return true;
+ }
+ }
+
+ if (mIsCurrentOfferer.isSome() && *mIsCurrentOfferer) {
+ if ((local.GetDirection() != transceiver.mJsDirection) &&
+ reverse(remote.GetDirection()) != transceiver.mJsDirection) {
+ MOZ_MTLOG(ML_DEBUG, "[" << mName
+ << "]: Negotiation needed because "
+ "the direction on our offer, and the remote "
+ "answer, does not "
+ "match the direction on a transceiver.");
+ return true;
+ }
+ } else if (local.GetDirection() !=
+ (transceiver.mJsDirection & reverse(remote.GetDirection()))) {
+ MOZ_MTLOG(
+ ML_DEBUG,
+ "[" << mName
+ << "]: Negotiation needed because "
+ "the direction on our answer doesn't match the direction on a "
+ "transceiver, even though the remote offer would have allowed "
+ "it.");
+ return true;
+ }
+ }
+
+ return false;
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/jsep/JsepSessionImpl.h b/dom/media/webrtc/jsep/JsepSessionImpl.h
new file mode 100644
index 0000000000..1cdbba9cfc
--- /dev/null
+++ b/dom/media/webrtc/jsep/JsepSessionImpl.h
@@ -0,0 +1,286 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _JSEPSESSIONIMPL_H_
+#define _JSEPSESSIONIMPL_H_
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "jsep/JsepCodecDescription.h"
+#include "jsep/JsepSession.h"
+#include "jsep/JsepTrack.h"
+#include "jsep/JsepTransceiver.h"
+#include "jsep/SsrcGenerator.h"
+#include "sdp/HybridSdpParser.h"
+#include "sdp/SdpHelper.h"
+
+namespace mozilla {
+
+// JsepSessionImpl members that have default copy c'tors, to simplify the
+// implementation of the copy c'tor for JsepSessionImpl
+class JsepSessionCopyableStuff {
+ protected:
+ struct JsepDtlsFingerprint {
+ std::string mAlgorithm;
+ std::vector<uint8_t> mValue;
+ };
+
+ Maybe<bool> mIsPendingOfferer;
+ Maybe<bool> mIsCurrentOfferer;
+ bool mIceControlling = false;
+ std::string mIceUfrag;
+ std::string mIcePwd;
+ std::string mOldIceUfrag;
+ std::string mOldIcePwd;
+ bool mRemoteIsIceLite = false;
+ std::vector<std::string> mIceOptions;
+ JsepBundlePolicy mBundlePolicy = kBundleBalanced;
+ std::vector<JsepDtlsFingerprint> mDtlsFingerprints;
+ uint64_t mSessionId = 0;
+ uint64_t mSessionVersion = 0;
+ size_t mMidCounter = 0;
+ std::set<std::string> mUsedMids;
+ size_t mTransportIdCounter = 0;
+ std::vector<JsepExtmapMediaType> mRtpExtensions;
+ std::set<uint16_t> mExtmapEntriesEverUsed;
+ std::map<uint16_t, std::string> mExtmapEntriesEverNegotiated;
+ std::string mDefaultRemoteStreamId;
+ std::string mCNAME;
+ // Used to prevent duplicate local SSRCs. Not used to prevent local/remote or
+ // remote-only duplication, which will be important for EKT but not now.
+ std::set<uint32_t> mSsrcs;
+ std::string mLastError;
+ std::vector<std::pair<size_t, std::string>> mLastSdpParsingErrors;
+ bool mEncodeTrackId = true;
+ SsrcGenerator mSsrcGenerator;
+ // !!!NOT INDEXED BY LEVEL!!! The level mapping is done with
+ // JsepTransceiver::mLevel. The keys are UUIDs.
+ std::vector<JsepTransceiver> mTransceivers;
+ // So we can rollback. Not as simple as just going back to the old, though...
+ std::vector<JsepTransceiver> mOldTransceivers;
+};
+
+class JsepSessionImpl : public JsepSession, public JsepSessionCopyableStuff {
+ public:
+ JsepSessionImpl(const std::string& name, UniquePtr<JsepUuidGenerator> uuidgen)
+ : JsepSession(name),
+ mUuidGen(std::move(uuidgen)),
+ mSdpHelper(&mLastError),
+ mParser(new HybridSdpParser()) {}
+
+ JsepSessionImpl(const JsepSessionImpl& aOrig);
+
+ JsepSession* Clone() const override { return new JsepSessionImpl(*this); }
+
+ // Implement JsepSession methods.
+ virtual nsresult Init() override;
+
+ nsresult SetBundlePolicy(JsepBundlePolicy policy) override;
+
+ virtual bool RemoteIsIceLite() const override { return mRemoteIsIceLite; }
+
+ virtual std::vector<std::string> GetIceOptions() const override {
+ return mIceOptions;
+ }
+
+ virtual nsresult AddDtlsFingerprint(
+ const std::string& algorithm, const std::vector<uint8_t>& value) override;
+
+ virtual nsresult AddRtpExtension(
+ JsepMediaType mediaType, const std::string& extensionName,
+ SdpDirectionAttribute::Direction direction) override;
+ virtual nsresult AddAudioRtpExtension(
+ const std::string& extensionName,
+ SdpDirectionAttribute::Direction direction =
+ SdpDirectionAttribute::Direction::kSendrecv) override;
+
+ virtual nsresult AddVideoRtpExtension(
+ const std::string& extensionName,
+ SdpDirectionAttribute::Direction direction =
+ SdpDirectionAttribute::Direction::kSendrecv) override;
+
+ virtual nsresult AddAudioVideoRtpExtension(
+ const std::string& extensionName,
+ SdpDirectionAttribute::Direction direction =
+ SdpDirectionAttribute::Direction::kSendrecv) override;
+
+ virtual std::vector<UniquePtr<JsepCodecDescription>>& Codecs() override {
+ return mSupportedCodecs;
+ }
+
+ virtual Result CreateOffer(const JsepOfferOptions& options,
+ std::string* offer) override;
+
+ virtual Result CreateAnswer(const JsepAnswerOptions& options,
+ std::string* answer) override;
+
+ virtual std::string GetLocalDescription(
+ JsepDescriptionPendingOrCurrent type) const override;
+
+ virtual std::string GetRemoteDescription(
+ JsepDescriptionPendingOrCurrent type) const override;
+
+ virtual Result SetLocalDescription(JsepSdpType type,
+ const std::string& sdp) override;
+
+ virtual Result SetRemoteDescription(JsepSdpType type,
+ const std::string& sdp) override;
+
+ virtual Result AddRemoteIceCandidate(const std::string& candidate,
+ const std::string& mid,
+ const Maybe<uint16_t>& level,
+ const std::string& ufrag,
+ std::string* transportId) override;
+
+ virtual nsresult AddLocalIceCandidate(const std::string& candidate,
+ const std::string& transportId,
+ const std::string& ufrag,
+ uint16_t* level, std::string* mid,
+ bool* skipped) override;
+
+ virtual nsresult UpdateDefaultCandidate(
+ const std::string& defaultCandidateAddr, uint16_t defaultCandidatePort,
+ const std::string& defaultRtcpCandidateAddr,
+ uint16_t defaultRtcpCandidatePort,
+ const std::string& transportId) override;
+
+ virtual nsresult Close() override;
+
+ virtual const std::string GetLastError() const override;
+
+ virtual const std::vector<std::pair<size_t, std::string>>&
+ GetLastSdpParsingErrors() const override;
+
+ virtual bool IsIceControlling() const override { return mIceControlling; }
+
+ virtual Maybe<bool> IsPendingOfferer() const override {
+ return mIsPendingOfferer;
+ }
+
+ virtual Maybe<bool> IsCurrentOfferer() const override {
+ return mIsCurrentOfferer;
+ }
+
+ virtual bool IsIceRestarting() const override {
+ return !mOldIceUfrag.empty();
+ }
+
+ virtual std::set<std::pair<std::string, std::string>> GetLocalIceCredentials()
+ const override;
+
+ virtual void AddTransceiver(const JsepTransceiver& transceiver) override;
+
+ virtual bool CheckNegotiationNeeded() const override;
+
+ virtual void SetDefaultCodecs(
+ const std::vector<UniquePtr<JsepCodecDescription>>& aPreferredCodecs)
+ override;
+
+ private:
+ friend class JsepSessionTest;
+ virtual const std::vector<JsepTransceiver>& GetTransceivers() const override {
+ return mTransceivers;
+ }
+
+ virtual std::vector<JsepTransceiver>& GetTransceivers() override {
+ return mTransceivers;
+ }
+
+ // Non-const so it can set mLastError
+ nsresult CreateGenericSDP(UniquePtr<Sdp>* sdp);
+ void AddExtmap(SdpMediaSection* msection);
+ std::vector<SdpExtmapAttributeList::Extmap> GetRtpExtensions(
+ const SdpMediaSection& msection);
+ std::string GetNewMid();
+
+ void AddCommonExtmaps(const SdpMediaSection& remoteMsection,
+ SdpMediaSection* msection);
+ uint16_t GetNeverUsedExtmapEntry();
+ nsresult SetupIds();
+ void SetState(JsepSignalingState state);
+ // Non-const so it can set mLastError
+ nsresult ParseSdp(const std::string& sdp, UniquePtr<Sdp>* parsedp);
+ nsresult SetLocalDescriptionOffer(UniquePtr<Sdp> offer);
+ nsresult SetLocalDescriptionAnswer(JsepSdpType type, UniquePtr<Sdp> answer);
+ nsresult SetRemoteDescriptionOffer(UniquePtr<Sdp> offer);
+ nsresult SetRemoteDescriptionAnswer(JsepSdpType type, UniquePtr<Sdp> answer);
+ nsresult ValidateLocalDescription(const Sdp& description, JsepSdpType type);
+ nsresult ValidateRemoteDescription(const Sdp& description);
+ nsresult ValidateOffer(const Sdp& offer);
+ nsresult ValidateAnswer(const Sdp& offer, const Sdp& answer);
+ nsresult UpdateTransceiversFromRemoteDescription(const Sdp& remote);
+ Maybe<JsepTransceiver> GetTransceiverForLevel(size_t level) const;
+ Maybe<JsepTransceiver> GetTransceiverForMid(const std::string& mid) const;
+ Maybe<JsepTransceiver> GetTransceiverForLocal(size_t level);
+ Maybe<JsepTransceiver> GetTransceiverForRemote(
+ const SdpMediaSection& msection);
+ Maybe<JsepTransceiver> GetTransceiverWithTransport(
+ const std::string& transportId) const;
+ // The w3c and IETF specs have a lot of "magical" behavior that happens when
+ // addTrack is used. This was a deliberate design choice. Sadface.
+ Maybe<JsepTransceiver> FindUnassociatedTransceiver(
+ SdpMediaSection::MediaType type, bool magic);
+ // Called for rollback of local description
+ void RollbackLocalOffer();
+ // Called for rollback of remote description
+ void RollbackRemoteOffer();
+ nsresult HandleNegotiatedSession(const UniquePtr<Sdp>& local,
+ const UniquePtr<Sdp>& remote);
+ nsresult AddTransportAttributes(SdpMediaSection* msection,
+ SdpSetupAttribute::Role dtlsRole);
+ nsresult CopyPreviousTransportParams(const Sdp& oldAnswer,
+ const Sdp& offerersPreviousSdp,
+ const Sdp& newOffer, Sdp* newLocal);
+ void EnsureMsid(Sdp* remote);
+ void SetupBundle(Sdp* sdp) const;
+ nsresult CreateOfferMsection(const JsepOfferOptions& options,
+ JsepTransceiver& transceiver, Sdp* local);
+ nsresult CreateAnswerMsection(const JsepAnswerOptions& options,
+ JsepTransceiver& transceiver,
+ const SdpMediaSection& remoteMsection,
+ Sdp* sdp);
+ nsresult DetermineAnswererSetupRole(const SdpMediaSection& remoteMsection,
+ SdpSetupAttribute::Role* rolep);
+ nsresult MakeNegotiatedTransceiver(const SdpMediaSection& remote,
+ const SdpMediaSection& local,
+ JsepTransceiver& transceiverOut);
+ void EnsureHasOwnTransport(const SdpMediaSection& msection,
+ JsepTransceiver& transceiver);
+ void CopyBundleTransports();
+
+ nsresult FinalizeTransport(const SdpAttributeList& remote,
+ const SdpAttributeList& answer,
+ JsepTransport* transport) const;
+
+ nsresult GetNegotiatedBundledMids(SdpHelper::BundledMids* bundledMids);
+
+ nsresult EnableOfferMsection(SdpMediaSection* msection);
+
+ mozilla::Sdp* GetParsedLocalDescription(
+ JsepDescriptionPendingOrCurrent type) const;
+ mozilla::Sdp* GetParsedRemoteDescription(
+ JsepDescriptionPendingOrCurrent type) const;
+ const Sdp* GetAnswer() const;
+ void SetIceRestarting(bool restarting);
+
+ void InitTransceiver(JsepTransceiver& aTransceiver);
+
+ UniquePtr<JsepUuidGenerator> mUuidGen;
+ UniquePtr<Sdp> mGeneratedOffer; // Created but not set.
+ UniquePtr<Sdp> mGeneratedAnswer; // Created but not set.
+ UniquePtr<Sdp> mCurrentLocalDescription;
+ UniquePtr<Sdp> mCurrentRemoteDescription;
+ UniquePtr<Sdp> mPendingLocalDescription;
+ UniquePtr<Sdp> mPendingRemoteDescription;
+ std::vector<UniquePtr<JsepCodecDescription>> mSupportedCodecs;
+ SdpHelper mSdpHelper;
+ UniquePtr<SdpParser> mParser;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/jsep/JsepTrack.cpp b/dom/media/webrtc/jsep/JsepTrack.cpp
new file mode 100644
index 0000000000..f60e4ce0d7
--- /dev/null
+++ b/dom/media/webrtc/jsep/JsepTrack.cpp
@@ -0,0 +1,670 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "jsep/JsepTrack.h"
+#include "jsep/JsepCodecDescription.h"
+#include "jsep/JsepTrackEncoding.h"
+
+#include <algorithm>
+
+namespace mozilla {
+void JsepTrack::GetNegotiatedPayloadTypes(
+ std::vector<uint16_t>* payloadTypes) const {
+ if (!mNegotiatedDetails) {
+ return;
+ }
+
+ for (const auto& encoding : mNegotiatedDetails->mEncodings) {
+ GetPayloadTypes(encoding->GetCodecs(), payloadTypes);
+ }
+
+ // Prune out dupes
+ std::sort(payloadTypes->begin(), payloadTypes->end());
+ auto newEnd = std::unique(payloadTypes->begin(), payloadTypes->end());
+ payloadTypes->erase(newEnd, payloadTypes->end());
+}
+
+/* static */
+void JsepTrack::GetPayloadTypes(
+ const std::vector<UniquePtr<JsepCodecDescription>>& codecs,
+ std::vector<uint16_t>* payloadTypes) {
+ for (const auto& codec : codecs) {
+ uint16_t pt;
+ if (!codec->GetPtAsInt(&pt)) {
+ MOZ_ASSERT(false);
+ continue;
+ }
+ payloadTypes->push_back(pt);
+ }
+}
+
+void JsepTrack::EnsureNoDuplicatePayloadTypes(
+ std::vector<UniquePtr<JsepCodecDescription>>* codecs) {
+ std::set<std::string> uniquePayloadTypes;
+ for (auto& codec : *codecs) {
+ codec->EnsureNoDuplicatePayloadTypes(uniquePayloadTypes);
+ }
+}
+
+void JsepTrack::EnsureSsrcs(SsrcGenerator& ssrcGenerator, size_t aNumber) {
+ while (mSsrcs.size() < aNumber) {
+ uint32_t ssrc, rtxSsrc;
+ if (!ssrcGenerator.GenerateSsrc(&ssrc) ||
+ !ssrcGenerator.GenerateSsrc(&rtxSsrc)) {
+ return;
+ }
+ mSsrcs.push_back(ssrc);
+ mSsrcToRtxSsrc[ssrc] = rtxSsrc;
+ MOZ_ASSERT(mSsrcs.size() == mSsrcToRtxSsrc.size());
+ }
+}
+
+void JsepTrack::PopulateCodecs(
+ const std::vector<UniquePtr<JsepCodecDescription>>& prototype) {
+ mPrototypeCodecs.clear();
+ for (const auto& prototypeCodec : prototype) {
+ if (prototypeCodec->Type() == mType) {
+ mPrototypeCodecs.emplace_back(prototypeCodec->Clone());
+ mPrototypeCodecs.back()->mDirection = mDirection;
+ }
+ }
+
+ EnsureNoDuplicatePayloadTypes(&mPrototypeCodecs);
+}
+
+void JsepTrack::AddToOffer(SsrcGenerator& ssrcGenerator,
+ SdpMediaSection* offer) {
+ AddToMsection(mPrototypeCodecs, offer);
+
+ if (mDirection == sdp::kSend) {
+ std::vector<std::string> rids;
+ if (offer->IsSending()) {
+ rids = mRids;
+ }
+
+ AddToMsection(rids, sdp::kSend, ssrcGenerator,
+ IsRtxEnabled(mPrototypeCodecs), offer);
+ }
+}
+
+void JsepTrack::AddToAnswer(const SdpMediaSection& offer,
+ SsrcGenerator& ssrcGenerator,
+ SdpMediaSection* answer) {
+ // We do not modify mPrototypeCodecs here, since we're only creating an
+ // answer. Once offer/answer concludes, we will update mPrototypeCodecs.
+ std::vector<UniquePtr<JsepCodecDescription>> codecs =
+ NegotiateCodecs(offer, true, Nothing());
+ if (codecs.empty()) {
+ return;
+ }
+
+ AddToMsection(codecs, answer);
+
+ if (mDirection == sdp::kSend) {
+ AddToMsection(mRids, sdp::kSend, ssrcGenerator, IsRtxEnabled(codecs),
+ answer);
+ }
+}
+
+void JsepTrack::SetRids(const std::vector<std::string>& aRids) {
+ MOZ_ASSERT(!aRids.empty());
+ if (!mRids.empty()) {
+ return;
+ }
+ mRids = aRids;
+}
+
+void JsepTrack::SetMaxEncodings(size_t aMax) {
+ mMaxEncodings = aMax;
+ if (mRids.size() > mMaxEncodings) {
+ mRids.resize(mMaxEncodings);
+ }
+}
+
+void JsepTrack::RecvTrackSetRemote(const Sdp& aSdp,
+ const SdpMediaSection& aMsection) {
+ mInHaveRemote = true;
+ MOZ_ASSERT(mDirection == sdp::kRecv);
+ MOZ_ASSERT(aMsection.GetMediaType() !=
+ SdpMediaSection::MediaType::kApplication);
+ std::string error;
+ SdpHelper helper(&error);
+
+ mRemoteSetSendBit = aMsection.IsSending();
+ if (!mRemoteSetSendBit) {
+ mReceptive = false;
+ }
+
+ if (aMsection.IsSending()) {
+ (void)helper.GetIdsFromMsid(aSdp, aMsection, &mStreamIds);
+ } else {
+ mStreamIds.clear();
+ }
+
+ // We do this whether or not the track is active
+ SetCNAME(helper.GetCNAME(aMsection));
+ mSsrcs.clear();
+ if (aMsection.GetAttributeList().HasAttribute(SdpAttribute::kSsrcAttribute)) {
+ for (const auto& ssrcAttr : aMsection.GetAttributeList().GetSsrc().mSsrcs) {
+ mSsrcs.push_back(ssrcAttr.ssrc);
+ }
+ }
+
+ // Use FID ssrc-group to associate rtx ssrcs with "regular" ssrcs. Despite
+ // not being part of RFC 4588, this is how rtx is negotiated by libwebrtc
+ // and jitsi.
+ mSsrcToRtxSsrc.clear();
+ if (aMsection.GetAttributeList().HasAttribute(
+ SdpAttribute::kSsrcGroupAttribute)) {
+ for (const auto& group :
+ aMsection.GetAttributeList().GetSsrcGroup().mSsrcGroups) {
+ if (group.semantics == SdpSsrcGroupAttributeList::kFid &&
+ group.ssrcs.size() == 2) {
+ // Ensure we have a "regular" ssrc for each rtx ssrc.
+ if (std::find(mSsrcs.begin(), mSsrcs.end(), group.ssrcs[0]) !=
+ mSsrcs.end()) {
+ mSsrcToRtxSsrc[group.ssrcs[0]] = group.ssrcs[1];
+
+ // Remove rtx ssrcs from mSsrcs
+ auto res = std::remove_if(
+ mSsrcs.begin(), mSsrcs.end(),
+ [group](uint32_t ssrc) { return ssrc == group.ssrcs[1]; });
+ mSsrcs.erase(res, mSsrcs.end());
+ }
+ }
+ }
+ }
+}
+
+void JsepTrack::RecvTrackSetLocal(const SdpMediaSection& aMsection) {
+ MOZ_ASSERT(mDirection == sdp::kRecv);
+
+ // TODO: Should more stuff live in here? Anything that needs to happen when we
+ // decide we're ready to receive packets should probably go in here.
+ mReceptive = aMsection.IsReceiving();
+}
+
+void JsepTrack::SendTrackSetRemote(SsrcGenerator& aSsrcGenerator,
+ const SdpMediaSection& aRemoteMsection) {
+ mInHaveRemote = true;
+ if (mType == SdpMediaSection::kApplication) {
+ return;
+ }
+
+ std::vector<SdpRidAttributeList::Rid> rids;
+
+ // TODO: Current language in webrtc-pc is completely broken, and so I will
+ // not be quoting it here.
+ if ((mType == SdpMediaSection::kVideo) &&
+ aRemoteMsection.GetAttributeList().HasAttribute(
+ SdpAttribute::kSimulcastAttribute)) {
+ // Note: webrtc-pc does not appear to support the full IETF simulcast
+ // spec. In particular, the IETF simulcast spec supports requesting
+ // multiple different sets of encodings. For example, "a=simulcast:send
+ // 1,2;3,4;5,6" means that there are three simulcast streams, the first of
+ // which can use either rid 1 or 2 (but not both), the second of which can
+ // use rid 3 or 4 (but not both), and the third of which can use rid 5 or
+ // 6 (but not both). webrtc-pc does not support this either/or stuff for
+ // rid; each simulcast stream gets exactly one rid.
+ // Also, webrtc-pc does not support the '~' pause syntax at all
+ // See https://github.com/w3c/webrtc-pc/issues/2769
+ GetRids(aRemoteMsection, sdp::kRecv, &rids);
+ }
+
+ if (mRids.empty()) {
+ // Initial configuration
+ for (const auto& ridAttr : rids) {
+ // TODO: Spec might change, making a length > 16 invalid SDP.
+ std::string dummy;
+ if (SdpRidAttributeList::CheckRidValidity(ridAttr.id, &dummy) &&
+ ridAttr.id.size() <= SdpRidAttributeList::kMaxRidLength) {
+ mRids.push_back(ridAttr.id);
+ }
+ }
+ if (mRids.size() > mMaxEncodings) {
+ mRids.resize(mMaxEncodings);
+ }
+ } else {
+ // JSEP is allowed to remove or reorder rids. RTCRtpSender won't pay
+ // attention to reordering.
+ std::vector<std::string> newRids;
+ for (const auto& ridAttr : rids) {
+ for (const auto& oldRid : mRids) {
+ if (oldRid == ridAttr.id) {
+ newRids.push_back(oldRid);
+ break;
+ }
+ }
+ }
+ mRids = std::move(newRids);
+ }
+
+ if (mRids.empty()) {
+ mRids.push_back("");
+ }
+
+ UpdateSsrcs(aSsrcGenerator, mRids.size());
+}
+
+void JsepTrack::AddToMsection(
+ const std::vector<UniquePtr<JsepCodecDescription>>& codecs,
+ SdpMediaSection* msection) const {
+ MOZ_ASSERT(msection->GetMediaType() == mType);
+ MOZ_ASSERT(!codecs.empty());
+
+ for (const auto& codec : codecs) {
+ codec->AddToMediaSection(*msection);
+ }
+
+ if ((mDirection == sdp::kSend) && (mType != SdpMediaSection::kApplication) &&
+ msection->IsSending()) {
+ if (mStreamIds.empty()) {
+ msection->AddMsid("-", mTrackId);
+ } else {
+ for (const std::string& streamId : mStreamIds) {
+ msection->AddMsid(streamId, mTrackId);
+ }
+ }
+ }
+}
+
+void JsepTrack::UpdateSsrcs(SsrcGenerator& ssrcGenerator, size_t encodings) {
+ MOZ_ASSERT(mDirection == sdp::kSend);
+ MOZ_ASSERT(mType != SdpMediaSection::kApplication);
+ size_t numSsrcs = std::max<size_t>(encodings, 1U);
+
+ EnsureSsrcs(ssrcGenerator, numSsrcs);
+ PruneSsrcs(numSsrcs);
+ if (mNegotiatedDetails && mNegotiatedDetails->GetEncodingCount() > numSsrcs) {
+ mNegotiatedDetails->TruncateEncodings(numSsrcs);
+ }
+
+ MOZ_ASSERT(!mSsrcs.empty());
+}
+
+void JsepTrack::PruneSsrcs(size_t aNumSsrcs) {
+ mSsrcs.resize(aNumSsrcs);
+
+ // We might have duplicate entries in mSsrcs, so we need to resize first and
+ // then remove ummatched rtx ssrcs.
+ auto itor = mSsrcToRtxSsrc.begin();
+ while (itor != mSsrcToRtxSsrc.end()) {
+ if (std::find(mSsrcs.begin(), mSsrcs.end(), itor->first) == mSsrcs.end()) {
+ itor = mSsrcToRtxSsrc.erase(itor);
+ } else {
+ ++itor;
+ }
+ }
+}
+
+bool JsepTrack::IsRtxEnabled(
+ const std::vector<UniquePtr<JsepCodecDescription>>& codecs) const {
+ for (const auto& codec : codecs) {
+ if (codec->Type() == SdpMediaSection::kVideo &&
+ static_cast<const JsepVideoCodecDescription*>(codec.get())
+ ->mRtxEnabled) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void JsepTrack::AddToMsection(const std::vector<std::string>& aRids,
+ sdp::Direction direction,
+ SsrcGenerator& ssrcGenerator, bool rtxEnabled,
+ SdpMediaSection* msection) {
+ if (aRids.size() > 1) {
+ UniquePtr<SdpSimulcastAttribute> simulcast(new SdpSimulcastAttribute);
+ UniquePtr<SdpRidAttributeList> ridAttrs(new SdpRidAttributeList);
+ for (const std::string& rid : aRids) {
+ SdpRidAttributeList::Rid ridAttr;
+ ridAttr.id = rid;
+ ridAttr.direction = direction;
+ ridAttrs->mRids.push_back(ridAttr);
+
+ SdpSimulcastAttribute::Version version;
+ version.choices.push_back(SdpSimulcastAttribute::Encoding(rid, false));
+ if (direction == sdp::kSend) {
+ simulcast->sendVersions.push_back(version);
+ } else {
+ simulcast->recvVersions.push_back(version);
+ }
+ }
+
+ msection->GetAttributeList().SetAttribute(simulcast.release());
+ msection->GetAttributeList().SetAttribute(ridAttrs.release());
+ }
+
+ bool requireRtxSsrcs = rtxEnabled && msection->IsSending();
+
+ if (mType != SdpMediaSection::kApplication && mDirection == sdp::kSend) {
+ UpdateSsrcs(ssrcGenerator, aRids.size());
+
+ if (requireRtxSsrcs) {
+ MOZ_ASSERT(mSsrcs.size() == mSsrcToRtxSsrc.size());
+ std::vector<uint32_t> allSsrcs;
+ UniquePtr<SdpSsrcGroupAttributeList> group(new SdpSsrcGroupAttributeList);
+ for (const auto& ssrc : mSsrcs) {
+ const auto rtxSsrc = mSsrcToRtxSsrc[ssrc];
+ allSsrcs.push_back(ssrc);
+ allSsrcs.push_back(rtxSsrc);
+ group->PushEntry(SdpSsrcGroupAttributeList::kFid, {ssrc, rtxSsrc});
+ }
+ msection->SetSsrcs(allSsrcs, mCNAME);
+ msection->GetAttributeList().SetAttribute(group.release());
+ } else {
+ msection->SetSsrcs(mSsrcs, mCNAME);
+ }
+ }
+}
+
+void JsepTrack::GetRids(const SdpMediaSection& msection,
+ sdp::Direction direction,
+ std::vector<SdpRidAttributeList::Rid>* rids) const {
+ rids->clear();
+ if (!msection.GetAttributeList().HasAttribute(
+ SdpAttribute::kSimulcastAttribute)) {
+ return;
+ }
+
+ const SdpSimulcastAttribute& simulcast(
+ msection.GetAttributeList().GetSimulcast());
+
+ const SdpSimulcastAttribute::Versions* versions = nullptr;
+ switch (direction) {
+ case sdp::kSend:
+ versions = &simulcast.sendVersions;
+ break;
+ case sdp::kRecv:
+ versions = &simulcast.recvVersions;
+ break;
+ }
+
+ if (!versions->IsSet()) {
+ return;
+ }
+
+ // RFC 8853 does not seem to forbid duplicate rids in a simulcast attribute.
+ // So, while this is obviously silly, we should be prepared for it and
+ // ignore those duplicate rids.
+ std::set<std::string> uniqueRids;
+ for (const SdpSimulcastAttribute::Version& version : *versions) {
+ if (!version.choices.empty() && !uniqueRids.count(version.choices[0].rid)) {
+ // We validate that rids are present (and sane) elsewhere.
+ rids->push_back(*msection.FindRid(version.choices[0].rid));
+ uniqueRids.insert(version.choices[0].rid);
+ }
+ }
+}
+
+void JsepTrack::CreateEncodings(
+ const SdpMediaSection& remote,
+ const std::vector<UniquePtr<JsepCodecDescription>>& negotiatedCodecs,
+ JsepTrackNegotiatedDetails* negotiatedDetails) {
+ negotiatedDetails->mTias = remote.GetBandwidth("TIAS");
+
+ webrtc::RtcpMode rtcpMode = webrtc::RtcpMode::kCompound;
+ // rtcp-rsize (video only)
+ if (remote.GetMediaType() == SdpMediaSection::kVideo &&
+ remote.GetAttributeList().HasAttribute(
+ SdpAttribute::kRtcpRsizeAttribute)) {
+ rtcpMode = webrtc::RtcpMode::kReducedSize;
+ }
+ negotiatedDetails->mRtpRtcpConf = RtpRtcpConfig(rtcpMode);
+
+ // TODO add support for b=AS if TIAS is not set (bug 976521)
+
+ if (mRids.empty()) {
+ mRids.push_back("");
+ }
+
+ size_t numEncodings = mRids.size();
+
+ // Drop SSRCs if fewer RIDs were offered than we have encodings
+ if (mSsrcs.size() > numEncodings) {
+ PruneSsrcs(numEncodings);
+ }
+
+ // For each stream make sure we have an encoding, and configure
+ // that encoding appropriately.
+ for (size_t i = 0; i < numEncodings; ++i) {
+ UniquePtr<JsepTrackEncoding> encoding(new JsepTrackEncoding);
+ if (mRids.size() > i) {
+ encoding->mRid = mRids[i];
+ }
+ for (const auto& codec : negotiatedCodecs) {
+ encoding->AddCodec(*codec);
+ }
+ negotiatedDetails->mEncodings.push_back(std::move(encoding));
+ }
+}
+
+std::vector<UniquePtr<JsepCodecDescription>> JsepTrack::GetCodecClones() const {
+ std::vector<UniquePtr<JsepCodecDescription>> clones;
+ for (const auto& codec : mPrototypeCodecs) {
+ clones.emplace_back(codec->Clone());
+ }
+ return clones;
+}
+
+static bool CompareCodec(const UniquePtr<JsepCodecDescription>& lhs,
+ const UniquePtr<JsepCodecDescription>& rhs) {
+ return lhs->mStronglyPreferred && !rhs->mStronglyPreferred;
+}
+
+std::vector<UniquePtr<JsepCodecDescription>> JsepTrack::NegotiateCodecs(
+ const SdpMediaSection& remote, bool remoteIsOffer,
+ Maybe<const SdpMediaSection&> local) {
+ std::vector<UniquePtr<JsepCodecDescription>> negotiatedCodecs;
+ std::vector<UniquePtr<JsepCodecDescription>> newPrototypeCodecs;
+
+ // Outer loop establishes the remote side's preference
+ for (const std::string& fmt : remote.GetFormats()) {
+ for (auto& codec : mPrototypeCodecs) {
+ if (!codec || !codec->mEnabled || !codec->Matches(fmt, remote)) {
+ continue;
+ }
+
+ // First codec of ours that matches. See if we can negotiate it.
+ UniquePtr<JsepCodecDescription> clone(codec->Clone());
+ if (clone->Negotiate(fmt, remote, remoteIsOffer, local)) {
+ // If negotiation succeeded, remember the payload type the other side
+ // used for reoffers.
+ codec->mDefaultPt = fmt;
+
+ // Remember whether we negotiated rtx and the associated pt for later.
+ if (codec->Type() == SdpMediaSection::kVideo) {
+ JsepVideoCodecDescription* videoCodec =
+ static_cast<JsepVideoCodecDescription*>(codec.get());
+ JsepVideoCodecDescription* cloneVideoCodec =
+ static_cast<JsepVideoCodecDescription*>(clone.get());
+ bool useRtx =
+ mRtxIsAllowed &&
+ Preferences::GetBool("media.peerconnection.video.use_rtx", false);
+ videoCodec->mRtxEnabled = useRtx && cloneVideoCodec->mRtxEnabled;
+ videoCodec->mRtxPayloadType = cloneVideoCodec->mRtxPayloadType;
+ }
+
+ // Moves the codec out of mPrototypeCodecs, leaving an empty
+ // UniquePtr, so we don't use it again. Also causes successfully
+ // negotiated codecs to be placed up front in the future.
+ newPrototypeCodecs.emplace_back(std::move(codec));
+ negotiatedCodecs.emplace_back(std::move(clone));
+ break;
+ }
+ }
+ }
+
+ // newPrototypeCodecs contains just the negotiated stuff so far. Add the rest.
+ for (auto& codec : mPrototypeCodecs) {
+ if (codec) {
+ newPrototypeCodecs.emplace_back(std::move(codec));
+ }
+ }
+
+ // Negotiated stuff is up front, so it will take precedence when ensuring
+ // there are no duplicate payload types.
+ EnsureNoDuplicatePayloadTypes(&newPrototypeCodecs);
+ std::swap(newPrototypeCodecs, mPrototypeCodecs);
+
+ // Find the (potential) red codec and ulpfec codec or telephone-event
+ JsepVideoCodecDescription* red = nullptr;
+ JsepVideoCodecDescription* ulpfec = nullptr;
+ JsepAudioCodecDescription* dtmf = nullptr;
+ // We can safely cast here since JsepTrack has a MediaType and only codecs
+ // that match that MediaType (kAudio or kVideo) are added.
+ for (auto& codec : negotiatedCodecs) {
+ if (codec->mName == "red") {
+ red = static_cast<JsepVideoCodecDescription*>(codec.get());
+ } else if (codec->mName == "ulpfec") {
+ ulpfec = static_cast<JsepVideoCodecDescription*>(codec.get());
+ } else if (codec->mName == "telephone-event") {
+ dtmf = static_cast<JsepAudioCodecDescription*>(codec.get());
+ }
+ }
+ // if we have a red codec remove redundant encodings that don't exist
+ if (red) {
+ // Since we could have an externally specified redundant endcodings
+ // list, we shouldn't simply rebuild the redundant encodings list
+ // based on the current list of codecs.
+ std::vector<uint8_t> unnegotiatedEncodings;
+ std::swap(unnegotiatedEncodings, red->mRedundantEncodings);
+ for (auto redundantPt : unnegotiatedEncodings) {
+ std::string pt = std::to_string(redundantPt);
+ for (const auto& codec : negotiatedCodecs) {
+ if (pt == codec->mDefaultPt) {
+ red->mRedundantEncodings.push_back(redundantPt);
+ break;
+ }
+ }
+ }
+ }
+ // Video FEC is indicated by the existence of the red and ulpfec
+ // codecs and not an attribute on the particular video codec (like in
+ // a rtcpfb attr). If we see both red and ulpfec codecs, we enable FEC
+ // on all the other codecs.
+ if (red && ulpfec) {
+ for (auto& codec : negotiatedCodecs) {
+ if (codec->mName != "red" && codec->mName != "ulpfec") {
+ JsepVideoCodecDescription* videoCodec =
+ static_cast<JsepVideoCodecDescription*>(codec.get());
+ videoCodec->EnableFec(red->mDefaultPt, ulpfec->mDefaultPt);
+ }
+ }
+ }
+
+ // Dtmf support is indicated by the existence of the telephone-event
+ // codec, and not an attribute on the particular audio codec (like in a
+ // rtcpfb attr). If we see the telephone-event codec, we enabled dtmf
+ // support on all the other audio codecs.
+ if (dtmf) {
+ for (auto& codec : negotiatedCodecs) {
+ JsepAudioCodecDescription* audioCodec =
+ static_cast<JsepAudioCodecDescription*>(codec.get());
+ audioCodec->mDtmfEnabled = true;
+ }
+ }
+
+ // Make sure strongly preferred codecs are up front, overriding the remote
+ // side's preference.
+ std::stable_sort(negotiatedCodecs.begin(), negotiatedCodecs.end(),
+ CompareCodec);
+
+ if (!red) {
+ // No red, remove ulpfec
+ negotiatedCodecs.erase(
+ std::remove_if(negotiatedCodecs.begin(), negotiatedCodecs.end(),
+ [ulpfec](const UniquePtr<JsepCodecDescription>& codec) {
+ return codec.get() == ulpfec;
+ }),
+ negotiatedCodecs.end());
+ // Make sure there's no dangling ptr here
+ ulpfec = nullptr;
+ }
+
+ return negotiatedCodecs;
+}
+
+nsresult JsepTrack::Negotiate(const SdpMediaSection& answer,
+ const SdpMediaSection& remote,
+ const SdpMediaSection& local) {
+ std::vector<UniquePtr<JsepCodecDescription>> negotiatedCodecs =
+ NegotiateCodecs(remote, &answer != &remote, SomeRef(local));
+
+ if (negotiatedCodecs.empty()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ UniquePtr<JsepTrackNegotiatedDetails> negotiatedDetails =
+ MakeUnique<JsepTrackNegotiatedDetails>();
+
+ CreateEncodings(remote, negotiatedCodecs, negotiatedDetails.get());
+
+ if (answer.GetAttributeList().HasAttribute(SdpAttribute::kExtmapAttribute)) {
+ for (auto& extmapAttr : answer.GetAttributeList().GetExtmap().mExtmaps) {
+ SdpDirectionAttribute::Direction direction = extmapAttr.direction;
+ if (&remote == &answer) {
+ // Answer is remote, we need to flip this.
+ direction = reverse(direction);
+ }
+
+ if (direction & mDirection) {
+ negotiatedDetails->mExtmap[extmapAttr.extensionname] = extmapAttr;
+ }
+ }
+ }
+
+ mInHaveRemote = false;
+ mNegotiatedDetails = std::move(negotiatedDetails);
+ return NS_OK;
+}
+
+// When doing bundle, if all else fails we can try to figure out which m-line a
+// given RTP packet belongs to by looking at the payload type field. This only
+// works, however, if that payload type appeared in only one m-section.
+// We figure that out here.
+/* static */
+void JsepTrack::SetUniquePayloadTypes(std::vector<JsepTrack*>& tracks) {
+ // Maps to track details if no other track contains the payload type,
+ // otherwise maps to nullptr.
+ std::map<uint16_t, JsepTrackNegotiatedDetails*> payloadTypeToDetailsMap;
+
+ for (JsepTrack* track : tracks) {
+ if (track->GetMediaType() == SdpMediaSection::kApplication) {
+ continue;
+ }
+
+ auto* details = track->GetNegotiatedDetails();
+ if (!details) {
+ // Can happen if negotiation fails on a track
+ continue;
+ }
+
+ std::vector<uint16_t> payloadTypesForTrack;
+ track->GetNegotiatedPayloadTypes(&payloadTypesForTrack);
+
+ for (uint16_t pt : payloadTypesForTrack) {
+ if (payloadTypeToDetailsMap.count(pt)) {
+ // Found in more than one track, not unique
+ payloadTypeToDetailsMap[pt] = nullptr;
+ } else {
+ payloadTypeToDetailsMap[pt] = details;
+ }
+ }
+ }
+
+ for (auto ptAndDetails : payloadTypeToDetailsMap) {
+ uint16_t uniquePt = ptAndDetails.first;
+ MOZ_ASSERT(uniquePt <= UINT8_MAX);
+ auto trackDetails = ptAndDetails.second;
+
+ if (trackDetails) {
+ trackDetails->mUniquePayloadTypes.push_back(
+ static_cast<uint8_t>(uniquePt));
+ }
+ }
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/jsep/JsepTrack.h b/dom/media/webrtc/jsep/JsepTrack.h
new file mode 100644
index 0000000000..70583ccb71
--- /dev/null
+++ b/dom/media/webrtc/jsep/JsepTrack.h
@@ -0,0 +1,305 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _JSEPTRACK_H_
+#define _JSEPTRACK_H_
+
+#include <functional>
+#include <algorithm>
+#include <string>
+#include <map>
+#include <set>
+#include <vector>
+
+#include <mozilla/UniquePtr.h>
+#include "mozilla/Preferences.h"
+#include "nsError.h"
+
+#include "jsep/JsepTrackEncoding.h"
+#include "jsep/SsrcGenerator.h"
+#include "sdp/Sdp.h"
+#include "sdp/SdpAttribute.h"
+#include "sdp/SdpMediaSection.h"
+#include "libwebrtcglue/RtpRtcpConfig.h"
+namespace mozilla {
+
+class JsepTrackNegotiatedDetails {
+ public:
+ JsepTrackNegotiatedDetails()
+ : mTias(0), mRtpRtcpConf(webrtc::RtcpMode::kCompound) {}
+
+ JsepTrackNegotiatedDetails(const JsepTrackNegotiatedDetails& orig)
+ : mExtmap(orig.mExtmap),
+ mUniquePayloadTypes(orig.mUniquePayloadTypes),
+ mTias(orig.mTias),
+ mRtpRtcpConf(orig.mRtpRtcpConf) {
+ for (const auto& encoding : orig.mEncodings) {
+ mEncodings.emplace_back(new JsepTrackEncoding(*encoding));
+ }
+ }
+
+ size_t GetEncodingCount() const { return mEncodings.size(); }
+
+ const JsepTrackEncoding& GetEncoding(size_t index) const {
+ MOZ_RELEASE_ASSERT(index < mEncodings.size());
+ return *mEncodings[index];
+ }
+
+ void TruncateEncodings(size_t aSize) {
+ if (mEncodings.size() < aSize) {
+ MOZ_ASSERT(false);
+ return;
+ }
+ mEncodings.resize(aSize);
+ }
+
+ const SdpExtmapAttributeList::Extmap* GetExt(
+ const std::string& ext_name) const {
+ auto it = mExtmap.find(ext_name);
+ if (it != mExtmap.end()) {
+ return &it->second;
+ }
+ return nullptr;
+ }
+
+ void ForEachRTPHeaderExtension(
+ const std::function<void(const SdpExtmapAttributeList::Extmap& extmap)>&
+ fn) const {
+ for (auto entry : mExtmap) {
+ fn(entry.second);
+ }
+ }
+
+ std::vector<uint8_t> GetUniquePayloadTypes() const {
+ return mUniquePayloadTypes;
+ }
+
+ uint32_t GetTias() const { return mTias; }
+
+ RtpRtcpConfig GetRtpRtcpConfig() const { return mRtpRtcpConf; }
+
+ private:
+ friend class JsepTrack;
+
+ std::map<std::string, SdpExtmapAttributeList::Extmap> mExtmap;
+ std::vector<uint8_t> mUniquePayloadTypes;
+ std::vector<UniquePtr<JsepTrackEncoding>> mEncodings;
+ uint32_t mTias; // bits per second
+ RtpRtcpConfig mRtpRtcpConf;
+};
+
+class JsepTrack {
+ public:
+ JsepTrack(mozilla::SdpMediaSection::MediaType type, sdp::Direction direction)
+ : mType(type),
+ mDirection(direction),
+ mActive(false),
+ mRemoteSetSendBit(false) {}
+
+ virtual ~JsepTrack() {}
+
+ void UpdateStreamIds(const std::vector<std::string>& streamIds) {
+ mStreamIds = streamIds;
+ }
+
+ void SetTrackId(const std::string& aTrackId) { mTrackId = aTrackId; }
+
+ void ClearStreamIds() { mStreamIds.clear(); }
+
+ void RecvTrackSetRemote(const Sdp& aSdp, const SdpMediaSection& aMsection);
+ void RecvTrackSetLocal(const SdpMediaSection& aMsection);
+
+ // This is called whenever a remote description is set; we do not wait for
+ // offer/answer to complete, since there's nothing to actually negotiate here.
+ void SendTrackSetRemote(SsrcGenerator& aSsrcGenerator,
+ const SdpMediaSection& aRemoteMsection);
+
+ JsepTrack(const JsepTrack& orig) { *this = orig; }
+
+ JsepTrack(JsepTrack&& orig) = default;
+ JsepTrack& operator=(JsepTrack&& rhs) = default;
+
+ JsepTrack& operator=(const JsepTrack& rhs) {
+ if (this != &rhs) {
+ mType = rhs.mType;
+ mStreamIds = rhs.mStreamIds;
+ mTrackId = rhs.mTrackId;
+ mCNAME = rhs.mCNAME;
+ mDirection = rhs.mDirection;
+ mRids = rhs.mRids;
+ mSsrcs = rhs.mSsrcs;
+ mSsrcToRtxSsrc = rhs.mSsrcToRtxSsrc;
+ mActive = rhs.mActive;
+ mRemoteSetSendBit = rhs.mRemoteSetSendBit;
+ mReceptive = rhs.mReceptive;
+ mMaxEncodings = rhs.mMaxEncodings;
+ mInHaveRemote = rhs.mInHaveRemote;
+ mRtxIsAllowed = rhs.mRtxIsAllowed;
+
+ mPrototypeCodecs.clear();
+ for (const auto& codec : rhs.mPrototypeCodecs) {
+ mPrototypeCodecs.emplace_back(codec->Clone());
+ }
+ if (rhs.mNegotiatedDetails) {
+ mNegotiatedDetails.reset(
+ new JsepTrackNegotiatedDetails(*rhs.mNegotiatedDetails));
+ }
+ }
+ return *this;
+ }
+
+ virtual mozilla::SdpMediaSection::MediaType GetMediaType() const {
+ return mType;
+ }
+
+ virtual const std::vector<std::string>& GetStreamIds() const {
+ return mStreamIds;
+ }
+
+ virtual const std::string& GetCNAME() const { return mCNAME; }
+
+ virtual void SetCNAME(const std::string& cname) { mCNAME = cname; }
+
+ virtual sdp::Direction GetDirection() const { return mDirection; }
+
+ virtual const std::vector<uint32_t>& GetSsrcs() const { return mSsrcs; }
+
+ virtual std::vector<uint32_t> GetRtxSsrcs() const {
+ std::vector<uint32_t> result;
+ if (mRtxIsAllowed &&
+ Preferences::GetBool("media.peerconnection.video.use_rtx", false)) {
+ std::for_each(
+ mSsrcToRtxSsrc.begin(), mSsrcToRtxSsrc.end(),
+ [&result](const auto& pair) { result.push_back(pair.second); });
+ }
+ return result;
+ }
+
+ virtual void EnsureSsrcs(SsrcGenerator& ssrcGenerator, size_t aNumber);
+
+ bool GetActive() const { return mActive; }
+
+ void SetActive(bool active) { mActive = active; }
+
+ bool GetRemoteSetSendBit() const { return mRemoteSetSendBit; }
+ bool GetReceptive() const { return mReceptive; }
+
+ virtual void PopulateCodecs(
+ const std::vector<UniquePtr<JsepCodecDescription>>& prototype);
+
+ template <class UnaryFunction>
+ void ForEachCodec(UnaryFunction func) {
+ std::for_each(mPrototypeCodecs.begin(), mPrototypeCodecs.end(), func);
+ }
+
+ template <class BinaryPredicate>
+ void SortCodecs(BinaryPredicate sorter) {
+ std::stable_sort(mPrototypeCodecs.begin(), mPrototypeCodecs.end(), sorter);
+ }
+
+ // These two are non-const because this is where ssrcs are chosen.
+ virtual void AddToOffer(SsrcGenerator& ssrcGenerator, SdpMediaSection* offer);
+ virtual void AddToAnswer(const SdpMediaSection& offer,
+ SsrcGenerator& ssrcGenerator,
+ SdpMediaSection* answer);
+
+ virtual nsresult Negotiate(const SdpMediaSection& answer,
+ const SdpMediaSection& remote,
+ const SdpMediaSection& local);
+ static void SetUniquePayloadTypes(std::vector<JsepTrack*>& tracks);
+ virtual void GetNegotiatedPayloadTypes(
+ std::vector<uint16_t>* payloadTypes) const;
+
+ // This will be set when negotiation is carried out.
+ virtual const JsepTrackNegotiatedDetails* GetNegotiatedDetails() const {
+ if (mNegotiatedDetails) {
+ return mNegotiatedDetails.get();
+ }
+ return nullptr;
+ }
+
+ virtual JsepTrackNegotiatedDetails* GetNegotiatedDetails() {
+ if (mNegotiatedDetails) {
+ return mNegotiatedDetails.get();
+ }
+ return nullptr;
+ }
+
+ virtual void ClearNegotiatedDetails() { mNegotiatedDetails.reset(); }
+
+ void SetRids(const std::vector<std::string>& aRids);
+ void ClearRids() { mRids.clear(); }
+ const std::vector<std::string>& GetRids() const { return mRids; }
+
+ void AddToMsection(const std::vector<std::string>& aRids,
+ sdp::Direction direction, SsrcGenerator& ssrcGenerator,
+ bool rtxEnabled, SdpMediaSection* msection);
+
+ // See Bug 1642419, this can be removed when all sites are working with RTX.
+ void SetRtxIsAllowed(bool aRtxIsAllowed) { mRtxIsAllowed = aRtxIsAllowed; }
+
+ void SetMaxEncodings(size_t aMax);
+ bool IsInHaveRemote() const { return mInHaveRemote; }
+
+ private:
+ std::vector<UniquePtr<JsepCodecDescription>> GetCodecClones() const;
+ static void EnsureNoDuplicatePayloadTypes(
+ std::vector<UniquePtr<JsepCodecDescription>>* codecs);
+ static void GetPayloadTypes(
+ const std::vector<UniquePtr<JsepCodecDescription>>& codecs,
+ std::vector<uint16_t>* pts);
+ void AddToMsection(const std::vector<UniquePtr<JsepCodecDescription>>& codecs,
+ SdpMediaSection* msection) const;
+ void GetRids(const SdpMediaSection& msection, sdp::Direction direction,
+ std::vector<SdpRidAttributeList::Rid>* rids) const;
+ void CreateEncodings(
+ const SdpMediaSection& remote,
+ const std::vector<UniquePtr<JsepCodecDescription>>& negotiatedCodecs,
+ JsepTrackNegotiatedDetails* details);
+
+ virtual std::vector<UniquePtr<JsepCodecDescription>> NegotiateCodecs(
+ const SdpMediaSection& remote, bool remoteIsOffer,
+ Maybe<const SdpMediaSection&> local);
+
+ void UpdateSsrcs(SsrcGenerator& ssrcGenerator, size_t encodings);
+ void PruneSsrcs(size_t aNumSsrcs);
+ bool IsRtxEnabled(
+ const std::vector<UniquePtr<JsepCodecDescription>>& codecs) const;
+
+ mozilla::SdpMediaSection::MediaType mType;
+ // These are the ids that everyone outside of JsepSession care about
+ std::vector<std::string> mStreamIds;
+ std::string mTrackId;
+ std::string mCNAME;
+ sdp::Direction mDirection;
+ std::vector<UniquePtr<JsepCodecDescription>> mPrototypeCodecs;
+ // List of rids. May be initially populated from JS, or from a remote SDP.
+ // Can be updated by remote SDP. If no negotiation has taken place at all,
+ // this will be empty. If negotiation has taken place, but no simulcast
+ // attr was negotiated, this will contain the empty string as a single
+ // element. If a simulcast attribute was negotiated, this will contain the
+ // negotiated rids.
+ std::vector<std::string> mRids;
+ UniquePtr<JsepTrackNegotiatedDetails> mNegotiatedDetails;
+ std::vector<uint32_t> mSsrcs;
+ std::map<uint32_t, uint32_t> mSsrcToRtxSsrc;
+ bool mActive;
+ bool mRemoteSetSendBit;
+ // This is used to drive RTCRtpTransceiver.[[Receptive]]. Basically, this
+ // denotes whether we are prepared to receive RTP. When we apply a local
+ // description with the recv bit set, this is set to true, even if we have
+ // not seen the remote description yet. If we apply either a local or remote
+ // description without the recv bit set (from our perspective), this is set
+ // to false.
+ bool mReceptive = false;
+ size_t mMaxEncodings = 3;
+ bool mInHaveRemote = false;
+
+ // See Bug 1642419, this can be removed when all sites are working with RTX.
+ bool mRtxIsAllowed = true;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/jsep/JsepTrackEncoding.h b/dom/media/webrtc/jsep/JsepTrackEncoding.h
new file mode 100644
index 0000000000..c17dd8534e
--- /dev/null
+++ b/dom/media/webrtc/jsep/JsepTrackEncoding.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _JESPTRACKENCODING_H_
+#define _JESPTRACKENCODING_H_
+
+#include "jsep/JsepCodecDescription.h"
+#include "common/EncodingConstraints.h"
+
+#include <vector>
+
+namespace mozilla {
+// Represents a single encoding of a media track. When simulcast is used, there
+// may be multiple. Each encoding may have some constraints (imposed by JS), and
+// may be able to use any one of multiple codecs (JsepCodecDescription) at any
+// given time.
+class JsepTrackEncoding {
+ public:
+ JsepTrackEncoding() = default;
+ JsepTrackEncoding(const JsepTrackEncoding& orig) { *this = orig; }
+
+ JsepTrackEncoding(JsepTrackEncoding&& aOrig) = default;
+
+ JsepTrackEncoding& operator=(const JsepTrackEncoding& aRhs) {
+ if (this != &aRhs) {
+ mRid = aRhs.mRid;
+ mCodecs.clear();
+ for (const auto& codec : aRhs.mCodecs) {
+ mCodecs.emplace_back(codec->Clone());
+ }
+ }
+ return *this;
+ }
+
+ const std::vector<UniquePtr<JsepCodecDescription>>& GetCodecs() const {
+ return mCodecs;
+ }
+
+ void AddCodec(const JsepCodecDescription& codec) {
+ mCodecs.emplace_back(codec.Clone());
+ }
+
+ bool HasFormat(const std::string& format) const {
+ for (const auto& codec : mCodecs) {
+ if (codec->mDefaultPt == format) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ std::string mRid;
+
+ private:
+ std::vector<UniquePtr<JsepCodecDescription>> mCodecs;
+};
+} // namespace mozilla
+
+#endif // _JESPTRACKENCODING_H_
diff --git a/dom/media/webrtc/jsep/JsepTransceiver.h b/dom/media/webrtc/jsep/JsepTransceiver.h
new file mode 100644
index 0000000000..45c9b85120
--- /dev/null
+++ b/dom/media/webrtc/jsep/JsepTransceiver.h
@@ -0,0 +1,220 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _JSEPTRANSCEIVER_H_
+#define _JSEPTRANSCEIVER_H_
+
+#include <string>
+
+#include "sdp/SdpAttribute.h"
+#include "sdp/SdpMediaSection.h"
+#include "sdp/Sdp.h"
+#include "jsep/JsepTransport.h"
+#include "jsep/JsepTrack.h"
+
+#include "nsError.h"
+
+namespace mozilla {
+
+class JsepUuidGenerator {
+ public:
+ virtual ~JsepUuidGenerator() = default;
+ virtual bool Generate(std::string* id) = 0;
+ virtual JsepUuidGenerator* Clone() const = 0;
+};
+
+class JsepTransceiver {
+ public:
+ explicit JsepTransceiver(SdpMediaSection::MediaType type,
+ JsepUuidGenerator& aUuidGen,
+ SdpDirectionAttribute::Direction jsDirection =
+ SdpDirectionAttribute::kSendrecv)
+ : mJsDirection(jsDirection),
+ mSendTrack(type, sdp::kSend),
+ mRecvTrack(type, sdp::kRecv),
+ mLevel(SIZE_MAX),
+ mBundleLevel(SIZE_MAX),
+ mAddTrackMagic(false),
+ mOnlyExistsBecauseOfSetRemote(false),
+ mStopped(false),
+ mRemoved(false),
+ mNegotiated(false),
+ mCanRecycle(false) {
+ if (!aUuidGen.Generate(&mUuid)) {
+ MOZ_CRASH();
+ }
+ }
+
+ JsepTransceiver(const JsepTransceiver& orig) = default;
+ JsepTransceiver(JsepTransceiver&& orig) = default;
+ JsepTransceiver& operator=(const JsepTransceiver& aRhs) = default;
+ JsepTransceiver& operator=(JsepTransceiver&& aRhs) = default;
+
+ ~JsepTransceiver() = default;
+
+ void Rollback(JsepTransceiver& oldTransceiver, bool aRemote) {
+ MOZ_ASSERT(oldTransceiver.GetMediaType() == GetMediaType());
+ MOZ_ASSERT(!oldTransceiver.IsNegotiated() || !oldTransceiver.HasLevel() ||
+ !HasLevel() || oldTransceiver.GetLevel() == GetLevel());
+ mTransport = oldTransceiver.mTransport;
+ if (aRemote) {
+ mLevel = oldTransceiver.mLevel;
+ mBundleLevel = oldTransceiver.mBundleLevel;
+ mSendTrack = oldTransceiver.mSendTrack;
+ }
+ mRecvTrack = oldTransceiver.mRecvTrack;
+
+ // Don't allow rollback to re-associate a transceiver.
+ if (!oldTransceiver.IsAssociated()) {
+ Disassociate();
+ }
+ }
+
+ bool IsAssociated() const { return !mMid.empty(); }
+
+ const std::string& GetMid() const {
+ MOZ_ASSERT(IsAssociated());
+ return mMid;
+ }
+
+ void Associate(const std::string& mid) {
+ MOZ_ASSERT(HasLevel());
+ mMid = mid;
+ }
+
+ void Disassociate() { mMid.clear(); }
+
+ bool HasLevel() const { return mLevel != SIZE_MAX; }
+
+ void SetLevel(size_t level) {
+ MOZ_ASSERT(level != SIZE_MAX);
+ MOZ_ASSERT(!HasLevel());
+ MOZ_ASSERT(!IsStopped());
+
+ mLevel = level;
+ }
+
+ void ClearLevel() {
+ MOZ_ASSERT(!IsAssociated());
+ mLevel = SIZE_MAX;
+ mBundleLevel = SIZE_MAX;
+ }
+
+ size_t GetLevel() const {
+ MOZ_ASSERT(HasLevel());
+ return mLevel;
+ }
+
+ void Stop() { mStopped = true; }
+
+ bool IsStopped() const { return mStopped; }
+
+ void RestartDatachannelTransceiver() {
+ MOZ_RELEASE_ASSERT(GetMediaType() == SdpMediaSection::kApplication);
+ mStopped = false;
+ mCanRecycle = false;
+ }
+
+ void SetRemoved() { mRemoved = true; }
+
+ bool IsRemoved() const { return mRemoved; }
+
+ bool HasBundleLevel() const { return mBundleLevel != SIZE_MAX; }
+
+ size_t BundleLevel() const {
+ MOZ_ASSERT(HasBundleLevel());
+ return mBundleLevel;
+ }
+
+ void SetBundleLevel(size_t aBundleLevel) {
+ MOZ_ASSERT(aBundleLevel != SIZE_MAX);
+ mBundleLevel = aBundleLevel;
+ }
+
+ void ClearBundleLevel() { mBundleLevel = SIZE_MAX; }
+
+ size_t GetTransportLevel() const {
+ MOZ_ASSERT(HasLevel());
+ if (HasBundleLevel()) {
+ return BundleLevel();
+ }
+ return GetLevel();
+ }
+
+ void SetAddTrackMagic() { mAddTrackMagic = true; }
+
+ bool HasAddTrackMagic() const { return mAddTrackMagic; }
+
+ void SetOnlyExistsBecauseOfSetRemote(bool aValue) {
+ mOnlyExistsBecauseOfSetRemote = aValue;
+ }
+
+ bool OnlyExistsBecauseOfSetRemote() const {
+ return mOnlyExistsBecauseOfSetRemote;
+ }
+
+ void SetNegotiated() {
+ MOZ_ASSERT(IsAssociated());
+ MOZ_ASSERT(HasLevel());
+ mNegotiated = true;
+ }
+
+ bool IsNegotiated() const { return mNegotiated; }
+
+ void SetCanRecycle() { mCanRecycle = true; }
+
+ bool CanRecycle() const { return mCanRecycle; }
+
+ const std::string& GetUuid() const { return mUuid; }
+
+ // Convenience function
+ SdpMediaSection::MediaType GetMediaType() const {
+ MOZ_ASSERT(mRecvTrack.GetMediaType() == mSendTrack.GetMediaType());
+ return mRecvTrack.GetMediaType();
+ }
+
+ bool HasTransport() const { return mTransport.mComponents != 0; }
+
+ bool HasOwnTransport() const {
+ if (HasTransport() &&
+ (!HasBundleLevel() || (GetLevel() == BundleLevel()))) {
+ return true;
+ }
+ return false;
+ }
+
+ // See Bug 1642419, this can be removed when all sites are working with RTX.
+ void SetRtxIsAllowed(bool aRtxIsAllowed) {
+ mSendTrack.SetRtxIsAllowed(aRtxIsAllowed);
+ mRecvTrack.SetRtxIsAllowed(aRtxIsAllowed);
+ }
+
+ // This is the direction JS wants. It might not actually happen.
+ SdpDirectionAttribute::Direction mJsDirection;
+
+ JsepTrack mSendTrack;
+ JsepTrack mRecvTrack;
+ JsepTransport mTransport;
+
+ private:
+ std::string mUuid;
+ // Stuff that is not negotiated
+ std::string mMid;
+ size_t mLevel; // SIZE_MAX if no level
+ // Is this track pair sharing a transport with another?
+ size_t mBundleLevel; // SIZE_MAX if no bundle level
+ // The w3c and IETF specs have a lot of "magical" behavior that happens
+ // when addTrack is used to create a transceiver. This was a deliberate
+ // design choice. Sadface.
+ bool mAddTrackMagic;
+ bool mOnlyExistsBecauseOfSetRemote;
+ bool mStopped;
+ bool mRemoved;
+ bool mNegotiated;
+ bool mCanRecycle;
+};
+
+} // namespace mozilla
+
+#endif // _JSEPTRANSCEIVER_H_
diff --git a/dom/media/webrtc/jsep/JsepTransport.h b/dom/media/webrtc/jsep/JsepTransport.h
new file mode 100644
index 0000000000..67690b1fd4
--- /dev/null
+++ b/dom/media/webrtc/jsep/JsepTransport.h
@@ -0,0 +1,102 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _JSEPTRANSPORT_H_
+#define _JSEPTRANSPORT_H_
+
+#include <string>
+#include <vector>
+
+#include <mozilla/RefPtr.h>
+#include <mozilla/UniquePtr.h>
+#include "nsISupportsImpl.h"
+
+#include "sdp/SdpAttribute.h"
+
+namespace mozilla {
+
+class JsepDtlsTransport {
+ public:
+ JsepDtlsTransport() : mRole(kJsepDtlsInvalidRole) {}
+
+ virtual ~JsepDtlsTransport() {}
+
+ enum Role { kJsepDtlsClient, kJsepDtlsServer, kJsepDtlsInvalidRole };
+
+ virtual const SdpFingerprintAttributeList& GetFingerprints() const {
+ return mFingerprints;
+ }
+
+ virtual Role GetRole() const { return mRole; }
+
+ private:
+ friend class JsepSessionImpl;
+
+ SdpFingerprintAttributeList mFingerprints;
+ Role mRole;
+};
+
+class JsepIceTransport {
+ public:
+ JsepIceTransport() {}
+
+ virtual ~JsepIceTransport() {}
+
+ const std::string& GetUfrag() const { return mUfrag; }
+ const std::string& GetPassword() const { return mPwd; }
+ const std::vector<std::string>& GetCandidates() const { return mCandidates; }
+
+ private:
+ friend class JsepSessionImpl;
+
+ std::string mUfrag;
+ std::string mPwd;
+ std::vector<std::string> mCandidates;
+};
+
+class JsepTransport {
+ public:
+ JsepTransport() : mComponents(0) {}
+
+ JsepTransport(const JsepTransport& orig) { *this = orig; }
+
+ ~JsepTransport() {}
+
+ JsepTransport& operator=(const JsepTransport& orig) {
+ if (this != &orig) {
+ mIce.reset(orig.mIce ? new JsepIceTransport(*orig.mIce) : nullptr);
+ mDtls.reset(orig.mDtls ? new JsepDtlsTransport(*orig.mDtls) : nullptr);
+ mTransportId = orig.mTransportId;
+ mComponents = orig.mComponents;
+ mLocalUfrag = orig.mLocalUfrag;
+ mLocalPwd = orig.mLocalPwd;
+ }
+ return *this;
+ }
+
+ void Close() {
+ mComponents = 0;
+ mTransportId.clear();
+ mIce.reset();
+ mDtls.reset();
+ mLocalUfrag.clear();
+ mLocalPwd.clear();
+ }
+
+ // Unique identifier for this transport within this call. Group?
+ std::string mTransportId;
+
+ // ICE stuff.
+ UniquePtr<JsepIceTransport> mIce;
+ UniquePtr<JsepDtlsTransport> mDtls;
+
+ // Number of required components.
+ size_t mComponents;
+ std::string mLocalUfrag;
+ std::string mLocalPwd;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/jsep/SsrcGenerator.cpp b/dom/media/webrtc/jsep/SsrcGenerator.cpp
new file mode 100644
index 0000000000..50025007d1
--- /dev/null
+++ b/dom/media/webrtc/jsep/SsrcGenerator.cpp
@@ -0,0 +1,22 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "jsep/SsrcGenerator.h"
+#include "pk11pub.h"
+
+namespace mozilla {
+
+bool SsrcGenerator::GenerateSsrc(uint32_t* ssrc) {
+ do {
+ SECStatus rv = PK11_GenerateRandom(reinterpret_cast<unsigned char*>(ssrc),
+ sizeof(uint32_t));
+ if (rv != SECSuccess) {
+ return false;
+ }
+ } while (mSsrcs.count(*ssrc));
+ mSsrcs.insert(*ssrc);
+
+ return true;
+}
+} // namespace mozilla
diff --git a/dom/media/webrtc/jsep/SsrcGenerator.h b/dom/media/webrtc/jsep/SsrcGenerator.h
new file mode 100644
index 0000000000..b106358f32
--- /dev/null
+++ b/dom/media/webrtc/jsep/SsrcGenerator.h
@@ -0,0 +1,20 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _SSRCGENERATOR_H_
+#define _SSRCGENERATOR_H_
+
+#include <set>
+
+namespace mozilla {
+class SsrcGenerator {
+ public:
+ bool GenerateSsrc(uint32_t* ssrc);
+
+ private:
+ std::set<uint32_t> mSsrcs;
+};
+} // namespace mozilla
+
+#endif // _SSRCGENERATOR_H_
diff --git a/dom/media/webrtc/jsep/moz.build b/dom/media/webrtc/jsep/moz.build
new file mode 100644
index 0000000000..750ac76a30
--- /dev/null
+++ b/dom/media/webrtc/jsep/moz.build
@@ -0,0 +1,18 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+include("/dom/media/webrtc/third_party_build/webrtc.mozbuild")
+
+LOCAL_INCLUDES += [
+ "/dom/media/webrtc",
+ "/media/webrtc",
+ "/third_party/libwebrtc",
+ "/third_party/libwebrtc/third_party/abseil-cpp",
+ "/third_party/sipcc",
+]
+
+UNIFIED_SOURCES += ["JsepSessionImpl.cpp", "JsepTrack.cpp", "SsrcGenerator.cpp"]
+
+FINAL_LIBRARY = "xul"
diff --git a/dom/media/webrtc/libwebrtcglue/AudioConduit.cpp b/dom/media/webrtc/libwebrtcglue/AudioConduit.cpp
new file mode 100644
index 0000000000..d8492f779a
--- /dev/null
+++ b/dom/media/webrtc/libwebrtcglue/AudioConduit.cpp
@@ -0,0 +1,975 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AudioConduit.h"
+
+#include "common/browser_logging/CSFLog.h"
+#include "MediaConduitControl.h"
+#include "mozilla/media/MediaUtils.h"
+#include "mozilla/Telemetry.h"
+#include "transport/runnable_utils.h"
+#include "transport/SrtpFlow.h" // For SRTP_MAX_EXPANSION
+#include "WebrtcCallWrapper.h"
+
+// libwebrtc includes
+#include "api/audio_codecs/builtin_audio_encoder_factory.h"
+#include "audio/audio_receive_stream.h"
+#include "media/base/media_constants.h"
+
+// for ntohs
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#elif defined XP_WIN
+# include <winsock2.h>
+#endif
+
+#ifdef MOZ_WIDGET_ANDROID
+# include "AndroidBridge.h"
+#endif
+
+namespace mozilla {
+
+namespace {
+
+static const char* acLogTag = "WebrtcAudioSessionConduit";
+#ifdef LOGTAG
+# undef LOGTAG
+#endif
+#define LOGTAG acLogTag
+
+using namespace cricket;
+using LocalDirection = MediaSessionConduitLocalDirection;
+
+const char kCodecParamCbr[] = "cbr";
+
+} // namespace
+
+/**
+ * Factory Method for AudioConduit
+ */
+RefPtr<AudioSessionConduit> AudioSessionConduit::Create(
+ RefPtr<WebrtcCallWrapper> aCall,
+ nsCOMPtr<nsISerialEventTarget> aStsThread) {
+ CSFLogDebug(LOGTAG, "%s ", __FUNCTION__);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return MakeRefPtr<WebrtcAudioConduit>(std::move(aCall),
+ std::move(aStsThread));
+}
+
+#define INIT_MIRROR(name, val) \
+ name(aCallThread, val, "WebrtcAudioConduit::Control::" #name " (Mirror)")
+WebrtcAudioConduit::Control::Control(const RefPtr<AbstractThread>& aCallThread)
+ : INIT_MIRROR(mReceiving, false),
+ INIT_MIRROR(mTransmitting, false),
+ INIT_MIRROR(mLocalSsrcs, Ssrcs()),
+ INIT_MIRROR(mLocalCname, std::string()),
+ INIT_MIRROR(mMid, std::string()),
+ INIT_MIRROR(mRemoteSsrc, 0),
+ INIT_MIRROR(mSyncGroup, std::string()),
+ INIT_MIRROR(mLocalRecvRtpExtensions, RtpExtList()),
+ INIT_MIRROR(mLocalSendRtpExtensions, RtpExtList()),
+ INIT_MIRROR(mSendCodec, Nothing()),
+ INIT_MIRROR(mRecvCodecs, std::vector<AudioCodecConfig>()) {}
+#undef INIT_MIRROR
+
+RefPtr<GenericPromise> WebrtcAudioConduit::Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return InvokeAsync(mCallThread, "WebrtcAudioConduit::Shutdown (main thread)",
+ [this, self = RefPtr<WebrtcAudioConduit>(this)] {
+ mControl.mReceiving.DisconnectIfConnected();
+ mControl.mTransmitting.DisconnectIfConnected();
+ mControl.mLocalSsrcs.DisconnectIfConnected();
+ mControl.mLocalCname.DisconnectIfConnected();
+ mControl.mMid.DisconnectIfConnected();
+ mControl.mRemoteSsrc.DisconnectIfConnected();
+ mControl.mSyncGroup.DisconnectIfConnected();
+ mControl.mLocalRecvRtpExtensions.DisconnectIfConnected();
+ mControl.mLocalSendRtpExtensions.DisconnectIfConnected();
+ mControl.mSendCodec.DisconnectIfConnected();
+ mControl.mRecvCodecs.DisconnectIfConnected();
+ mControl.mOnDtmfEventListener.DisconnectIfExists();
+ mWatchManager.Shutdown();
+
+ {
+ AutoWriteLock lock(mLock);
+ DeleteSendStream();
+ DeleteRecvStream();
+ }
+
+ return GenericPromise::CreateAndResolve(
+ true, "WebrtcAudioConduit::Shutdown (call thread)");
+ });
+}
+
+WebrtcAudioConduit::WebrtcAudioConduit(
+ RefPtr<WebrtcCallWrapper> aCall, nsCOMPtr<nsISerialEventTarget> aStsThread)
+ : mCall(std::move(aCall)),
+ mSendTransport(this),
+ mRecvTransport(this),
+ mRecvStreamConfig(),
+ mRecvStream(nullptr),
+ mSendStreamConfig(&mSendTransport),
+ mSendStream(nullptr),
+ mSendStreamRunning(false),
+ mRecvStreamRunning(false),
+ mDtmfEnabled(false),
+ mLock("WebrtcAudioConduit::mLock"),
+ mCallThread(std::move(mCall->mCallThread)),
+ mStsThread(std::move(aStsThread)),
+ mControl(mCall->mCallThread),
+ mWatchManager(this, mCall->mCallThread) {
+ mRecvStreamConfig.rtcp_send_transport = &mRecvTransport;
+ mRecvStreamConfig.rtp.rtcp_event_observer = this;
+}
+
+/**
+ * Destruction defines for our super-classes
+ */
+WebrtcAudioConduit::~WebrtcAudioConduit() {
+ CSFLogDebug(LOGTAG, "%s ", __FUNCTION__);
+ MOZ_ASSERT(!mSendStream && !mRecvStream,
+ "Call DeleteStreams prior to ~WebrtcAudioConduit.");
+}
+
+#define CONNECT(aCanonical, aMirror) \
+ do { \
+ (aMirror).Connect(aCanonical); \
+ mWatchManager.Watch(aMirror, &WebrtcAudioConduit::OnControlConfigChange); \
+ } while (0)
+
+void WebrtcAudioConduit::InitControl(AudioConduitControlInterface* aControl) {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+
+ CONNECT(aControl->CanonicalReceiving(), mControl.mReceiving);
+ CONNECT(aControl->CanonicalTransmitting(), mControl.mTransmitting);
+ CONNECT(aControl->CanonicalLocalSsrcs(), mControl.mLocalSsrcs);
+ CONNECT(aControl->CanonicalLocalCname(), mControl.mLocalCname);
+ CONNECT(aControl->CanonicalMid(), mControl.mMid);
+ CONNECT(aControl->CanonicalRemoteSsrc(), mControl.mRemoteSsrc);
+ CONNECT(aControl->CanonicalSyncGroup(), mControl.mSyncGroup);
+ CONNECT(aControl->CanonicalLocalRecvRtpExtensions(),
+ mControl.mLocalRecvRtpExtensions);
+ CONNECT(aControl->CanonicalLocalSendRtpExtensions(),
+ mControl.mLocalSendRtpExtensions);
+ CONNECT(aControl->CanonicalAudioSendCodec(), mControl.mSendCodec);
+ CONNECT(aControl->CanonicalAudioRecvCodecs(), mControl.mRecvCodecs);
+ mControl.mOnDtmfEventListener = aControl->OnDtmfEvent().Connect(
+ mCall->mCallThread, this, &WebrtcAudioConduit::OnDtmfEvent);
+}
+
+#undef CONNECT
+
+void WebrtcAudioConduit::OnDtmfEvent(const DtmfEvent& aEvent) {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ MOZ_ASSERT(mSendStream);
+ MOZ_ASSERT(mDtmfEnabled);
+ mSendStream->SendTelephoneEvent(aEvent.mPayloadType, aEvent.mPayloadFrequency,
+ aEvent.mEventCode, aEvent.mLengthMs);
+}
+
+void WebrtcAudioConduit::OnControlConfigChange() {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+
+ bool recvStreamReconfigureNeeded = false;
+ bool sendStreamReconfigureNeeded = false;
+ bool recvStreamRecreationNeeded = false;
+ bool sendStreamRecreationNeeded = false;
+
+ if (!mControl.mLocalSsrcs.Ref().empty()) {
+ if (mControl.mLocalSsrcs.Ref()[0] != mSendStreamConfig.rtp.ssrc) {
+ sendStreamRecreationNeeded = true;
+
+ // For now...
+ recvStreamRecreationNeeded = true;
+ }
+ mRecvStreamConfig.rtp.local_ssrc = mControl.mLocalSsrcs.Ref()[0];
+ mSendStreamConfig.rtp.ssrc = mControl.mLocalSsrcs.Ref()[0];
+
+ // In the future we can do this instead of recreating the recv stream:
+ // if (mRecvStream) {
+ // mCall->Call()->OnLocalSsrcUpdated(mRecvStream,
+ // mControl.mLocalSsrcs.Ref()[0]);
+ // }
+ }
+
+ if (mControl.mLocalCname.Ref() != mSendStreamConfig.rtp.c_name) {
+ mSendStreamConfig.rtp.c_name = mControl.mLocalCname.Ref();
+ sendStreamReconfigureNeeded = true;
+ }
+
+ if (mControl.mMid.Ref() != mSendStreamConfig.rtp.mid) {
+ mSendStreamConfig.rtp.mid = mControl.mMid.Ref();
+ sendStreamReconfigureNeeded = true;
+ }
+
+ if (mControl.mRemoteSsrc.Ref() != mControl.mConfiguredRemoteSsrc) {
+ mRecvStreamConfig.rtp.remote_ssrc = mControl.mConfiguredRemoteSsrc =
+ mControl.mRemoteSsrc.Ref();
+ recvStreamRecreationNeeded = true;
+ }
+
+ if (mControl.mSyncGroup.Ref() != mRecvStreamConfig.sync_group) {
+ mRecvStreamConfig.sync_group = mControl.mSyncGroup.Ref();
+ // For now...
+ recvStreamRecreationNeeded = true;
+ // In the future we can do this instead of recreating the recv stream:
+ // if (mRecvStream) {
+ // mCall->Call()->OnUpdateSyncGroup(mRecvStream,
+ // mRecvStreamConfig.sync_group);
+ // }
+ }
+
+ if (auto filteredExtensions = FilterExtensions(
+ LocalDirection::kRecv, mControl.mLocalRecvRtpExtensions);
+ filteredExtensions != mRecvStreamConfig.rtp.extensions) {
+ mRecvStreamConfig.rtp.extensions = std::move(filteredExtensions);
+ // For now...
+ recvStreamRecreationNeeded = true;
+ // In the future we can do this instead of recreating the recv stream:
+ // if (mRecvStream) {
+ // mRecvStream->SetRtpExtensions(mRecvStreamConfig.rtp.extensions);
+ //}
+ }
+
+ if (auto filteredExtensions = FilterExtensions(
+ LocalDirection::kSend, mControl.mLocalSendRtpExtensions);
+ filteredExtensions != mSendStreamConfig.rtp.extensions) {
+ // At the very least, we need a reconfigure. Recreation needed if the
+ // extmap for any extension has changed, but not for adding/removing
+ // extensions.
+ sendStreamReconfigureNeeded = true;
+
+ for (const auto& newExt : filteredExtensions) {
+ if (sendStreamRecreationNeeded) {
+ break;
+ }
+ for (const auto& oldExt : mSendStreamConfig.rtp.extensions) {
+ if (newExt.uri == oldExt.uri) {
+ if (newExt.id != oldExt.id) {
+ sendStreamRecreationNeeded = true;
+ }
+ // We're done handling newExt, one way or another
+ break;
+ }
+ }
+ }
+
+ mSendStreamConfig.rtp.extensions = std::move(filteredExtensions);
+ }
+
+ mControl.mSendCodec.Ref().apply([&](const auto& aConfig) {
+ if (mControl.mConfiguredSendCodec != mControl.mSendCodec.Ref()) {
+ mControl.mConfiguredSendCodec = mControl.mSendCodec;
+ if (ValidateCodecConfig(aConfig, true) == kMediaConduitNoError) {
+ mSendStreamConfig.encoder_factory =
+ webrtc::CreateBuiltinAudioEncoderFactory();
+
+ webrtc::AudioSendStream::Config::SendCodecSpec spec(
+ aConfig.mType, CodecConfigToLibwebrtcFormat(aConfig));
+ mSendStreamConfig.send_codec_spec = spec;
+
+ mDtmfEnabled = aConfig.mDtmfEnabled;
+ sendStreamReconfigureNeeded = true;
+ }
+ }
+ });
+
+ if (mControl.mConfiguredRecvCodecs != mControl.mRecvCodecs.Ref()) {
+ mControl.mConfiguredRecvCodecs = mControl.mRecvCodecs;
+ mRecvStreamConfig.decoder_factory = mCall->mAudioDecoderFactory;
+ mRecvStreamConfig.decoder_map.clear();
+
+ for (const auto& codec : mControl.mRecvCodecs.Ref()) {
+ if (ValidateCodecConfig(codec, false) != kMediaConduitNoError) {
+ continue;
+ }
+ mRecvStreamConfig.decoder_map.emplace(
+ codec.mType, CodecConfigToLibwebrtcFormat(codec));
+ }
+
+ recvStreamReconfigureNeeded = true;
+ }
+
+ if (!recvStreamReconfigureNeeded && !sendStreamReconfigureNeeded &&
+ !recvStreamRecreationNeeded && !sendStreamRecreationNeeded &&
+ mControl.mReceiving == mRecvStreamRunning &&
+ mControl.mTransmitting == mSendStreamRunning) {
+ // No changes applied -- no need to lock.
+ return;
+ }
+
+ if (recvStreamRecreationNeeded) {
+ recvStreamReconfigureNeeded = false;
+ }
+ if (sendStreamRecreationNeeded) {
+ sendStreamReconfigureNeeded = false;
+ }
+
+ {
+ AutoWriteLock lock(mLock);
+ // Recreate/Stop/Start streams as needed.
+ if (recvStreamRecreationNeeded) {
+ DeleteRecvStream();
+ }
+ if (mControl.mReceiving) {
+ CreateRecvStream();
+ }
+ if (sendStreamRecreationNeeded) {
+ DeleteSendStream();
+ }
+ if (mControl.mTransmitting) {
+ CreateSendStream();
+ }
+ }
+
+ // We make sure to not hold the lock while stopping/starting/reconfiguring
+ // streams, so as to not cause deadlocks. These methods can cause our platform
+ // codecs to dispatch sync runnables to main, and main may grab the lock.
+
+ if (mRecvStream && recvStreamReconfigureNeeded) {
+ MOZ_ASSERT(!recvStreamRecreationNeeded);
+ mRecvStream->SetDecoderMap(mRecvStreamConfig.decoder_map);
+ }
+
+ if (mSendStream && sendStreamReconfigureNeeded) {
+ MOZ_ASSERT(!sendStreamRecreationNeeded);
+ // TODO: Pass a callback here, so we can react to RTCErrors thrown by
+ // libwebrtc.
+ mSendStream->Reconfigure(mSendStreamConfig, nullptr);
+ }
+
+ if (!mControl.mReceiving) {
+ StopReceiving();
+ }
+ if (!mControl.mTransmitting) {
+ StopTransmitting();
+ }
+
+ if (mControl.mReceiving) {
+ StartReceiving();
+ }
+ if (mControl.mTransmitting) {
+ StartTransmitting();
+ }
+}
+
+std::vector<uint32_t> WebrtcAudioConduit::GetLocalSSRCs() const {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ return std::vector<uint32_t>(1, mRecvStreamConfig.rtp.local_ssrc);
+}
+
+bool WebrtcAudioConduit::OverrideRemoteSSRC(uint32_t aSsrc) {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+
+ if (mRecvStreamConfig.rtp.remote_ssrc == aSsrc) {
+ return true;
+ }
+ mRecvStreamConfig.rtp.remote_ssrc = aSsrc;
+
+ const bool wasReceiving = mRecvStreamRunning;
+ const bool hadRecvStream = mRecvStream;
+
+ StopReceiving();
+
+ if (hadRecvStream) {
+ AutoWriteLock lock(mLock);
+ DeleteRecvStream();
+ CreateRecvStream();
+ }
+
+ if (wasReceiving) {
+ StartReceiving();
+ }
+ return true;
+}
+
+Maybe<Ssrc> WebrtcAudioConduit::GetRemoteSSRC() const {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ // libwebrtc uses 0 to mean a lack of SSRC. That is not to spec.
+ return mRecvStreamConfig.rtp.remote_ssrc == 0
+ ? Nothing()
+ : Some(mRecvStreamConfig.rtp.remote_ssrc);
+}
+
+Maybe<webrtc::AudioReceiveStreamInterface::Stats>
+WebrtcAudioConduit::GetReceiverStats() const {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ if (!mRecvStream) {
+ return Nothing();
+ }
+ return Some(mRecvStream->GetStats());
+}
+
+Maybe<webrtc::AudioSendStream::Stats> WebrtcAudioConduit::GetSenderStats()
+ const {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ if (!mSendStream) {
+ return Nothing();
+ }
+ return Some(mSendStream->GetStats());
+}
+
+Maybe<webrtc::CallBasicStats> WebrtcAudioConduit::GetCallStats() const {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ if (!mCall->Call()) {
+ return Nothing();
+ }
+ return Some(mCall->Call()->GetStats());
+}
+
+void WebrtcAudioConduit::OnRtcpBye() { mRtcpByeEvent.Notify(); }
+
+void WebrtcAudioConduit::OnRtcpTimeout() { mRtcpTimeoutEvent.Notify(); }
+
+void WebrtcAudioConduit::SetTransportActive(bool aActive) {
+ MOZ_ASSERT(mStsThread->IsOnCurrentThread());
+ if (mTransportActive == aActive) {
+ return;
+ }
+
+ // If false, This stops us from sending
+ mTransportActive = aActive;
+
+ // We queue this because there might be notifications to these listeners
+ // pending, and we don't want to drop them by letting this jump ahead of
+ // those notifications. We move the listeners into the lambda in case the
+ // transport comes back up before we disconnect them. (The Connect calls
+ // happen in MediaPipeline)
+ // We retain a strong reference to ourself, because the listeners are holding
+ // a non-refcounted reference to us, and moving them into the lambda could
+ // conceivably allow them to outlive us.
+ if (!aActive) {
+ MOZ_ALWAYS_SUCCEEDS(mCallThread->Dispatch(NS_NewRunnableFunction(
+ __func__,
+ [self = RefPtr<WebrtcAudioConduit>(this),
+ recvRtpListener = std::move(mReceiverRtpEventListener),
+ recvRtcpListener = std::move(mReceiverRtcpEventListener),
+ sendRtcpListener = std::move(mSenderRtcpEventListener)]() mutable {
+ recvRtpListener.DisconnectIfExists();
+ recvRtcpListener.DisconnectIfExists();
+ sendRtcpListener.DisconnectIfExists();
+ })));
+ }
+}
+
+// AudioSessionConduit Implementation
+MediaConduitErrorCode WebrtcAudioConduit::SendAudioFrame(
+ std::unique_ptr<webrtc::AudioFrame> frame) {
+ CSFLogDebug(LOGTAG, "%s ", __FUNCTION__);
+ // Following checks need to be performed
+ // 1. Non null audio buffer pointer, and
+ // 2. Valid sample rate, and
+ // 3. Appropriate Sample Length for 10 ms audio-frame. This represents the
+ // block size used upstream for processing.
+ // Ex: for 16000 sample rate , valid block-length is 160.
+ // Similarly for 32000 sample rate, valid block length is 320.
+
+ if (!frame->data() ||
+ (IsSamplingFreqSupported(frame->sample_rate_hz()) == false) ||
+ ((frame->samples_per_channel() % (frame->sample_rate_hz() / 100) != 0))) {
+ CSFLogError(LOGTAG, "%s Invalid Parameters ", __FUNCTION__);
+ MOZ_ASSERT(PR_FALSE);
+ return kMediaConduitMalformedArgument;
+ }
+
+ // This is the AudioProxyThread, blocking it for a bit is fine.
+ AutoReadLock lock(mLock);
+ if (!mSendStreamRunning) {
+ CSFLogError(LOGTAG, "%s Engine not transmitting ", __FUNCTION__);
+ return kMediaConduitSessionNotInited;
+ }
+
+ mSendStream->SendAudioData(std::move(frame));
+ return kMediaConduitNoError;
+}
+
+MediaConduitErrorCode WebrtcAudioConduit::GetAudioFrame(
+ int32_t samplingFreqHz, webrtc::AudioFrame* frame) {
+ CSFLogDebug(LOGTAG, "%s ", __FUNCTION__);
+
+ // validate params
+ if (!frame) {
+ CSFLogError(LOGTAG, "%s Null Audio Buffer Pointer", __FUNCTION__);
+ MOZ_ASSERT(PR_FALSE);
+ return kMediaConduitMalformedArgument;
+ }
+
+ // Validate sample length
+ if (GetNum10msSamplesForFrequency(samplingFreqHz) == 0) {
+ CSFLogError(LOGTAG, "%s Invalid Sampling Frequency ", __FUNCTION__);
+ MOZ_ASSERT(PR_FALSE);
+ return kMediaConduitMalformedArgument;
+ }
+
+ // If the lock is taken, skip this chunk to avoid blocking the audio thread.
+ AutoTryReadLock tryLock(mLock);
+ if (!tryLock) {
+ CSFLogError(LOGTAG, "%s Conduit going through negotiation ", __FUNCTION__);
+ return kMediaConduitPlayoutError;
+ }
+
+ // Conduit should have reception enabled before we ask for decoded
+ // samples
+ if (!mRecvStreamRunning) {
+ CSFLogError(LOGTAG, "%s Engine not Receiving ", __FUNCTION__);
+ return kMediaConduitSessionNotInited;
+ }
+
+ // Unfortunate to have to cast to an internal class, but that looks like the
+ // only way short of interfacing with a layer above (which mixes all streams,
+ // which we don't want) or a layer below (which we try to avoid because it is
+ // less stable).
+ auto info = static_cast<webrtc::AudioReceiveStreamImpl*>(mRecvStream)
+ ->GetAudioFrameWithInfo(samplingFreqHz, frame);
+
+ if (info == webrtc::AudioMixer::Source::AudioFrameInfo::kError) {
+ CSFLogError(LOGTAG, "%s Getting audio frame failed", __FUNCTION__);
+ return kMediaConduitPlayoutError;
+ }
+
+ CSFLogDebug(LOGTAG, "%s Got %zu channels of %zu samples", __FUNCTION__,
+ frame->num_channels(), frame->samples_per_channel());
+ return kMediaConduitNoError;
+}
+
+// Transport Layer Callbacks
+void WebrtcAudioConduit::OnRtpReceived(webrtc::RtpPacketReceived&& aPacket,
+ webrtc::RTPHeader&& aHeader) {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+
+ if (mAllowSsrcChange && mRecvStreamConfig.rtp.remote_ssrc != aHeader.ssrc) {
+ CSFLogDebug(LOGTAG, "%s: switching from SSRC %u to %u", __FUNCTION__,
+ mRecvStreamConfig.rtp.remote_ssrc, aHeader.ssrc);
+ OverrideRemoteSSRC(aHeader.ssrc);
+ }
+
+ CSFLogVerbose(LOGTAG, "%s: seq# %u, Len %zu, SSRC %u (0x%x) ", __FUNCTION__,
+ aPacket.SequenceNumber(), aPacket.size(), aPacket.Ssrc(),
+ aPacket.Ssrc());
+
+ mRtpPacketEvent.Notify();
+ if (mCall->Call()) {
+ mCall->Call()->Receiver()->DeliverRtpPacket(
+ webrtc::MediaType::AUDIO, std::move(aPacket),
+ [self = RefPtr<WebrtcAudioConduit>(this)](
+ const webrtc::RtpPacketReceived& packet) {
+ CSFLogVerbose(
+ LOGTAG,
+ "AudioConduit %p: failed demuxing packet, ssrc: %u seq: %u",
+ self.get(), packet.Ssrc(), packet.SequenceNumber());
+ return false;
+ });
+ }
+}
+
+void WebrtcAudioConduit::OnRtcpReceived(MediaPacket&& aPacket) {
+ CSFLogDebug(LOGTAG, "%s", __FUNCTION__);
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+
+ if (mCall->Call()) {
+ mCall->Call()->Receiver()->DeliverRtcpPacket(
+ rtc::CopyOnWriteBuffer(aPacket.data(), aPacket.len()));
+ }
+}
+
+Maybe<uint16_t> WebrtcAudioConduit::RtpSendBaseSeqFor(uint32_t aSsrc) const {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ auto it = mRtpSendBaseSeqs.find(aSsrc);
+ if (it == mRtpSendBaseSeqs.end()) {
+ return Nothing();
+ }
+ return Some(it->second);
+}
+
+const dom::RTCStatsTimestampMaker& WebrtcAudioConduit::GetTimestampMaker()
+ const {
+ return mCall->GetTimestampMaker();
+}
+
+void WebrtcAudioConduit::StopTransmitting() {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ MOZ_ASSERT(!mLock.LockedForWritingByCurrentThread());
+
+ if (!mSendStreamRunning) {
+ return;
+ }
+
+ if (mSendStream) {
+ CSFLogDebug(LOGTAG, "%s Stopping send stream", __FUNCTION__);
+ mSendStream->Stop();
+ }
+
+ mSendStreamRunning = false;
+}
+
+void WebrtcAudioConduit::StartTransmitting() {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ MOZ_ASSERT(mSendStream);
+ MOZ_ASSERT(!mLock.LockedForWritingByCurrentThread());
+
+ if (mSendStreamRunning) {
+ return;
+ }
+
+ CSFLogDebug(LOGTAG, "%s Starting send stream", __FUNCTION__);
+
+ mCall->Call()->SignalChannelNetworkState(webrtc::MediaType::AUDIO,
+ webrtc::kNetworkUp);
+ mSendStream->Start();
+ mSendStreamRunning = true;
+}
+
+void WebrtcAudioConduit::StopReceiving() {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ MOZ_ASSERT(!mLock.LockedForWritingByCurrentThread());
+
+ if (!mRecvStreamRunning) {
+ return;
+ }
+
+ if (mRecvStream) {
+ CSFLogDebug(LOGTAG, "%s Stopping recv stream", __FUNCTION__);
+ mRecvStream->Stop();
+ }
+
+ mRecvStreamRunning = false;
+}
+
+void WebrtcAudioConduit::StartReceiving() {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ MOZ_ASSERT(mRecvStream);
+ MOZ_ASSERT(!mLock.LockedForWritingByCurrentThread());
+
+ if (mRecvStreamRunning) {
+ return;
+ }
+
+ CSFLogDebug(LOGTAG, "%s Starting receive stream (SSRC %u (0x%x))",
+ __FUNCTION__, mRecvStreamConfig.rtp.remote_ssrc,
+ mRecvStreamConfig.rtp.remote_ssrc);
+
+ mCall->Call()->SignalChannelNetworkState(webrtc::MediaType::AUDIO,
+ webrtc::kNetworkUp);
+ mRecvStream->Start();
+ mRecvStreamRunning = true;
+}
+
+bool WebrtcAudioConduit::SendRtp(const uint8_t* aData, size_t aLength,
+ const webrtc::PacketOptions& aOptions) {
+ MOZ_ASSERT(aLength >= 12);
+ const uint16_t seqno = ntohs(*((uint16_t*)&aData[2]));
+ const uint32_t ssrc = ntohl(*((uint32_t*)&aData[8]));
+
+ CSFLogVerbose(
+ LOGTAG,
+ "AudioConduit %p: Sending RTP Packet seq# %u, len %zu, SSRC %u (0x%x)",
+ this, seqno, aLength, ssrc, ssrc);
+
+ if (!mTransportActive) {
+ CSFLogError(LOGTAG, "AudioConduit %p: RTP Packet Send Failed ", this);
+ return false;
+ }
+
+ MediaPacket packet;
+ packet.Copy(aData, aLength, aLength + SRTP_MAX_EXPANSION);
+ packet.SetType(MediaPacket::RTP);
+ mSenderRtpSendEvent.Notify(std::move(packet));
+
+ // Parse the sequence number of the first rtp packet as base_seq.
+ const auto inserted = mRtpSendBaseSeqs_n.insert({ssrc, seqno}).second;
+
+ if (inserted || aOptions.packet_id >= 0) {
+ int64_t now_ms = PR_Now() / 1000;
+ MOZ_ALWAYS_SUCCEEDS(mCallThread->Dispatch(NS_NewRunnableFunction(
+ __func__, [this, self = RefPtr<WebrtcAudioConduit>(this),
+ packet_id = aOptions.packet_id, now_ms, ssrc, seqno] {
+ mRtpSendBaseSeqs.insert({ssrc, seqno});
+ if (packet_id >= 0) {
+ if (mCall->Call()) {
+ // TODO: This notification should ideally happen after the
+ // transport layer has sent the packet on the wire.
+ mCall->Call()->OnSentPacket({packet_id, now_ms});
+ }
+ }
+ })));
+ }
+ return true;
+}
+
+bool WebrtcAudioConduit::SendSenderRtcp(const uint8_t* aData, size_t aLength) {
+ CSFLogVerbose(
+ LOGTAG,
+ "AudioConduit %p: Sending RTCP SR Packet, len %zu, SSRC %u (0x%x)", this,
+ aLength, (uint32_t)ntohl(*((uint32_t*)&aData[4])),
+ (uint32_t)ntohl(*((uint32_t*)&aData[4])));
+
+ if (!mTransportActive) {
+ CSFLogError(LOGTAG, "%s RTCP SR Packet Send Failed ", __FUNCTION__);
+ return false;
+ }
+
+ MediaPacket packet;
+ packet.Copy(aData, aLength, aLength + SRTP_MAX_EXPANSION);
+ packet.SetType(MediaPacket::RTCP);
+ mSenderRtcpSendEvent.Notify(std::move(packet));
+ return true;
+}
+
+bool WebrtcAudioConduit::SendReceiverRtcp(const uint8_t* aData,
+ size_t aLength) {
+ CSFLogVerbose(
+ LOGTAG,
+ "AudioConduit %p: Sending RTCP RR Packet, len %zu, SSRC %u (0x%x)", this,
+ aLength, (uint32_t)ntohl(*((uint32_t*)&aData[4])),
+ (uint32_t)ntohl(*((uint32_t*)&aData[4])));
+
+ if (!mTransportActive) {
+ CSFLogError(LOGTAG, "AudioConduit %p: RTCP RR Packet Send Failed", this);
+ return false;
+ }
+
+ MediaPacket packet;
+ packet.Copy(aData, aLength, aLength + SRTP_MAX_EXPANSION);
+ packet.SetType(MediaPacket::RTCP);
+ mReceiverRtcpSendEvent.Notify(std::move(packet));
+ return true;
+}
+
+/**
+ * Supported Sampling Frequencies.
+ */
+bool WebrtcAudioConduit::IsSamplingFreqSupported(int freq) const {
+ return GetNum10msSamplesForFrequency(freq) != 0;
+}
+
+std::vector<webrtc::RtpSource> WebrtcAudioConduit::GetUpstreamRtpSources()
+ const {
+ MOZ_ASSERT(NS_IsMainThread());
+ std::vector<webrtc::RtpSource> sources;
+ {
+ AutoReadLock lock(mLock);
+ if (mRecvStream) {
+ sources = mRecvStream->GetSources();
+ }
+ }
+ return sources;
+}
+
+/* Return block-length of 10 ms audio frame in number of samples */
+unsigned int WebrtcAudioConduit::GetNum10msSamplesForFrequency(
+ int samplingFreqHz) const {
+ switch (samplingFreqHz) {
+ case 16000:
+ return 160; // 160 samples
+ case 32000:
+ return 320; // 320 samples
+ case 44100:
+ return 441; // 441 samples
+ case 48000:
+ return 480; // 480 samples
+ default:
+ return 0; // invalid or unsupported
+ }
+}
+
+/**
+ * Perform validation on the codecConfig to be applied.
+ * Verifies if the codec is already applied.
+ */
+MediaConduitErrorCode WebrtcAudioConduit::ValidateCodecConfig(
+ const AudioCodecConfig& codecInfo, bool send) {
+ if (codecInfo.mName.empty()) {
+ CSFLogError(LOGTAG, "%s Empty Payload Name ", __FUNCTION__);
+ return kMediaConduitMalformedArgument;
+ }
+
+ // Only mono or stereo channels supported
+ if ((codecInfo.mChannels != 1) && (codecInfo.mChannels != 2)) {
+ CSFLogError(LOGTAG, "%s Channel Unsupported ", __FUNCTION__);
+ return kMediaConduitMalformedArgument;
+ }
+
+ return kMediaConduitNoError;
+}
+
+RtpExtList WebrtcAudioConduit::FilterExtensions(LocalDirection aDirection,
+ const RtpExtList& aExtensions) {
+ const bool isSend = aDirection == LocalDirection::kSend;
+ RtpExtList filteredExtensions;
+
+ for (const auto& extension : aExtensions) {
+ // ssrc-audio-level RTP header extension
+ if (extension.uri == webrtc::RtpExtension::kAudioLevelUri) {
+ filteredExtensions.push_back(
+ webrtc::RtpExtension(extension.uri, extension.id));
+ }
+
+ // csrc-audio-level RTP header extension
+ if (extension.uri == webrtc::RtpExtension::kCsrcAudioLevelsUri) {
+ if (isSend) {
+ continue;
+ }
+ filteredExtensions.push_back(
+ webrtc::RtpExtension(extension.uri, extension.id));
+ }
+
+ // MID RTP header extension
+ if (extension.uri == webrtc::RtpExtension::kMidUri) {
+ if (!isSend) {
+ // TODO: recv mid support, see also bug 1727211
+ continue;
+ }
+ filteredExtensions.push_back(
+ webrtc::RtpExtension(extension.uri, extension.id));
+ }
+ }
+
+ return filteredExtensions;
+}
+
+webrtc::SdpAudioFormat WebrtcAudioConduit::CodecConfigToLibwebrtcFormat(
+ const AudioCodecConfig& aConfig) {
+ webrtc::SdpAudioFormat::Parameters parameters;
+ if (aConfig.mName == kOpusCodecName) {
+ if (aConfig.mChannels == 2) {
+ parameters[kCodecParamStereo] = kParamValueTrue;
+ }
+ if (aConfig.mFECEnabled) {
+ parameters[kCodecParamUseInbandFec] = kParamValueTrue;
+ }
+ if (aConfig.mDTXEnabled) {
+ parameters[kCodecParamUseDtx] = kParamValueTrue;
+ }
+ if (aConfig.mMaxPlaybackRate) {
+ parameters[kCodecParamMaxPlaybackRate] =
+ std::to_string(aConfig.mMaxPlaybackRate);
+ }
+ if (aConfig.mMaxAverageBitrate) {
+ parameters[kCodecParamMaxAverageBitrate] =
+ std::to_string(aConfig.mMaxAverageBitrate);
+ }
+ if (aConfig.mFrameSizeMs) {
+ parameters[kCodecParamPTime] = std::to_string(aConfig.mFrameSizeMs);
+ }
+ if (aConfig.mMinFrameSizeMs) {
+ parameters[kCodecParamMinPTime] = std::to_string(aConfig.mMinFrameSizeMs);
+ }
+ if (aConfig.mMaxFrameSizeMs) {
+ parameters[kCodecParamMaxPTime] = std::to_string(aConfig.mMaxFrameSizeMs);
+ }
+ if (aConfig.mCbrEnabled) {
+ parameters[kCodecParamCbr] = kParamValueTrue;
+ }
+ }
+
+ return webrtc::SdpAudioFormat(aConfig.mName, aConfig.mFreq, aConfig.mChannels,
+ parameters);
+}
+
+void WebrtcAudioConduit::DeleteSendStream() {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ MOZ_ASSERT(mLock.LockedForWritingByCurrentThread());
+
+ if (!mSendStream) {
+ return;
+ }
+
+ mCall->Call()->DestroyAudioSendStream(mSendStream);
+ mSendStreamRunning = false;
+ mSendStream = nullptr;
+
+ // Reset base_seqs in case ssrcs get re-used.
+ mRtpSendBaseSeqs.clear();
+}
+
+void WebrtcAudioConduit::CreateSendStream() {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ MOZ_ASSERT(mLock.LockedForWritingByCurrentThread());
+
+ if (mSendStream) {
+ return;
+ }
+
+ mSendStream = mCall->Call()->CreateAudioSendStream(mSendStreamConfig);
+}
+
+void WebrtcAudioConduit::DeleteRecvStream() {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ MOZ_ASSERT(mLock.LockedForWritingByCurrentThread());
+
+ if (!mRecvStream) {
+ return;
+ }
+
+ mCall->Call()->DestroyAudioReceiveStream(mRecvStream);
+ mRecvStreamRunning = false;
+ mRecvStream = nullptr;
+}
+
+void WebrtcAudioConduit::CreateRecvStream() {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ MOZ_ASSERT(mLock.LockedForWritingByCurrentThread());
+
+ if (mRecvStream) {
+ return;
+ }
+
+ mRecvStream = mCall->Call()->CreateAudioReceiveStream(mRecvStreamConfig);
+ // Ensure that we set the jitter buffer target on this stream.
+ mRecvStream->SetBaseMinimumPlayoutDelayMs(mJitterBufferTargetMs);
+}
+
+void WebrtcAudioConduit::SetJitterBufferTarget(DOMHighResTimeStamp aTargetMs) {
+ MOZ_RELEASE_ASSERT(aTargetMs <= std::numeric_limits<uint16_t>::max());
+ MOZ_RELEASE_ASSERT(aTargetMs >= 0);
+
+ MOZ_ALWAYS_SUCCEEDS(mCallThread->Dispatch(NS_NewRunnableFunction(
+ __func__,
+ [this, self = RefPtr<WebrtcAudioConduit>(this), targetMs = aTargetMs] {
+ mJitterBufferTargetMs = static_cast<uint16_t>(targetMs);
+ if (mRecvStream) {
+ mRecvStream->SetBaseMinimumPlayoutDelayMs(targetMs);
+ }
+ })));
+}
+
+void WebrtcAudioConduit::DeliverPacket(rtc::CopyOnWriteBuffer packet,
+ PacketType type) {
+ // Currently unused.
+ MOZ_ASSERT(false);
+}
+
+Maybe<int> WebrtcAudioConduit::ActiveSendPayloadType() const {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+
+ auto stats = GetSenderStats();
+ if (!stats) {
+ return Nothing();
+ }
+
+ if (!stats->codec_payload_type) {
+ return Nothing();
+ }
+
+ return Some(*stats->codec_payload_type);
+}
+
+Maybe<int> WebrtcAudioConduit::ActiveRecvPayloadType() const {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+
+ auto stats = GetReceiverStats();
+ if (!stats) {
+ return Nothing();
+ }
+
+ if (!stats->codec_payload_type) {
+ return Nothing();
+ }
+
+ return Some(*stats->codec_payload_type);
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/libwebrtcglue/AudioConduit.h b/dom/media/webrtc/libwebrtcglue/AudioConduit.h
new file mode 100644
index 0000000000..e8de331e12
--- /dev/null
+++ b/dom/media/webrtc/libwebrtcglue/AudioConduit.h
@@ -0,0 +1,303 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef AUDIO_SESSION_H_
+#define AUDIO_SESSION_H_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "mozilla/RWLock.h"
+#include "mozilla/StateMirroring.h"
+#include "mozilla/TimeStamp.h"
+
+#include "MediaConduitInterface.h"
+#include "common/MediaEngineWrapper.h"
+
+/**
+ * This file hosts several structures identifying different aspects of a RTP
+ * Session.
+ */
+namespace mozilla {
+
+struct DtmfEvent;
+
+/**
+ * Concrete class for Audio session. Hooks up
+ * - media-source and target to external transport
+ */
+class WebrtcAudioConduit : public AudioSessionConduit,
+ public webrtc::RtcpEventObserver {
+ public:
+ Maybe<int> ActiveSendPayloadType() const override;
+ Maybe<int> ActiveRecvPayloadType() const override;
+
+ void OnRtpReceived(webrtc::RtpPacketReceived&& aPacket,
+ webrtc::RTPHeader&& aHeader);
+ void OnRtcpReceived(MediaPacket&& aPacket);
+
+ void OnRtcpBye() override;
+ void OnRtcpTimeout() override;
+
+ void SetTransportActive(bool aActive) override;
+
+ MediaEventSourceExc<MediaPacket>& SenderRtpSendEvent() override {
+ return mSenderRtpSendEvent;
+ }
+ MediaEventSourceExc<MediaPacket>& SenderRtcpSendEvent() override {
+ return mSenderRtcpSendEvent;
+ }
+ MediaEventSourceExc<MediaPacket>& ReceiverRtcpSendEvent() override {
+ return mReceiverRtcpSendEvent;
+ }
+ void ConnectReceiverRtpEvent(
+ MediaEventSourceExc<webrtc::RtpPacketReceived, webrtc::RTPHeader>& aEvent)
+ override {
+ mReceiverRtpEventListener =
+ aEvent.Connect(mCallThread, this, &WebrtcAudioConduit::OnRtpReceived);
+ }
+ void ConnectReceiverRtcpEvent(
+ MediaEventSourceExc<MediaPacket>& aEvent) override {
+ mReceiverRtcpEventListener =
+ aEvent.Connect(mCallThread, this, &WebrtcAudioConduit::OnRtcpReceived);
+ }
+ void ConnectSenderRtcpEvent(
+ MediaEventSourceExc<MediaPacket>& aEvent) override {
+ mSenderRtcpEventListener =
+ aEvent.Connect(mCallThread, this, &WebrtcAudioConduit::OnRtcpReceived);
+ }
+
+ Maybe<uint16_t> RtpSendBaseSeqFor(uint32_t aSsrc) const override;
+
+ const dom::RTCStatsTimestampMaker& GetTimestampMaker() const override;
+
+ void StopTransmitting();
+ void StartTransmitting();
+ void StopReceiving();
+ void StartReceiving();
+
+ /**
+ * Function to deliver externally captured audio sample for encoding and
+ * transport
+ * @param frame [in]: AudioFrame in upstream's format for forwarding to the
+ * send stream. Ownership is passed along.
+ * NOTE: ConfigureSendMediaCodec() SHOULD be called before this function can
+ * be invoked. This ensures the inserted audio-samples can be transmitted by
+ * the conduit.
+ */
+ MediaConduitErrorCode SendAudioFrame(
+ std::unique_ptr<webrtc::AudioFrame> frame) override;
+
+ /**
+ * Function to grab a decoded audio-sample from the media engine for
+ * rendering / playout of length 10 milliseconds.
+ *
+ * @param samplingFreqHz [in]: Frequency of the sampling for playback in
+ * Hertz (16000, 32000,..)
+ * @param frame [in/out]: Pointer to an AudioFrame to which audio data will be
+ * copied
+ * NOTE: This function should be invoked every 10 milliseconds for the best
+ * performance
+ * NOTE: ConfigureRecvMediaCodec() SHOULD be called before this function can
+ * be invoked
+ * This ensures the decoded samples are ready for reading and playout is
+ * enabled.
+ */
+ MediaConduitErrorCode GetAudioFrame(int32_t samplingFreqHz,
+ webrtc::AudioFrame* frame) override;
+
+ bool SendRtp(const uint8_t* aData, size_t aLength,
+ const webrtc::PacketOptions& aOptions) override;
+ bool SendSenderRtcp(const uint8_t* aData, size_t aLength) override;
+ bool SendReceiverRtcp(const uint8_t* aData, size_t aLength) override;
+
+ bool HasCodecPluginID(uint64_t aPluginID) const override { return false; }
+
+ void SetJitterBufferTarget(DOMHighResTimeStamp aTargetMs) override;
+
+ void DeliverPacket(rtc::CopyOnWriteBuffer packet, PacketType type) override;
+
+ RefPtr<GenericPromise> Shutdown() override;
+
+ WebrtcAudioConduit(RefPtr<WebrtcCallWrapper> aCall,
+ nsCOMPtr<nsISerialEventTarget> aStsThread);
+
+ virtual ~WebrtcAudioConduit();
+
+ // Call thread.
+ void InitControl(AudioConduitControlInterface* aControl) override;
+
+ // Handle a DTMF event from mControl.mOnDtmfEventListener.
+ void OnDtmfEvent(const DtmfEvent& aEvent);
+
+ // Called when a parameter in mControl has changed. Call thread.
+ void OnControlConfigChange();
+
+ Ssrcs GetLocalSSRCs() const override;
+ Maybe<Ssrc> GetRemoteSSRC() const override;
+
+ void DisableSsrcChanges() override {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ mAllowSsrcChange = false;
+ }
+
+ private:
+ /**
+ * Override the remote ssrc configured on mRecvStreamConfig.
+ *
+ * Recreates and restarts the recv stream if needed. The overriden value is
+ * overwritten the next time the mControl.mRemoteSsrc mirror changes value.
+ *
+ * Call thread only.
+ */
+ bool OverrideRemoteSSRC(uint32_t aSsrc);
+
+ public:
+ void UnsetRemoteSSRC(uint32_t aSsrc) override {}
+
+ Maybe<webrtc::AudioReceiveStreamInterface::Stats> GetReceiverStats()
+ const override;
+ Maybe<webrtc::AudioSendStream::Stats> GetSenderStats() const override;
+ Maybe<webrtc::CallBasicStats> GetCallStats() const override;
+
+ bool IsSamplingFreqSupported(int freq) const override;
+
+ MediaEventSource<void>& RtcpByeEvent() override { return mRtcpByeEvent; }
+ MediaEventSource<void>& RtcpTimeoutEvent() override {
+ return mRtcpTimeoutEvent;
+ }
+ MediaEventSource<void>& RtpPacketEvent() override { return mRtpPacketEvent; }
+
+ std::vector<webrtc::RtpSource> GetUpstreamRtpSources() const override;
+
+ private:
+ WebrtcAudioConduit(const WebrtcAudioConduit& other) = delete;
+ void operator=(const WebrtcAudioConduit& other) = delete;
+
+ // Generate block size in sample length for a given sampling frequency
+ unsigned int GetNum10msSamplesForFrequency(int samplingFreqHz) const;
+
+ // Checks the codec to be applied
+ static MediaConduitErrorCode ValidateCodecConfig(
+ const AudioCodecConfig& codecInfo, bool send);
+ /**
+ * Of all extensions in aExtensions, returns a list of supported extensions.
+ */
+ static RtpExtList FilterExtensions(
+ MediaSessionConduitLocalDirection aDirection,
+ const RtpExtList& aExtensions);
+ static webrtc::SdpAudioFormat CodecConfigToLibwebrtcFormat(
+ const AudioCodecConfig& aConfig);
+
+ void CreateSendStream();
+ void DeleteSendStream();
+ void CreateRecvStream();
+ void DeleteRecvStream();
+
+ // Are SSRC changes without signaling allowed or not.
+ // Call thread only.
+ bool mAllowSsrcChange = true;
+
+ // Const so can be accessed on any thread. Most methods are called on the Call
+ // thread.
+ const RefPtr<WebrtcCallWrapper> mCall;
+
+ // Set up in the ctor and then not touched. Called through by the streams on
+ // any thread.
+ WebrtcSendTransport mSendTransport;
+ WebrtcReceiveTransport mRecvTransport;
+
+ // Accessed only on the Call thread.
+ webrtc::AudioReceiveStreamInterface::Config mRecvStreamConfig;
+
+ // Written only on the Call thread. Guarded by mLock, except for reads on the
+ // Call thread.
+ webrtc::AudioReceiveStreamInterface* mRecvStream;
+
+ // Accessed only on the Call thread.
+ webrtc::AudioSendStream::Config mSendStreamConfig;
+
+ // Written only on the Call thread. Guarded by mLock, except for reads on the
+ // Call thread.
+ webrtc::AudioSendStream* mSendStream;
+
+ // If true => mSendStream started and not stopped
+ // Written only on the Call thread.
+ Atomic<bool> mSendStreamRunning;
+ // If true => mRecvStream started and not stopped
+ // Written only on the Call thread.
+ Atomic<bool> mRecvStreamRunning;
+
+ // Accessed only on the Call thread.
+ bool mDtmfEnabled;
+
+ mutable RWLock mLock MOZ_UNANNOTATED;
+
+ // Call worker thread. All access to mCall->Call() happens here.
+ const RefPtr<AbstractThread> mCallThread;
+
+ // Socket transport service thread. Any thread.
+ const nsCOMPtr<nsISerialEventTarget> mStsThread;
+
+ // Target jitter buffer to be applied to the receive stream in milliseconds.
+ uint16_t mJitterBufferTargetMs = 0;
+
+ struct Control {
+ // Mirrors and events that map to AudioConduitControlInterface for control.
+ // Call thread only.
+ Mirror<bool> mReceiving;
+ Mirror<bool> mTransmitting;
+ Mirror<Ssrcs> mLocalSsrcs;
+ Mirror<std::string> mLocalCname;
+ Mirror<std::string> mMid;
+ Mirror<Ssrc> mRemoteSsrc;
+ Mirror<std::string> mSyncGroup;
+ Mirror<RtpExtList> mLocalRecvRtpExtensions;
+ Mirror<RtpExtList> mLocalSendRtpExtensions;
+ Mirror<Maybe<AudioCodecConfig>> mSendCodec;
+ Mirror<std::vector<AudioCodecConfig>> mRecvCodecs;
+ MediaEventListener mOnDtmfEventListener;
+
+ // For caching mRemoteSsrc, since another caller may change the remote ssrc
+ // in the stream config directly.
+ Ssrc mConfiguredRemoteSsrc = 0;
+ // For tracking changes to mSendCodec.
+ Maybe<AudioCodecConfig> mConfiguredSendCodec;
+ // For tracking changes to mRecvCodecs.
+ std::vector<AudioCodecConfig> mConfiguredRecvCodecs;
+
+ Control() = delete;
+ explicit Control(const RefPtr<AbstractThread>& aCallThread);
+ } mControl;
+
+ // WatchManager allowing Mirrors to trigger functions that will update the
+ // webrtc.org configuration.
+ WatchManager<WebrtcAudioConduit> mWatchManager;
+
+ // Accessed from mStsThread. Last successfully polled RTT
+ Maybe<DOMHighResTimeStamp> mRttSec;
+
+ // Call thread only. ssrc -> base_seq
+ std::map<uint32_t, uint16_t> mRtpSendBaseSeqs;
+ // libwebrtc network thread only. ssrc -> base_seq.
+ // To track changes needed to mRtpSendBaseSeqs.
+ std::map<uint32_t, uint16_t> mRtpSendBaseSeqs_n;
+
+ // Thread safe
+ Atomic<bool> mTransportActive = Atomic<bool>(false);
+ MediaEventProducer<void> mRtcpByeEvent;
+ MediaEventProducer<void> mRtcpTimeoutEvent;
+ MediaEventProducer<void> mRtpPacketEvent;
+ MediaEventProducerExc<MediaPacket> mSenderRtpSendEvent;
+ MediaEventProducerExc<MediaPacket> mSenderRtcpSendEvent;
+ MediaEventProducerExc<MediaPacket> mReceiverRtcpSendEvent;
+
+ // Assigned and revoked on mStsThread. Listeners for receiving packets.
+ MediaEventListener mSenderRtcpEventListener; // Rtp-transmitting pipeline
+ MediaEventListener mReceiverRtcpEventListener; // Rtp-receiving pipeline
+ MediaEventListener mReceiverRtpEventListener; // Rtp-receiving pipeline
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/libwebrtcglue/CallWorkerThread.h b/dom/media/webrtc/libwebrtcglue/CallWorkerThread.h
new file mode 100644
index 0000000000..12d21fbee4
--- /dev/null
+++ b/dom/media/webrtc/libwebrtcglue/CallWorkerThread.h
@@ -0,0 +1,116 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DOM_MEDIA_WEBRTC_LIBWEBRTCGLUE_CALLWORKERTHREAD_H_
+#define DOM_MEDIA_WEBRTC_LIBWEBRTCGLUE_CALLWORKERTHREAD_H_
+
+#include "mozilla/AbstractThread.h"
+#include "nsIDirectTaskDispatcher.h"
+#include "TaskQueueWrapper.h"
+
+namespace mozilla {
+
+// Implements AbstractThread for running things on the webrtc TaskQueue.
+// Webrtc TaskQueues are not refcounted so cannot implement AbstractThread
+// directly.
+class CallWorkerThread final : public AbstractThread,
+ public nsIDirectTaskDispatcher {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIDIRECTTASKDISPATCHER
+
+ explicit CallWorkerThread(
+ UniquePtr<TaskQueueWrapper<DeletionPolicy::NonBlocking>> aWebrtcTaskQueue)
+ : AbstractThread(aWebrtcTaskQueue->mTaskQueue->SupportsTailDispatch()),
+ mWebrtcTaskQueue(std::move(aWebrtcTaskQueue)) {}
+
+ // AbstractThread overrides
+ nsresult Dispatch(already_AddRefed<nsIRunnable> aRunnable,
+ DispatchReason aReason) override;
+ bool IsCurrentThreadIn() const override;
+ TaskDispatcher& TailDispatcher() override;
+ nsIEventTarget* AsEventTarget() override;
+ NS_IMETHOD
+ DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
+ uint32_t aDelayMs) override;
+
+ NS_IMETHOD RegisterShutdownTask(nsITargetShutdownTask* aTask) override;
+ NS_IMETHOD UnregisterShutdownTask(nsITargetShutdownTask* aTask) override;
+
+ const UniquePtr<TaskQueueWrapper<DeletionPolicy::NonBlocking>>
+ mWebrtcTaskQueue;
+
+ protected:
+ ~CallWorkerThread() = default;
+};
+
+NS_IMPL_ISUPPORTS(CallWorkerThread, nsIDirectTaskDispatcher,
+ nsISerialEventTarget, nsIEventTarget);
+
+//-----------------------------------------------------------------------------
+// AbstractThread
+//-----------------------------------------------------------------------------
+
+nsresult CallWorkerThread::Dispatch(already_AddRefed<nsIRunnable> aRunnable,
+ DispatchReason aReason) {
+ RefPtr<nsIRunnable> runnable = aRunnable;
+ return mWebrtcTaskQueue->mTaskQueue->Dispatch(
+ mWebrtcTaskQueue->CreateTaskRunner(std::move(runnable)), aReason);
+}
+
+bool CallWorkerThread::IsCurrentThreadIn() const {
+ return mWebrtcTaskQueue->mTaskQueue->IsOnCurrentThreadInfallible() &&
+ mWebrtcTaskQueue->IsCurrent();
+}
+
+TaskDispatcher& CallWorkerThread::TailDispatcher() {
+ return mWebrtcTaskQueue->mTaskQueue->TailDispatcher();
+}
+
+nsIEventTarget* CallWorkerThread::AsEventTarget() {
+ return mWebrtcTaskQueue->mTaskQueue->AsEventTarget();
+}
+
+NS_IMETHODIMP
+CallWorkerThread::DelayedDispatch(already_AddRefed<nsIRunnable> aEvent,
+ uint32_t aDelayMs) {
+ RefPtr<nsIRunnable> event = aEvent;
+ return mWebrtcTaskQueue->mTaskQueue->DelayedDispatch(
+ mWebrtcTaskQueue->CreateTaskRunner(std::move(event)), aDelayMs);
+}
+
+NS_IMETHODIMP CallWorkerThread::RegisterShutdownTask(
+ nsITargetShutdownTask* aTask) {
+ return mWebrtcTaskQueue->mTaskQueue->RegisterShutdownTask(aTask);
+}
+
+NS_IMETHODIMP CallWorkerThread::UnregisterShutdownTask(
+ nsITargetShutdownTask* aTask) {
+ return mWebrtcTaskQueue->mTaskQueue->UnregisterShutdownTask(aTask);
+}
+
+//-----------------------------------------------------------------------------
+// nsIDirectTaskDispatcher
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+CallWorkerThread::DispatchDirectTask(already_AddRefed<nsIRunnable> aEvent) {
+ nsCOMPtr<nsIRunnable> event = aEvent;
+ return mWebrtcTaskQueue->mTaskQueue->DispatchDirectTask(
+ mWebrtcTaskQueue->CreateTaskRunner(std::move(event)));
+}
+
+NS_IMETHODIMP CallWorkerThread::DrainDirectTasks() {
+ return mWebrtcTaskQueue->mTaskQueue->DrainDirectTasks();
+}
+
+NS_IMETHODIMP CallWorkerThread::HaveDirectTasks(bool* aValue) {
+ return mWebrtcTaskQueue->mTaskQueue->HaveDirectTasks(aValue);
+}
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/libwebrtcglue/CodecConfig.h b/dom/media/webrtc/libwebrtcglue/CodecConfig.h
new file mode 100644
index 0000000000..023ea98783
--- /dev/null
+++ b/dom/media/webrtc/libwebrtcglue/CodecConfig.h
@@ -0,0 +1,237 @@
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CODEC_CONFIG_H_
+#define CODEC_CONFIG_H_
+
+#include <string>
+#include <vector>
+
+#include "common/EncodingConstraints.h"
+
+namespace mozilla {
+
+/**
+ * Minimalistic Audio Codec Config Params
+ */
+struct AudioCodecConfig {
+ /*
+ * The data-types for these properties mimic the
+ * corresponding webrtc::CodecInst data-types.
+ */
+ int mType;
+ std::string mName;
+ int mFreq;
+ int mChannels;
+
+ bool mFECEnabled;
+ bool mDtmfEnabled;
+ uint32_t mFrameSizeMs;
+ uint32_t mMaxFrameSizeMs;
+ uint32_t mMinFrameSizeMs;
+
+ // OPUS-specific
+ bool mDTXEnabled;
+ uint32_t mMaxAverageBitrate;
+ int mMaxPlaybackRate;
+ bool mCbrEnabled;
+
+ AudioCodecConfig(int type, std::string name, int freq, int channels,
+ bool FECEnabled)
+ : mType(type),
+ mName(name),
+ mFreq(freq),
+ mChannels(channels),
+ mFECEnabled(FECEnabled),
+ mDtmfEnabled(false),
+ mFrameSizeMs(0),
+ mMaxFrameSizeMs(0),
+ mMinFrameSizeMs(0),
+ mDTXEnabled(false),
+ mMaxAverageBitrate(0),
+ mMaxPlaybackRate(0),
+ mCbrEnabled(false) {}
+
+ bool operator==(const AudioCodecConfig& aOther) const {
+ return mType == aOther.mType && mName == aOther.mName &&
+ mFreq == aOther.mFreq && mChannels == aOther.mChannels &&
+ mFECEnabled == aOther.mFECEnabled &&
+ mDtmfEnabled == aOther.mDtmfEnabled &&
+ mFrameSizeMs == aOther.mFrameSizeMs &&
+ mMaxFrameSizeMs == aOther.mMaxFrameSizeMs &&
+ mMinFrameSizeMs == aOther.mMinFrameSizeMs &&
+ mDTXEnabled == aOther.mDTXEnabled &&
+ mMaxAverageBitrate == aOther.mMaxAverageBitrate &&
+ mMaxPlaybackRate == aOther.mMaxPlaybackRate &&
+ mCbrEnabled == aOther.mCbrEnabled;
+ }
+};
+
+/*
+ * Minimalistic video codec configuration
+ * More to be added later depending on the use-case
+ */
+
+#define MAX_SPROP_LEN 128
+
+// used for holding SDP negotiation results
+struct VideoCodecConfigH264 {
+ char sprop_parameter_sets[MAX_SPROP_LEN];
+ int packetization_mode;
+ int profile_level_id;
+ int tias_bw;
+
+ bool operator==(const VideoCodecConfigH264& aOther) const {
+ return strncmp(sprop_parameter_sets, aOther.sprop_parameter_sets,
+ MAX_SPROP_LEN) == 0 &&
+ packetization_mode == aOther.packetization_mode &&
+ profile_level_id == aOther.profile_level_id &&
+ tias_bw == aOther.tias_bw;
+ }
+};
+
+// class so the std::strings can get freed more easily/reliably
+class VideoCodecConfig {
+ public:
+ /*
+ * The data-types for these properties mimic the
+ * corresponding webrtc::VideoCodec data-types.
+ */
+ int mType; // payload type
+ std::string mName;
+
+ std::vector<std::string> mAckFbTypes;
+ std::vector<std::string> mNackFbTypes;
+ std::vector<std::string> mCcmFbTypes;
+ // Don't pass mOtherFbTypes from JsepVideoCodecDescription because we'd have
+ // to drag SdpRtcpFbAttributeList::Feedback along too.
+ bool mRembFbSet;
+ bool mFECFbSet;
+ bool mTransportCCFbSet;
+
+ int mULPFECPayloadType;
+ int mREDPayloadType;
+ int mREDRTXPayloadType;
+ int mRTXPayloadType;
+
+ uint32_t mTias;
+ EncodingConstraints mEncodingConstraints;
+ struct Encoding {
+ std::string rid;
+ EncodingConstraints constraints;
+ bool active = true;
+ // TODO(bug 1744116): Use = default here
+ bool operator==(const Encoding& aOther) const {
+ return rid == aOther.rid && constraints == aOther.constraints &&
+ active == aOther.active;
+ }
+ };
+ std::vector<Encoding> mEncodings;
+ std::string mSpropParameterSets;
+ uint8_t mProfile;
+ uint8_t mConstraints;
+ uint8_t mLevel;
+ uint8_t mPacketizationMode;
+ // TODO: add external negotiated SPS/PPS
+
+ // TODO(bug 1744116): Use = default here
+ bool operator==(const VideoCodecConfig& aRhs) const {
+ return mType == aRhs.mType && mName == aRhs.mName &&
+ mAckFbTypes == aRhs.mAckFbTypes &&
+ mNackFbTypes == aRhs.mNackFbTypes &&
+ mCcmFbTypes == aRhs.mCcmFbTypes && mRembFbSet == aRhs.mRembFbSet &&
+ mFECFbSet == aRhs.mFECFbSet &&
+ mTransportCCFbSet == aRhs.mTransportCCFbSet &&
+ mULPFECPayloadType == aRhs.mULPFECPayloadType &&
+ mREDPayloadType == aRhs.mREDPayloadType &&
+ mREDRTXPayloadType == aRhs.mREDRTXPayloadType &&
+ mRTXPayloadType == aRhs.mRTXPayloadType && mTias == aRhs.mTias &&
+ mEncodingConstraints == aRhs.mEncodingConstraints &&
+ mEncodings == aRhs.mEncodings &&
+ mSpropParameterSets == aRhs.mSpropParameterSets &&
+ mProfile == aRhs.mProfile && mConstraints == aRhs.mConstraints &&
+ mLevel == aRhs.mLevel &&
+ mPacketizationMode == aRhs.mPacketizationMode;
+ }
+
+ VideoCodecConfig(int type, std::string name,
+ const EncodingConstraints& constraints,
+ const struct VideoCodecConfigH264* h264 = nullptr)
+ : mType(type),
+ mName(name),
+ mRembFbSet(false),
+ mFECFbSet(false),
+ mTransportCCFbSet(false),
+ mULPFECPayloadType(-1),
+ mREDPayloadType(-1),
+ mREDRTXPayloadType(-1),
+ mRTXPayloadType(-1),
+ mTias(0),
+ mEncodingConstraints(constraints),
+ mProfile(0x42),
+ mConstraints(0xE0),
+ mLevel(0x0C),
+ mPacketizationMode(1) {
+ if (h264) {
+ mProfile = (h264->profile_level_id & 0x00FF0000) >> 16;
+ mConstraints = (h264->profile_level_id & 0x0000FF00) >> 8;
+ mLevel = (h264->profile_level_id & 0x000000FF);
+ mPacketizationMode = h264->packetization_mode;
+ mSpropParameterSets = h264->sprop_parameter_sets;
+ }
+ }
+
+ bool ResolutionEquals(const VideoCodecConfig& aConfig) const {
+ if (mEncodings.size() != aConfig.mEncodings.size()) {
+ return false;
+ }
+ for (size_t i = 0; i < mEncodings.size(); ++i) {
+ if (!mEncodings[i].constraints.ResolutionEquals(
+ aConfig.mEncodings[i].constraints)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Nothing seems to use this right now. Do we intend to support this
+ // someday?
+ bool RtcpFbAckIsSet(const std::string& type) const {
+ for (auto i = mAckFbTypes.begin(); i != mAckFbTypes.end(); ++i) {
+ if (*i == type) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool RtcpFbNackIsSet(const std::string& type) const {
+ for (auto i = mNackFbTypes.begin(); i != mNackFbTypes.end(); ++i) {
+ if (*i == type) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool RtcpFbCcmIsSet(const std::string& type) const {
+ for (auto i = mCcmFbTypes.begin(); i != mCcmFbTypes.end(); ++i) {
+ if (*i == type) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool RtcpFbRembIsSet() const { return mRembFbSet; }
+
+ bool RtcpFbFECIsSet() const { return mFECFbSet; }
+
+ bool RtcpFbTransportCCIsSet() const { return mTransportCCFbSet; }
+
+ bool RtxPayloadTypeIsSet() const { return mRTXPayloadType != -1; }
+};
+} // namespace mozilla
+#endif
diff --git a/dom/media/webrtc/libwebrtcglue/GmpVideoCodec.cpp b/dom/media/webrtc/libwebrtcglue/GmpVideoCodec.cpp
new file mode 100644
index 0000000000..ccadd846e2
--- /dev/null
+++ b/dom/media/webrtc/libwebrtcglue/GmpVideoCodec.cpp
@@ -0,0 +1,22 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebrtcGmpVideoCodec.h"
+#include "GmpVideoCodec.h"
+
+namespace mozilla {
+
+WebrtcVideoEncoder* GmpVideoCodec::CreateEncoder(
+ const webrtc::SdpVideoFormat& aFormat, std::string aPCHandle) {
+ return new WebrtcVideoEncoderProxy(
+ new WebrtcGmpVideoEncoder(aFormat, std::move(aPCHandle)));
+}
+
+WebrtcVideoDecoder* GmpVideoCodec::CreateDecoder(std::string aPCHandle,
+ TrackingId aTrackingId) {
+ return new WebrtcVideoDecoderProxy(std::move(aPCHandle),
+ std::move(aTrackingId));
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/libwebrtcglue/GmpVideoCodec.h b/dom/media/webrtc/libwebrtcglue/GmpVideoCodec.h
new file mode 100644
index 0000000000..caf125c809
--- /dev/null
+++ b/dom/media/webrtc/libwebrtcglue/GmpVideoCodec.h
@@ -0,0 +1,27 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPVIDEOCODEC_H_
+#define GMPVIDEOCODEC_H_
+
+#include <string>
+
+#include "PerformanceRecorder.h"
+
+namespace mozilla {
+
+class WebrtcVideoDecoder;
+class WebrtcVideoEncoder;
+
+class GmpVideoCodec {
+ public:
+ static WebrtcVideoEncoder* CreateEncoder(
+ const webrtc::SdpVideoFormat& aFormat, std::string aPCHandle);
+ static WebrtcVideoDecoder* CreateDecoder(std::string aPCHandle,
+ TrackingId aTrackingId);
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/libwebrtcglue/MediaConduitControl.h b/dom/media/webrtc/libwebrtcglue/MediaConduitControl.h
new file mode 100644
index 0000000000..a860fab146
--- /dev/null
+++ b/dom/media/webrtc/libwebrtcglue/MediaConduitControl.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DOM_MEDIA_WEBRTC_LIBWEBRTCGLUE_MEDIACONDUITCONTROL_H_
+#define DOM_MEDIA_WEBRTC_LIBWEBRTCGLUE_MEDIACONDUITCONTROL_H_
+
+#include "jsapi/RTCDTMFSender.h" // For DtmfEvent
+#include "mozilla/StateMirroring.h"
+#include "RtpRtcpConfig.h"
+#include <vector>
+#include <string>
+#include "mozilla/Maybe.h"
+#include "CodecConfig.h" // For Audio/VideoCodecConfig
+#include "api/rtp_parameters.h" // For webrtc::RtpExtension
+#include "api/video_codecs/video_codec.h" // For webrtc::VideoCodecMode
+
+namespace mozilla {
+
+using RtpExtList = std::vector<webrtc::RtpExtension>;
+using Ssrc = uint32_t;
+using Ssrcs = std::vector<uint32_t>;
+
+/**
+ * These are the interfaces used to control the async conduits. Some parameters
+ * are common, and some are tied to the conduit type. See
+ * MediaSessionConduit::InitConduitControl for how they are used.
+ *
+ * Put simply, the implementer of the interfaces below may set its canonicals on
+ * any thread, and the conduits will react to those changes accordingly, on
+ * their dedicated worker thread. One instance of these interfaces could control
+ * multiple conduits as each canonical can connect to any number of mirrors.
+ */
+
+class MediaConduitControlInterface {
+ public:
+ virtual AbstractCanonical<bool>* CanonicalReceiving() = 0;
+ virtual AbstractCanonical<bool>* CanonicalTransmitting() = 0;
+ virtual AbstractCanonical<Ssrcs>* CanonicalLocalSsrcs() = 0;
+ virtual AbstractCanonical<std::string>* CanonicalLocalCname() = 0;
+ virtual AbstractCanonical<std::string>* CanonicalMid() = 0;
+ virtual AbstractCanonical<Ssrc>* CanonicalRemoteSsrc() = 0;
+ virtual AbstractCanonical<std::string>* CanonicalSyncGroup() = 0;
+ virtual AbstractCanonical<RtpExtList>* CanonicalLocalRecvRtpExtensions() = 0;
+ virtual AbstractCanonical<RtpExtList>* CanonicalLocalSendRtpExtensions() = 0;
+};
+
+class AudioConduitControlInterface : public MediaConduitControlInterface {
+ public:
+ virtual AbstractCanonical<Maybe<AudioCodecConfig>>*
+ CanonicalAudioSendCodec() = 0;
+ virtual AbstractCanonical<std::vector<AudioCodecConfig>>*
+ CanonicalAudioRecvCodecs() = 0;
+ virtual MediaEventSource<DtmfEvent>& OnDtmfEvent() = 0;
+};
+
+class VideoConduitControlInterface : public MediaConduitControlInterface {
+ public:
+ virtual AbstractCanonical<Ssrcs>* CanonicalLocalVideoRtxSsrcs() = 0;
+ virtual AbstractCanonical<Ssrc>* CanonicalRemoteVideoRtxSsrc() = 0;
+ virtual AbstractCanonical<Maybe<VideoCodecConfig>>*
+ CanonicalVideoSendCodec() = 0;
+ virtual AbstractCanonical<Maybe<RtpRtcpConfig>>*
+ CanonicalVideoSendRtpRtcpConfig() = 0;
+ virtual AbstractCanonical<std::vector<VideoCodecConfig>>*
+ CanonicalVideoRecvCodecs() = 0;
+ virtual AbstractCanonical<Maybe<RtpRtcpConfig>>*
+ CanonicalVideoRecvRtpRtcpConfig() = 0;
+ virtual AbstractCanonical<webrtc::VideoCodecMode>*
+ CanonicalVideoCodecMode() = 0;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/libwebrtcglue/MediaConduitErrors.h b/dom/media/webrtc/libwebrtcglue/MediaConduitErrors.h
new file mode 100644
index 0000000000..34487d77a0
--- /dev/null
+++ b/dom/media/webrtc/libwebrtcglue/MediaConduitErrors.h
@@ -0,0 +1,46 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MEDIA_SESSION_ERRORS_H_
+#define MEDIA_SESSION_ERRORS_H_
+
+namespace mozilla {
+enum MediaConduitErrorCode {
+ kMediaConduitNoError = 0, // 0 for Success,greater than 0 imples error
+ kMediaConduitSessionNotInited =
+ 10100, // Session not initialized.10100 serves as
+ // base for the conduit errors
+ kMediaConduitMalformedArgument, // Malformed input to Conduit API
+ kMediaConduitCaptureError, // WebRTC capture APIs failed
+ kMediaConduitInvalidSendCodec, // Wrong Send codec
+ kMediaConduitInvalidReceiveCodec, // Wrong Recv Codec
+ kMediaConduitCodecInUse, // Already applied Codec
+ kMediaConduitInvalidRenderer, // Null or Wrong Renderer object
+ kMediaConduitRendererFail, // Add Render called multiple times
+ kMediaConduitSendingAlready, // Engine already trasmitting
+ kMediaConduitReceivingAlready, // Engine already receiving
+ kMediaConduitTransportRegistrationFail, // Null or wrong transport interface
+ kMediaConduitInvalidTransport, // Null or wrong transport interface
+ kMediaConduitChannelError, // Configuration Error
+ kMediaConduitSocketError, // Media Engine transport socket error
+ kMediaConduitRTPRTCPModuleError, // Couldn't start RTP/RTCP processing
+ kMediaConduitRTPProcessingFailed, // Processing incoming RTP frame failed
+ kMediaConduitUnknownError, // More information can be found in logs
+ kMediaConduitExternalRecordingError, // Couldn't start external recording
+ kMediaConduitRecordingError, // Runtime recording error
+ kMediaConduitExternalPlayoutError, // Couldn't start external playout
+ kMediaConduitPlayoutError, // Runtime playout error
+ kMediaConduitMTUError, // Can't set MTU
+ kMediaConduitRTCPStatusError, // Can't set RTCP mode
+ kMediaConduitKeyFrameRequestError, // Can't set KeyFrameRequest mode
+ kMediaConduitNACKStatusError, // Can't set NACK mode
+ kMediaConduitTMMBRStatusError, // Can't set TMMBR mode
+ kMediaConduitFECStatusError, // Can't set FEC mode
+ kMediaConduitHybridNACKFECStatusError, // Can't set Hybrid NACK / FEC mode
+ kMediaConduitVideoSendStreamError // WebRTC video send stream failure
+};
+
+}
+
+#endif
diff --git a/dom/media/webrtc/libwebrtcglue/MediaConduitInterface.cpp b/dom/media/webrtc/libwebrtcglue/MediaConduitInterface.cpp
new file mode 100644
index 0000000000..28079b2478
--- /dev/null
+++ b/dom/media/webrtc/libwebrtcglue/MediaConduitInterface.cpp
@@ -0,0 +1,151 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MediaConduitInterface.h"
+
+#include "nsTArray.h"
+#include "mozilla/Assertions.h"
+#include "MainThreadUtils.h"
+#include "SystemTime.h"
+
+#include "system_wrappers/include/clock.h"
+
+namespace mozilla {
+
+void MediaSessionConduit::GetRtpSources(
+ nsTArray<dom::RTCRtpSourceEntry>& outSources) const {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mSourcesUpdateNeeded) {
+ UpdateRtpSources(GetUpstreamRtpSources());
+ OnSourcesUpdated();
+ }
+ outSources.Clear();
+ for (auto& [key, entry] : mSourcesCache) {
+ (void)key;
+ outSources.AppendElement(entry);
+ }
+
+ struct TimestampComparator {
+ bool LessThan(const dom::RTCRtpSourceEntry& aLhs,
+ const dom::RTCRtpSourceEntry& aRhs) const {
+ // Sort descending!
+ return aLhs.mTimestamp > aRhs.mTimestamp;
+ }
+
+ bool Equals(const dom::RTCRtpSourceEntry& aLhs,
+ const dom::RTCRtpSourceEntry& aRhs) const {
+ return aLhs.mTimestamp == aRhs.mTimestamp;
+ }
+ };
+
+ // *sigh* We have to re-sort this by JS timestamp; we can run into cases
+ // where the libwebrtc timestamps are not in exactly the same order as JS
+ // timestamps due to clock differences (wibbly-wobbly, timey-wimey stuff)
+ outSources.Sort(TimestampComparator());
+}
+
+static double rtpToDomAudioLevel(uint8_t aAudioLevel) {
+ if (aAudioLevel == 127) {
+ // Spec indicates that a value of 127 should be set to 0
+ return 0;
+ }
+
+ // All other values are calculated as 10^(-rfc_level/20)
+ return std::pow(10, -aAudioLevel / 20.0);
+}
+
+void MediaSessionConduit::UpdateRtpSources(
+ const std::vector<webrtc::RtpSource>& aSources) const {
+ MOZ_ASSERT(NS_IsMainThread());
+ // Empty out the cache; we'll copy things back as needed
+ auto cache = std::move(mSourcesCache);
+
+ for (const auto& source : aSources) {
+ SourceKey key(source);
+ auto it = cache.find(key);
+ if (it != cache.end()) {
+ // This source entry was already in the cache, and should continue to be
+ // present in exactly the same form as before. This means we do _not_
+ // want to perform the timestamp adjustment again, since it might yield a
+ // slightly different result. This is why we copy this entry from the old
+ // cache instead of simply rebuilding it, and is also why we key the
+ // cache based on timestamp (keying the cache based on timestamp also
+ // gets us the ordering we want, conveniently).
+ mSourcesCache[key] = it->second;
+ continue;
+ }
+
+ // This is something we did not already have in the cache.
+ dom::RTCRtpSourceEntry domEntry;
+ domEntry.mSource = source.source_id();
+ switch (source.source_type()) {
+ case webrtc::RtpSourceType::SSRC:
+ domEntry.mSourceType = dom::RTCRtpSourceEntryType::Synchronization;
+ break;
+ case webrtc::RtpSourceType::CSRC:
+ domEntry.mSourceType = dom::RTCRtpSourceEntryType::Contributing;
+ break;
+ default:
+ MOZ_CRASH("Unexpected RTCRtpSourceEntryType");
+ }
+
+ if (source.audio_level()) {
+ domEntry.mAudioLevel.Construct(rtpToDomAudioLevel(*source.audio_level()));
+ }
+
+ // These timestamps are always **rounded** to milliseconds. That means they
+ // can jump up to half a millisecond into the future. We compensate for that
+ // here so that things seem consistent to js.
+ domEntry.mTimestamp = dom::RTCStatsTimestamp::FromRealtime(
+ GetTimestampMaker(),
+ webrtc::Timestamp::Millis(source.timestamp_ms()) -
+ webrtc::TimeDelta::Micros(500))
+ .ToDom();
+ domEntry.mRtpTimestamp = source.rtp_timestamp();
+ mSourcesCache[key] = domEntry;
+ }
+}
+
+void MediaSessionConduit::OnSourcesUpdated() const {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mSourcesUpdateNeeded);
+ mSourcesUpdateNeeded = false;
+ // Reset the updateNeeded flag and clear the cache in a direct task, i.e.,
+ // as soon as the current task has finished.
+ AbstractThread::GetCurrent()->TailDispatcher().AddDirectTask(
+ NS_NewRunnableFunction(
+ __func__, [this, self = RefPtr<const MediaSessionConduit>(this)] {
+ mSourcesUpdateNeeded = true;
+ mSourcesCache.clear();
+ }));
+}
+
+void MediaSessionConduit::InsertAudioLevelForContributingSource(
+ const uint32_t aCsrcSource, const int64_t aTimestamp,
+ const uint32_t aRtpTimestamp, const bool aHasAudioLevel,
+ const uint8_t aAudioLevel) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mSourcesUpdateNeeded) {
+ OnSourcesUpdated();
+ }
+
+ dom::RTCRtpSourceEntry domEntry;
+ domEntry.mSource = aCsrcSource;
+ domEntry.mSourceType = dom::RTCRtpSourceEntryType::Contributing;
+ domEntry.mTimestamp = aTimestamp;
+ domEntry.mRtpTimestamp = aRtpTimestamp;
+ if (aHasAudioLevel) {
+ domEntry.mAudioLevel.Construct(rtpToDomAudioLevel(aAudioLevel));
+ }
+
+ auto now = GetTimestampMaker().GetNow();
+ webrtc::Timestamp convertedTimestamp =
+ now.ToRealtime() - webrtc::TimeDelta::Millis(now.ToDom() - aTimestamp);
+
+ SourceKey key(convertedTimestamp.ms<uint32_t>(), aCsrcSource);
+ mSourcesCache[key] = domEntry;
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/libwebrtcglue/MediaConduitInterface.h b/dom/media/webrtc/libwebrtcglue/MediaConduitInterface.h
new file mode 100644
index 0000000000..86e148dc5c
--- /dev/null
+++ b/dom/media/webrtc/libwebrtcglue/MediaConduitInterface.h
@@ -0,0 +1,495 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MEDIA_CONDUIT_ABSTRACTION_
+#define MEDIA_CONDUIT_ABSTRACTION_
+
+#include <vector>
+#include <functional>
+#include <map>
+
+#include "CodecConfig.h"
+#include "ImageContainer.h"
+#include "jsapi/RTCStatsReport.h"
+#include "MediaConduitErrors.h"
+#include "mozilla/media/MediaUtils.h"
+#include "mozilla/MozPromise.h"
+#include "WebrtcVideoCodecFactory.h"
+#include "nsTArray.h"
+#include "mozilla/dom/RTCRtpSourcesBinding.h"
+#include "PerformanceRecorder.h"
+#include "transport/mediapacket.h"
+#include "MediaConduitControl.h"
+
+// libwebrtc includes
+#include "api/audio/audio_frame.h"
+#include "api/call/transport.h"
+#include "api/rtp_headers.h"
+#include "api/rtp_parameters.h"
+#include "api/transport/rtp/rtp_source.h"
+#include "api/video/video_frame_buffer.h"
+#include "call/audio_receive_stream.h"
+#include "call/audio_send_stream.h"
+#include "call/call_basic_stats.h"
+#include "call/video_receive_stream.h"
+#include "call/video_send_stream.h"
+#include "rtc_base/copy_on_write_buffer.h"
+
+namespace webrtc {
+class RtpPacketReceived;
+class VideoFrame;
+} // namespace webrtc
+
+namespace mozilla {
+namespace dom {
+struct RTCRtpSourceEntry;
+}
+
+namespace dom {
+struct RTCRtpSourceEntry;
+}
+
+enum class MediaSessionConduitLocalDirection : int { kSend, kRecv };
+
+class VideoSessionConduit;
+class AudioSessionConduit;
+class WebrtcCallWrapper;
+
+/**
+ * 1. Abstract renderer for video data
+ * 2. This class acts as abstract interface between the video-engine and
+ * video-engine agnostic renderer implementation.
+ * 3. Concrete implementation of this interface is responsible for
+ * processing and/or rendering the obtained raw video frame to appropriate
+ * output , say, <video>
+ */
+class VideoRenderer {
+ protected:
+ virtual ~VideoRenderer() {}
+
+ public:
+ /**
+ * Callback Function reportng any change in the video-frame dimensions
+ * @param width: current width of the video @ decoder
+ * @param height: current height of the video @ decoder
+ */
+ virtual void FrameSizeChange(unsigned int width, unsigned int height) = 0;
+
+ /**
+ * Callback Function reporting decoded frame for processing.
+ * @param buffer: reference to decoded video frame
+ * @param buffer_size: size of the decoded frame
+ * @param time_stamp: Decoder timestamp, typically 90KHz as per RTP
+ * @render_time: Wall-clock time at the decoder for synchronization
+ * purposes in milliseconds
+ * NOTE: If decoded video frame is passed through buffer , it is the
+ * responsibility of the concrete implementations of this class to own copy
+ * of the frame if needed for time longer than scope of this callback.
+ * Such implementations should be quick in processing the frames and return
+ * immediately.
+ */
+ virtual void RenderVideoFrame(const webrtc::VideoFrameBuffer& buffer,
+ uint32_t time_stamp, int64_t render_time) = 0;
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VideoRenderer)
+};
+
+/**
+ * Generic Interface for representing Audio/Video Session
+ * MediaSession conduit is identified by 2 main components
+ * 1. Attached Transport Interface (through events) for inbound and outbound RTP
+ * transport
+ * 2. Attached Renderer Interface for rendering media data off the network
+ * This class hides specifics of Media-Engine implementation from the consumers
+ * of this interface.
+ * Also provides codec configuration API for the media sent and recevied
+ */
+class MediaSessionConduit {
+ protected:
+ virtual ~MediaSessionConduit() {}
+
+ public:
+ enum Type { AUDIO, VIDEO };
+ enum class PacketType { RTP, RTCP };
+
+ static std::string LocalDirectionToString(
+ const MediaSessionConduitLocalDirection aDirection) {
+ return aDirection == MediaSessionConduitLocalDirection::kSend ? "send"
+ : "receive";
+ }
+
+ virtual Type type() const = 0;
+
+ // Call thread only
+ virtual Maybe<int> ActiveSendPayloadType() const = 0;
+ virtual Maybe<int> ActiveRecvPayloadType() const = 0;
+
+ // Whether transport is currently sending and receiving packets
+ virtual void SetTransportActive(bool aActive) = 0;
+
+ // Sending packets
+ virtual MediaEventSourceExc<MediaPacket>& SenderRtpSendEvent() = 0;
+ virtual MediaEventSourceExc<MediaPacket>& SenderRtcpSendEvent() = 0;
+ virtual MediaEventSourceExc<MediaPacket>& ReceiverRtcpSendEvent() = 0;
+
+ // Receiving packets...
+ // from an rtp-receiving pipeline
+ virtual void ConnectReceiverRtpEvent(
+ MediaEventSourceExc<webrtc::RtpPacketReceived, webrtc::RTPHeader>&
+ aEvent) = 0;
+ // from an rtp-receiving pipeline
+ virtual void ConnectReceiverRtcpEvent(
+ MediaEventSourceExc<MediaPacket>& aEvent) = 0;
+ // from an rtp-transmitting pipeline
+ virtual void ConnectSenderRtcpEvent(
+ MediaEventSourceExc<MediaPacket>& aEvent) = 0;
+
+ // Sts thread only.
+ virtual Maybe<uint16_t> RtpSendBaseSeqFor(uint32_t aSsrc) const = 0;
+
+ // Any thread.
+ virtual const dom::RTCStatsTimestampMaker& GetTimestampMaker() const = 0;
+
+ virtual Ssrcs GetLocalSSRCs() const = 0;
+
+ virtual Maybe<Ssrc> GetRemoteSSRC() const = 0;
+ virtual void UnsetRemoteSSRC(Ssrc aSsrc) = 0;
+
+ virtual void DisableSsrcChanges() = 0;
+
+ virtual bool HasCodecPluginID(uint64_t aPluginID) const = 0;
+
+ // Stuff for driving mute/unmute events
+ virtual MediaEventSource<void>& RtcpByeEvent() = 0;
+ virtual MediaEventSource<void>& RtcpTimeoutEvent() = 0;
+ virtual MediaEventSource<void>& RtpPacketEvent() = 0;
+
+ virtual bool SendRtp(const uint8_t* aData, size_t aLength,
+ const webrtc::PacketOptions& aOptions) = 0;
+ virtual bool SendSenderRtcp(const uint8_t* aData, size_t aLength) = 0;
+ virtual bool SendReceiverRtcp(const uint8_t* aData, size_t aLength) = 0;
+
+ virtual void DeliverPacket(rtc::CopyOnWriteBuffer packet,
+ PacketType type) = 0;
+
+ virtual RefPtr<GenericPromise> Shutdown() = 0;
+
+ virtual Maybe<RefPtr<AudioSessionConduit>> AsAudioSessionConduit() = 0;
+ virtual Maybe<RefPtr<VideoSessionConduit>> AsVideoSessionConduit() = 0;
+
+ virtual Maybe<webrtc::CallBasicStats> GetCallStats() const = 0;
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaSessionConduit)
+
+ void GetRtpSources(nsTArray<dom::RTCRtpSourceEntry>& outSources) const;
+
+ virtual void SetJitterBufferTarget(DOMHighResTimeStamp aTargetMs) = 0;
+
+ // test-only: inserts fake CSRCs and audio level data.
+ // NB: fake data is only valid during the current main thread task.
+ void InsertAudioLevelForContributingSource(const uint32_t aCsrcSource,
+ const int64_t aTimestamp,
+ const uint32_t aRtpTimestamp,
+ const bool aHasAudioLevel,
+ const uint8_t aAudioLevel);
+
+ protected:
+ virtual std::vector<webrtc::RtpSource> GetUpstreamRtpSources() const = 0;
+
+ private:
+ void UpdateRtpSources(const std::vector<webrtc::RtpSource>& aSources) const;
+
+ // Marks the cache as having been updated in the current task, and keeps it
+ // stable until the current task is finished.
+ void OnSourcesUpdated() const;
+
+ // Accessed only on main thread. This exists for a couple of reasons:
+ // 1. The webrtc spec says that source stats are updated using a queued task;
+ // libwebrtc's internal representation of these stats is updated without
+ // any task queueing, which means we need a mainthread-only cache.
+ // 2. libwebrtc uses its own clock that is not consistent with the one we
+ // need to use for stats (the so-called JS timestamps), which means we need
+ // to adjust the timestamps. Since timestamp adjustment is inexact and will
+ // not necessarily yield exactly the same result if performed again later, we
+ // need to avoid performing it more than once for each entry, which means we
+ // need to remember both the JS timestamp (in dom::RTCRtpSourceEntry) and the
+ // libwebrtc timestamp (in SourceKey::mLibwebrtcTimestampMs).
+ class SourceKey {
+ public:
+ explicit SourceKey(const webrtc::RtpSource& aSource)
+ : SourceKey(aSource.timestamp_ms(), aSource.source_id()) {}
+
+ SourceKey(uint32_t aTimestamp, uint32_t aSrc)
+ : mLibwebrtcTimestampMs(aTimestamp), mSrc(aSrc) {}
+
+ // TODO: Once we support = default for this in our toolchain, do so
+ auto operator>(const SourceKey& aRhs) const {
+ if (mLibwebrtcTimestampMs == aRhs.mLibwebrtcTimestampMs) {
+ return mSrc > aRhs.mSrc;
+ }
+ return mLibwebrtcTimestampMs > aRhs.mLibwebrtcTimestampMs;
+ }
+
+ private:
+ uint32_t mLibwebrtcTimestampMs;
+ uint32_t mSrc;
+ };
+ mutable std::map<SourceKey, dom::RTCRtpSourceEntry, std::greater<SourceKey>>
+ mSourcesCache;
+ // Accessed only on main thread. A flag saying whether mSourcesCache needs
+ // updating. Ensures that get*Sources() appear stable from javascript
+ // throughout a main thread task, even though we don't follow the spec to the
+ // letter (dispatch a task to update the sources).
+ mutable bool mSourcesUpdateNeeded = true;
+};
+
+class WebrtcSendTransport : public webrtc::Transport {
+ // WeakRef to the owning conduit
+ MediaSessionConduit* mConduit;
+
+ public:
+ explicit WebrtcSendTransport(MediaSessionConduit* aConduit)
+ : mConduit(aConduit) {}
+ bool SendRtp(const uint8_t* aPacket, size_t aLength,
+ const webrtc::PacketOptions& aOptions) override {
+ return mConduit->SendRtp(aPacket, aLength, aOptions);
+ }
+ bool SendRtcp(const uint8_t* aPacket, size_t aLength) override {
+ return mConduit->SendSenderRtcp(aPacket, aLength);
+ }
+};
+
+class WebrtcReceiveTransport : public webrtc::Transport {
+ // WeakRef to the owning conduit
+ MediaSessionConduit* mConduit;
+
+ public:
+ explicit WebrtcReceiveTransport(MediaSessionConduit* aConduit)
+ : mConduit(aConduit) {}
+ bool SendRtp(const uint8_t* aPacket, size_t aLength,
+ const webrtc::PacketOptions& aOptions) override {
+ MOZ_CRASH("Unexpected RTP packet");
+ }
+ bool SendRtcp(const uint8_t* aPacket, size_t aLength) override {
+ return mConduit->SendReceiverRtcp(aPacket, aLength);
+ }
+};
+
+// Abstract base classes for external encoder/decoder.
+
+// Interface to help signal PluginIDs
+class CodecPluginID {
+ public:
+ virtual MediaEventSource<uint64_t>* InitPluginEvent() { return nullptr; }
+ virtual MediaEventSource<uint64_t>* ReleasePluginEvent() { return nullptr; }
+ virtual ~CodecPluginID() {}
+};
+
+class VideoEncoder : public CodecPluginID {
+ public:
+ virtual ~VideoEncoder() {}
+};
+
+class VideoDecoder : public CodecPluginID {
+ public:
+ virtual ~VideoDecoder() {}
+};
+
+/**
+ * MediaSessionConduit for video
+ * Refer to the comments on MediaSessionConduit above for overall
+ * information
+ */
+class VideoSessionConduit : public MediaSessionConduit {
+ public:
+ struct Options {
+ bool mVideoLatencyTestEnable = false;
+ // All in bps.
+ int mMinBitrate = 0;
+ int mStartBitrate = 0;
+ int mPrefMaxBitrate = 0;
+ int mMinBitrateEstimate = 0;
+ bool mDenoising = false;
+ bool mLockScaling = false;
+ uint8_t mSpatialLayers = 1;
+ uint8_t mTemporalLayers = 1;
+ };
+
+ /**
+ * Factory function to create and initialize a Video Conduit Session
+ * @param webrtc::Call instance shared by paired audio and video
+ * media conduits
+ * @param aOptions are a number of options, typically from prefs, used to
+ * configure the created VideoConduit.
+ * @param aPCHandle is a string representing the RTCPeerConnection that is
+ * creating this VideoConduit. This is used when reporting GMP plugin
+ * crashes.
+ * @result Concrete VideoSessionConduitObject or nullptr in the case
+ * of failure
+ */
+ static RefPtr<VideoSessionConduit> Create(
+ RefPtr<WebrtcCallWrapper> aCall,
+ nsCOMPtr<nsISerialEventTarget> aStsThread, Options aOptions,
+ std::string aPCHandle, const TrackingId& aRecvTrackingId);
+
+ enum FrameRequestType {
+ FrameRequestNone,
+ FrameRequestFir,
+ FrameRequestPli,
+ FrameRequestUnknown
+ };
+
+ VideoSessionConduit()
+ : mFrameRequestMethod(FrameRequestNone),
+ mUsingNackBasic(false),
+ mUsingTmmbr(false),
+ mUsingFEC(false) {}
+
+ virtual ~VideoSessionConduit() {}
+
+ Type type() const override { return VIDEO; }
+
+ Maybe<RefPtr<AudioSessionConduit>> AsAudioSessionConduit() override {
+ return Nothing();
+ }
+
+ Maybe<RefPtr<VideoSessionConduit>> AsVideoSessionConduit() override {
+ return Some(RefPtr<VideoSessionConduit>(this));
+ }
+
+ /**
+ * Hooks up mControl Mirrors with aControl Canonicals, and sets up
+ * mWatchManager to react on Mirror changes.
+ */
+ virtual void InitControl(VideoConduitControlInterface* aControl) = 0;
+
+ /**
+ * Function to attach Renderer end-point of the Media-Video conduit.
+ * @param aRenderer : Reference to the concrete Video renderer implementation
+ * Note: Multiple invocations of this API shall remove an existing renderer
+ * and attaches the new to the Conduit.
+ */
+ virtual MediaConduitErrorCode AttachRenderer(
+ RefPtr<mozilla::VideoRenderer> aRenderer) = 0;
+ virtual void DetachRenderer() = 0;
+
+ /**
+ * Function to deliver a capture video frame for encoding and transport.
+ * If the frame's timestamp is 0, it will be automatcally generated.
+ *
+ * NOTE: ConfigureSendMediaCodec() must be called before this function can
+ * be invoked. This ensures the inserted video-frames can be
+ * transmitted by the conduit.
+ */
+ virtual MediaConduitErrorCode SendVideoFrame(webrtc::VideoFrame aFrame) = 0;
+
+ /**
+ * These methods allow unit tests to double-check that the
+ * rtcp-fb settings are as expected.
+ */
+ FrameRequestType FrameRequestMethod() const { return mFrameRequestMethod; }
+
+ bool UsingNackBasic() const { return mUsingNackBasic; }
+
+ bool UsingTmmbr() const { return mUsingTmmbr; }
+
+ bool UsingFEC() const { return mUsingFEC; }
+
+ virtual Maybe<webrtc::VideoReceiveStreamInterface::Stats> GetReceiverStats()
+ const = 0;
+ virtual Maybe<webrtc::VideoSendStream::Stats> GetSenderStats() const = 0;
+
+ virtual void CollectTelemetryData() = 0;
+
+ virtual bool AddFrameHistory(
+ dom::Sequence<dom::RTCVideoFrameHistoryInternal>* outHistories) const = 0;
+
+ virtual Maybe<Ssrc> GetAssociatedLocalRtxSSRC(Ssrc aSsrc) const = 0;
+
+ protected:
+ /* RTCP feedback settings, for unit testing purposes */
+ FrameRequestType mFrameRequestMethod;
+ bool mUsingNackBasic;
+ bool mUsingTmmbr;
+ bool mUsingFEC;
+};
+
+/**
+ * MediaSessionConduit for audio
+ * Refer to the comments on MediaSessionConduit above for overall
+ * information
+ */
+class AudioSessionConduit : public MediaSessionConduit {
+ public:
+ /**
+ * Factory function to create and initialize an Audio Conduit Session
+ * @param webrtc::Call instance shared by paired audio and video
+ * media conduits
+ * @result Concrete AudioSessionConduitObject or nullptr in the case
+ * of failure
+ */
+ static RefPtr<AudioSessionConduit> Create(
+ RefPtr<WebrtcCallWrapper> aCall,
+ nsCOMPtr<nsISerialEventTarget> aStsThread);
+
+ virtual ~AudioSessionConduit() {}
+
+ Type type() const override { return AUDIO; }
+
+ Maybe<RefPtr<AudioSessionConduit>> AsAudioSessionConduit() override {
+ return Some(this);
+ }
+
+ Maybe<RefPtr<VideoSessionConduit>> AsVideoSessionConduit() override {
+ return Nothing();
+ }
+
+ /**
+ * Hooks up mControl Mirrors with aControl Canonicals, and sets up
+ * mWatchManager to react on Mirror changes.
+ */
+ virtual void InitControl(AudioConduitControlInterface* aControl) = 0;
+
+ /**
+ * Function to deliver externally captured audio sample for encoding and
+ * transport
+ * @param frame [in]: AudioFrame in upstream's format for forwarding to the
+ * send stream. Ownership is passed along.
+ * NOTE: ConfigureSendMediaCodec() SHOULD be called before this function can
+ * be invoked. This ensures the inserted audio-samples can be transmitted by
+ * the conduit.
+ */
+ virtual MediaConduitErrorCode SendAudioFrame(
+ std::unique_ptr<webrtc::AudioFrame> frame) = 0;
+
+ /**
+ * Function to grab a decoded audio-sample from the media engine for
+ * rendering / playout of length 10 milliseconds.
+ *
+ * @param samplingFreqHz [in]: Frequency of the sampling for playback in
+ * Hertz (16000, 32000,..)
+ * @param frame [in/out]: Pointer to an AudioFrame to which audio data will be
+ * copied
+ * NOTE: This function should be invoked every 10 milliseconds for the best
+ * performance
+ * NOTE: ConfigureRecvMediaCodec() SHOULD be called before this function can
+ * be invoked
+ * This ensures the decoded samples are ready for reading and playout is
+ * enabled.
+ */
+ virtual MediaConduitErrorCode GetAudioFrame(int32_t samplingFreqHz,
+ webrtc::AudioFrame* frame) = 0;
+
+ /**
+ * Checks if given sampling frequency is supported
+ * @param freq: Sampling rate (in Hz) to check
+ */
+ virtual bool IsSamplingFreqSupported(int freq) const = 0;
+
+ virtual Maybe<webrtc::AudioReceiveStreamInterface::Stats> GetReceiverStats()
+ const = 0;
+ virtual Maybe<webrtc::AudioSendStream::Stats> GetSenderStats() const = 0;
+};
+} // namespace mozilla
+#endif
diff --git a/dom/media/webrtc/libwebrtcglue/MediaDataCodec.cpp b/dom/media/webrtc/libwebrtcglue/MediaDataCodec.cpp
new file mode 100644
index 0000000000..895fcdc432
--- /dev/null
+++ b/dom/media/webrtc/libwebrtcglue/MediaDataCodec.cpp
@@ -0,0 +1,70 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MediaDataCodec.h"
+
+#include "PDMFactory.h"
+#include "WebrtcGmpVideoCodec.h"
+#include "WebrtcMediaDataDecoderCodec.h"
+#include "WebrtcMediaDataEncoderCodec.h"
+#include "mozilla/StaticPrefs_media.h"
+
+namespace mozilla {
+
+/* static */
+WebrtcVideoEncoder* MediaDataCodec::CreateEncoder(
+ const webrtc::SdpVideoFormat& aFormat) {
+ if (!StaticPrefs::media_webrtc_platformencoder()) {
+ return nullptr;
+ }
+ if (!WebrtcMediaDataEncoder::CanCreate(
+ webrtc::PayloadStringToCodecType(aFormat.name))) {
+ return nullptr;
+ }
+
+ return new WebrtcVideoEncoderProxy(new WebrtcMediaDataEncoder(aFormat));
+}
+
+/* static */
+WebrtcVideoDecoder* MediaDataCodec::CreateDecoder(
+ webrtc::VideoCodecType aCodecType, TrackingId aTrackingId) {
+ switch (aCodecType) {
+ case webrtc::VideoCodecType::kVideoCodecVP8:
+ case webrtc::VideoCodecType::kVideoCodecVP9:
+ if (!StaticPrefs::media_navigator_mediadatadecoder_vpx_enabled()) {
+ return nullptr;
+ }
+ break;
+ case webrtc::VideoCodecType::kVideoCodecH264:
+ if (!StaticPrefs::media_navigator_mediadatadecoder_h264_enabled()) {
+ return nullptr;
+ }
+ break;
+ default:
+ return nullptr;
+ }
+
+ nsAutoCString codec;
+ switch (aCodecType) {
+ case webrtc::VideoCodecType::kVideoCodecVP8:
+ codec = "video/vp8";
+ break;
+ case webrtc::VideoCodecType::kVideoCodecVP9:
+ codec = "video/vp9";
+ break;
+ case webrtc::VideoCodecType::kVideoCodecH264:
+ codec = "video/avc";
+ break;
+ default:
+ return nullptr;
+ }
+ RefPtr<PDMFactory> pdm = new PDMFactory();
+ if (pdm->SupportsMimeType(codec) == media::DecodeSupport::Unsupported) {
+ return nullptr;
+ }
+
+ return new WebrtcMediaDataDecoder(codec, aTrackingId);
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/libwebrtcglue/MediaDataCodec.h b/dom/media/webrtc/libwebrtcglue/MediaDataCodec.h
new file mode 100644
index 0000000000..b885d6ae0c
--- /dev/null
+++ b/dom/media/webrtc/libwebrtcglue/MediaDataCodec.h
@@ -0,0 +1,32 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MEDIA_DATA_CODEC_H_
+#define MEDIA_DATA_CODEC_H_
+
+#include "MediaConduitInterface.h"
+
+namespace mozilla {
+
+class WebrtcVideoDecoder;
+class WebrtcVideoEncoder;
+class MediaDataCodec {
+ public:
+ /**
+ * Create encoder object for codec format |aFormat|. Return |nullptr| when
+ * failed.
+ */
+ static WebrtcVideoEncoder* CreateEncoder(
+ const webrtc::SdpVideoFormat& aFormat);
+
+ /**
+ * Create decoder object for codec type |aCodecType|. Return |nullptr| when
+ * failed.
+ */
+ static WebrtcVideoDecoder* CreateDecoder(webrtc::VideoCodecType aCodecType,
+ TrackingId aTrackingId);
+};
+} // namespace mozilla
+
+#endif // MEDIA_DATA_CODEC_H_
diff --git a/dom/media/webrtc/libwebrtcglue/RtpRtcpConfig.h b/dom/media/webrtc/libwebrtcglue/RtpRtcpConfig.h
new file mode 100644
index 0000000000..03a774ec3b
--- /dev/null
+++ b/dom/media/webrtc/libwebrtcglue/RtpRtcpConfig.h
@@ -0,0 +1,24 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __RTPRTCP_CONFIG_H__
+#define __RTPRTCP_CONFIG_H__
+#include "api/rtp_headers.h"
+
+namespace mozilla {
+class RtpRtcpConfig {
+ public:
+ RtpRtcpConfig() = delete;
+ explicit RtpRtcpConfig(const webrtc::RtcpMode aMode) : mRtcpMode(aMode) {}
+ webrtc::RtcpMode GetRtcpMode() const { return mRtcpMode; }
+
+ bool operator==(const RtpRtcpConfig& aOther) const {
+ return mRtcpMode == aOther.mRtcpMode;
+ }
+
+ private:
+ webrtc::RtcpMode mRtcpMode;
+};
+} // namespace mozilla
+#endif
diff --git a/dom/media/webrtc/libwebrtcglue/RunningStat.h b/dom/media/webrtc/libwebrtcglue/RunningStat.h
new file mode 100644
index 0000000000..7a0e88f193
--- /dev/null
+++ b/dom/media/webrtc/libwebrtcglue/RunningStat.h
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+/* Adapted from "Accurately computing running variance - John D. Cook"
+ http://www.johndcook.com/standard_deviation.html */
+
+#ifndef RUNNING_STAT_H_
+#define RUNNING_STAT_H_
+#include <math.h>
+
+namespace mozilla {
+
+class RunningStat {
+ public:
+ RunningStat() : mN(0), mOldM(0.0), mNewM(0.0), mOldS(0.0), mNewS(0.0) {}
+
+ void Clear() { mN = 0; }
+
+ void Push(double x) {
+ mN++;
+
+ // See Knuth TAOCP vol 2, 3rd edition, page 232
+ if (mN == 1) {
+ mOldM = mNewM = x;
+ mOldS = 0.0;
+ } else {
+ mNewM = mOldM + (x - mOldM) / mN;
+ mNewS = mOldS + (x - mOldM) * (x - mNewM);
+
+ // set up for next iteration
+ mOldM = mNewM;
+ mOldS = mNewS;
+ }
+ }
+
+ int NumDataValues() const { return mN; }
+
+ double Mean() const { return (mN > 0) ? mNewM : 0.0; }
+
+ double Variance() const { return (mN > 1) ? mNewS / (mN - 1) : 0.0; }
+
+ double StandardDeviation() const { return sqrt(Variance()); }
+
+ private:
+ int mN;
+ double mOldM, mNewM, mOldS, mNewS;
+};
+} // namespace mozilla
+#endif // RUNNING_STAT_H_
diff --git a/dom/media/webrtc/libwebrtcglue/SystemTime.cpp b/dom/media/webrtc/libwebrtcglue/SystemTime.cpp
new file mode 100644
index 0000000000..bba7fd788e
--- /dev/null
+++ b/dom/media/webrtc/libwebrtcglue/SystemTime.cpp
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SystemTime.h"
+
+#include "TimeUnits.h"
+
+namespace mozilla {
+
+// webrtc::Timestamp may not be negative. `now-base` for the first call to
+// WebrtcSystemTime() is always 0, which makes it impossible for libwebrtc
+// code to calculate a timestamp older than the first one returned. This
+// offset makes sure the clock starts at a value equivalent to roughly 4.5h.
+static constexpr webrtc::TimeDelta kWebrtcTimeOffset =
+ webrtc::TimeDelta::Micros(0x10000000);
+
+RTCStatsTimestampMakerRealtimeClock::RTCStatsTimestampMakerRealtimeClock(
+ const dom::RTCStatsTimestampMaker& aTimestampMaker)
+ : mTimestampMaker(aTimestampMaker) {}
+
+webrtc::Timestamp RTCStatsTimestampMakerRealtimeClock::CurrentTime() {
+ return mTimestampMaker.GetNow().ToRealtime();
+}
+
+webrtc::NtpTime RTCStatsTimestampMakerRealtimeClock::ConvertTimestampToNtpTime(
+ webrtc::Timestamp aRealtime) {
+ return CreateNtp(
+ dom::RTCStatsTimestamp::FromRealtime(mTimestampMaker, aRealtime).ToNtp());
+}
+
+TimeStamp WebrtcSystemTimeBase() {
+ static TimeStamp now = TimeStamp::Now();
+ return now;
+}
+
+webrtc::Timestamp WebrtcSystemTime() {
+ const TimeStamp base = WebrtcSystemTimeBase();
+ const TimeStamp now = TimeStamp::Now();
+ return webrtc::Timestamp::Micros((now - base).ToMicroseconds()) +
+ kWebrtcTimeOffset;
+}
+
+webrtc::NtpTime CreateNtp(webrtc::Timestamp aTime) {
+ const int64_t timeNtpUs = aTime.us();
+ const uint32_t seconds = static_cast<uint32_t>(timeNtpUs / USECS_PER_S);
+
+ constexpr int64_t fractionsPerSec = 1LL << 32;
+ const int64_t fractionsUs = timeNtpUs % USECS_PER_S;
+ const uint32_t fractions = (fractionsUs * fractionsPerSec) / USECS_PER_S;
+
+ return webrtc::NtpTime(seconds, fractions);
+}
+} // namespace mozilla
+
+namespace rtc {
+int64_t SystemTimeNanos() { return mozilla::WebrtcSystemTime().us() * 1000; }
+} // namespace rtc
diff --git a/dom/media/webrtc/libwebrtcglue/SystemTime.h b/dom/media/webrtc/libwebrtcglue/SystemTime.h
new file mode 100644
index 0000000000..5c042e689f
--- /dev/null
+++ b/dom/media/webrtc/libwebrtcglue/SystemTime.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DOM_MEDIA_WEBRTC_LIBWEBRTCGLUE_SYSTEMTIMENANOS_H_
+#define DOM_MEDIA_WEBRTC_LIBWEBRTCGLUE_SYSTEMTIMENANOS_H_
+
+#include "jsapi/RTCStatsReport.h"
+#include "mozilla/TimeStamp.h"
+#include "system_wrappers/include/clock.h"
+
+namespace mozilla {
+class RTCStatsTimestampMakerRealtimeClock : public webrtc::Clock {
+ public:
+ explicit RTCStatsTimestampMakerRealtimeClock(
+ const dom::RTCStatsTimestampMaker& aTimestampMaker);
+
+ webrtc::Timestamp CurrentTime() override;
+
+ // Upstream, this method depend on rtc::TimeUTCMicros for converting the
+ // monotonic system clock to Ntp, if only for the first call when deciding the
+ // Ntp offset.
+ // We override this to be able to use our own clock instead of
+ // rtc::TimeUTCMicros for ntp timestamps.
+ webrtc::NtpTime ConvertTimestampToNtpTime(
+ webrtc::Timestamp aRealtime) override;
+
+ const dom::RTCStatsTimestampMaker mTimestampMaker;
+};
+
+// The time base used for WebrtcSystemTime(). Completely arbitrary. Constant.
+TimeStamp WebrtcSystemTimeBase();
+
+// The returned timestamp denotes the monotonic time passed since
+// WebrtcSystemTimeBase(). Libwebrtc uses this to track how time advances from a
+// specific point in time. It adds an offset to make the timestamps absolute.
+webrtc::Timestamp WebrtcSystemTime();
+
+webrtc::NtpTime CreateNtp(webrtc::Timestamp aTime);
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/libwebrtcglue/TaskQueueWrapper.h b/dom/media/webrtc/libwebrtcglue/TaskQueueWrapper.h
new file mode 100644
index 0000000000..da11bfdf8b
--- /dev/null
+++ b/dom/media/webrtc/libwebrtcglue/TaskQueueWrapper.h
@@ -0,0 +1,182 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DOM_MEDIA_WEBRTC_LIBWEBRTCGLUE_TASKQUEUEWRAPPER_H_
+#define DOM_MEDIA_WEBRTC_LIBWEBRTCGLUE_TASKQUEUEWRAPPER_H_
+
+#include "api/task_queue/task_queue_factory.h"
+#include "mozilla/DataMutex.h"
+#include "mozilla/RecursiveMutex.h"
+#include "mozilla/ProfilerRunnable.h"
+#include "mozilla/TaskQueue.h"
+#include "VideoUtils.h"
+#include "mozilla/media/MediaUtils.h" // For media::Await
+
+namespace mozilla {
+
+enum class DeletionPolicy : uint8_t { Blocking, NonBlocking };
+
+/**
+ * A wrapper around Mozilla TaskQueues in the shape of a libwebrtc TaskQueue.
+ *
+ * Allows libwebrtc to use Mozilla threads where tooling, e.g. profiling, is set
+ * up and just works.
+ *
+ * Mozilla APIs like Runnables, MozPromise, etc. can also be used with the
+ * wrapped TaskQueue to run things on the right thread when interacting with
+ * libwebrtc.
+ */
+template <DeletionPolicy Deletion>
+class TaskQueueWrapper : public webrtc::TaskQueueBase {
+ public:
+ TaskQueueWrapper(RefPtr<TaskQueue> aTaskQueue, nsCString aName)
+ : mTaskQueue(std::move(aTaskQueue)), mName(std::move(aName)) {}
+ ~TaskQueueWrapper() = default;
+
+ void Delete() override {
+ {
+ // Scope this to make sure it does not race against the promise chain we
+ // set up below.
+ auto hasShutdown = mHasShutdown.Lock();
+ *hasShutdown = true;
+ }
+
+ MOZ_RELEASE_ASSERT(Deletion == DeletionPolicy::NonBlocking ||
+ !mTaskQueue->IsOnCurrentThread());
+
+ nsCOMPtr<nsISerialEventTarget> backgroundTaskQueue;
+ NS_CreateBackgroundTaskQueue(__func__, getter_AddRefs(backgroundTaskQueue));
+ if (NS_WARN_IF(!backgroundTaskQueue)) {
+ // Ok... that's pretty broken. Try main instead.
+ MOZ_ASSERT(false);
+ backgroundTaskQueue = GetMainThreadSerialEventTarget();
+ }
+
+ RefPtr<GenericPromise> shutdownPromise = mTaskQueue->BeginShutdown()->Then(
+ backgroundTaskQueue, __func__, [this] {
+ // Wait until shutdown is complete, then delete for real. Although we
+ // prevent queued tasks from executing with mHasShutdown, that is a
+ // member variable, which means we still need to ensure that the
+ // queue is done executing tasks before destroying it.
+ delete this;
+ return GenericPromise::CreateAndResolve(true, __func__);
+ });
+ if constexpr (Deletion == DeletionPolicy::Blocking) {
+ media::Await(backgroundTaskQueue.forget(), shutdownPromise);
+ } else {
+ Unused << shutdownPromise;
+ }
+ }
+
+ already_AddRefed<Runnable> CreateTaskRunner(
+ absl::AnyInvocable<void() &&> aTask) {
+ return NS_NewRunnableFunction(
+ "TaskQueueWrapper::CreateTaskRunner",
+ [this, task = std::move(aTask),
+ name = nsPrintfCString("TQ %s: webrtc::QueuedTask",
+ mName.get())]() mutable {
+ CurrentTaskQueueSetter current(this);
+ auto hasShutdown = mHasShutdown.Lock();
+ if (*hasShutdown) {
+ return;
+ }
+ AUTO_PROFILE_FOLLOWING_RUNNABLE(name);
+ std::move(task)();
+ });
+ }
+
+ already_AddRefed<Runnable> CreateTaskRunner(nsCOMPtr<nsIRunnable> aRunnable) {
+ return NS_NewRunnableFunction(
+ "TaskQueueWrapper::CreateTaskRunner",
+ [this, runnable = std::move(aRunnable)]() mutable {
+ CurrentTaskQueueSetter current(this);
+ auto hasShutdown = mHasShutdown.Lock();
+ if (*hasShutdown) {
+ return;
+ }
+ AUTO_PROFILE_FOLLOWING_RUNNABLE(runnable);
+ runnable->Run();
+ });
+ }
+
+ void PostTask(absl::AnyInvocable<void() &&> aTask) override {
+ MOZ_ALWAYS_SUCCEEDS(
+ mTaskQueue->Dispatch(CreateTaskRunner(std::move(aTask))));
+ }
+
+ void PostDelayedTask(absl::AnyInvocable<void() &&> aTask,
+ webrtc::TimeDelta aDelay) override {
+ if (aDelay.ms() == 0) {
+ // AbstractThread::DelayedDispatch doesn't support delay 0
+ PostTask(std::move(aTask));
+ return;
+ }
+ MOZ_ALWAYS_SUCCEEDS(mTaskQueue->DelayedDispatch(
+ CreateTaskRunner(std::move(aTask)), aDelay.ms()));
+ }
+
+ void PostDelayedHighPrecisionTask(absl::AnyInvocable<void() &&> aTask,
+ webrtc::TimeDelta aDelay) override {
+ PostDelayedTask(std::move(aTask), aDelay);
+ }
+
+ const RefPtr<TaskQueue> mTaskQueue;
+ const nsCString mName;
+
+ // This is a recursive mutex because a TaskRunner holding this mutex while
+ // running its runnable may end up running other - tail dispatched - runnables
+ // too, and they'll again try to grab the mutex.
+ // The mutex must be held while running the runnable since otherwise there'd
+ // be a race between shutting down the underlying task queue and the runnable
+ // dispatching to that task queue (and we assert it succeeds in e.g.,
+ // PostTask()).
+ DataMutexBase<bool, RecursiveMutex> mHasShutdown{
+ false, "TaskQueueWrapper::mHasShutdown"};
+};
+
+template <DeletionPolicy Deletion>
+class DefaultDelete<TaskQueueWrapper<Deletion>>
+ : public webrtc::TaskQueueDeleter {
+ public:
+ void operator()(TaskQueueWrapper<Deletion>* aPtr) const {
+ webrtc::TaskQueueDeleter::operator()(aPtr);
+ }
+};
+
+class SharedThreadPoolWebRtcTaskQueueFactory : public webrtc::TaskQueueFactory {
+ public:
+ SharedThreadPoolWebRtcTaskQueueFactory() {}
+
+ template <DeletionPolicy Deletion>
+ UniquePtr<TaskQueueWrapper<Deletion>> CreateTaskQueueWrapper(
+ absl::string_view aName, bool aSupportTailDispatch, Priority aPriority,
+ MediaThreadType aThreadType = MediaThreadType::WEBRTC_WORKER) const {
+ // XXX Do something with aPriority
+ nsCString name(aName.data(), aName.size());
+ auto taskQueue = TaskQueue::Create(GetMediaThreadPool(aThreadType),
+ name.get(), aSupportTailDispatch);
+ return MakeUnique<TaskQueueWrapper<Deletion>>(std::move(taskQueue),
+ std::move(name));
+ }
+
+ std::unique_ptr<webrtc::TaskQueueBase, webrtc::TaskQueueDeleter>
+ CreateTaskQueue(absl::string_view aName, Priority aPriority) const override {
+ // libwebrtc will dispatch some tasks sync, i.e., block the origin thread
+ // until they've run, and that doesn't play nice with tail dispatching since
+ // there will never be a tail.
+ // DeletionPolicy::Blocking because this is for libwebrtc use and that's
+ // what they expect.
+ constexpr bool supportTailDispatch = false;
+ return std::unique_ptr<webrtc::TaskQueueBase, webrtc::TaskQueueDeleter>(
+ CreateTaskQueueWrapper<DeletionPolicy::Blocking>(
+ std::move(aName), supportTailDispatch, aPriority)
+ .release(),
+ webrtc::TaskQueueDeleter());
+ }
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/libwebrtcglue/VideoConduit.cpp b/dom/media/webrtc/libwebrtcglue/VideoConduit.cpp
new file mode 100644
index 0000000000..f99f818d48
--- /dev/null
+++ b/dom/media/webrtc/libwebrtcglue/VideoConduit.cpp
@@ -0,0 +1,1902 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "VideoConduit.h"
+
+#include <algorithm>
+#include <cinttypes>
+#include <cmath>
+
+#include "common/browser_logging/CSFLog.h"
+#include "common/YuvStamper.h"
+#include "GmpVideoCodec.h"
+#include "MediaConduitControl.h"
+#include "MediaDataCodec.h"
+#include "mozilla/dom/RTCRtpSourcesBinding.h"
+#include "mozilla/media/MediaUtils.h"
+#include "mozilla/StaticPrefs_media.h"
+#include "mozilla/TemplateLib.h"
+#include "nsIGfxInfo.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsServiceManagerUtils.h"
+#include "RtpRtcpConfig.h"
+#include "transport/SrtpFlow.h" // For SRTP_MAX_EXPANSION
+#include "Tracing.h"
+#include "VideoStreamFactory.h"
+#include "WebrtcCallWrapper.h"
+#include "WebrtcGmpVideoCodec.h"
+
+// libwebrtc includes
+#include "api/transport/bitrate_settings.h"
+#include "api/video_codecs/h264_profile_level_id.h"
+#include "api/video_codecs/sdp_video_format.h"
+#include "api/video_codecs/video_codec.h"
+#include "media/base/media_constants.h"
+#include "media/engine/encoder_simulcast_proxy.h"
+#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
+#include "modules/video_coding/codecs/vp8/include/vp8.h"
+#include "modules/video_coding/codecs/vp9/include/vp9.h"
+
+#ifdef MOZ_WIDGET_ANDROID
+# include "VideoEngine.h"
+#endif
+
+// for ntohs
+#ifdef _MSC_VER
+# include "Winsock2.h"
+#else
+# include <netinet/in.h>
+#endif
+
+#define INVALID_RTP_PAYLOAD 255 // valid payload types are 0 to 127
+
+namespace mozilla {
+
+namespace {
+
+const char* vcLogTag = "WebrtcVideoSessionConduit";
+#ifdef LOGTAG
+# undef LOGTAG
+#endif
+#define LOGTAG vcLogTag
+
+using namespace cricket;
+using LocalDirection = MediaSessionConduitLocalDirection;
+
+const int kNullPayloadType = -1;
+const char kRtcpFbCcmParamTmmbr[] = "tmmbr";
+
+// The number of frame buffers WebrtcVideoConduit may create before returning
+// errors.
+// Sometimes these are released synchronously but they can be forwarded all the
+// way to the encoder for asynchronous encoding. With a pool size of 5,
+// we allow 1 buffer for the current conversion, and 4 buffers to be queued at
+// the encoder.
+#define SCALER_BUFFER_POOL_SIZE 5
+
+// The pixel alignment to use for the highest resolution layer when simulcast
+// is active and one or more layers are being scaled.
+#define SIMULCAST_RESOLUTION_ALIGNMENT 16
+
+template <class t>
+void ConstrainPreservingAspectRatioExact(uint32_t max_fs, t* width, t* height) {
+ // We could try to pick a better starting divisor, but it won't make any real
+ // performance difference.
+ for (size_t d = 1; d < std::min(*width, *height); ++d) {
+ if ((*width % d) || (*height % d)) {
+ continue; // Not divisible
+ }
+
+ if (((*width) * (*height)) / (d * d) <= max_fs) {
+ *width /= d;
+ *height /= d;
+ return;
+ }
+ }
+
+ *width = 0;
+ *height = 0;
+}
+
+/**
+ * Perform validation on the codecConfig to be applied
+ */
+MediaConduitErrorCode ValidateCodecConfig(const VideoCodecConfig& codecInfo) {
+ if (codecInfo.mName.empty()) {
+ CSFLogError(LOGTAG, "%s Empty Payload Name ", __FUNCTION__);
+ return kMediaConduitMalformedArgument;
+ }
+
+ return kMediaConduitNoError;
+}
+
+webrtc::VideoCodecType SupportedCodecType(webrtc::VideoCodecType aType) {
+ switch (aType) {
+ case webrtc::VideoCodecType::kVideoCodecVP8:
+ case webrtc::VideoCodecType::kVideoCodecVP9:
+ case webrtc::VideoCodecType::kVideoCodecH264:
+ return aType;
+ default:
+ return webrtc::VideoCodecType::kVideoCodecGeneric;
+ }
+ // NOTREACHED
+}
+
+// Call thread only.
+rtc::scoped_refptr<webrtc::VideoEncoderConfig::EncoderSpecificSettings>
+ConfigureVideoEncoderSettings(const VideoCodecConfig& aConfig,
+ const WebrtcVideoConduit* aConduit,
+ webrtc::SdpVideoFormat::Parameters& aParameters) {
+ bool is_screencast =
+ aConduit->CodecMode() == webrtc::VideoCodecMode::kScreensharing;
+ // No automatic resizing when using simulcast or screencast.
+ bool automatic_resize = !is_screencast && aConfig.mEncodings.size() <= 1;
+ bool denoising;
+ bool codec_default_denoising = false;
+ if (is_screencast) {
+ denoising = false;
+ } else {
+ // Use codec default if video_noise_reduction is unset.
+ denoising = aConduit->Denoising();
+ codec_default_denoising = !denoising;
+ }
+
+ if (aConfig.mName == kH264CodecName) {
+ aParameters[kH264FmtpPacketizationMode] =
+ std::to_string(aConfig.mPacketizationMode);
+ {
+ std::stringstream ss;
+ ss << std::hex << std::setfill('0');
+ ss << std::setw(2) << static_cast<uint32_t>(aConfig.mProfile);
+ ss << std::setw(2) << static_cast<uint32_t>(aConfig.mConstraints);
+ ss << std::setw(2) << static_cast<uint32_t>(aConfig.mLevel);
+ std::string profileLevelId = ss.str();
+ auto parsedProfileLevelId =
+ webrtc::ParseH264ProfileLevelId(profileLevelId.c_str());
+ MOZ_DIAGNOSTIC_ASSERT(parsedProfileLevelId);
+ if (parsedProfileLevelId) {
+ aParameters[kH264FmtpProfileLevelId] = profileLevelId;
+ }
+ }
+ aParameters[kH264FmtpSpropParameterSets] = aConfig.mSpropParameterSets;
+ }
+ if (aConfig.mName == kVp8CodecName) {
+ webrtc::VideoCodecVP8 vp8_settings =
+ webrtc::VideoEncoder::GetDefaultVp8Settings();
+ vp8_settings.automaticResizeOn = automatic_resize;
+ // VP8 denoising is enabled by default.
+ vp8_settings.denoisingOn = codec_default_denoising ? true : denoising;
+ return rtc::scoped_refptr<
+ webrtc::VideoEncoderConfig::EncoderSpecificSettings>(
+ new rtc::RefCountedObject<
+ webrtc::VideoEncoderConfig::Vp8EncoderSpecificSettings>(
+ vp8_settings));
+ }
+ if (aConfig.mName == kVp9CodecName) {
+ webrtc::VideoCodecVP9 vp9_settings =
+ webrtc::VideoEncoder::GetDefaultVp9Settings();
+ if (!is_screencast) {
+ // Always configure only 1 spatial layer for screencapture as libwebrtc
+ // has some special requirements when SVC is active. For non-screencapture
+ // the spatial layers are experimentally configurable via a pref.
+ vp9_settings.numberOfSpatialLayers = aConduit->SpatialLayers();
+ }
+ // VP9 denoising is disabled by default.
+ vp9_settings.denoisingOn = codec_default_denoising ? false : denoising;
+ return rtc::scoped_refptr<
+ webrtc::VideoEncoderConfig::EncoderSpecificSettings>(
+ new rtc::RefCountedObject<
+ webrtc::VideoEncoderConfig::Vp9EncoderSpecificSettings>(
+ vp9_settings));
+ }
+ return nullptr;
+}
+
+uint32_t GenerateRandomSSRC() {
+ uint32_t ssrc;
+ do {
+ SECStatus rv = PK11_GenerateRandom(reinterpret_cast<unsigned char*>(&ssrc),
+ sizeof(ssrc));
+ MOZ_RELEASE_ASSERT(rv == SECSuccess);
+ } while (ssrc == 0); // webrtc.org code has fits if you select an SSRC of 0
+
+ return ssrc;
+}
+
+// TODO: Make this a defaulted operator when we have c++20 (bug 1731036).
+bool operator==(const rtc::VideoSinkWants& aThis,
+ const rtc::VideoSinkWants& aOther) {
+ // This would have to be expanded should we make use of more members of
+ // rtc::VideoSinkWants.
+ return aThis.max_pixel_count == aOther.max_pixel_count &&
+ aThis.max_framerate_fps == aOther.max_framerate_fps &&
+ aThis.resolution_alignment == aOther.resolution_alignment;
+}
+
+// TODO: Make this a defaulted operator when we have c++20 (bug 1731036).
+bool operator!=(const rtc::VideoSinkWants& aThis,
+ const rtc::VideoSinkWants& aOther) {
+ return !(aThis == aOther);
+}
+
+// TODO: Make this a defaulted operator when we have c++20 (bug 1731036).
+bool operator!=(
+ const webrtc::VideoReceiveStreamInterface::Config::Rtp& aThis,
+ const webrtc::VideoReceiveStreamInterface::Config::Rtp& aOther) {
+ return aThis.remote_ssrc != aOther.remote_ssrc ||
+ aThis.local_ssrc != aOther.local_ssrc ||
+ aThis.rtcp_mode != aOther.rtcp_mode ||
+ aThis.rtcp_xr.receiver_reference_time_report !=
+ aOther.rtcp_xr.receiver_reference_time_report ||
+ aThis.remb != aOther.remb || aThis.tmmbr != aOther.tmmbr ||
+ aThis.keyframe_method != aOther.keyframe_method ||
+ aThis.lntf.enabled != aOther.lntf.enabled ||
+ aThis.nack.rtp_history_ms != aOther.nack.rtp_history_ms ||
+ aThis.ulpfec_payload_type != aOther.ulpfec_payload_type ||
+ aThis.red_payload_type != aOther.red_payload_type ||
+ aThis.rtx_ssrc != aOther.rtx_ssrc ||
+ aThis.protected_by_flexfec != aOther.protected_by_flexfec ||
+ aThis.rtx_associated_payload_types !=
+ aOther.rtx_associated_payload_types ||
+ aThis.raw_payload_types != aOther.raw_payload_types ||
+ aThis.extensions != aOther.extensions;
+}
+
+#ifdef DEBUG
+// TODO: Make this a defaulted operator when we have c++20 (bug 1731036).
+bool operator==(
+ const webrtc::VideoReceiveStreamInterface::Config::Rtp& aThis,
+ const webrtc::VideoReceiveStreamInterface::Config::Rtp& aOther) {
+ return !(aThis != aOther);
+}
+#endif
+
+// TODO: Make this a defaulted operator when we have c++20 (bug 1731036).
+bool operator!=(const webrtc::RtpConfig& aThis,
+ const webrtc::RtpConfig& aOther) {
+ return aThis.ssrcs != aOther.ssrcs || aThis.rids != aOther.rids ||
+ aThis.mid != aOther.mid || aThis.rtcp_mode != aOther.rtcp_mode ||
+ aThis.max_packet_size != aOther.max_packet_size ||
+ aThis.extmap_allow_mixed != aOther.extmap_allow_mixed ||
+ aThis.extensions != aOther.extensions ||
+ aThis.payload_name != aOther.payload_name ||
+ aThis.payload_type != aOther.payload_type ||
+ aThis.raw_payload != aOther.raw_payload ||
+ aThis.lntf.enabled != aOther.lntf.enabled ||
+ aThis.nack.rtp_history_ms != aOther.nack.rtp_history_ms ||
+ !(aThis.ulpfec == aOther.ulpfec) ||
+ aThis.flexfec.payload_type != aOther.flexfec.payload_type ||
+ aThis.flexfec.ssrc != aOther.flexfec.ssrc ||
+ aThis.flexfec.protected_media_ssrcs !=
+ aOther.flexfec.protected_media_ssrcs ||
+ aThis.rtx.ssrcs != aOther.rtx.ssrcs ||
+ aThis.rtx.payload_type != aOther.rtx.payload_type ||
+ aThis.c_name != aOther.c_name;
+}
+
+#ifdef DEBUG
+// TODO: Make this a defaulted operator when we have c++20 (bug 1731036).
+bool operator==(const webrtc::RtpConfig& aThis,
+ const webrtc::RtpConfig& aOther) {
+ return !(aThis != aOther);
+}
+#endif
+
+} // namespace
+
+/**
+ * Factory Method for VideoConduit
+ */
+RefPtr<VideoSessionConduit> VideoSessionConduit::Create(
+ RefPtr<WebrtcCallWrapper> aCall, nsCOMPtr<nsISerialEventTarget> aStsThread,
+ Options aOptions, std::string aPCHandle,
+ const TrackingId& aRecvTrackingId) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aCall, "missing required parameter: aCall");
+ CSFLogVerbose(LOGTAG, "%s", __FUNCTION__);
+
+ if (!aCall) {
+ return nullptr;
+ }
+
+ auto obj = MakeRefPtr<WebrtcVideoConduit>(
+ std::move(aCall), std::move(aStsThread), std::move(aOptions),
+ std::move(aPCHandle), aRecvTrackingId);
+ if (obj->Init() != kMediaConduitNoError) {
+ CSFLogError(LOGTAG, "%s VideoConduit Init Failed ", __FUNCTION__);
+ return nullptr;
+ }
+ CSFLogVerbose(LOGTAG, "%s Successfully created VideoConduit ", __FUNCTION__);
+ return obj.forget();
+}
+
+#define INIT_MIRROR(name, val) \
+ name(aCallThread, val, "WebrtcVideoConduit::Control::" #name " (Mirror)")
+WebrtcVideoConduit::Control::Control(const RefPtr<AbstractThread>& aCallThread)
+ : INIT_MIRROR(mReceiving, false),
+ INIT_MIRROR(mTransmitting, false),
+ INIT_MIRROR(mLocalSsrcs, Ssrcs()),
+ INIT_MIRROR(mLocalRtxSsrcs, Ssrcs()),
+ INIT_MIRROR(mLocalCname, std::string()),
+ INIT_MIRROR(mMid, std::string()),
+ INIT_MIRROR(mRemoteSsrc, 0),
+ INIT_MIRROR(mRemoteRtxSsrc, 0),
+ INIT_MIRROR(mSyncGroup, std::string()),
+ INIT_MIRROR(mLocalRecvRtpExtensions, RtpExtList()),
+ INIT_MIRROR(mLocalSendRtpExtensions, RtpExtList()),
+ INIT_MIRROR(mSendCodec, Nothing()),
+ INIT_MIRROR(mSendRtpRtcpConfig, Nothing()),
+ INIT_MIRROR(mRecvCodecs, std::vector<VideoCodecConfig>()),
+ INIT_MIRROR(mRecvRtpRtcpConfig, Nothing()),
+ INIT_MIRROR(mCodecMode, webrtc::VideoCodecMode::kRealtimeVideo) {}
+#undef INIT_MIRROR
+
+WebrtcVideoConduit::WebrtcVideoConduit(
+ RefPtr<WebrtcCallWrapper> aCall, nsCOMPtr<nsISerialEventTarget> aStsThread,
+ Options aOptions, std::string aPCHandle, const TrackingId& aRecvTrackingId)
+ : mRendererMonitor("WebrtcVideoConduit::mRendererMonitor"),
+ mCallThread(aCall->mCallThread),
+ mStsThread(std::move(aStsThread)),
+ mControl(aCall->mCallThread),
+ mWatchManager(this, aCall->mCallThread),
+ mMutex("WebrtcVideoConduit::mMutex"),
+ mDecoderFactory(MakeUnique<WebrtcVideoDecoderFactory>(
+ mCallThread.get(), aPCHandle, aRecvTrackingId)),
+ mEncoderFactory(MakeUnique<WebrtcVideoEncoderFactory>(
+ mCallThread.get(), std::move(aPCHandle))),
+ mBufferPool(false, SCALER_BUFFER_POOL_SIZE),
+ mEngineTransmitting(false),
+ mEngineReceiving(false),
+ mVideoLatencyTestEnable(aOptions.mVideoLatencyTestEnable),
+ mMinBitrate(aOptions.mMinBitrate),
+ mStartBitrate(aOptions.mStartBitrate),
+ mPrefMaxBitrate(aOptions.mPrefMaxBitrate),
+ mMinBitrateEstimate(aOptions.mMinBitrateEstimate),
+ mDenoising(aOptions.mDenoising),
+ mLockScaling(aOptions.mLockScaling),
+ mSpatialLayers(aOptions.mSpatialLayers),
+ mTemporalLayers(aOptions.mTemporalLayers),
+ mCall(std::move(aCall)),
+ mSendTransport(this),
+ mRecvTransport(this),
+ mSendStreamConfig(&mSendTransport),
+ mVideoStreamFactory("WebrtcVideoConduit::mVideoStreamFactory"),
+ mRecvStreamConfig(&mRecvTransport) {
+ mRecvStreamConfig.rtp.rtcp_event_observer = this;
+}
+
+WebrtcVideoConduit::~WebrtcVideoConduit() {
+ CSFLogDebug(LOGTAG, "%s ", __FUNCTION__);
+
+ MOZ_ASSERT(!mSendStream && !mRecvStream,
+ "Call DeleteStreams prior to ~WebrtcVideoConduit.");
+}
+
+#define CONNECT(aCanonical, aMirror) \
+ do { \
+ (aMirror).Connect(aCanonical); \
+ mWatchManager.Watch(aMirror, &WebrtcVideoConduit::OnControlConfigChange); \
+ } while (0)
+
+void WebrtcVideoConduit::InitControl(VideoConduitControlInterface* aControl) {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+
+ CONNECT(aControl->CanonicalReceiving(), mControl.mReceiving);
+ CONNECT(aControl->CanonicalTransmitting(), mControl.mTransmitting);
+ CONNECT(aControl->CanonicalLocalSsrcs(), mControl.mLocalSsrcs);
+ CONNECT(aControl->CanonicalLocalVideoRtxSsrcs(), mControl.mLocalRtxSsrcs);
+ CONNECT(aControl->CanonicalLocalCname(), mControl.mLocalCname);
+ CONNECT(aControl->CanonicalMid(), mControl.mMid);
+ CONNECT(aControl->CanonicalRemoteSsrc(), mControl.mRemoteSsrc);
+ CONNECT(aControl->CanonicalRemoteVideoRtxSsrc(), mControl.mRemoteRtxSsrc);
+ CONNECT(aControl->CanonicalSyncGroup(), mControl.mSyncGroup);
+ CONNECT(aControl->CanonicalLocalRecvRtpExtensions(),
+ mControl.mLocalRecvRtpExtensions);
+ CONNECT(aControl->CanonicalLocalSendRtpExtensions(),
+ mControl.mLocalSendRtpExtensions);
+ CONNECT(aControl->CanonicalVideoSendCodec(), mControl.mSendCodec);
+ CONNECT(aControl->CanonicalVideoSendRtpRtcpConfig(),
+ mControl.mSendRtpRtcpConfig);
+ CONNECT(aControl->CanonicalVideoRecvCodecs(), mControl.mRecvCodecs);
+ CONNECT(aControl->CanonicalVideoRecvRtpRtcpConfig(),
+ mControl.mRecvRtpRtcpConfig);
+ CONNECT(aControl->CanonicalVideoCodecMode(), mControl.mCodecMode);
+}
+
+#undef CONNECT
+
+void WebrtcVideoConduit::OnControlConfigChange() {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+
+ bool encoderReconfigureNeeded = false;
+ bool remoteSsrcUpdateNeeded = false;
+ bool sendStreamRecreationNeeded = false;
+
+ if (mControl.mRemoteSsrc.Ref() != mControl.mConfiguredRemoteSsrc) {
+ mControl.mConfiguredRemoteSsrc = mControl.mRemoteSsrc;
+ remoteSsrcUpdateNeeded = true;
+ }
+
+ if (mControl.mRemoteRtxSsrc.Ref() != mControl.mConfiguredRemoteRtxSsrc) {
+ mControl.mConfiguredRemoteRtxSsrc = mControl.mRemoteRtxSsrc;
+ remoteSsrcUpdateNeeded = true;
+ }
+
+ if (mControl.mSyncGroup.Ref() != mRecvStreamConfig.sync_group) {
+ mRecvStreamConfig.sync_group = mControl.mSyncGroup;
+ }
+
+ if (mControl.mLocalRecvRtpExtensions.Ref() !=
+ mRecvStreamConfig.rtp.extensions) {
+ mRecvStreamConfig.rtp.extensions = mControl.mLocalRecvRtpExtensions;
+ }
+
+ if (const auto [codecConfigList, rtpRtcpConfig] = std::make_pair(
+ mControl.mRecvCodecs.Ref(), mControl.mRecvRtpRtcpConfig.Ref());
+ !codecConfigList.empty() && rtpRtcpConfig.isSome() &&
+ (codecConfigList != mControl.mConfiguredRecvCodecs ||
+ rtpRtcpConfig != mControl.mConfiguredRecvRtpRtcpConfig)) {
+ mControl.mConfiguredRecvCodecs = codecConfigList;
+ mControl.mConfiguredRecvRtpRtcpConfig = rtpRtcpConfig;
+
+ webrtc::VideoReceiveStreamInterface::Config::Rtp newRtp(
+ mRecvStreamConfig.rtp);
+ MOZ_ASSERT(newRtp == mRecvStreamConfig.rtp);
+ newRtp.rtx_associated_payload_types.clear();
+ newRtp.rtcp_mode = rtpRtcpConfig->GetRtcpMode();
+ newRtp.nack.rtp_history_ms = 0;
+ newRtp.remb = false;
+ newRtp.tmmbr = false;
+ newRtp.keyframe_method = webrtc::KeyFrameReqMethod::kNone;
+ newRtp.ulpfec_payload_type = kNullPayloadType;
+ newRtp.red_payload_type = kNullPayloadType;
+ bool use_fec = false;
+ bool configuredH264 = false;
+ std::vector<webrtc::VideoReceiveStreamInterface::Decoder> recv_codecs;
+
+ // Try Applying the codecs in the list
+ // we treat as success if at least one codec was applied and reception was
+ // started successfully.
+ for (const auto& codec_config : codecConfigList) {
+ if (auto condError = ValidateCodecConfig(codec_config);
+ condError != kMediaConduitNoError) {
+ CSFLogError(LOGTAG, "Invalid recv codec config for %s decoder: %i",
+ codec_config.mName.c_str(), condError);
+ continue;
+ }
+
+ if (codec_config.mName == kH264CodecName) {
+ // TODO(bug 1200768): We can only handle configuring one recv H264 codec
+ if (configuredH264) {
+ continue;
+ }
+ configuredH264 = true;
+ }
+
+ if (codec_config.mName == kUlpfecCodecName) {
+ newRtp.ulpfec_payload_type = codec_config.mType;
+ continue;
+ }
+
+ if (codec_config.mName == kRedCodecName) {
+ newRtp.red_payload_type = codec_config.mType;
+ continue;
+ }
+
+ if (SupportedCodecType(
+ webrtc::PayloadStringToCodecType(codec_config.mName)) ==
+ webrtc::VideoCodecType::kVideoCodecGeneric) {
+ CSFLogError(LOGTAG, "%s Unknown decoder type: %s", __FUNCTION__,
+ codec_config.mName.c_str());
+ continue;
+ }
+
+ // Check for the keyframe request type: PLI is preferred over FIR, and FIR
+ // is preferred over none.
+ if (codec_config.RtcpFbNackIsSet(kRtcpFbNackParamPli)) {
+ newRtp.keyframe_method = webrtc::KeyFrameReqMethod::kPliRtcp;
+ } else if (newRtp.keyframe_method !=
+ webrtc::KeyFrameReqMethod::kPliRtcp &&
+ codec_config.RtcpFbCcmIsSet(kRtcpFbCcmParamFir)) {
+ newRtp.keyframe_method = webrtc::KeyFrameReqMethod::kFirRtcp;
+ }
+
+ // What if codec A has Nack and REMB, and codec B has TMMBR, and codec C
+ // has none? In practice, that's not a useful configuration, and
+ // VideoReceiveStream::Config can't represent that, so simply union the
+ // (boolean) settings
+ if (codec_config.RtcpFbNackIsSet(kParamValueEmpty)) {
+ newRtp.nack.rtp_history_ms = 1000;
+ }
+ newRtp.tmmbr |= codec_config.RtcpFbCcmIsSet(kRtcpFbCcmParamTmmbr);
+ newRtp.remb |= codec_config.RtcpFbRembIsSet();
+ use_fec |= codec_config.RtcpFbFECIsSet();
+
+ if (codec_config.RtxPayloadTypeIsSet()) {
+ newRtp.rtx_associated_payload_types[codec_config.mRTXPayloadType] =
+ codec_config.mType;
+ }
+
+ auto& decoder = recv_codecs.emplace_back();
+ decoder.video_format = webrtc::SdpVideoFormat(codec_config.mName);
+ decoder.payload_type = codec_config.mType;
+ }
+
+ if (!use_fec) {
+ // Reset to defaults
+ newRtp.ulpfec_payload_type = kNullPayloadType;
+ newRtp.red_payload_type = kNullPayloadType;
+ }
+
+ // TODO: This would be simpler, but for some reason gives
+ // "error: invalid operands to binary expression
+ // ('webrtc::VideoReceiveStreamInterface::Decoder' and
+ // 'webrtc::VideoReceiveStreamInterface::Decoder')"
+ // if (recv_codecs != mRecvStreamConfig.decoders) {
+ if (!std::equal(recv_codecs.begin(), recv_codecs.end(),
+ mRecvStreamConfig.decoders.begin(),
+ mRecvStreamConfig.decoders.end(),
+ [](const auto& aLeft, const auto& aRight) {
+ return aLeft == aRight;
+ })) {
+ if (recv_codecs.empty()) {
+ CSFLogError(LOGTAG, "%s Found no valid receive codecs", __FUNCTION__);
+ }
+ mRecvStreamConfig.decoders = std::move(recv_codecs);
+ }
+
+ if (mRecvStreamConfig.rtp != newRtp) {
+ mRecvStreamConfig.rtp = newRtp;
+ }
+ }
+
+ {
+ // mSendStreamConfig and other members need the lock
+ MutexAutoLock lock(mMutex);
+ if (mControl.mLocalSsrcs.Ref() != mSendStreamConfig.rtp.ssrcs) {
+ mSendStreamConfig.rtp.ssrcs = mControl.mLocalSsrcs;
+ sendStreamRecreationNeeded = true;
+
+ const uint32_t localSsrc = mSendStreamConfig.rtp.ssrcs.empty()
+ ? 0
+ : mSendStreamConfig.rtp.ssrcs.front();
+ if (localSsrc != mRecvStreamConfig.rtp.local_ssrc) {
+ mRecvStreamConfig.rtp.local_ssrc = localSsrc;
+ }
+ }
+
+ {
+ Ssrcs localRtxSsrcs = mControl.mLocalRtxSsrcs.Ref();
+ if (!mControl.mSendCodec.Ref()
+ .map([](const auto& aCodec) {
+ return aCodec.RtxPayloadTypeIsSet();
+ })
+ .valueOr(false)) {
+ localRtxSsrcs.clear();
+ }
+ if (localRtxSsrcs != mSendStreamConfig.rtp.rtx.ssrcs) {
+ mSendStreamConfig.rtp.rtx.ssrcs = localRtxSsrcs;
+ sendStreamRecreationNeeded = true;
+ }
+ }
+
+ if (mControl.mLocalCname.Ref() != mSendStreamConfig.rtp.c_name) {
+ mSendStreamConfig.rtp.c_name = mControl.mLocalCname;
+ sendStreamRecreationNeeded = true;
+ }
+
+ if (mControl.mMid.Ref() != mSendStreamConfig.rtp.mid) {
+ mSendStreamConfig.rtp.mid = mControl.mMid;
+ sendStreamRecreationNeeded = true;
+ }
+
+ if (mControl.mLocalSendRtpExtensions.Ref() !=
+ mSendStreamConfig.rtp.extensions) {
+ mSendStreamConfig.rtp.extensions = mControl.mLocalSendRtpExtensions;
+ sendStreamRecreationNeeded = true;
+ }
+
+ if (const auto [codecConfig, rtpRtcpConfig] = std::make_pair(
+ mControl.mSendCodec.Ref(), mControl.mSendRtpRtcpConfig.Ref());
+ codecConfig.isSome() && rtpRtcpConfig.isSome() &&
+ (codecConfig != mControl.mConfiguredSendCodec ||
+ rtpRtcpConfig != mControl.mConfiguredSendRtpRtcpConfig)) {
+ CSFLogDebug(LOGTAG, "Configuring codec %s", codecConfig->mName.c_str());
+ mControl.mConfiguredSendCodec = codecConfig;
+ mControl.mConfiguredSendRtpRtcpConfig = rtpRtcpConfig;
+
+ if (ValidateCodecConfig(*codecConfig) == kMediaConduitNoError) {
+ encoderReconfigureNeeded = true;
+
+ mCurSendCodecConfig = codecConfig;
+
+ size_t streamCount = std::min(codecConfig->mEncodings.size(),
+ (size_t)webrtc::kMaxSimulcastStreams);
+ size_t highestResolutionIndex = 0;
+ for (size_t i = 1; i < streamCount; ++i) {
+ if (codecConfig->mEncodings[i].constraints.scaleDownBy <
+ codecConfig->mEncodings[highestResolutionIndex]
+ .constraints.scaleDownBy) {
+ highestResolutionIndex = i;
+ }
+ }
+ MOZ_RELEASE_ASSERT(streamCount >= 1,
+ "streamCount should be at least one");
+
+ CSFLogDebug(LOGTAG,
+ "Updating send codec for VideoConduit:%p stream count:%zu",
+ this, streamCount);
+
+ // So we can comply with b=TIAS/b=AS/maxbr=X when input resolution
+ // changes
+ MOZ_ASSERT(codecConfig->mTias < INT_MAX);
+ mNegotiatedMaxBitrate = static_cast<int>(codecConfig->mTias);
+
+ if (mLastWidth == 0 && mMinBitrateEstimate != 0) {
+ // Only do this at the start; use "have we sent a frame" as a
+ // reasonable stand-in. min <= start <= max (but all three parameters
+ // are optional)
+ webrtc::BitrateSettings settings;
+ settings.min_bitrate_bps = mMinBitrateEstimate;
+ settings.start_bitrate_bps = mMinBitrateEstimate;
+ mCall->Call()->SetClientBitratePreferences(settings);
+ }
+
+ // XXX parse the encoded SPS/PPS data and set
+ // spsData/spsLen/ppsData/ppsLen
+ mEncoderConfig.video_format =
+ webrtc::SdpVideoFormat(codecConfig->mName);
+ mEncoderConfig.encoder_specific_settings =
+ ConfigureVideoEncoderSettings(
+ *codecConfig, this, mEncoderConfig.video_format.parameters);
+
+ mEncoderConfig.codec_type = SupportedCodecType(
+ webrtc::PayloadStringToCodecType(codecConfig->mName));
+ MOZ_RELEASE_ASSERT(mEncoderConfig.codec_type !=
+ webrtc::VideoCodecType::kVideoCodecGeneric);
+
+ mEncoderConfig.content_type =
+ mControl.mCodecMode.Ref() == webrtc::VideoCodecMode::kRealtimeVideo
+ ? webrtc::VideoEncoderConfig::ContentType::kRealtimeVideo
+ : webrtc::VideoEncoderConfig::ContentType::kScreen;
+
+ mEncoderConfig.frame_drop_enabled =
+ mControl.mCodecMode.Ref() != webrtc::VideoCodecMode::kScreensharing;
+
+ mEncoderConfig.min_transmit_bitrate_bps = mMinBitrate;
+
+ // Set the max bitrate, defaulting to 10Mbps, checking:
+ // - pref
+ // - b=TIAS
+ // - codec constraints
+ // - encoding parameter if there's a single stream
+ int maxBps = KBPS(10000);
+ maxBps = MinIgnoreZero(maxBps, mPrefMaxBitrate);
+ maxBps = MinIgnoreZero(maxBps, mNegotiatedMaxBitrate);
+ maxBps = MinIgnoreZero(
+ maxBps, static_cast<int>(codecConfig->mEncodingConstraints.maxBr));
+ if (codecConfig->mEncodings.size() == 1) {
+ maxBps = MinIgnoreZero(
+ maxBps,
+ static_cast<int>(codecConfig->mEncodings[0].constraints.maxBr));
+ }
+ mEncoderConfig.max_bitrate_bps = maxBps;
+
+ // TODO this is for webrtc-priority, but needs plumbing bits
+ mEncoderConfig.bitrate_priority = 1.0;
+
+ // Expected max number of encodings
+ mEncoderConfig.number_of_streams = streamCount;
+
+ // libwebrtc disables this by default.
+ mSendStreamConfig.suspend_below_min_bitrate = false;
+
+ webrtc::RtpConfig newRtp = mSendStreamConfig.rtp;
+ MOZ_ASSERT(newRtp == mSendStreamConfig.rtp);
+ newRtp.payload_name = codecConfig->mName;
+ newRtp.payload_type = codecConfig->mType;
+ newRtp.rtcp_mode = rtpRtcpConfig->GetRtcpMode();
+ newRtp.max_packet_size = kVideoMtu;
+ newRtp.rtx.payload_type = codecConfig->RtxPayloadTypeIsSet()
+ ? codecConfig->mRTXPayloadType
+ : kNullPayloadType;
+
+ {
+ // See Bug 1297058, enabling FEC when basic NACK is to be enabled in
+ // H.264 is problematic
+ const bool useFECDefaults =
+ !codecConfig->RtcpFbFECIsSet() ||
+ (codecConfig->mName == kH264CodecName &&
+ codecConfig->RtcpFbNackIsSet(kParamValueEmpty));
+ newRtp.ulpfec.ulpfec_payload_type =
+ useFECDefaults ? kNullPayloadType
+ : codecConfig->mULPFECPayloadType;
+ newRtp.ulpfec.red_payload_type =
+ useFECDefaults ? kNullPayloadType : codecConfig->mREDPayloadType;
+ newRtp.ulpfec.red_rtx_payload_type =
+ useFECDefaults ? kNullPayloadType
+ : codecConfig->mREDRTXPayloadType;
+ }
+
+ newRtp.nack.rtp_history_ms =
+ codecConfig->RtcpFbNackIsSet(kParamValueEmpty) ? 1000 : 0;
+
+ {
+ newRtp.rids.clear();
+ bool has_rid = false;
+ for (size_t idx = 0; idx < streamCount; idx++) {
+ const auto& encoding = codecConfig->mEncodings[idx];
+ if (encoding.rid[0]) {
+ has_rid = true;
+ break;
+ }
+ }
+ if (has_rid) {
+ for (size_t idx = streamCount; idx > 0; idx--) {
+ const auto& encoding = codecConfig->mEncodings[idx - 1];
+ newRtp.rids.push_back(encoding.rid);
+ }
+ }
+ }
+ if (mSendStreamConfig.rtp != newRtp) {
+ mSendStreamConfig.rtp = newRtp;
+ sendStreamRecreationNeeded = true;
+ }
+
+ mEncoderConfig.video_stream_factory = CreateVideoStreamFactory();
+ }
+ }
+
+ {
+ const auto& mode = mControl.mCodecMode.Ref();
+ MOZ_ASSERT(mode == webrtc::VideoCodecMode::kRealtimeVideo ||
+ mode == webrtc::VideoCodecMode::kScreensharing);
+
+ auto contentType =
+ mode == webrtc::VideoCodecMode::kRealtimeVideo
+ ? webrtc::VideoEncoderConfig::ContentType::kRealtimeVideo
+ : webrtc::VideoEncoderConfig::ContentType::kScreen;
+
+ if (contentType != mEncoderConfig.content_type) {
+ mEncoderConfig.video_stream_factory = CreateVideoStreamFactory();
+ encoderReconfigureNeeded = true;
+ }
+ }
+
+ if (remoteSsrcUpdateNeeded) {
+ SetRemoteSSRCConfig(mControl.mConfiguredRemoteSsrc,
+ mControl.mConfiguredRemoteRtxSsrc);
+ }
+
+ // Handle un-signalled SSRCs by creating random ones and then when they
+ // actually get set, we'll destroy and recreate.
+ if (mControl.mReceiving || mControl.mTransmitting) {
+ const auto remoteSsrc = mRecvStreamConfig.rtp.remote_ssrc;
+ const auto localSsrc = mRecvStreamConfig.rtp.local_ssrc;
+ const auto localSsrcs = mSendStreamConfig.rtp.ssrcs;
+ EnsureLocalSSRC();
+ if (mControl.mReceiving) {
+ EnsureRemoteSSRC();
+ }
+ if (localSsrc != mRecvStreamConfig.rtp.local_ssrc ||
+ remoteSsrc != mRecvStreamConfig.rtp.remote_ssrc) {
+ }
+ if (localSsrcs != mSendStreamConfig.rtp.ssrcs) {
+ sendStreamRecreationNeeded = true;
+ }
+ }
+
+ // Recreate receiving streams
+ if (mControl.mReceiving) {
+ DeleteRecvStream();
+ CreateRecvStream();
+ }
+ if (sendStreamRecreationNeeded) {
+ DeleteSendStream();
+ }
+ if (mControl.mTransmitting) {
+ CreateSendStream();
+ }
+ }
+
+ // We make sure to not hold the lock while stopping/starting/reconfiguring
+ // streams, so as to not cause deadlocks. These methods can cause our platform
+ // codecs to dispatch sync runnables to main, and main may grab the lock.
+
+ if (mSendStream && encoderReconfigureNeeded) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ mSendStreamConfig.rtp.ssrcs.size() == mEncoderConfig.number_of_streams,
+ "Each video substream must have a corresponding ssrc.");
+ mSendStream->ReconfigureVideoEncoder(mEncoderConfig.Copy());
+ }
+
+ if (!mControl.mReceiving) {
+ StopReceiving();
+ }
+ if (!mControl.mTransmitting) {
+ StopTransmitting();
+ }
+
+ if (mControl.mReceiving) {
+ StartReceiving();
+ }
+ if (mControl.mTransmitting) {
+ StartTransmitting();
+ }
+}
+
+std::vector<unsigned int> WebrtcVideoConduit::GetLocalSSRCs() const {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ return mSendStreamConfig.rtp.ssrcs;
+}
+
+Maybe<Ssrc> WebrtcVideoConduit::GetAssociatedLocalRtxSSRC(Ssrc aSsrc) const {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ for (size_t i = 0; i < mSendStreamConfig.rtp.ssrcs.size() &&
+ i < mSendStreamConfig.rtp.rtx.ssrcs.size();
+ ++i) {
+ if (mSendStreamConfig.rtp.ssrcs[i] == aSsrc) {
+ return Some(mSendStreamConfig.rtp.rtx.ssrcs[i]);
+ }
+ }
+ return Nothing();
+}
+
+void WebrtcVideoConduit::DeleteSendStream() {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ mMutex.AssertCurrentThreadOwns();
+
+ if (!mSendStream) {
+ return;
+ }
+
+ mCall->Call()->DestroyVideoSendStream(mSendStream);
+ mEngineTransmitting = false;
+ mSendStream = nullptr;
+
+ // Reset base_seqs in case ssrcs get re-used.
+ mRtpSendBaseSeqs.clear();
+}
+
+void WebrtcVideoConduit::CreateSendStream() {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ mMutex.AssertCurrentThreadOwns();
+
+ if (mSendStream) {
+ return;
+ }
+
+ nsAutoString codecName;
+ codecName.AssignASCII(mSendStreamConfig.rtp.payload_name.c_str());
+ Telemetry::ScalarAdd(Telemetry::ScalarID::WEBRTC_VIDEO_SEND_CODEC_USED,
+ codecName, 1);
+
+ mSendStreamConfig.encoder_settings.encoder_factory = mEncoderFactory.get();
+ mSendStreamConfig.encoder_settings.bitrate_allocator_factory =
+ mCall->mVideoBitrateAllocatorFactory.get();
+
+ MOZ_DIAGNOSTIC_ASSERT(
+ mSendStreamConfig.rtp.ssrcs.size() == mEncoderConfig.number_of_streams,
+ "Each video substream must have a corresponding ssrc.");
+
+ mSendStream = mCall->Call()->CreateVideoSendStream(mSendStreamConfig.Copy(),
+ mEncoderConfig.Copy());
+
+ mSendStream->SetSource(this, webrtc::DegradationPreference::BALANCED);
+}
+
+void WebrtcVideoConduit::DeleteRecvStream() {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ mMutex.AssertCurrentThreadOwns();
+
+ if (!mRecvStream) {
+ return;
+ }
+
+ mCall->Call()->DestroyVideoReceiveStream(mRecvStream);
+ mEngineReceiving = false;
+ mRecvStream = nullptr;
+}
+
+void WebrtcVideoConduit::CreateRecvStream() {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ mMutex.AssertCurrentThreadOwns();
+
+ if (mRecvStream) {
+ return;
+ }
+
+ mRecvStreamConfig.renderer = this;
+
+ for (auto& decoder : mRecvStreamConfig.decoders) {
+ nsAutoString codecName;
+ codecName.AssignASCII(decoder.video_format.name.c_str());
+ Telemetry::ScalarAdd(Telemetry::ScalarID::WEBRTC_VIDEO_RECV_CODEC_USED,
+ codecName, 1);
+ }
+
+ mRecvStreamConfig.decoder_factory = mDecoderFactory.get();
+
+ mRecvStream =
+ mCall->Call()->CreateVideoReceiveStream(mRecvStreamConfig.Copy());
+ // Ensure that we set the jitter buffer target on this stream.
+ mRecvStream->SetBaseMinimumPlayoutDelayMs(mJitterBufferTargetMs);
+
+ CSFLogDebug(LOGTAG, "Created VideoReceiveStream %p for SSRC %u (0x%x)",
+ mRecvStream, mRecvStreamConfig.rtp.remote_ssrc,
+ mRecvStreamConfig.rtp.remote_ssrc);
+}
+
+void WebrtcVideoConduit::NotifyUnsetCurrentRemoteSSRC() {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ CSFLogDebug(LOGTAG, "%s (%p): Unsetting SSRC %u in other conduits",
+ __FUNCTION__, this, mRecvStreamConfig.rtp.remote_ssrc);
+ mCall->UnregisterConduit(this);
+ mCall->UnsetRemoteSSRC(mRecvStreamConfig.rtp.remote_ssrc);
+ mCall->RegisterConduit(this);
+}
+
+void WebrtcVideoConduit::SetRemoteSSRCConfig(uint32_t aSsrc,
+ uint32_t aRtxSsrc) {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+
+ CSFLogDebug(LOGTAG, "%s: SSRC %u (0x%x)", __FUNCTION__, aSsrc, aSsrc);
+
+ if (mRecvStreamConfig.rtp.remote_ssrc != aSsrc) {
+ nsCOMPtr<nsIDirectTaskDispatcher> dtd = do_QueryInterface(mCallThread);
+ MOZ_ALWAYS_SUCCEEDS(dtd->DispatchDirectTask(NewRunnableMethod(
+ "WebrtcVideoConduit::NotifyUnsetCurrentRemoteSSRC", this,
+ &WebrtcVideoConduit::NotifyUnsetCurrentRemoteSSRC)));
+ }
+
+ mRecvSSRC = mRecvStreamConfig.rtp.remote_ssrc = aSsrc;
+ mRecvStreamConfig.rtp.rtx_ssrc = aRtxSsrc;
+}
+
+void WebrtcVideoConduit::SetRemoteSSRCAndRestartAsNeeded(uint32_t aSsrc,
+ uint32_t aRtxSsrc) {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+
+ if (mRecvStreamConfig.rtp.remote_ssrc == aSsrc &&
+ mRecvStreamConfig.rtp.rtx_ssrc == aRtxSsrc) {
+ return;
+ }
+
+ SetRemoteSSRCConfig(aSsrc, aRtxSsrc);
+
+ const bool wasReceiving = mEngineReceiving;
+ const bool hadRecvStream = mRecvStream;
+
+ StopReceiving();
+
+ if (hadRecvStream) {
+ MutexAutoLock lock(mMutex);
+ DeleteRecvStream();
+ CreateRecvStream();
+ }
+
+ if (wasReceiving) {
+ StartReceiving();
+ }
+}
+
+void WebrtcVideoConduit::EnsureRemoteSSRC() {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ mMutex.AssertCurrentThreadOwns();
+
+ const auto& ssrcs = mSendStreamConfig.rtp.ssrcs;
+ if (mRecvStreamConfig.rtp.remote_ssrc != 0 &&
+ std::find(ssrcs.begin(), ssrcs.end(),
+ mRecvStreamConfig.rtp.remote_ssrc) == ssrcs.end()) {
+ return;
+ }
+
+ uint32_t ssrc;
+ do {
+ ssrc = GenerateRandomSSRC();
+ } while (
+ NS_WARN_IF(std::find(ssrcs.begin(), ssrcs.end(), ssrc) != ssrcs.end()));
+ CSFLogDebug(LOGTAG, "VideoConduit %p: Generated remote SSRC %u", this, ssrc);
+ SetRemoteSSRCConfig(ssrc, 0);
+}
+
+void WebrtcVideoConduit::EnsureLocalSSRC() {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ mMutex.AssertCurrentThreadOwns();
+
+ auto& ssrcs = mSendStreamConfig.rtp.ssrcs;
+ if (ssrcs.empty()) {
+ ssrcs.push_back(GenerateRandomSSRC());
+ }
+
+ // Reverse-iterating here so that the first dupe in `ssrcs` always wins.
+ for (auto& ssrc : Reversed(ssrcs)) {
+ if (ssrc != 0 && ssrc != mRecvStreamConfig.rtp.remote_ssrc &&
+ std::count(ssrcs.begin(), ssrcs.end(), ssrc) == 1) {
+ continue;
+ }
+ do {
+ ssrc = GenerateRandomSSRC();
+ } while (NS_WARN_IF(ssrc == mRecvStreamConfig.rtp.remote_ssrc) ||
+ NS_WARN_IF(std::count(ssrcs.begin(), ssrcs.end(), ssrc) > 1));
+ CSFLogDebug(LOGTAG, "%s (%p): Generated local SSRC %u", __FUNCTION__, this,
+ ssrc);
+ }
+ mRecvStreamConfig.rtp.local_ssrc = ssrcs[0];
+}
+
+void WebrtcVideoConduit::UnsetRemoteSSRC(uint32_t aSsrc) {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ mMutex.AssertNotCurrentThreadOwns();
+
+ if (mRecvStreamConfig.rtp.remote_ssrc != aSsrc &&
+ mRecvStreamConfig.rtp.rtx_ssrc != aSsrc) {
+ return;
+ }
+
+ const auto& ssrcs = mSendStreamConfig.rtp.ssrcs;
+ uint32_t our_ssrc = 0;
+ do {
+ our_ssrc = GenerateRandomSSRC();
+ } while (NS_WARN_IF(our_ssrc == aSsrc) ||
+ NS_WARN_IF(std::find(ssrcs.begin(), ssrcs.end(), our_ssrc) !=
+ ssrcs.end()));
+
+ CSFLogDebug(LOGTAG, "%s (%p): Generated remote SSRC %u", __FUNCTION__, this,
+ our_ssrc);
+
+ // There is a (tiny) chance that this new random ssrc will collide with some
+ // other conduit's remote ssrc, in which case that conduit will choose a new
+ // one.
+ SetRemoteSSRCAndRestartAsNeeded(our_ssrc, 0);
+}
+
+/*static*/
+unsigned WebrtcVideoConduit::ToLibwebrtcMaxFramerate(
+ const Maybe<double>& aMaxFramerate) {
+ Maybe<unsigned> negotiatedMaxFps;
+ if (aMaxFramerate.isSome()) {
+ // libwebrtc does not handle non-integer max framerate.
+ unsigned integerMaxFps = static_cast<unsigned>(std::round(*aMaxFramerate));
+ // libwebrtc crashes with a max framerate of 0, even though the
+ // spec says this is valid. For now, we treat this as no limit.
+ if (integerMaxFps) {
+ negotiatedMaxFps = Some(integerMaxFps);
+ }
+ }
+ // We do not use DEFAULT_VIDEO_MAX_FRAMERATE here; that is used at the very
+ // end in VideoStreamFactory, once codec-wide and per-encoding limits are
+ // known.
+ return negotiatedMaxFps.refOr(std::numeric_limits<unsigned int>::max());
+}
+
+Maybe<Ssrc> WebrtcVideoConduit::GetRemoteSSRC() const {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ // libwebrtc uses 0 to mean a lack of SSRC. That is not to spec.
+ return mRecvStreamConfig.rtp.remote_ssrc == 0
+ ? Nothing()
+ : Some(mRecvStreamConfig.rtp.remote_ssrc);
+}
+
+Maybe<webrtc::VideoReceiveStreamInterface::Stats>
+WebrtcVideoConduit::GetReceiverStats() const {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ if (!mRecvStream) {
+ return Nothing();
+ }
+ return Some(mRecvStream->GetStats());
+}
+
+Maybe<webrtc::VideoSendStream::Stats> WebrtcVideoConduit::GetSenderStats()
+ const {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ if (!mSendStream) {
+ return Nothing();
+ }
+ return Some(mSendStream->GetStats());
+}
+
+Maybe<webrtc::Call::Stats> WebrtcVideoConduit::GetCallStats() const {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ if (!mCall->Call()) {
+ return Nothing();
+ }
+ return Some(mCall->Call()->GetStats());
+}
+
+MediaConduitErrorCode WebrtcVideoConduit::Init() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ CSFLogDebug(LOGTAG, "%s this=%p", __FUNCTION__, this);
+
+#ifdef MOZ_WIDGET_ANDROID
+ if (mozilla::camera::VideoEngine::SetAndroidObjects() != 0) {
+ CSFLogError(LOGTAG, "%s: could not set Android objects", __FUNCTION__);
+ return kMediaConduitSessionNotInited;
+ }
+#endif // MOZ_WIDGET_ANDROID
+
+ mSendPluginCreated = mEncoderFactory->CreatedGmpPluginEvent().Connect(
+ GetMainThreadSerialEventTarget(),
+ [self = detail::RawPtr(this)](uint64_t aPluginID) {
+ self.get()->mSendCodecPluginIDs.AppendElement(aPluginID);
+ });
+ mSendPluginReleased = mEncoderFactory->ReleasedGmpPluginEvent().Connect(
+ GetMainThreadSerialEventTarget(),
+ [self = detail::RawPtr(this)](uint64_t aPluginID) {
+ self.get()->mSendCodecPluginIDs.RemoveElement(aPluginID);
+ });
+ mRecvPluginCreated = mDecoderFactory->CreatedGmpPluginEvent().Connect(
+ GetMainThreadSerialEventTarget(),
+ [self = detail::RawPtr(this)](uint64_t aPluginID) {
+ self.get()->mRecvCodecPluginIDs.AppendElement(aPluginID);
+ });
+ mRecvPluginReleased = mDecoderFactory->ReleasedGmpPluginEvent().Connect(
+ GetMainThreadSerialEventTarget(),
+ [self = detail::RawPtr(this)](uint64_t aPluginID) {
+ self.get()->mRecvCodecPluginIDs.RemoveElement(aPluginID);
+ });
+
+ MOZ_ALWAYS_SUCCEEDS(mCallThread->Dispatch(NS_NewRunnableFunction(
+ __func__, [this, self = RefPtr<WebrtcVideoConduit>(this)] {
+ mCall->RegisterConduit(this);
+ })));
+
+ CSFLogDebug(LOGTAG, "%s Initialization Done", __FUNCTION__);
+ return kMediaConduitNoError;
+}
+
+RefPtr<GenericPromise> WebrtcVideoConduit::Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mSendPluginCreated.DisconnectIfExists();
+ mSendPluginReleased.DisconnectIfExists();
+ mRecvPluginCreated.DisconnectIfExists();
+ mRecvPluginReleased.DisconnectIfExists();
+
+ return InvokeAsync(
+ mCallThread, __func__, [this, self = RefPtr<WebrtcVideoConduit>(this)] {
+ using namespace Telemetry;
+ if (mSendBitrate.NumDataValues() > 0) {
+ Accumulate(WEBRTC_VIDEO_ENCODER_BITRATE_AVG_PER_CALL_KBPS,
+ static_cast<unsigned>(mSendBitrate.Mean() / 1000));
+ Accumulate(
+ WEBRTC_VIDEO_ENCODER_BITRATE_STD_DEV_PER_CALL_KBPS,
+ static_cast<unsigned>(mSendBitrate.StandardDeviation() / 1000));
+ mSendBitrate.Clear();
+ }
+ if (mSendFramerate.NumDataValues() > 0) {
+ Accumulate(WEBRTC_VIDEO_ENCODER_FRAMERATE_AVG_PER_CALL,
+ static_cast<unsigned>(mSendFramerate.Mean()));
+ Accumulate(
+ WEBRTC_VIDEO_ENCODER_FRAMERATE_10X_STD_DEV_PER_CALL,
+ static_cast<unsigned>(mSendFramerate.StandardDeviation() * 10));
+ mSendFramerate.Clear();
+ }
+
+ if (mRecvBitrate.NumDataValues() > 0) {
+ Accumulate(WEBRTC_VIDEO_DECODER_BITRATE_AVG_PER_CALL_KBPS,
+ static_cast<unsigned>(mRecvBitrate.Mean() / 1000));
+ Accumulate(
+ WEBRTC_VIDEO_DECODER_BITRATE_STD_DEV_PER_CALL_KBPS,
+ static_cast<unsigned>(mRecvBitrate.StandardDeviation() / 1000));
+ mRecvBitrate.Clear();
+ }
+ if (mRecvFramerate.NumDataValues() > 0) {
+ Accumulate(WEBRTC_VIDEO_DECODER_FRAMERATE_AVG_PER_CALL,
+ static_cast<unsigned>(mRecvFramerate.Mean()));
+ Accumulate(
+ WEBRTC_VIDEO_DECODER_FRAMERATE_10X_STD_DEV_PER_CALL,
+ static_cast<unsigned>(mRecvFramerate.StandardDeviation() * 10));
+ mRecvFramerate.Clear();
+ }
+
+ mControl.mReceiving.DisconnectIfConnected();
+ mControl.mTransmitting.DisconnectIfConnected();
+ mControl.mLocalSsrcs.DisconnectIfConnected();
+ mControl.mLocalRtxSsrcs.DisconnectIfConnected();
+ mControl.mLocalCname.DisconnectIfConnected();
+ mControl.mMid.DisconnectIfConnected();
+ mControl.mRemoteSsrc.DisconnectIfConnected();
+ mControl.mRemoteRtxSsrc.DisconnectIfConnected();
+ mControl.mSyncGroup.DisconnectIfConnected();
+ mControl.mLocalRecvRtpExtensions.DisconnectIfConnected();
+ mControl.mLocalSendRtpExtensions.DisconnectIfConnected();
+ mControl.mSendCodec.DisconnectIfConnected();
+ mControl.mSendRtpRtcpConfig.DisconnectIfConnected();
+ mControl.mRecvCodecs.DisconnectIfConnected();
+ mControl.mRecvRtpRtcpConfig.DisconnectIfConnected();
+ mControl.mCodecMode.DisconnectIfConnected();
+ mWatchManager.Shutdown();
+
+ mCall->UnregisterConduit(this);
+ mDecoderFactory->DisconnectAll();
+ mEncoderFactory->DisconnectAll();
+ {
+ MutexAutoLock lock(mMutex);
+ DeleteSendStream();
+ DeleteRecvStream();
+ }
+
+ return GenericPromise::CreateAndResolve(true, __func__);
+ });
+}
+
+webrtc::VideoCodecMode WebrtcVideoConduit::CodecMode() const {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ return mControl.mCodecMode;
+}
+
+MediaConduitErrorCode WebrtcVideoConduit::AttachRenderer(
+ RefPtr<mozilla::VideoRenderer> aVideoRenderer) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ CSFLogDebug(LOGTAG, "%s", __FUNCTION__);
+
+ // null renderer
+ if (!aVideoRenderer) {
+ CSFLogError(LOGTAG, "%s NULL Renderer", __FUNCTION__);
+ MOZ_ASSERT(false);
+ return kMediaConduitInvalidRenderer;
+ }
+
+ // This function is called only from main, so we only need to protect against
+ // modifying mRenderer while any webrtc.org code is trying to use it.
+ {
+ ReentrantMonitorAutoEnter enter(mRendererMonitor);
+ mRenderer = aVideoRenderer;
+ // Make sure the renderer knows the resolution
+ mRenderer->FrameSizeChange(mReceivingWidth, mReceivingHeight);
+ }
+
+ return kMediaConduitNoError;
+}
+
+void WebrtcVideoConduit::DetachRenderer() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ ReentrantMonitorAutoEnter enter(mRendererMonitor);
+ if (mRenderer) {
+ mRenderer = nullptr;
+ }
+}
+
+rtc::RefCountedObject<mozilla::VideoStreamFactory>*
+WebrtcVideoConduit::CreateVideoStreamFactory() {
+ auto videoStreamFactory = mVideoStreamFactory.Lock();
+ *videoStreamFactory = new rtc::RefCountedObject<VideoStreamFactory>(
+ *mCurSendCodecConfig, mControl.mCodecMode, mMinBitrate, mStartBitrate,
+ mPrefMaxBitrate, mNegotiatedMaxBitrate, mVideoBroadcaster.wants(),
+ mLockScaling);
+ return videoStreamFactory->get();
+}
+
+void WebrtcVideoConduit::AddOrUpdateSink(
+ rtc::VideoSinkInterface<webrtc::VideoFrame>* sink,
+ const rtc::VideoSinkWants& wants) {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ if (!mRegisteredSinks.Contains(sink)) {
+ mRegisteredSinks.AppendElement(sink);
+ }
+ auto oldWants = mVideoBroadcaster.wants();
+ mVideoBroadcaster.AddOrUpdateSink(sink, wants);
+ if (oldWants != mVideoBroadcaster.wants()) {
+ mEncoderConfig.video_stream_factory = CreateVideoStreamFactory();
+ mSendStream->ReconfigureVideoEncoder(mEncoderConfig.Copy());
+ }
+}
+
+void WebrtcVideoConduit::RemoveSink(
+ rtc::VideoSinkInterface<webrtc::VideoFrame>* sink) {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+
+ mRegisteredSinks.RemoveElement(sink);
+ auto oldWants = mVideoBroadcaster.wants();
+ mVideoBroadcaster.RemoveSink(sink);
+ if (oldWants != mVideoBroadcaster.wants()) {
+ mEncoderConfig.video_stream_factory = CreateVideoStreamFactory();
+ mSendStream->ReconfigureVideoEncoder(mEncoderConfig.Copy());
+ }
+}
+
+MediaConduitErrorCode WebrtcVideoConduit::SendVideoFrame(
+ webrtc::VideoFrame aFrame) {
+ // XXX Google uses a "timestamp_aligner" to translate timestamps from the
+ // camera via TranslateTimestamp(); we should look at doing the same. This
+ // avoids sampling error when capturing frames, but google had to deal with
+ // some broken cameras, include Logitech c920's IIRC.
+
+ {
+ MutexAutoLock lock(mMutex);
+ if (mSendStreamConfig.rtp.ssrcs.empty()) {
+ CSFLogVerbose(LOGTAG, "WebrtcVideoConduit %p %s No SSRC set", this,
+ __FUNCTION__);
+ return kMediaConduitNoError;
+ }
+ if (!mCurSendCodecConfig) {
+ CSFLogVerbose(LOGTAG, "WebrtcVideoConduit %p %s No send codec set", this,
+ __FUNCTION__);
+ return kMediaConduitNoError;
+ }
+
+ // Workaround for bug in libwebrtc where all encodings are transmitted
+ // if they are all inactive.
+ bool anyActive = false;
+ for (const auto& encoding : mCurSendCodecConfig->mEncodings) {
+ if (encoding.active) {
+ anyActive = true;
+ break;
+ }
+ }
+ if (!anyActive) {
+ CSFLogVerbose(LOGTAG, "WebrtcVideoConduit %p %s No active encodings",
+ this, __FUNCTION__);
+ return kMediaConduitNoError;
+ }
+
+ CSFLogVerbose(LOGTAG, "WebrtcVideoConduit %p %s (send SSRC %u (0x%x))",
+ this, __FUNCTION__, mSendStreamConfig.rtp.ssrcs.front(),
+ mSendStreamConfig.rtp.ssrcs.front());
+
+ if (aFrame.width() != mLastWidth || aFrame.height() != mLastHeight) {
+ // See if we need to recalculate what we're sending.
+ CSFLogVerbose(LOGTAG, "%s: call SelectSendResolution with %ux%u",
+ __FUNCTION__, aFrame.width(), aFrame.height());
+ MOZ_ASSERT(aFrame.width() != 0 && aFrame.height() != 0);
+ // Note coverity will flag this since it thinks they can be 0
+ MOZ_ASSERT(mCurSendCodecConfig);
+
+ mLastWidth = aFrame.width();
+ mLastHeight = aFrame.height();
+ }
+
+ // adapt input video to wants of sink
+ if (!mVideoBroadcaster.frame_wanted()) {
+ return kMediaConduitNoError;
+ }
+
+ // Check if we need to drop this frame to meet a requested FPS
+ auto videoStreamFactory = mVideoStreamFactory.Lock();
+ auto& videoStreamFactoryRef = videoStreamFactory.ref();
+ if (videoStreamFactoryRef->ShouldDropFrame(aFrame)) {
+ return kMediaConduitNoError;
+ }
+ }
+
+ // If we have zero width or height, drop the frame here. Attempting to send
+ // it will cause all sorts of problems in the webrtc.org code.
+ if (aFrame.width() == 0 || aFrame.height() == 0) {
+ return kMediaConduitNoError;
+ }
+
+ rtc::scoped_refptr<webrtc::VideoFrameBuffer> buffer =
+ aFrame.video_frame_buffer();
+
+ MOZ_ASSERT(!aFrame.color_space(), "Unexpected use of color space");
+ MOZ_ASSERT(!aFrame.has_update_rect(), "Unexpected use of update rect");
+
+#ifdef MOZ_REAL_TIME_TRACING
+ if (profiler_is_active()) {
+ MutexAutoLock lock(mMutex);
+ nsAutoCStringN<256> ssrcsCommaSeparated;
+ bool first = true;
+ for (auto ssrc : mSendStreamConfig.rtp.ssrcs) {
+ if (!first) {
+ ssrcsCommaSeparated.AppendASCII(", ");
+ } else {
+ first = false;
+ }
+ ssrcsCommaSeparated.AppendInt(ssrc);
+ }
+ // The first frame has a delta of zero.
+ uint64_t timestampDelta =
+ mLastTimestampSendUs.isSome()
+ ? aFrame.timestamp_us() - mLastTimestampSendUs.value()
+ : 0;
+ mLastTimestampSendUs = Some(aFrame.timestamp_us());
+ TRACE_COMMENT("VideoConduit::SendVideoFrame", "t-delta=%.1fms, ssrcs=%s",
+ timestampDelta / 1000.f, ssrcsCommaSeparated.get());
+ }
+#endif
+
+ mVideoBroadcaster.OnFrame(aFrame);
+
+ return kMediaConduitNoError;
+}
+
+// Transport Layer Callbacks
+
+void WebrtcVideoConduit::DeliverPacket(rtc::CopyOnWriteBuffer packet,
+ PacketType type) {
+ // Currently unused.
+ MOZ_ASSERT(false);
+}
+
+void WebrtcVideoConduit::OnRtpReceived(webrtc::RtpPacketReceived&& aPacket,
+ webrtc::RTPHeader&& aHeader) {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+
+ mRemoteSendSSRC = aHeader.ssrc;
+
+ if (mAllowSsrcChange || mRecvStreamConfig.rtp.remote_ssrc == 0) {
+ bool switchRequired = mRecvStreamConfig.rtp.remote_ssrc != aHeader.ssrc;
+ if (switchRequired) {
+ // Handle the unknown ssrc (and ssrc-not-signaled case).
+
+ // We need to check that the newly received ssrc is not already
+ // associated with ulpfec or rtx. This is how webrtc.org handles
+ // things, see https://codereview.webrtc.org/1226093002.
+ const webrtc::VideoReceiveStreamInterface::Config::Rtp& rtp =
+ mRecvStreamConfig.rtp;
+ switchRequired =
+ rtp.rtx_associated_payload_types.find(aHeader.payloadType) ==
+ rtp.rtx_associated_payload_types.end() &&
+ rtp.ulpfec_payload_type != aHeader.payloadType;
+ }
+
+ if (switchRequired) {
+ CSFLogInfo(LOGTAG, "VideoConduit %p: Switching remote SSRC from %u to %u",
+ this, mRecvStreamConfig.rtp.remote_ssrc, aHeader.ssrc);
+ SetRemoteSSRCAndRestartAsNeeded(aHeader.ssrc, 0);
+ }
+ }
+
+ CSFLogVerbose(LOGTAG, "%s: seq# %u, Len %zu, SSRC %u (0x%x) ", __FUNCTION__,
+ aPacket.SequenceNumber(), aPacket.size(), aPacket.Ssrc(),
+ aPacket.Ssrc());
+
+ mRtpPacketEvent.Notify();
+ if (mCall->Call()) {
+ mCall->Call()->Receiver()->DeliverRtpPacket(
+ webrtc::MediaType::VIDEO, std::move(aPacket),
+ [self = RefPtr<WebrtcVideoConduit>(this)](
+ const webrtc::RtpPacketReceived& packet) {
+ CSFLogVerbose(
+ LOGTAG,
+ "VideoConduit %p: failed demuxing packet, ssrc: %u seq: %u",
+ self.get(), packet.Ssrc(), packet.SequenceNumber());
+ return false;
+ });
+ }
+}
+
+void WebrtcVideoConduit::OnRtcpReceived(MediaPacket&& aPacket) {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+
+ CSFLogVerbose(LOGTAG, "VideoConduit %p: Received RTCP Packet, len %zu ", this,
+ aPacket.len());
+
+ if (mCall->Call()) {
+ mCall->Call()->Receiver()->DeliverRtcpPacket(
+ rtc::CopyOnWriteBuffer(aPacket.data(), aPacket.len()));
+ }
+}
+
+Maybe<uint16_t> WebrtcVideoConduit::RtpSendBaseSeqFor(uint32_t aSsrc) const {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ auto it = mRtpSendBaseSeqs.find(aSsrc);
+ if (it == mRtpSendBaseSeqs.end()) {
+ return Nothing();
+ }
+ return Some(it->second);
+}
+
+const dom::RTCStatsTimestampMaker& WebrtcVideoConduit::GetTimestampMaker()
+ const {
+ return mCall->GetTimestampMaker();
+}
+
+void WebrtcVideoConduit::StopTransmitting() {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ mMutex.AssertNotCurrentThreadOwns();
+
+ if (!mEngineTransmitting) {
+ return;
+ }
+
+ if (mSendStream) {
+ CSFLogDebug(LOGTAG, "%s Stopping send stream", __FUNCTION__);
+ mSendStream->Stop();
+ }
+
+ mEngineTransmitting = false;
+}
+
+void WebrtcVideoConduit::StartTransmitting() {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ MOZ_ASSERT(mSendStream);
+ mMutex.AssertNotCurrentThreadOwns();
+
+ if (mEngineTransmitting) {
+ return;
+ }
+
+ CSFLogDebug(LOGTAG, "%s Starting send stream", __FUNCTION__);
+
+ mSendStream->Start();
+ // XXX File a bug to consider hooking this up to the state of mtransport
+ mCall->Call()->SignalChannelNetworkState(webrtc::MediaType::VIDEO,
+ webrtc::kNetworkUp);
+ mEngineTransmitting = true;
+}
+
+void WebrtcVideoConduit::StopReceiving() {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ mMutex.AssertNotCurrentThreadOwns();
+
+ // Are we receiving already? If so, stop receiving and playout
+ // since we can't apply new recv codec when the engine is playing.
+ if (!mEngineReceiving) {
+ return;
+ }
+
+ if (mRecvStream) {
+ CSFLogDebug(LOGTAG, "%s Stopping receive stream", __FUNCTION__);
+ mRecvStream->Stop();
+ }
+
+ mEngineReceiving = false;
+}
+
+void WebrtcVideoConduit::StartReceiving() {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ MOZ_ASSERT(mRecvStream);
+ mMutex.AssertNotCurrentThreadOwns();
+
+ if (mEngineReceiving) {
+ return;
+ }
+
+ CSFLogDebug(LOGTAG, "%s Starting receive stream (SSRC %u (0x%x))",
+ __FUNCTION__, mRecvStreamConfig.rtp.remote_ssrc,
+ mRecvStreamConfig.rtp.remote_ssrc);
+ // Start Receiving on the video engine
+ mRecvStream->Start();
+
+ // XXX File a bug to consider hooking this up to the state of mtransport
+ mCall->Call()->SignalChannelNetworkState(webrtc::MediaType::VIDEO,
+ webrtc::kNetworkUp);
+ mEngineReceiving = true;
+}
+
+bool WebrtcVideoConduit::SendRtp(const uint8_t* aData, size_t aLength,
+ const webrtc::PacketOptions& aOptions) {
+ MOZ_ASSERT(aLength >= 12);
+ const uint16_t seqno = ntohs(*((uint16_t*)&aData[2]));
+ const uint32_t ssrc = ntohl(*((uint32_t*)&aData[8]));
+
+ CSFLogVerbose(
+ LOGTAG,
+ "VideoConduit %p: Sending RTP Packet seq# %u, len %zu, SSRC %u (0x%x)",
+ this, seqno, aLength, ssrc, ssrc);
+
+ if (!mTransportActive) {
+ CSFLogError(LOGTAG, "VideoConduit %p: RTP Packet Send Failed", this);
+ return false;
+ }
+
+ MediaPacket packet;
+ packet.Copy(aData, aLength, aLength + SRTP_MAX_EXPANSION);
+ packet.SetType(MediaPacket::RTP);
+ mSenderRtpSendEvent.Notify(std::move(packet));
+
+ // Parse the sequence number of the first rtp packet as base_seq.
+ const auto inserted = mRtpSendBaseSeqs_n.insert({ssrc, seqno}).second;
+
+ if (inserted || aOptions.packet_id >= 0) {
+ int64_t now_ms = PR_Now() / 1000;
+ MOZ_ALWAYS_SUCCEEDS(mCallThread->Dispatch(NS_NewRunnableFunction(
+ __func__, [this, self = RefPtr<WebrtcVideoConduit>(this),
+ packet_id = aOptions.packet_id, now_ms, ssrc, seqno] {
+ mRtpSendBaseSeqs.insert({ssrc, seqno});
+ if (packet_id >= 0) {
+ if (mCall->Call()) {
+ // TODO: This notification should ideally happen after the
+ // transport layer has sent the packet on the wire.
+ mCall->Call()->OnSentPacket({packet_id, now_ms});
+ }
+ }
+ })));
+ }
+ return true;
+}
+
+bool WebrtcVideoConduit::SendSenderRtcp(const uint8_t* aData, size_t aLength) {
+ CSFLogVerbose(
+ LOGTAG,
+ "VideoConduit %p: Sending RTCP SR Packet, len %zu, SSRC %u (0x%x)", this,
+ aLength, (uint32_t)ntohl(*((uint32_t*)&aData[4])),
+ (uint32_t)ntohl(*((uint32_t*)&aData[4])));
+
+ if (!mTransportActive) {
+ CSFLogError(LOGTAG, "VideoConduit %p: RTCP SR Packet Send Failed", this);
+ return false;
+ }
+
+ MediaPacket packet;
+ packet.Copy(aData, aLength, aLength + SRTP_MAX_EXPANSION);
+ packet.SetType(MediaPacket::RTCP);
+ mSenderRtcpSendEvent.Notify(std::move(packet));
+ return true;
+}
+
+bool WebrtcVideoConduit::SendReceiverRtcp(const uint8_t* aData,
+ size_t aLength) {
+ CSFLogVerbose(
+ LOGTAG,
+ "VideoConduit %p: Sending RTCP RR Packet, len %zu, SSRC %u (0x%x)", this,
+ aLength, (uint32_t)ntohl(*((uint32_t*)&aData[4])),
+ (uint32_t)ntohl(*((uint32_t*)&aData[4])));
+
+ if (!mTransportActive) {
+ CSFLogError(LOGTAG, "VideoConduit %p: RTCP RR Packet Send Failed", this);
+ return false;
+ }
+
+ MediaPacket packet;
+ packet.Copy(aData, aLength, aLength + SRTP_MAX_EXPANSION);
+ packet.SetType(MediaPacket::RTCP);
+ mReceiverRtcpSendEvent.Notify(std::move(packet));
+ return true;
+}
+
+void WebrtcVideoConduit::OnFrame(const webrtc::VideoFrame& video_frame) {
+ const uint32_t localRecvSsrc = mRecvSSRC;
+ const uint32_t remoteSendSsrc = mRemoteSendSSRC;
+
+ CSFLogVerbose(
+ LOGTAG,
+ "VideoConduit %p: Rendering frame, Remote SSRC %u (0x%x), size %ux%u",
+ this, static_cast<uint32_t>(remoteSendSsrc),
+ static_cast<uint32_t>(remoteSendSsrc), video_frame.width(),
+ video_frame.height());
+ ReentrantMonitorAutoEnter enter(mRendererMonitor);
+
+ if (!mRenderer) {
+ CSFLogError(LOGTAG, "VideoConduit %p: Cannot render frame, no renderer",
+ this);
+ return;
+ }
+
+ bool needsNewHistoryElement = mReceivedFrameHistory.mEntries.IsEmpty();
+
+ if (mReceivingWidth != video_frame.width() ||
+ mReceivingHeight != video_frame.height()) {
+ mReceivingWidth = video_frame.width();
+ mReceivingHeight = video_frame.height();
+ mRenderer->FrameSizeChange(mReceivingWidth, mReceivingHeight);
+ needsNewHistoryElement = true;
+ }
+
+ if (!needsNewHistoryElement) {
+ auto& currentEntry = mReceivedFrameHistory.mEntries.LastElement();
+ needsNewHistoryElement =
+ currentEntry.mRotationAngle !=
+ static_cast<unsigned long>(video_frame.rotation()) ||
+ currentEntry.mLocalSsrc != localRecvSsrc ||
+ currentEntry.mRemoteSsrc != remoteSendSsrc;
+ }
+
+ // Record frame history
+ const auto historyNow = mCall->GetTimestampMaker().GetNow().ToDom();
+ if (needsNewHistoryElement) {
+ dom::RTCVideoFrameHistoryEntryInternal frameHistoryElement;
+ frameHistoryElement.mConsecutiveFrames = 0;
+ frameHistoryElement.mWidth = video_frame.width();
+ frameHistoryElement.mHeight = video_frame.height();
+ frameHistoryElement.mRotationAngle =
+ static_cast<unsigned long>(video_frame.rotation());
+ frameHistoryElement.mFirstFrameTimestamp = historyNow;
+ frameHistoryElement.mLocalSsrc = localRecvSsrc;
+ frameHistoryElement.mRemoteSsrc = remoteSendSsrc;
+ if (!mReceivedFrameHistory.mEntries.AppendElement(frameHistoryElement,
+ fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ }
+ auto& currentEntry = mReceivedFrameHistory.mEntries.LastElement();
+
+ currentEntry.mConsecutiveFrames++;
+ currentEntry.mLastFrameTimestamp = historyNow;
+ // Attempt to retrieve an timestamp encoded in the image pixels if enabled.
+ if (mVideoLatencyTestEnable && mReceivingWidth && mReceivingHeight) {
+ uint64_t now = PR_Now();
+ uint64_t timestamp = 0;
+ uint8_t* data = const_cast<uint8_t*>(
+ video_frame.video_frame_buffer()->GetI420()->DataY());
+ bool ok = YuvStamper::Decode(
+ mReceivingWidth, mReceivingHeight, mReceivingWidth, data,
+ reinterpret_cast<unsigned char*>(&timestamp), sizeof(timestamp), 0, 0);
+ if (ok) {
+ VideoLatencyUpdate(now - timestamp);
+ }
+ }
+#ifdef MOZ_REAL_TIME_TRACING
+ if (profiler_is_active()) {
+ MutexAutoLock lock(mMutex);
+ // The first frame has a delta of zero.
+ uint32_t rtpTimestamp = video_frame.timestamp();
+ uint32_t timestampDelta =
+ mLastRTPTimestampReceive.isSome()
+ ? rtpTimestamp - mLastRTPTimestampReceive.value()
+ : 0;
+ mLastRTPTimestampReceive = Some(rtpTimestamp);
+ TRACE_COMMENT("VideoConduit::OnFrame", "t-delta=%.1fms, ssrc=%u",
+ timestampDelta * 1000.f / webrtc::kVideoPayloadTypeFrequency,
+ localRecvSsrc);
+ }
+#endif
+
+ mRenderer->RenderVideoFrame(*video_frame.video_frame_buffer(),
+ video_frame.timestamp(),
+ video_frame.render_time_ms());
+}
+
+bool WebrtcVideoConduit::AddFrameHistory(
+ dom::Sequence<dom::RTCVideoFrameHistoryInternal>* outHistories) const {
+ ReentrantMonitorAutoEnter enter(mRendererMonitor);
+ if (!outHistories->AppendElement(mReceivedFrameHistory, fallible)) {
+ mozalloc_handle_oom(0);
+ return false;
+ }
+ return true;
+}
+
+void WebrtcVideoConduit::SetJitterBufferTarget(DOMHighResTimeStamp aTargetMs) {
+ MOZ_RELEASE_ASSERT(aTargetMs <= std::numeric_limits<uint16_t>::max());
+ MOZ_RELEASE_ASSERT(aTargetMs >= 0);
+
+ MOZ_ALWAYS_SUCCEEDS(mCallThread->Dispatch(NS_NewRunnableFunction(
+ __func__,
+ [this, self = RefPtr<WebrtcVideoConduit>(this), targetMs = aTargetMs] {
+ mJitterBufferTargetMs = static_cast<uint16_t>(targetMs);
+ if (mRecvStream) {
+ mRecvStream->SetBaseMinimumPlayoutDelayMs(targetMs);
+ }
+ })));
+}
+
+void WebrtcVideoConduit::DumpCodecDB() const {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+
+ for (const auto& entry : mControl.mConfiguredRecvCodecs) {
+ CSFLogDebug(LOGTAG, "Payload Name: %s", entry.mName.c_str());
+ CSFLogDebug(LOGTAG, "Payload Type: %d", entry.mType);
+ CSFLogDebug(LOGTAG, "Payload Max Frame Size: %d",
+ entry.mEncodingConstraints.maxFs);
+ if (entry.mEncodingConstraints.maxFps.isSome()) {
+ CSFLogDebug(LOGTAG, "Payload Max Frame Rate: %f",
+ *entry.mEncodingConstraints.maxFps);
+ }
+ }
+}
+
+void WebrtcVideoConduit::VideoLatencyUpdate(uint64_t aNewSample) {
+ mRendererMonitor.AssertCurrentThreadIn();
+
+ mVideoLatencyAvg =
+ (sRoundingPadding * aNewSample + sAlphaNum * mVideoLatencyAvg) /
+ sAlphaDen;
+}
+
+uint64_t WebrtcVideoConduit::MozVideoLatencyAvg() {
+ mRendererMonitor.AssertCurrentThreadIn();
+
+ return mVideoLatencyAvg / sRoundingPadding;
+}
+
+void WebrtcVideoConduit::CollectTelemetryData() {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+
+ if (mEngineTransmitting) {
+ webrtc::VideoSendStream::Stats stats = mSendStream->GetStats();
+ mSendBitrate.Push(stats.media_bitrate_bps);
+ mSendFramerate.Push(stats.encode_frame_rate);
+ }
+ if (mEngineReceiving) {
+ webrtc::VideoReceiveStreamInterface::Stats stats = mRecvStream->GetStats();
+ mRecvBitrate.Push(stats.total_bitrate_bps);
+ mRecvFramerate.Push(stats.decode_frame_rate);
+ }
+}
+
+void WebrtcVideoConduit::OnRtcpBye() { mRtcpByeEvent.Notify(); }
+
+void WebrtcVideoConduit::OnRtcpTimeout() { mRtcpTimeoutEvent.Notify(); }
+
+void WebrtcVideoConduit::SetTransportActive(bool aActive) {
+ MOZ_ASSERT(mStsThread->IsOnCurrentThread());
+ if (mTransportActive == aActive) {
+ return;
+ }
+
+ // If false, This stops us from sending
+ mTransportActive = aActive;
+
+ // We queue this because there might be notifications to these listeners
+ // pending, and we don't want to drop them by letting this jump ahead of
+ // those notifications. We move the listeners into the lambda in case the
+ // transport comes back up before we disconnect them. (The Connect calls
+ // happen in MediaPipeline)
+ // We retain a strong reference to ourself, because the listeners are holding
+ // a non-refcounted reference to us, and moving them into the lambda could
+ // conceivably allow them to outlive us.
+ if (!aActive) {
+ MOZ_ALWAYS_SUCCEEDS(mCallThread->Dispatch(NS_NewRunnableFunction(
+ __func__,
+ [self = RefPtr<WebrtcVideoConduit>(this),
+ recvRtpListener = std::move(mReceiverRtpEventListener),
+ recvRtcpListener = std::move(mReceiverRtcpEventListener),
+ sendRtcpListener = std::move(mSenderRtcpEventListener)]() mutable {
+ recvRtpListener.DisconnectIfExists();
+ recvRtcpListener.DisconnectIfExists();
+ sendRtcpListener.DisconnectIfExists();
+ })));
+ }
+}
+
+std::vector<webrtc::RtpSource> WebrtcVideoConduit::GetUpstreamRtpSources()
+ const {
+ MOZ_ASSERT(NS_IsMainThread());
+ std::vector<webrtc::RtpSource> sources;
+ {
+ MutexAutoLock lock(mMutex);
+ if (mRecvStream) {
+ sources = mRecvStream->GetSources();
+ }
+ }
+ return sources;
+}
+
+bool WebrtcVideoConduit::HasCodecPluginID(uint64_t aPluginID) const {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return mSendCodecPluginIDs.Contains(aPluginID) ||
+ mRecvCodecPluginIDs.Contains(aPluginID);
+}
+
+bool WebrtcVideoConduit::HasH264Hardware() {
+ nsCOMPtr<nsIGfxInfo> gfxInfo = do_GetService("@mozilla.org/gfx/info;1");
+ if (!gfxInfo) {
+ return false;
+ }
+ int32_t status;
+ nsCString discardFailureId;
+ return NS_SUCCEEDED(gfxInfo->GetFeatureStatus(
+ nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION_H264, discardFailureId,
+ &status)) &&
+ status == nsIGfxInfo::FEATURE_STATUS_OK;
+}
+
+Maybe<int> WebrtcVideoConduit::ActiveSendPayloadType() const {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+
+ if (!mSendStream) {
+ return Nothing();
+ }
+
+ if (mSendStreamConfig.rtp.payload_type == -1) {
+ return Nothing();
+ }
+
+ return Some(mSendStreamConfig.rtp.payload_type);
+}
+
+Maybe<int> WebrtcVideoConduit::ActiveRecvPayloadType() const {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+
+ auto stats = GetReceiverStats();
+ if (!stats) {
+ return Nothing();
+ }
+
+ if (stats->current_payload_type == -1) {
+ return Nothing();
+ }
+
+ return Some(stats->current_payload_type);
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/libwebrtcglue/VideoConduit.h b/dom/media/webrtc/libwebrtcglue/VideoConduit.h
new file mode 100644
index 0000000000..f46f1f962c
--- /dev/null
+++ b/dom/media/webrtc/libwebrtcglue/VideoConduit.h
@@ -0,0 +1,494 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef VIDEO_SESSION_H_
+#define VIDEO_SESSION_H_
+
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/DataMutex.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "mozilla/SharedThreadPool.h"
+#include "mozilla/StateMirroring.h"
+#include "mozilla/UniquePtr.h"
+#include "nsITimer.h"
+
+#include "MediaConduitInterface.h"
+#include "common/MediaEngineWrapper.h"
+#include "RtpRtcpConfig.h"
+#include "RunningStat.h"
+#include "transport/runnable_utils.h"
+
+// conflicts with #include of scoped_ptr.h
+#undef FF
+// Video Engine Includes
+#include "api/video_codecs/video_decoder.h"
+#include "api/video_codecs/video_encoder.h"
+#include "api/video_codecs/sdp_video_format.h"
+#include "call/call_basic_stats.h"
+#include "common_video/include/video_frame_buffer_pool.h"
+#include "media/base/video_broadcaster.h"
+#include <functional>
+#include <memory>
+/** This file hosts several structures identifying different aspects
+ * of a RTP Session.
+ */
+
+namespace mozilla {
+
+// Convert (SI) kilobits/sec to (SI) bits/sec
+#define KBPS(kbps) kbps * 1000
+
+const int kViEMinCodecBitrate_bps = KBPS(30);
+const unsigned int kVideoMtu = 1200;
+const int kQpMax = 56;
+
+template <typename T>
+T MinIgnoreZero(const T& a, const T& b) {
+ return std::min(a ? a : b, b ? b : a);
+}
+
+class VideoStreamFactory;
+class WebrtcAudioConduit;
+
+// Interface of external video encoder for WebRTC.
+class WebrtcVideoEncoder : public VideoEncoder, public webrtc::VideoEncoder {};
+
+// Interface of external video decoder for WebRTC.
+class WebrtcVideoDecoder : public VideoDecoder, public webrtc::VideoDecoder {};
+
+/**
+ * Concrete class for Video session. Hooks up
+ * - media-source and target to external transport
+ */
+class WebrtcVideoConduit
+ : public VideoSessionConduit,
+ public webrtc::RtcpEventObserver,
+ public rtc::VideoSinkInterface<webrtc::VideoFrame>,
+ public rtc::VideoSourceInterface<webrtc::VideoFrame> {
+ public:
+ // Returns true when both encoder and decoder are HW accelerated.
+ static bool HasH264Hardware();
+
+ Maybe<int> ActiveSendPayloadType() const override;
+ Maybe<int> ActiveRecvPayloadType() const override;
+
+ /**
+ * Function to attach Renderer end-point for the Media-Video conduit.
+ * @param aRenderer : Reference to the concrete mozilla Video renderer
+ * implementation Note: Multiple invocations of this API shall remove an
+ * existing renderer and attaches the new to the Conduit.
+ */
+ MediaConduitErrorCode AttachRenderer(
+ RefPtr<mozilla::VideoRenderer> aVideoRenderer) override;
+ void DetachRenderer() override;
+
+ Maybe<uint16_t> RtpSendBaseSeqFor(uint32_t aSsrc) const override;
+
+ const dom::RTCStatsTimestampMaker& GetTimestampMaker() const override;
+
+ void StopTransmitting();
+ void StartTransmitting();
+ void StopReceiving();
+ void StartReceiving();
+
+ /**
+ * Function to deliver a capture video frame for encoding and transport.
+ * If the frame's timestamp is 0, it will be automatically generated.
+ *
+ * NOTE: ConfigureSendMediaCodec() must be called before this function can
+ * be invoked. This ensures the inserted video-frames can be
+ * transmitted by the conduit.
+ */
+ MediaConduitErrorCode SendVideoFrame(webrtc::VideoFrame aFrame) override;
+
+ bool SendRtp(const uint8_t* aData, size_t aLength,
+ const webrtc::PacketOptions& aOptions) override;
+ bool SendSenderRtcp(const uint8_t* aData, size_t aLength) override;
+ bool SendReceiverRtcp(const uint8_t* aData, size_t aLength) override;
+
+ /*
+ * webrtc:VideoSinkInterface implementation
+ * -------------------------------
+ */
+ void OnFrame(const webrtc::VideoFrame& frame) override;
+
+ /*
+ * webrtc:VideoSourceInterface implementation
+ * -------------------------------
+ */
+ void AddOrUpdateSink(rtc::VideoSinkInterface<webrtc::VideoFrame>* sink,
+ const rtc::VideoSinkWants& wants) override;
+ void RemoveSink(rtc::VideoSinkInterface<webrtc::VideoFrame>* sink) override;
+
+ bool HasCodecPluginID(uint64_t aPluginID) const override;
+
+ RefPtr<GenericPromise> Shutdown() override;
+
+ bool Denoising() const { return mDenoising; }
+
+ uint8_t SpatialLayers() const { return mSpatialLayers; }
+
+ uint8_t TemporalLayers() const { return mTemporalLayers; }
+
+ webrtc::VideoCodecMode CodecMode() const;
+
+ WebrtcVideoConduit(RefPtr<WebrtcCallWrapper> aCall,
+ nsCOMPtr<nsISerialEventTarget> aStsThread,
+ Options aOptions, std::string aPCHandle,
+ const TrackingId& aRecvTrackingId);
+ virtual ~WebrtcVideoConduit();
+
+ // Call thread.
+ void InitControl(VideoConduitControlInterface* aControl) override;
+
+ // Called when a parameter in mControl has changed. Call thread.
+ void OnControlConfigChange();
+
+ // Necessary Init steps on main thread.
+ MediaConduitErrorCode Init();
+
+ Ssrcs GetLocalSSRCs() const override;
+ Maybe<Ssrc> GetAssociatedLocalRtxSSRC(Ssrc aSsrc) const override;
+ Maybe<Ssrc> GetRemoteSSRC() const override;
+
+ // Call thread.
+ void UnsetRemoteSSRC(uint32_t aSsrc) override;
+
+ static unsigned ToLibwebrtcMaxFramerate(const Maybe<double>& aMaxFramerate);
+
+ private:
+ void NotifyUnsetCurrentRemoteSSRC();
+ void SetRemoteSSRCConfig(uint32_t aSsrc, uint32_t aRtxSsrc);
+ void SetRemoteSSRCAndRestartAsNeeded(uint32_t aSsrc, uint32_t aRtxSsrc);
+ rtc::RefCountedObject<mozilla::VideoStreamFactory>*
+ CreateVideoStreamFactory();
+
+ public:
+ // Creating a recv stream or a send stream requires a local ssrc to be
+ // configured. This method will generate one if needed.
+ void EnsureLocalSSRC();
+ // Creating a recv stream requires a remote ssrc to be configured. This method
+ // will generate one if needed.
+ void EnsureRemoteSSRC();
+
+ Maybe<webrtc::VideoReceiveStreamInterface::Stats> GetReceiverStats()
+ const override;
+ Maybe<webrtc::VideoSendStream::Stats> GetSenderStats() const override;
+ Maybe<webrtc::CallBasicStats> GetCallStats() const override;
+
+ bool AddFrameHistory(dom::Sequence<dom::RTCVideoFrameHistoryInternal>*
+ outHistories) const override;
+
+ void SetJitterBufferTarget(DOMHighResTimeStamp aTargetMs) override;
+
+ uint64_t MozVideoLatencyAvg();
+
+ void DisableSsrcChanges() override {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ mAllowSsrcChange = false;
+ }
+
+ void CollectTelemetryData() override;
+
+ void OnRtpReceived(webrtc::RtpPacketReceived&& aPacket,
+ webrtc::RTPHeader&& aHeader);
+ void OnRtcpReceived(MediaPacket&& aPacket);
+
+ void OnRtcpBye() override;
+ void OnRtcpTimeout() override;
+
+ void SetTransportActive(bool aActive) override;
+
+ MediaEventSourceExc<MediaPacket>& SenderRtpSendEvent() override {
+ return mSenderRtpSendEvent;
+ }
+ MediaEventSourceExc<MediaPacket>& SenderRtcpSendEvent() override {
+ return mSenderRtcpSendEvent;
+ }
+ MediaEventSourceExc<MediaPacket>& ReceiverRtcpSendEvent() override {
+ return mReceiverRtcpSendEvent;
+ }
+ void ConnectReceiverRtpEvent(
+ MediaEventSourceExc<webrtc::RtpPacketReceived, webrtc::RTPHeader>& aEvent)
+ override {
+ mReceiverRtpEventListener =
+ aEvent.Connect(mCallThread, this, &WebrtcVideoConduit::OnRtpReceived);
+ }
+ void ConnectReceiverRtcpEvent(
+ MediaEventSourceExc<MediaPacket>& aEvent) override {
+ mReceiverRtcpEventListener =
+ aEvent.Connect(mCallThread, this, &WebrtcVideoConduit::OnRtcpReceived);
+ }
+ void ConnectSenderRtcpEvent(
+ MediaEventSourceExc<MediaPacket>& aEvent) override {
+ mSenderRtcpEventListener =
+ aEvent.Connect(mCallThread, this, &WebrtcVideoConduit::OnRtcpReceived);
+ }
+
+ std::vector<webrtc::RtpSource> GetUpstreamRtpSources() const override;
+
+ private:
+ // Don't allow copying/assigning.
+ WebrtcVideoConduit(const WebrtcVideoConduit&) = delete;
+ void operator=(const WebrtcVideoConduit&) = delete;
+
+ // Utility function to dump recv codec database
+ void DumpCodecDB() const;
+
+ // Video Latency Test averaging filter
+ void VideoLatencyUpdate(uint64_t aNewSample);
+
+ void CreateSendStream();
+ void DeleteSendStream();
+ void CreateRecvStream();
+ void DeleteRecvStream();
+
+ void DeliverPacket(rtc::CopyOnWriteBuffer packet, PacketType type) override;
+
+ MediaEventSource<void>& RtcpByeEvent() override { return mRtcpByeEvent; }
+ MediaEventSource<void>& RtcpTimeoutEvent() override {
+ return mRtcpTimeoutEvent;
+ }
+ MediaEventSource<void>& RtpPacketEvent() override { return mRtpPacketEvent; }
+
+ bool RequiresNewSendStream(const VideoCodecConfig& newConfig) const;
+
+ mutable mozilla::ReentrantMonitor mRendererMonitor MOZ_UNANNOTATED;
+
+ // Accessed on any thread under mRendererMonitor.
+ RefPtr<mozilla::VideoRenderer> mRenderer;
+
+ // Accessed on any thread under mRendererMonitor.
+ unsigned short mReceivingWidth = 0;
+
+ // Accessed on any thread under mRendererMonitor.
+ unsigned short mReceivingHeight = 0;
+
+ // Call worker thread. All access to mCall->Call() happens here.
+ const nsCOMPtr<nsISerialEventTarget> mCallThread;
+
+ // Socket transport service thread that runs stats queries against us. Any
+ // thread.
+ const nsCOMPtr<nsISerialEventTarget> mStsThread;
+
+ // Thread on which we are fed video frames. Set lazily on first call to
+ // SendVideoFrame().
+ nsCOMPtr<nsISerialEventTarget> mFrameSendingThread;
+
+ struct Control {
+ // Mirrors that map to VideoConduitControlInterface for control. Call thread
+ // only.
+ Mirror<bool> mReceiving;
+ Mirror<bool> mTransmitting;
+ Mirror<Ssrcs> mLocalSsrcs;
+ Mirror<Ssrcs> mLocalRtxSsrcs;
+ Mirror<std::string> mLocalCname;
+ Mirror<std::string> mMid;
+ Mirror<Ssrc> mRemoteSsrc;
+ Mirror<Ssrc> mRemoteRtxSsrc;
+ Mirror<std::string> mSyncGroup;
+ Mirror<RtpExtList> mLocalRecvRtpExtensions;
+ Mirror<RtpExtList> mLocalSendRtpExtensions;
+ Mirror<Maybe<VideoCodecConfig>> mSendCodec;
+ Mirror<Maybe<RtpRtcpConfig>> mSendRtpRtcpConfig;
+ Mirror<std::vector<VideoCodecConfig>> mRecvCodecs;
+ Mirror<Maybe<RtpRtcpConfig>> mRecvRtpRtcpConfig;
+ Mirror<webrtc::VideoCodecMode> mCodecMode;
+
+ // For caching mRemoteSsrc and mRemoteRtxSsrc, since another caller may
+ // change the remote ssrc in the stream config directly.
+ Ssrc mConfiguredRemoteSsrc = 0;
+ Ssrc mConfiguredRemoteRtxSsrc = 0;
+ // For tracking changes to mSendCodec and mSendRtpRtcpConfig.
+ Maybe<VideoCodecConfig> mConfiguredSendCodec;
+ Maybe<RtpRtcpConfig> mConfiguredSendRtpRtcpConfig;
+ // For tracking changes to mRecvCodecs and mRecvRtpRtcpConfig.
+ std::vector<VideoCodecConfig> mConfiguredRecvCodecs;
+ Maybe<RtpRtcpConfig> mConfiguredRecvRtpRtcpConfig;
+
+ Control() = delete;
+ explicit Control(const RefPtr<AbstractThread>& aCallThread);
+ } mControl;
+
+ // WatchManager allowing Mirrors and other watch targets to trigger functions
+ // that will update the webrtc.org configuration.
+ WatchManager<WebrtcVideoConduit> mWatchManager;
+
+ mutable Mutex mMutex MOZ_UNANNOTATED;
+
+ // Decoder factory used by mRecvStream when it needs new decoders. This is
+ // not shared broader like some state in the WebrtcCallWrapper because it
+ // handles CodecPluginID plumbing tied to this VideoConduit.
+ const UniquePtr<WebrtcVideoDecoderFactory> mDecoderFactory;
+
+ // Encoder factory used by mSendStream when it needs new encoders. This is
+ // not shared broader like some state in the WebrtcCallWrapper because it
+ // handles CodecPluginID plumbing tied to this VideoConduit.
+ const UniquePtr<WebrtcVideoEncoderFactory> mEncoderFactory;
+
+ // Our own record of the sinks added to mVideoBroadcaster so we can support
+ // dispatching updates to sinks from off-Call-thread. Call thread only.
+ AutoTArray<rtc::VideoSinkInterface<webrtc::VideoFrame>*, 1> mRegisteredSinks;
+
+ // Broadcaster that distributes our frames to all registered sinks.
+ // Threadsafe.
+ rtc::VideoBroadcaster mVideoBroadcaster;
+
+ // Buffer pool used for scaling frames.
+ // Accessed on the frame-feeding thread only.
+ webrtc::VideoFrameBufferPool mBufferPool;
+
+ // Engine state we are concerned with. Written on the Call thread and read
+ // anywhere.
+ mozilla::Atomic<bool>
+ mEngineTransmitting; // If true ==> Transmit Subsystem is up and running
+ mozilla::Atomic<bool>
+ mEngineReceiving; // if true ==> Receive Subsystem up and running
+
+ // Written only on the Call thread. Guarded by mMutex, except for reads on the
+ // Call thread.
+ Maybe<VideoCodecConfig> mCurSendCodecConfig;
+
+ // Bookkeeping of stats for telemetry. Call thread only.
+ RunningStat mSendFramerate;
+ RunningStat mSendBitrate;
+ RunningStat mRecvFramerate;
+ RunningStat mRecvBitrate;
+
+ // Must call webrtc::Call::DestroyVideoReceive/SendStream to delete this.
+ // Written only on the Call thread. Guarded by mMutex, except for reads on the
+ // Call thread.
+ webrtc::VideoReceiveStreamInterface* mRecvStream = nullptr;
+
+ // Must call webrtc::Call::DestroyVideoReceive/SendStream to delete this.
+ webrtc::VideoSendStream* mSendStream = nullptr;
+
+ // Written on the frame feeding thread.
+ // Guarded by mMutex, except for reads on the frame feeding thread.
+ unsigned short mLastWidth = 0;
+
+ // Written on the frame feeding thread.
+ // Guarded by mMutex, except for reads on the frame feeding thread.
+ unsigned short mLastHeight = 0;
+
+ // Written on the frame feeding thread, the timestamp of the last frame on the
+ // send side, in microseconds. This is a local timestamp using the system
+ // clock with a unspecified epoch (Like mozilla::TimeStamp).
+ // Guarded by mMutex.
+ Maybe<uint64_t> mLastTimestampSendUs;
+
+ // Written on the frame receive thread, the rtp timestamp of the last frame
+ // on the receive side, in 90kHz base. This comes from the RTP packet.
+ // Guarded by mMutex.
+ Maybe<uint32_t> mLastRTPTimestampReceive;
+
+ // Accessed from any thread under mRendererMonitor.
+ uint64_t mVideoLatencyAvg = 0;
+
+ const bool mVideoLatencyTestEnable;
+
+ // All in bps.
+ const int mMinBitrate;
+ const int mStartBitrate;
+ const int mPrefMaxBitrate;
+ const int mMinBitrateEstimate;
+
+ // Max bitrate in bps as provided by negotiation. Call thread only.
+ int mNegotiatedMaxBitrate = 0;
+
+ // Set to true to force denoising on.
+ const bool mDenoising;
+
+ // Set to true to ignore sink wants (scaling due to bwe and cpu usage).
+ const bool mLockScaling;
+
+ const uint8_t mSpatialLayers;
+ const uint8_t mTemporalLayers;
+
+ static const unsigned int sAlphaNum = 7;
+ static const unsigned int sAlphaDen = 8;
+ static const unsigned int sRoundingPadding = 1024;
+
+ // Target jitter buffer to be applied to the receive stream in milliseconds.
+ uint16_t mJitterBufferTargetMs = 0;
+
+ // WEBRTC.ORG Call API
+ // Const so can be accessed on any thread. All methods are called on the Call
+ // thread.
+ const RefPtr<WebrtcCallWrapper> mCall;
+
+ // Set up in the ctor and then not touched. Called through by the streams on
+ // any thread. Safe since we own and control the lifetime of the streams.
+ WebrtcSendTransport mSendTransport;
+ WebrtcReceiveTransport mRecvTransport;
+
+ // Written only on the Call thread. Guarded by mMutex, except for reads on the
+ // Call thread. Typical non-Call thread access is on the frame delivery
+ // thread.
+ webrtc::VideoSendStream::Config mSendStreamConfig;
+
+ // Call thread only.
+ webrtc::VideoEncoderConfig mEncoderConfig;
+
+ // Written only on the Call thread. Guarded by mMutex, except for reads on the
+ // Call thread. Calls can happen under mMutex on any thread.
+ DataMutex<RefPtr<rtc::RefCountedObject<VideoStreamFactory>>>
+ mVideoStreamFactory;
+
+ // Call thread only.
+ webrtc::VideoReceiveStreamInterface::Config mRecvStreamConfig;
+
+ // Are SSRC changes without signaling allowed or not.
+ // Call thread only.
+ bool mAllowSsrcChange = true;
+
+ // Accessed during configuration/signaling (Call thread), and on the frame
+ // delivery thread for frame history tracking. Set only on the Call thread.
+ Atomic<uint32_t> mRecvSSRC =
+ Atomic<uint32_t>(0); // this can change during a stream!
+
+ // Accessed from both the STS and frame delivery thread for frame history
+ // tracking. Set when receiving packets.
+ Atomic<uint32_t> mRemoteSendSSRC =
+ Atomic<uint32_t>(0); // this can change during a stream!
+
+ // Main thread only
+ nsTArray<uint64_t> mSendCodecPluginIDs;
+ // Main thread only
+ nsTArray<uint64_t> mRecvCodecPluginIDs;
+
+ // Main thread only
+ MediaEventListener mSendPluginCreated;
+ MediaEventListener mSendPluginReleased;
+ MediaEventListener mRecvPluginCreated;
+ MediaEventListener mRecvPluginReleased;
+
+ // Call thread only. ssrc -> base_seq
+ std::map<uint32_t, uint16_t> mRtpSendBaseSeqs;
+ // libwebrtc network thread only. ssrc -> base_seq.
+ // To track changes needed to mRtpSendBaseSeqs.
+ std::map<uint32_t, uint16_t> mRtpSendBaseSeqs_n;
+
+ // Tracking the attributes of received frames over time
+ // Protected by mRendererMonitor
+ dom::RTCVideoFrameHistoryInternal mReceivedFrameHistory;
+
+ // Thread safe
+ Atomic<bool> mTransportActive = Atomic<bool>(false);
+ MediaEventProducer<void> mRtcpByeEvent;
+ MediaEventProducer<void> mRtcpTimeoutEvent;
+ MediaEventProducer<void> mRtpPacketEvent;
+ MediaEventProducerExc<MediaPacket> mSenderRtpSendEvent;
+ MediaEventProducerExc<MediaPacket> mSenderRtcpSendEvent;
+ MediaEventProducerExc<MediaPacket> mReceiverRtcpSendEvent;
+
+ // Assigned and revoked on mStsThread. Listeners for receiving packets.
+ MediaEventListener mSenderRtcpEventListener; // Rtp-transmitting pipeline
+ MediaEventListener mReceiverRtcpEventListener; // Rtp-receiving pipeline
+ MediaEventListener mReceiverRtpEventListener; // Rtp-receiving pipeline
+};
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/libwebrtcglue/VideoStreamFactory.cpp b/dom/media/webrtc/libwebrtcglue/VideoStreamFactory.cpp
new file mode 100644
index 0000000000..9782f8760b
--- /dev/null
+++ b/dom/media/webrtc/libwebrtcglue/VideoStreamFactory.cpp
@@ -0,0 +1,387 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "VideoStreamFactory.h"
+
+#include "common/browser_logging/CSFLog.h"
+#include "nsThreadUtils.h"
+#include "VideoConduit.h"
+
+template <class t>
+void ConstrainPreservingAspectRatio(uint16_t aMaxWidth, uint16_t aMaxHeight,
+ t* aWidth, t* aHeight) {
+ if (((*aWidth) <= aMaxWidth) && ((*aHeight) <= aMaxHeight)) {
+ return;
+ }
+
+ if ((*aWidth) * aMaxHeight > aMaxWidth * (*aHeight)) {
+ (*aHeight) = aMaxWidth * (*aHeight) / (*aWidth);
+ (*aWidth) = aMaxWidth;
+ } else {
+ (*aWidth) = aMaxHeight * (*aWidth) / (*aHeight);
+ (*aHeight) = aMaxHeight;
+ }
+}
+
+namespace mozilla {
+
+#ifdef LOGTAG
+# undef LOGTAG
+#endif
+#define LOGTAG "WebrtcVideoSessionConduit"
+
+#define DEFAULT_VIDEO_MAX_FRAMERATE 30u
+
+#define MB_OF(w, h) \
+ ((unsigned int)((((w + 15) >> 4)) * ((unsigned int)((h + 15) >> 4))))
+// For now, try to set the max rates well above the knee in the curve.
+// Chosen somewhat arbitrarily; it's hard to find good data oriented for
+// realtime interactive/talking-head recording. These rates assume
+// 30fps.
+
+// XXX Populate this based on a pref (which we should consider sorting because
+// people won't assume they need to).
+static VideoStreamFactory::ResolutionAndBitrateLimits
+ kResolutionAndBitrateLimits[] = {
+ // clang-format off
+ {MB_OF(1920, 1200), KBPS(1500), KBPS(2000), KBPS(10000)}, // >HD (3K, 4K, etc)
+ {MB_OF(1280, 720), KBPS(1200), KBPS(1500), KBPS(5000)}, // HD ~1080-1200
+ {MB_OF(800, 480), KBPS(200), KBPS(800), KBPS(2500)}, // HD ~720
+ {MB_OF(480, 270), KBPS(150), KBPS(500), KBPS(2000)}, // WVGA
+ {tl::Max<MB_OF(400, 240), MB_OF(352, 288)>::value, KBPS(125), KBPS(300), KBPS(1300)}, // VGA
+ {MB_OF(176, 144), KBPS(100), KBPS(150), KBPS(500)}, // WQVGA, CIF
+ {0 , KBPS(40), KBPS(80), KBPS(250)} // QCIF and below
+ // clang-format on
+};
+
+auto VideoStreamFactory::GetLimitsFor(unsigned int aWidth, unsigned int aHeight,
+ int aCapBps /* = 0 */)
+ -> ResolutionAndBitrateLimits {
+ // max bandwidth should be proportional (not linearly!) to resolution, and
+ // proportional (perhaps linearly, or close) to current frame rate.
+ int fs = MB_OF(aWidth, aHeight);
+
+ for (const auto& resAndLimits : kResolutionAndBitrateLimits) {
+ if (fs > resAndLimits.resolution_in_mb &&
+ // pick the highest range where at least start rate is within cap
+ // (or if we're at the end of the array).
+ (aCapBps == 0 || resAndLimits.start_bitrate_bps <= aCapBps ||
+ resAndLimits.resolution_in_mb == 0)) {
+ return resAndLimits;
+ }
+ }
+
+ MOZ_CRASH("Loop should have handled fallback");
+}
+
+/**
+ * Function to set the encoding bitrate limits based on incoming frame size and
+ * rate
+ * @param width, height: dimensions of the frame
+ * @param min: minimum bitrate in bps
+ * @param start: bitrate in bps that the encoder should start with
+ * @param cap: user-enforced max bitrate, or 0
+ * @param pref_cap: cap enforced by prefs
+ * @param negotiated_cap: cap negotiated through SDP
+ * @param aVideoStream stream to apply bitrates to
+ */
+static void SelectBitrates(unsigned short width, unsigned short height, int min,
+ int start, int cap, int pref_cap, int negotiated_cap,
+ webrtc::VideoStream& aVideoStream) {
+ int& out_min = aVideoStream.min_bitrate_bps;
+ int& out_start = aVideoStream.target_bitrate_bps;
+ int& out_max = aVideoStream.max_bitrate_bps;
+
+ VideoStreamFactory::ResolutionAndBitrateLimits resAndLimits =
+ VideoStreamFactory::GetLimitsFor(width, height);
+ out_min = MinIgnoreZero(resAndLimits.min_bitrate_bps, cap);
+ out_start = MinIgnoreZero(resAndLimits.start_bitrate_bps, cap);
+ out_max = MinIgnoreZero(resAndLimits.max_bitrate_bps, cap);
+
+ // Note: negotiated_cap is the max transport bitrate - it applies to
+ // a single codec encoding, but should also apply to the sum of all
+ // simulcast layers in this encoding! So sum(layers.maxBitrate) <=
+ // negotiated_cap
+ // Note that out_max already has had pref_cap applied to it
+ out_max = MinIgnoreZero(negotiated_cap, out_max);
+ out_min = std::min(out_min, out_max);
+ out_start = std::min(out_start, out_max);
+
+ if (min && min > out_min) {
+ out_min = min;
+ }
+ // If we try to set a minimum bitrate that is too low, ViE will reject it.
+ out_min = std::max(kViEMinCodecBitrate_bps, out_min);
+ out_max = std::max(kViEMinCodecBitrate_bps, out_max);
+ if (start && start > out_start) {
+ out_start = start;
+ }
+
+ // Ensure that min <= start <= max
+ if (out_min > out_max) {
+ out_min = out_max;
+ }
+ out_start = std::min(out_max, std::max(out_start, out_min));
+
+ MOZ_ASSERT(pref_cap == 0 || out_max <= pref_cap);
+}
+
+std::vector<webrtc::VideoStream> VideoStreamFactory::CreateEncoderStreams(
+ int aWidth, int aHeight, const webrtc::VideoEncoderConfig& aConfig) {
+ // We only allow one layer when screensharing
+ const size_t streamCount =
+ mCodecMode == webrtc::VideoCodecMode::kScreensharing
+ ? 1
+ : aConfig.number_of_streams;
+
+ MOZ_RELEASE_ASSERT(streamCount >= 1, "Should request at least one stream");
+
+ std::vector<webrtc::VideoStream> streams;
+ streams.reserve(streamCount);
+
+ {
+ auto frameRateController = mFramerateController.Lock();
+ frameRateController->Reset();
+ }
+
+ for (int idx = streamCount - 1; idx >= 0; --idx) {
+ webrtc::VideoStream video_stream;
+ auto& encoding = mCodecConfig.mEncodings[idx];
+ video_stream.active = encoding.active;
+ MOZ_ASSERT(encoding.constraints.scaleDownBy >= 1.0);
+
+ gfx::IntSize newSize(0, 0);
+
+ if (aWidth && aHeight) {
+ auto maxPixelCount = mLockScaling ? 0U : mWants.max_pixel_count;
+ newSize = CalculateScaledResolution(
+ aWidth, aHeight, encoding.constraints.scaleDownBy, maxPixelCount);
+ }
+
+ if (newSize.width == 0 || newSize.height == 0) {
+ CSFLogInfo(LOGTAG,
+ "%s Stream with RID %s ignored because of no resolution.",
+ __FUNCTION__, encoding.rid.c_str());
+ continue;
+ }
+
+ uint16_t max_width = mCodecConfig.mEncodingConstraints.maxWidth;
+ uint16_t max_height = mCodecConfig.mEncodingConstraints.maxHeight;
+ if (max_width || max_height) {
+ max_width = max_width ? max_width : UINT16_MAX;
+ max_height = max_height ? max_height : UINT16_MAX;
+ ConstrainPreservingAspectRatio(max_width, max_height, &newSize.width,
+ &newSize.height);
+ }
+
+ MOZ_ASSERT(newSize.width > 0);
+ MOZ_ASSERT(newSize.height > 0);
+ video_stream.width = newSize.width;
+ video_stream.height = newSize.height;
+ SelectMaxFramerateForAllStreams(newSize.width, newSize.height);
+
+ CSFLogInfo(LOGTAG, "%s Input frame %ux%u, RID %s scaling to %zux%zu",
+ __FUNCTION__, aWidth, aHeight, encoding.rid.c_str(),
+ video_stream.width, video_stream.height);
+
+ // mMaxFramerateForAllStreams is based on codec-wide stuff like fmtp, and
+ // hard-coded limits based on the source resolution.
+ // mCodecConfig.mEncodingConstraints.maxFps does not take the hard-coded
+ // limits into account, so we have mMaxFramerateForAllStreams which
+ // incorporates those. Per-encoding max framerate is based on parameters
+ // from JS, and maybe rid
+ unsigned int max_framerate = SelectFrameRate(
+ mMaxFramerateForAllStreams, video_stream.width, video_stream.height);
+ max_framerate = std::min(WebrtcVideoConduit::ToLibwebrtcMaxFramerate(
+ encoding.constraints.maxFps),
+ max_framerate);
+ if (max_framerate >= std::numeric_limits<int>::max()) {
+ // If nothing has specified any kind of limit (uncommon), pick something
+ // reasonable.
+ max_framerate = DEFAULT_VIDEO_MAX_FRAMERATE;
+ }
+ video_stream.max_framerate = static_cast<int>(max_framerate);
+ CSFLogInfo(LOGTAG, "%s Stream with RID %s maxFps=%d (global max fps = %u)",
+ __FUNCTION__, encoding.rid.c_str(), video_stream.max_framerate,
+ (unsigned)mMaxFramerateForAllStreams);
+
+ SelectBitrates(video_stream.width, video_stream.height, mMinBitrate,
+ mStartBitrate, encoding.constraints.maxBr, mPrefMaxBitrate,
+ mNegotiatedMaxBitrate, video_stream);
+
+ video_stream.bitrate_priority = aConfig.bitrate_priority;
+ video_stream.max_qp = kQpMax;
+
+ if (streamCount > 1) {
+ if (mCodecMode == webrtc::VideoCodecMode::kScreensharing) {
+ video_stream.num_temporal_layers = 1;
+ } else {
+ video_stream.num_temporal_layers = 2;
+ }
+ // XXX Bug 1390215 investigate using more of
+ // simulcast.cc:GetSimulcastConfig() or our own algorithm to replace it
+ }
+
+ if (mCodecConfig.mName == "H264") {
+ if (mCodecConfig.mEncodingConstraints.maxMbps > 0) {
+ // Not supported yet!
+ CSFLogError(LOGTAG, "%s H.264 max_mbps not supported yet",
+ __FUNCTION__);
+ }
+ }
+ streams.push_back(video_stream);
+ }
+
+ MOZ_RELEASE_ASSERT(streams.size(), "Should configure at least one stream");
+ return streams;
+}
+
+gfx::IntSize VideoStreamFactory::CalculateScaledResolution(
+ int aWidth, int aHeight, double aScaleDownByResolution,
+ unsigned int aMaxPixelCount) {
+ // If any adjustments like scaleResolutionDownBy or maxFS are being given
+ // we want to choose a height and width here to provide for more variety
+ // in possible resolutions.
+ int width = aWidth;
+ int height = aHeight;
+
+ if (aScaleDownByResolution > 1) {
+ width = static_cast<int>(aWidth / aScaleDownByResolution);
+ height = static_cast<int>(aHeight / aScaleDownByResolution);
+ }
+
+ // Check if we still need to adjust resolution down more due to other
+ // constraints.
+ if (mCodecConfig.mEncodingConstraints.maxFs > 0 || aMaxPixelCount > 0) {
+ auto currentFs = static_cast<unsigned int>(width * height);
+ auto maxFs =
+ (mCodecConfig.mEncodingConstraints.maxFs > 0 && aMaxPixelCount > 0)
+ ? std::min((mCodecConfig.mEncodingConstraints.maxFs * 16 * 16),
+ aMaxPixelCount)
+ : std::max((mCodecConfig.mEncodingConstraints.maxFs * 16 * 16),
+ aMaxPixelCount);
+
+ // If our currentFs is greater than maxFs we calculate a width and height
+ // that will get as close as possible to maxFs and try to maintain aspect
+ // ratio.
+ if (currentFs > maxFs) {
+ if (aWidth > aHeight) { // Landscape
+ auto aspectRatio = static_cast<double>(aWidth) / aHeight;
+
+ height = static_cast<int>(std::sqrt(maxFs / aspectRatio));
+ width = static_cast<int>(height * aspectRatio);
+ } else { // Portrait
+ auto aspectRatio = static_cast<double>(aHeight) / aWidth;
+
+ width = static_cast<int>(std::sqrt(maxFs / aspectRatio));
+ height = static_cast<int>(width * aspectRatio);
+ }
+ }
+ }
+
+ // Simplest possible adaptation to resolution alignment.
+ width -= width % mWants.resolution_alignment;
+ height -= height % mWants.resolution_alignment;
+
+ // Dont scale below our minimum value to prevent problems.
+ const int minSize = 1;
+ if (width < minSize || height < minSize) {
+ width = minSize;
+ height = minSize;
+ }
+
+ return gfx::IntSize(width, height);
+}
+
+void VideoStreamFactory::SelectMaxFramerateForAllStreams(
+ unsigned short aWidth, unsigned short aHeight) {
+ int max_fs = std::numeric_limits<int>::max();
+ if (!mLockScaling) {
+ max_fs = mWants.max_pixel_count;
+ }
+ // Limit resolution to max-fs
+ if (mCodecConfig.mEncodingConstraints.maxFs) {
+ // max-fs is in macroblocks, convert to pixels
+ max_fs = std::min(
+ max_fs,
+ static_cast<int>(mCodecConfig.mEncodingConstraints.maxFs * (16 * 16)));
+ }
+
+ unsigned int framerate_all_streams =
+ SelectFrameRate(mMaxFramerateForAllStreams, aWidth, aHeight);
+ unsigned int maxFrameRate = mMaxFramerateForAllStreams;
+ if (mMaxFramerateForAllStreams != framerate_all_streams) {
+ CSFLogDebug(LOGTAG, "%s: framerate changing to %u (from %u)", __FUNCTION__,
+ framerate_all_streams, maxFrameRate);
+ mMaxFramerateForAllStreams = framerate_all_streams;
+ }
+
+ int framerate_with_wants;
+ if (framerate_all_streams > std::numeric_limits<int>::max()) {
+ framerate_with_wants = std::numeric_limits<int>::max();
+ } else {
+ framerate_with_wants = static_cast<int>(framerate_all_streams);
+ }
+
+ framerate_with_wants =
+ std::min(framerate_with_wants, mWants.max_framerate_fps);
+ CSFLogDebug(LOGTAG,
+ "%s: Calling OnOutputFormatRequest, max_fs=%d, max_fps=%d",
+ __FUNCTION__, max_fs, framerate_with_wants);
+ auto frameRateController = mFramerateController.Lock();
+ frameRateController->SetMaxFramerate(framerate_with_wants);
+}
+
+unsigned int VideoStreamFactory::SelectFrameRate(
+ unsigned int aOldFramerate, unsigned short aSendingWidth,
+ unsigned short aSendingHeight) {
+ unsigned int new_framerate = aOldFramerate;
+
+ // Limit frame rate based on max-mbps
+ if (mCodecConfig.mEncodingConstraints.maxMbps) {
+ unsigned int cur_fs, mb_width, mb_height;
+
+ mb_width = (aSendingWidth + 15) >> 4;
+ mb_height = (aSendingHeight + 15) >> 4;
+
+ cur_fs = mb_width * mb_height;
+ if (cur_fs > 0) { // in case no frames have been sent
+ new_framerate = mCodecConfig.mEncodingConstraints.maxMbps / cur_fs;
+ }
+ }
+
+ new_framerate =
+ std::min(new_framerate, WebrtcVideoConduit::ToLibwebrtcMaxFramerate(
+ mCodecConfig.mEncodingConstraints.maxFps));
+ return new_framerate;
+}
+
+bool VideoStreamFactory::ShouldDropFrame(const webrtc::VideoFrame& aFrame) {
+ bool hasNonZeroLayer = false;
+ {
+ const size_t streamCount =
+ mCodecMode == webrtc::VideoCodecMode::kScreensharing
+ ? 1
+ : mCodecConfig.mEncodings.size();
+ for (int idx = streamCount - 1; idx >= 0; --idx) {
+ const auto& encoding = mCodecConfig.mEncodings[idx];
+ if (aFrame.width() / encoding.constraints.scaleDownBy >= 1.0 &&
+ aFrame.height() / encoding.constraints.scaleDownBy >= 1.0) {
+ hasNonZeroLayer = true;
+ break;
+ }
+ }
+ }
+ if (!hasNonZeroLayer) {
+ return true;
+ }
+
+ auto frameRateController = mFramerateController.Lock();
+ return frameRateController->ShouldDropFrame(aFrame.timestamp_us() *
+ rtc::kNumNanosecsPerMicrosec);
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/libwebrtcglue/VideoStreamFactory.h b/dom/media/webrtc/libwebrtcglue/VideoStreamFactory.h
new file mode 100644
index 0000000000..832d1e9399
--- /dev/null
+++ b/dom/media/webrtc/libwebrtcglue/VideoStreamFactory.h
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef VideoStreamFactory_h
+#define VideoStreamFactory_h
+
+#include "CodecConfig.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/DataMutex.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/UniquePtr.h"
+#include "api/video/video_source_interface.h"
+#include "common_video/framerate_controller.h"
+#include "rtc_base/time_utils.h"
+#include "video/config/video_encoder_config.h"
+
+namespace webrtc {
+class VideoFrame;
+}
+
+namespace mozilla {
+
+// Factory class for VideoStreams... vie_encoder.cc will call this to
+// reconfigure.
+class VideoStreamFactory
+ : public webrtc::VideoEncoderConfig::VideoStreamFactoryInterface {
+ public:
+ struct ResolutionAndBitrateLimits {
+ int resolution_in_mb;
+ int min_bitrate_bps;
+ int start_bitrate_bps;
+ int max_bitrate_bps;
+ };
+
+ static ResolutionAndBitrateLimits GetLimitsFor(unsigned int aWidth,
+ unsigned int aHeight,
+ int aCapBps = 0);
+
+ VideoStreamFactory(VideoCodecConfig aConfig,
+ webrtc::VideoCodecMode aCodecMode, int aMinBitrate,
+ int aStartBitrate, int aPrefMaxBitrate,
+ int aNegotiatedMaxBitrate,
+ const rtc::VideoSinkWants& aWants, bool aLockScaling)
+ : mCodecMode(aCodecMode),
+ mMaxFramerateForAllStreams(std::numeric_limits<unsigned int>::max()),
+ mCodecConfig(std::forward<VideoCodecConfig>(aConfig)),
+ mMinBitrate(aMinBitrate),
+ mStartBitrate(aStartBitrate),
+ mPrefMaxBitrate(aPrefMaxBitrate),
+ mNegotiatedMaxBitrate(aNegotiatedMaxBitrate),
+ mFramerateController("VideoStreamFactory::mFramerateController"),
+ mWants(aWants),
+ mLockScaling(aLockScaling) {}
+
+ // This gets called off-main thread and may hold internal webrtc.org
+ // locks. May *NOT* lock the conduit's mutex, to avoid deadlocks.
+ std::vector<webrtc::VideoStream> CreateEncoderStreams(
+ int aWidth, int aHeight,
+ const webrtc::VideoEncoderConfig& aConfig) override;
+ /**
+ * Function to select and change the encoding resolution based on incoming
+ * frame size and current available bandwidth.
+ * @param width, height: dimensions of the frame
+ */
+ void SelectMaxFramerateForAllStreams(unsigned short aWidth,
+ unsigned short aHeight);
+
+ /**
+ * Function to determine if the frame should be dropped based on the given
+ * frame's resolution (combined with the factory's scaleResolutionDownBy) or
+ * timestamp.
+ * @param aFrame frame to be evaluated.
+ * @return true if frame should be dropped, false otehrwise.
+ */
+ bool ShouldDropFrame(const webrtc::VideoFrame& aFrame);
+
+ private:
+ /**
+ * Function to calculate a scaled down width and height based on
+ * scaleDownByResolution, maxFS, and max pixel count settings.
+ * @param aWidth current frame width
+ * @param aHeight current frame height
+ * @param aScaleDownByResolution value to scale width and height down by.
+ * @param aMaxPixelCount maximum number of pixels wanted in a frame.
+ * @return a gfx:IntSize containing width and height to use. These may match
+ * the aWidth and aHeight passed in if no scaling was needed.
+ */
+ gfx::IntSize CalculateScaledResolution(int aWidth, int aHeight,
+ double aScaleDownByResolution,
+ unsigned int aMaxPixelCount);
+
+ /**
+ * Function to select and change the encoding frame rate based on incoming
+ * frame rate, current frame size and max-mbps setting.
+ * @param aOldFramerate current framerate
+ * @param aSendingWidth width of frames being sent
+ * @param aSendingHeight height of frames being sent
+ * @return new framerate meeting max-mbps requriements based on frame size
+ */
+ unsigned int SelectFrameRate(unsigned int aOldFramerate,
+ unsigned short aSendingWidth,
+ unsigned short aSendingHeight);
+
+ // Used to limit number of streams for screensharing.
+ Atomic<webrtc::VideoCodecMode> mCodecMode;
+
+ // The framerate we're currently sending at.
+ Atomic<unsigned int> mMaxFramerateForAllStreams;
+
+ // The current send codec config, containing simulcast layer configs.
+ const VideoCodecConfig mCodecConfig;
+
+ // Bitrate limits in bps.
+ const int mMinBitrate = 0;
+ const int mStartBitrate = 0;
+ const int mPrefMaxBitrate = 0;
+ const int mNegotiatedMaxBitrate = 0;
+
+ // DatamMutex used as object is mutated from a libwebrtc thread and
+ // a seperate thread used to pass video frames to libwebrtc.
+ DataMutex<webrtc::FramerateController> mFramerateController;
+
+ const rtc::VideoSinkWants mWants;
+ const bool mLockScaling;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/libwebrtcglue/WebrtcCallWrapper.cpp b/dom/media/webrtc/libwebrtcglue/WebrtcCallWrapper.cpp
new file mode 100644
index 0000000000..7d4547ddb6
--- /dev/null
+++ b/dom/media/webrtc/libwebrtcglue/WebrtcCallWrapper.cpp
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebrtcCallWrapper.h"
+
+#include "jsapi/PeerConnectionCtx.h"
+#include "MediaConduitInterface.h"
+#include "TaskQueueWrapper.h"
+
+// libwebrtc includes
+#include "call/rtp_transport_controller_send_factory.h"
+
+namespace mozilla {
+
+/* static */ RefPtr<WebrtcCallWrapper> WebrtcCallWrapper::Create(
+ const dom::RTCStatsTimestampMaker& aTimestampMaker,
+ UniquePtr<media::ShutdownBlockingTicket> aShutdownTicket,
+ const RefPtr<SharedWebrtcState>& aSharedState) {
+ auto eventLog = MakeUnique<webrtc::RtcEventLogNull>();
+ auto taskQueueFactory = MakeUnique<SharedThreadPoolWebRtcTaskQueueFactory>();
+ auto videoBitrateAllocatorFactory =
+ WrapUnique(webrtc::CreateBuiltinVideoBitrateAllocatorFactory().release());
+ RefPtr<WebrtcCallWrapper> wrapper = new WebrtcCallWrapper(
+ aSharedState, std::move(videoBitrateAllocatorFactory),
+ std::move(eventLog), std::move(taskQueueFactory), aTimestampMaker,
+ std::move(aShutdownTicket));
+
+ wrapper->mCallThread->Dispatch(
+ NS_NewRunnableFunction(__func__, [wrapper, aSharedState] {
+ webrtc::Call::Config config(wrapper->mEventLog.get());
+ config.audio_state =
+ webrtc::AudioState::Create(aSharedState->mAudioStateConfig);
+ config.task_queue_factory = wrapper->mTaskQueueFactory.get();
+ config.trials = aSharedState->mTrials.get();
+ wrapper->SetCall(WrapUnique(webrtc::Call::Create(
+ config, &wrapper->mClock,
+ webrtc::RtpTransportControllerSendFactory().Create(
+ config.ExtractTransportConfig(), &wrapper->mClock))));
+ }));
+
+ return wrapper;
+}
+
+void WebrtcCallWrapper::SetCall(UniquePtr<webrtc::Call> aCall) {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ MOZ_ASSERT(!mCall);
+ mCall = std::move(aCall);
+}
+
+webrtc::Call* WebrtcCallWrapper::Call() const {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ return mCall.get();
+}
+
+void WebrtcCallWrapper::UnsetRemoteSSRC(uint32_t aSsrc) {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ for (const auto& conduit : mConduits) {
+ conduit->UnsetRemoteSSRC(aSsrc);
+ }
+}
+
+void WebrtcCallWrapper::RegisterConduit(MediaSessionConduit* conduit) {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ mConduits.insert(conduit);
+}
+
+void WebrtcCallWrapper::UnregisterConduit(MediaSessionConduit* conduit) {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ mConduits.erase(conduit);
+}
+
+void WebrtcCallWrapper::Destroy() {
+ MOZ_ASSERT(mCallThread->IsOnCurrentThread());
+ mCall = nullptr;
+ mShutdownTicket = nullptr;
+}
+
+const dom::RTCStatsTimestampMaker& WebrtcCallWrapper::GetTimestampMaker()
+ const {
+ return mClock.mTimestampMaker;
+}
+
+WebrtcCallWrapper::~WebrtcCallWrapper() { MOZ_ASSERT(!mCall); }
+
+WebrtcCallWrapper::WebrtcCallWrapper(
+ RefPtr<SharedWebrtcState> aSharedState,
+ UniquePtr<webrtc::VideoBitrateAllocatorFactory>
+ aVideoBitrateAllocatorFactory,
+ UniquePtr<webrtc::RtcEventLog> aEventLog,
+ UniquePtr<webrtc::TaskQueueFactory> aTaskQueueFactory,
+ const dom::RTCStatsTimestampMaker& aTimestampMaker,
+ UniquePtr<media::ShutdownBlockingTicket> aShutdownTicket)
+ : mSharedState(std::move(aSharedState)),
+ mClock(aTimestampMaker),
+ mShutdownTicket(std::move(aShutdownTicket)),
+ mCallThread(mSharedState->mCallWorkerThread),
+ mAudioDecoderFactory(mSharedState->mAudioDecoderFactory),
+ mVideoBitrateAllocatorFactory(std::move(aVideoBitrateAllocatorFactory)),
+ mEventLog(std::move(aEventLog)),
+ mTaskQueueFactory(std::move(aTaskQueueFactory)) {}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/libwebrtcglue/WebrtcCallWrapper.h b/dom/media/webrtc/libwebrtcglue/WebrtcCallWrapper.h
new file mode 100644
index 0000000000..17054c5f75
--- /dev/null
+++ b/dom/media/webrtc/libwebrtcglue/WebrtcCallWrapper.h
@@ -0,0 +1,114 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DOM_MEDIA_WEBRTC_LIBWEBRTCGLUE_WEBRTCCALLWRAPPER_H_
+#define DOM_MEDIA_WEBRTC_LIBWEBRTCGLUE_WEBRTCCALLWRAPPER_H_
+
+#include <set>
+
+#include "domstubs.h"
+#include "jsapi/RTCStatsReport.h"
+#include "nsISupportsImpl.h"
+#include "SystemTime.h"
+
+// libwebrtc includes
+#include "api/video/builtin_video_bitrate_allocator_factory.h"
+#include "call/call.h"
+#include "call/call_config.h"
+
+namespace mozilla {
+class AbstractThread;
+class MediaSessionConduit;
+class SharedWebrtcState;
+
+namespace media {
+class ShutdownBlockingTicket;
+}
+
+// Wrap the webrtc.org Call class adding mozilla add/ref support.
+class WebrtcCallWrapper {
+ public:
+ typedef webrtc::CallConfig Config;
+
+ static RefPtr<WebrtcCallWrapper> Create(
+ const dom::RTCStatsTimestampMaker& aTimestampMaker,
+ UniquePtr<media::ShutdownBlockingTicket> aShutdownTicket,
+ const RefPtr<SharedWebrtcState>& aSharedState);
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebrtcCallWrapper)
+
+ // Don't allow copying/assigning.
+ WebrtcCallWrapper(const WebrtcCallWrapper&) = delete;
+ void operator=(const WebrtcCallWrapper&) = delete;
+
+ void SetCall(UniquePtr<webrtc::Call> aCall);
+
+ webrtc::Call* Call() const;
+
+ void UnsetRemoteSSRC(uint32_t aSsrc);
+
+ // Idempotent.
+ void RegisterConduit(MediaSessionConduit* conduit);
+
+ // Idempotent.
+ void UnregisterConduit(MediaSessionConduit* conduit);
+
+ // Allow destroying the Call instance on the Call worker thread.
+ //
+ // Note that shutdown is blocked until the Call instance is destroyed.
+ //
+ // This CallWrapper is designed to be sharable, and is held by several objects
+ // that are cycle-collectable. TaskQueueWrapper that the Call instances use
+ // for worker threads are based off SharedThreadPools, and will block
+ // xpcom-shutdown-threads until destroyed. The Call instance however will hold
+ // on to its worker threads until destruction.
+ //
+ // If the last ref to this CallWrapper is held to cycle collector shutdown we
+ // end up in a deadlock where cycle collector shutdown is required to destroy
+ // the SharedThreadPool that is blocking xpcom-shutdown-threads from finishing
+ // and triggering cycle collector shutdown.
+ //
+ // It would be nice to have the invariant where this class is immutable to the
+ // degree that mCall is const, but given the above that is not possible.
+ void Destroy();
+
+ const dom::RTCStatsTimestampMaker& GetTimestampMaker() const;
+
+ protected:
+ virtual ~WebrtcCallWrapper();
+
+ WebrtcCallWrapper(RefPtr<SharedWebrtcState> aSharedState,
+ UniquePtr<webrtc::VideoBitrateAllocatorFactory>
+ aVideoBitrateAllocatorFactory,
+ UniquePtr<webrtc::RtcEventLog> aEventLog,
+ UniquePtr<webrtc::TaskQueueFactory> aTaskQueueFactory,
+ const dom::RTCStatsTimestampMaker& aTimestampMaker,
+ UniquePtr<media::ShutdownBlockingTicket> aShutdownTicket);
+
+ const RefPtr<SharedWebrtcState> mSharedState;
+
+ // Allows conduits to know about one another, to avoid remote SSRC
+ // collisions.
+ std::set<RefPtr<MediaSessionConduit>> mConduits;
+ RTCStatsTimestampMakerRealtimeClock mClock;
+ UniquePtr<media::ShutdownBlockingTicket> mShutdownTicket;
+
+ public:
+ const RefPtr<AbstractThread> mCallThread;
+ const RefPtr<webrtc::AudioDecoderFactory> mAudioDecoderFactory;
+ const UniquePtr<webrtc::VideoBitrateAllocatorFactory>
+ mVideoBitrateAllocatorFactory;
+ const UniquePtr<webrtc::RtcEventLog> mEventLog;
+ const UniquePtr<webrtc::TaskQueueFactory> mTaskQueueFactory;
+
+ protected:
+ // Call worker thread only.
+ UniquePtr<webrtc::Call> mCall;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/libwebrtcglue/WebrtcGmpVideoCodec.cpp b/dom/media/webrtc/libwebrtcglue/WebrtcGmpVideoCodec.cpp
new file mode 100644
index 0000000000..50e39b22d0
--- /dev/null
+++ b/dom/media/webrtc/libwebrtcglue/WebrtcGmpVideoCodec.cpp
@@ -0,0 +1,1043 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebrtcGmpVideoCodec.h"
+
+#include <utility>
+#include <vector>
+
+#include "GMPLog.h"
+#include "MainThreadUtils.h"
+#include "VideoConduit.h"
+#include "gmp-video-frame-encoded.h"
+#include "gmp-video-frame-i420.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/SyncRunnable.h"
+#include "nsServiceManagerUtils.h"
+#include "transport/runnable_utils.h"
+#include "api/video/video_frame_type.h"
+#include "common_video/include/video_frame_buffer.h"
+#include "media/base/media_constants.h"
+// #include "rtc_base/bind.h"
+
+namespace mozilla {
+
+// QP scaling thresholds.
+static const int kLowH264QpThreshold = 24;
+static const int kHighH264QpThreshold = 37;
+
+// Encoder.
+WebrtcGmpVideoEncoder::WebrtcGmpVideoEncoder(
+ const webrtc::SdpVideoFormat& aFormat, std::string aPCHandle)
+ : mGMP(nullptr),
+ mInitting(false),
+ mHost(nullptr),
+ mMaxPayloadSize(0),
+ mFormatParams(aFormat.parameters),
+ mCallbackMutex("WebrtcGmpVideoEncoder encoded callback mutex"),
+ mCallback(nullptr),
+ mPCHandle(std::move(aPCHandle)),
+ mInputImageMap("WebrtcGmpVideoEncoder::mInputImageMap") {
+ mCodecParams.mGMPApiVersion = 0;
+ mCodecParams.mCodecType = kGMPVideoCodecInvalid;
+ mCodecParams.mPLType = 0;
+ mCodecParams.mWidth = 0;
+ mCodecParams.mHeight = 0;
+ mCodecParams.mStartBitrate = 0;
+ mCodecParams.mMaxBitrate = 0;
+ mCodecParams.mMinBitrate = 0;
+ mCodecParams.mMaxFramerate = 0;
+ mCodecParams.mFrameDroppingOn = false;
+ mCodecParams.mKeyFrameInterval = 0;
+ mCodecParams.mQPMax = 0;
+ mCodecParams.mNumberOfSimulcastStreams = 0;
+ mCodecParams.mMode = kGMPCodecModeInvalid;
+ mCodecParams.mLogLevel = GetGMPLibraryLogLevel();
+ MOZ_ASSERT(!mPCHandle.empty());
+}
+
+WebrtcGmpVideoEncoder::~WebrtcGmpVideoEncoder() {
+ // We should not have been destroyed if we never closed our GMP
+ MOZ_ASSERT(!mGMP);
+}
+
+static int WebrtcFrameTypeToGmpFrameType(webrtc::VideoFrameType aIn,
+ GMPVideoFrameType* aOut) {
+ MOZ_ASSERT(aOut);
+ switch (aIn) {
+ case webrtc::VideoFrameType::kVideoFrameKey:
+ *aOut = kGMPKeyFrame;
+ break;
+ case webrtc::VideoFrameType::kVideoFrameDelta:
+ *aOut = kGMPDeltaFrame;
+ break;
+ case webrtc::VideoFrameType::kEmptyFrame:
+ *aOut = kGMPSkipFrame;
+ break;
+ default:
+ MOZ_CRASH("Unexpected webrtc::FrameType");
+ }
+
+ return WEBRTC_VIDEO_CODEC_OK;
+}
+
+static int GmpFrameTypeToWebrtcFrameType(GMPVideoFrameType aIn,
+ webrtc::VideoFrameType* aOut) {
+ MOZ_ASSERT(aOut);
+ switch (aIn) {
+ case kGMPKeyFrame:
+ *aOut = webrtc::VideoFrameType::kVideoFrameKey;
+ break;
+ case kGMPDeltaFrame:
+ *aOut = webrtc::VideoFrameType::kVideoFrameDelta;
+ break;
+ case kGMPSkipFrame:
+ *aOut = webrtc::VideoFrameType::kEmptyFrame;
+ break;
+ default:
+ MOZ_CRASH("Unexpected GMPVideoFrameType");
+ }
+
+ return WEBRTC_VIDEO_CODEC_OK;
+}
+
+static int SizeNumBytes(GMPBufferType aBufferType) {
+ switch (aBufferType) {
+ case GMP_BufferSingle:
+ return 0;
+ case GMP_BufferLength8:
+ return 1;
+ case GMP_BufferLength16:
+ return 2;
+ case GMP_BufferLength24:
+ return 3;
+ case GMP_BufferLength32:
+ return 4;
+ default:
+ MOZ_CRASH("Unexpected buffer type");
+ }
+}
+
+int32_t WebrtcGmpVideoEncoder::InitEncode(
+ const webrtc::VideoCodec* aCodecSettings,
+ const webrtc::VideoEncoder::Settings& aSettings) {
+ if (!mMPS) {
+ mMPS = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+ }
+ MOZ_ASSERT(mMPS);
+
+ if (!mGMPThread) {
+ if (NS_WARN_IF(NS_FAILED(mMPS->GetThread(getter_AddRefs(mGMPThread))))) {
+ return WEBRTC_VIDEO_CODEC_ERROR;
+ }
+ }
+
+ MOZ_ASSERT(aCodecSettings->numberOfSimulcastStreams == 1,
+ "Simulcast not implemented for GMP-H264");
+
+ // Bug XXXXXX: transfer settings from codecSettings to codec.
+ GMPVideoCodec codecParams;
+ memset(&codecParams, 0, sizeof(codecParams));
+
+ codecParams.mGMPApiVersion = kGMPVersion34;
+ codecParams.mLogLevel = GetGMPLibraryLogLevel();
+ codecParams.mStartBitrate = aCodecSettings->startBitrate;
+ codecParams.mMinBitrate = aCodecSettings->minBitrate;
+ codecParams.mMaxBitrate = aCodecSettings->maxBitrate;
+ codecParams.mMaxFramerate = aCodecSettings->maxFramerate;
+
+ memset(&mCodecSpecificInfo.codecSpecific, 0,
+ sizeof(mCodecSpecificInfo.codecSpecific));
+ mCodecSpecificInfo.codecType = webrtc::kVideoCodecH264;
+ mCodecSpecificInfo.codecSpecific.H264.packetization_mode =
+ mFormatParams.count(cricket::kH264FmtpPacketizationMode) == 1 &&
+ mFormatParams.at(cricket::kH264FmtpPacketizationMode) == "1"
+ ? webrtc::H264PacketizationMode::NonInterleaved
+ : webrtc::H264PacketizationMode::SingleNalUnit;
+
+ uint32_t maxPayloadSize = aSettings.max_payload_size;
+ if (mCodecSpecificInfo.codecSpecific.H264.packetization_mode ==
+ webrtc::H264PacketizationMode::NonInterleaved) {
+ maxPayloadSize = 0; // No limit, use FUAs
+ }
+
+ if (aCodecSettings->mode == webrtc::VideoCodecMode::kScreensharing) {
+ codecParams.mMode = kGMPScreensharing;
+ } else {
+ codecParams.mMode = kGMPRealtimeVideo;
+ }
+
+ codecParams.mWidth = aCodecSettings->width;
+ codecParams.mHeight = aCodecSettings->height;
+
+ RefPtr<GmpInitDoneRunnable> initDone(new GmpInitDoneRunnable(mPCHandle));
+ mGMPThread->Dispatch(
+ WrapRunnableNM(WebrtcGmpVideoEncoder::InitEncode_g,
+ RefPtr<WebrtcGmpVideoEncoder>(this), codecParams,
+ aSettings.number_of_cores, maxPayloadSize, initDone),
+ NS_DISPATCH_NORMAL);
+
+ // Since init of the GMP encoder is a multi-step async dispatch (including
+ // dispatches to main), and since this function is invoked on main, there's
+ // no safe way to block until this init is done. If an error occurs, we'll
+ // handle it later.
+ return WEBRTC_VIDEO_CODEC_OK;
+}
+
+/* static */
+void WebrtcGmpVideoEncoder::InitEncode_g(
+ const RefPtr<WebrtcGmpVideoEncoder>& aThis,
+ const GMPVideoCodec& aCodecParams, int32_t aNumberOfCores,
+ uint32_t aMaxPayloadSize, const RefPtr<GmpInitDoneRunnable>& aInitDone) {
+ nsTArray<nsCString> tags;
+ tags.AppendElement("h264"_ns);
+ UniquePtr<GetGMPVideoEncoderCallback> callback(
+ new InitDoneCallback(aThis, aInitDone, aCodecParams));
+ aThis->mInitting = true;
+ aThis->mMaxPayloadSize = aMaxPayloadSize;
+ nsresult rv = aThis->mMPS->GetGMPVideoEncoder(nullptr, &tags, ""_ns,
+ std::move(callback));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ GMP_LOG_DEBUG("GMP Encode: GetGMPVideoEncoder failed");
+ aThis->Close_g();
+ aInitDone->Dispatch(WEBRTC_VIDEO_CODEC_ERROR,
+ "GMP Encode: GetGMPVideoEncoder failed");
+ }
+}
+
+int32_t WebrtcGmpVideoEncoder::GmpInitDone(GMPVideoEncoderProxy* aGMP,
+ GMPVideoHost* aHost,
+ std::string* aErrorOut) {
+ if (!mInitting || !aGMP || !aHost) {
+ *aErrorOut =
+ "GMP Encode: Either init was aborted, "
+ "or init failed to supply either a GMP Encoder or GMP host.";
+ if (aGMP) {
+ // This could destroy us, since aGMP may be the last thing holding a ref
+ // Return immediately.
+ aGMP->Close();
+ }
+ return WEBRTC_VIDEO_CODEC_ERROR;
+ }
+
+ mInitting = false;
+
+ if (mGMP && mGMP != aGMP) {
+ Close_g();
+ }
+
+ mGMP = aGMP;
+ mHost = aHost;
+ mCachedPluginId = Some(mGMP->GetPluginId());
+ mInitPluginEvent.Notify(*mCachedPluginId);
+ return WEBRTC_VIDEO_CODEC_OK;
+}
+
+int32_t WebrtcGmpVideoEncoder::GmpInitDone(GMPVideoEncoderProxy* aGMP,
+ GMPVideoHost* aHost,
+ const GMPVideoCodec& aCodecParams,
+ std::string* aErrorOut) {
+ int32_t r = GmpInitDone(aGMP, aHost, aErrorOut);
+ if (r != WEBRTC_VIDEO_CODEC_OK) {
+ // We might have been destroyed if GmpInitDone failed.
+ // Return immediately.
+ return r;
+ }
+ mCodecParams = aCodecParams;
+ return InitEncoderForSize(aCodecParams.mWidth, aCodecParams.mHeight,
+ aErrorOut);
+}
+
+void WebrtcGmpVideoEncoder::Close_g() {
+ GMPVideoEncoderProxy* gmp(mGMP);
+ mGMP = nullptr;
+ mHost = nullptr;
+ mInitting = false;
+
+ if (mCachedPluginId) {
+ mReleasePluginEvent.Notify(*mCachedPluginId);
+ }
+ mCachedPluginId = Nothing();
+
+ if (gmp) {
+ // Do this last, since this could cause us to be destroyed
+ gmp->Close();
+ }
+}
+
+int32_t WebrtcGmpVideoEncoder::InitEncoderForSize(unsigned short aWidth,
+ unsigned short aHeight,
+ std::string* aErrorOut) {
+ mCodecParams.mWidth = aWidth;
+ mCodecParams.mHeight = aHeight;
+ // Pass dummy codecSpecific data for now...
+ nsTArray<uint8_t> codecSpecific;
+
+ GMPErr err =
+ mGMP->InitEncode(mCodecParams, codecSpecific, this, 1, mMaxPayloadSize);
+ if (err != GMPNoErr) {
+ *aErrorOut = "GMP Encode: InitEncode failed";
+ return WEBRTC_VIDEO_CODEC_ERROR;
+ }
+
+ return WEBRTC_VIDEO_CODEC_OK;
+}
+
+int32_t WebrtcGmpVideoEncoder::Encode(
+ const webrtc::VideoFrame& aInputImage,
+ const std::vector<webrtc::VideoFrameType>* aFrameTypes) {
+ MOZ_ASSERT(aInputImage.width() >= 0 && aInputImage.height() >= 0);
+ if (!aFrameTypes) {
+ return WEBRTC_VIDEO_CODEC_ERROR;
+ }
+
+ // It is safe to copy aInputImage here because the frame buffer is held by
+ // a refptr.
+ mGMPThread->Dispatch(WrapRunnableNM(&WebrtcGmpVideoEncoder::Encode_g,
+ RefPtr<WebrtcGmpVideoEncoder>(this),
+ aInputImage, *aFrameTypes),
+ NS_DISPATCH_NORMAL);
+
+ return WEBRTC_VIDEO_CODEC_OK;
+}
+
+void WebrtcGmpVideoEncoder::RegetEncoderForResolutionChange(
+ uint32_t aWidth, uint32_t aHeight,
+ const RefPtr<GmpInitDoneRunnable>& aInitDone) {
+ Close_g();
+
+ UniquePtr<GetGMPVideoEncoderCallback> callback(
+ new InitDoneForResolutionChangeCallback(this, aInitDone, aWidth,
+ aHeight));
+
+ // OpenH264 codec (at least) can't handle dynamic input resolution changes
+ // re-init the plugin when the resolution changes
+ // XXX allow codec to indicate it doesn't need re-init!
+ nsTArray<nsCString> tags;
+ tags.AppendElement("h264"_ns);
+ mInitting = true;
+ if (NS_WARN_IF(NS_FAILED(mMPS->GetGMPVideoEncoder(nullptr, &tags, ""_ns,
+ std::move(callback))))) {
+ aInitDone->Dispatch(WEBRTC_VIDEO_CODEC_ERROR,
+ "GMP Encode: GetGMPVideoEncoder failed");
+ }
+}
+
+void WebrtcGmpVideoEncoder::Encode_g(
+ const RefPtr<WebrtcGmpVideoEncoder>& aEncoder,
+ webrtc::VideoFrame aInputImage,
+ std::vector<webrtc::VideoFrameType> aFrameTypes) {
+ if (!aEncoder->mGMP) {
+ // destroyed via Terminate(), failed to init, or just not initted yet
+ GMP_LOG_DEBUG("GMP Encode: not initted yet");
+ return;
+ }
+ MOZ_ASSERT(aEncoder->mHost);
+
+ if (static_cast<uint32_t>(aInputImage.width()) !=
+ aEncoder->mCodecParams.mWidth ||
+ static_cast<uint32_t>(aInputImage.height()) !=
+ aEncoder->mCodecParams.mHeight) {
+ GMP_LOG_DEBUG("GMP Encode: resolution change from %ux%u to %dx%d",
+ aEncoder->mCodecParams.mWidth, aEncoder->mCodecParams.mHeight,
+ aInputImage.width(), aInputImage.height());
+
+ RefPtr<GmpInitDoneRunnable> initDone(
+ new GmpInitDoneRunnable(aEncoder->mPCHandle));
+ aEncoder->RegetEncoderForResolutionChange(aInputImage.width(),
+ aInputImage.height(), initDone);
+ if (!aEncoder->mGMP) {
+ // We needed to go async to re-get the encoder. Bail.
+ return;
+ }
+ }
+
+ GMPVideoFrame* ftmp = nullptr;
+ GMPErr err = aEncoder->mHost->CreateFrame(kGMPI420VideoFrame, &ftmp);
+ if (err != GMPNoErr) {
+ GMP_LOG_DEBUG("GMP Encode: failed to create frame on host");
+ return;
+ }
+ GMPUniquePtr<GMPVideoi420Frame> frame(static_cast<GMPVideoi420Frame*>(ftmp));
+ const webrtc::I420BufferInterface* input_image =
+ aInputImage.video_frame_buffer()->GetI420();
+ // check for overflow of stride * height
+ CheckedInt32 ysize =
+ CheckedInt32(input_image->StrideY()) * input_image->height();
+ MOZ_RELEASE_ASSERT(ysize.isValid());
+ // I will assume that if that doesn't overflow, the others case - YUV
+ // 4:2:0 has U/V widths <= Y, even with alignment issues.
+ err = frame->CreateFrame(
+ ysize.value(), input_image->DataY(),
+ input_image->StrideU() * ((input_image->height() + 1) / 2),
+ input_image->DataU(),
+ input_image->StrideV() * ((input_image->height() + 1) / 2),
+ input_image->DataV(), input_image->width(), input_image->height(),
+ input_image->StrideY(), input_image->StrideU(), input_image->StrideV());
+ if (err != GMPNoErr) {
+ GMP_LOG_DEBUG("GMP Encode: failed to create frame");
+ return;
+ }
+ frame->SetTimestamp((aInputImage.timestamp() * 1000ll) /
+ 90); // note: rounds down!
+ // frame->SetDuration(1000000ll/30); // XXX base duration on measured current
+ // FPS - or don't bother
+
+ // Bug XXXXXX: Set codecSpecific info
+ GMPCodecSpecificInfo info;
+ memset(&info, 0, sizeof(info));
+ info.mCodecType = kGMPVideoCodecH264;
+ nsTArray<uint8_t> codecSpecificInfo;
+ codecSpecificInfo.AppendElements((uint8_t*)&info,
+ sizeof(GMPCodecSpecificInfo));
+
+ nsTArray<GMPVideoFrameType> gmp_frame_types;
+ for (auto it = aFrameTypes.begin(); it != aFrameTypes.end(); ++it) {
+ GMPVideoFrameType ft;
+
+ int32_t ret = WebrtcFrameTypeToGmpFrameType(*it, &ft);
+ if (ret != WEBRTC_VIDEO_CODEC_OK) {
+ GMP_LOG_DEBUG(
+ "GMP Encode: failed to map webrtc frame type to gmp frame type");
+ return;
+ }
+
+ gmp_frame_types.AppendElement(ft);
+ }
+
+ {
+ auto inputImageMap = aEncoder->mInputImageMap.Lock();
+ DebugOnly<bool> inserted = false;
+ std::tie(std::ignore, inserted) = inputImageMap->insert(
+ {frame->Timestamp(), {aInputImage.timestamp_us()}});
+ MOZ_ASSERT(inserted, "Duplicate timestamp");
+ }
+
+ GMP_LOG_DEBUG("GMP Encode: %" PRIu64, (frame->Timestamp()));
+ err = aEncoder->mGMP->Encode(std::move(frame), codecSpecificInfo,
+ gmp_frame_types);
+ if (err != GMPNoErr) {
+ GMP_LOG_DEBUG("GMP Encode: failed to encode frame");
+ }
+}
+
+int32_t WebrtcGmpVideoEncoder::RegisterEncodeCompleteCallback(
+ webrtc::EncodedImageCallback* aCallback) {
+ MutexAutoLock lock(mCallbackMutex);
+ mCallback = aCallback;
+
+ return WEBRTC_VIDEO_CODEC_OK;
+}
+
+/* static */
+void WebrtcGmpVideoEncoder::ReleaseGmp_g(
+ const RefPtr<WebrtcGmpVideoEncoder>& aEncoder) {
+ aEncoder->Close_g();
+}
+
+int32_t WebrtcGmpVideoEncoder::Shutdown() {
+ GMP_LOG_DEBUG("GMP Released:");
+ RegisterEncodeCompleteCallback(nullptr);
+ if (mGMPThread) {
+ mGMPThread->Dispatch(WrapRunnableNM(&WebrtcGmpVideoEncoder::ReleaseGmp_g,
+ RefPtr<WebrtcGmpVideoEncoder>(this)),
+ NS_DISPATCH_NORMAL);
+ }
+ return WEBRTC_VIDEO_CODEC_OK;
+}
+
+int32_t WebrtcGmpVideoEncoder::SetRates(
+ const webrtc::VideoEncoder::RateControlParameters& aParameters) {
+ MOZ_ASSERT(mGMPThread);
+ MOZ_ASSERT(aParameters.bitrate.IsSpatialLayerUsed(0));
+ MOZ_ASSERT(!aParameters.bitrate.HasBitrate(0, 1),
+ "No simulcast support for H264");
+ MOZ_ASSERT(!aParameters.bitrate.IsSpatialLayerUsed(1),
+ "No simulcast support for H264");
+ mGMPThread->Dispatch(
+ WrapRunnableNM(&WebrtcGmpVideoEncoder::SetRates_g,
+ RefPtr<WebrtcGmpVideoEncoder>(this),
+ aParameters.bitrate.GetBitrate(0, 0) / 1000,
+ aParameters.framerate_fps > 0.0
+ ? Some(aParameters.framerate_fps)
+ : Nothing()),
+ NS_DISPATCH_NORMAL);
+
+ return WEBRTC_VIDEO_CODEC_OK;
+}
+
+WebrtcVideoEncoder::EncoderInfo WebrtcGmpVideoEncoder::GetEncoderInfo() const {
+ WebrtcVideoEncoder::EncoderInfo info;
+ info.supports_native_handle = false;
+ info.implementation_name = "GMPOpenH264";
+ info.scaling_settings = WebrtcVideoEncoder::ScalingSettings(
+ kLowH264QpThreshold, kHighH264QpThreshold);
+ info.is_hardware_accelerated = false;
+ info.supports_simulcast = false;
+ return info;
+}
+
+/* static */
+int32_t WebrtcGmpVideoEncoder::SetRates_g(RefPtr<WebrtcGmpVideoEncoder> aThis,
+ uint32_t aNewBitRateKbps,
+ Maybe<double> aFrameRate) {
+ if (!aThis->mGMP) {
+ // destroyed via Terminate()
+ return WEBRTC_VIDEO_CODEC_ERROR;
+ }
+
+ GMPErr err = aThis->mGMP->SetRates(
+ aNewBitRateKbps, aFrameRate
+ .map([](double aFr) {
+ // Avoid rounding to 0
+ return std::max(1U, static_cast<uint32_t>(aFr));
+ })
+ .valueOr(aThis->mCodecParams.mMaxFramerate));
+ if (err != GMPNoErr) {
+ return WEBRTC_VIDEO_CODEC_ERROR;
+ }
+
+ return WEBRTC_VIDEO_CODEC_OK;
+}
+
+// GMPVideoEncoderCallback virtual functions.
+void WebrtcGmpVideoEncoder::Terminated() {
+ GMP_LOG_DEBUG("GMP Encoder Terminated: %p", (void*)this);
+
+ GMPVideoEncoderProxy* gmp(mGMP);
+ mGMP = nullptr;
+ mHost = nullptr;
+ mInitting = false;
+
+ if (gmp) {
+ // Do this last, since this could cause us to be destroyed
+ gmp->Close();
+ }
+
+ // Could now notify that it's dead
+}
+
+void WebrtcGmpVideoEncoder::Encoded(
+ GMPVideoEncodedFrame* aEncodedFrame,
+ const nsTArray<uint8_t>& aCodecSpecificInfo) {
+ webrtc::Timestamp capture_time = webrtc::Timestamp::Micros(0);
+ {
+ auto inputImageMap = mInputImageMap.Lock();
+ auto handle = inputImageMap->extract(aEncodedFrame->TimeStamp());
+ MOZ_ASSERT(handle);
+ if (handle) {
+ capture_time = webrtc::Timestamp::Micros(handle.mapped().timestamp_us);
+ }
+ }
+
+ MutexAutoLock lock(mCallbackMutex);
+ if (!mCallback) {
+ return;
+ }
+
+ webrtc::VideoFrameType ft;
+ GmpFrameTypeToWebrtcFrameType(aEncodedFrame->FrameType(), &ft);
+ uint32_t timestamp = (aEncodedFrame->TimeStamp() * 90ll + 999) / 1000;
+
+ GMP_LOG_DEBUG("GMP Encoded: %" PRIu64 ", type %d, len %d",
+ aEncodedFrame->TimeStamp(), aEncodedFrame->BufferType(),
+ aEncodedFrame->Size());
+
+ if (!aEncodedFrame->Buffer()) {
+ GMP_LOG_ERROR("GMP plugin returned null buffer");
+ return;
+ }
+
+ // Libwebrtc's RtpPacketizerH264 expects a 3- or 4-byte NALU start sequence
+ // before the start of the NALU payload. {0,0,1} or {0,0,0,1}. We set this
+ // in-place. Any other length of the length field we reject.
+
+ const int sizeNumBytes = SizeNumBytes(aEncodedFrame->BufferType());
+ uint32_t unitOffset = 0;
+ uint32_t unitSize = 0;
+ // Make sure we don't read past the end of the buffer getting the size
+ while (unitOffset + sizeNumBytes < aEncodedFrame->Size()) {
+ uint8_t* unitBuffer = aEncodedFrame->Buffer() + unitOffset;
+ switch (aEncodedFrame->BufferType()) {
+ case GMP_BufferLength24: {
+#if MOZ_LITTLE_ENDIAN()
+ unitSize = (static_cast<uint32_t>(*unitBuffer)) |
+ (static_cast<uint32_t>(*(unitBuffer + 1)) << 8) |
+ (static_cast<uint32_t>(*(unitBuffer + 2)) << 16);
+#else
+ unitSize = (static_cast<uint32_t>(*unitBuffer) << 16) |
+ (static_cast<uint32_t>(*(unitBuffer + 1)) << 8) |
+ (static_cast<uint32_t>(*(unitBuffer + 2)));
+#endif
+ const uint8_t startSequence[] = {0, 0, 1};
+ if (memcmp(unitBuffer, startSequence, 3) == 0) {
+ // This is a bug in OpenH264 where it misses to convert the NALU start
+ // sequence to the NALU size per the GMP protocol. We mitigate this by
+ // letting it through as this is what libwebrtc already expects and
+ // scans for.
+ unitSize = aEncodedFrame->Size() - 3;
+ break;
+ }
+ memcpy(unitBuffer, startSequence, 3);
+ break;
+ }
+ case GMP_BufferLength32: {
+#if MOZ_LITTLE_ENDIAN()
+ unitSize = LittleEndian::readUint32(unitBuffer);
+#else
+ unitSize = BigEndian::readUint32(unitBuffer);
+#endif
+ const uint8_t startSequence[] = {0, 0, 0, 1};
+ if (memcmp(unitBuffer, startSequence, 4) == 0) {
+ // This is a bug in OpenH264 where it misses to convert the NALU start
+ // sequence to the NALU size per the GMP protocol. We mitigate this by
+ // letting it through as this is what libwebrtc already expects and
+ // scans for.
+ unitSize = aEncodedFrame->Size() - 4;
+ break;
+ }
+ memcpy(unitBuffer, startSequence, 4);
+ break;
+ }
+ default:
+ GMP_LOG_ERROR("GMP plugin returned type we cannot handle (%d)",
+ aEncodedFrame->BufferType());
+ return;
+ }
+
+ MOZ_ASSERT(unitSize != 0);
+ MOZ_ASSERT(unitOffset + sizeNumBytes + unitSize <= aEncodedFrame->Size());
+ if (unitSize == 0 ||
+ unitOffset + sizeNumBytes + unitSize > aEncodedFrame->Size()) {
+ // XXX Should we kill the plugin for returning extra bytes? Probably
+ GMP_LOG_ERROR(
+ "GMP plugin returned badly formatted encoded data: "
+ "unitOffset=%u, sizeNumBytes=%d, unitSize=%u, size=%u",
+ unitOffset, sizeNumBytes, unitSize, aEncodedFrame->Size());
+ return;
+ }
+
+ unitOffset += sizeNumBytes + unitSize;
+ }
+
+ if (unitOffset != aEncodedFrame->Size()) {
+ // At most 3 bytes can be left over, depending on buffertype
+ GMP_LOG_DEBUG("GMP plugin returned %u extra bytes",
+ aEncodedFrame->Size() - unitOffset);
+ }
+
+ webrtc::EncodedImage unit;
+ unit.SetEncodedData(webrtc::EncodedImageBuffer::Create(
+ aEncodedFrame->Buffer(), aEncodedFrame->Size()));
+ unit._frameType = ft;
+ unit.SetTimestamp(timestamp);
+ unit.capture_time_ms_ = capture_time.ms();
+ unit._encodedWidth = aEncodedFrame->EncodedWidth();
+ unit._encodedHeight = aEncodedFrame->EncodedHeight();
+
+ // Parse QP.
+ mH264BitstreamParser.ParseBitstream(unit);
+ unit.qp_ = mH264BitstreamParser.GetLastSliceQp().value_or(-1);
+
+ // TODO: Currently the OpenH264 codec does not preserve any codec
+ // specific info passed into it and just returns default values.
+ // If this changes in the future, it would be nice to get rid of
+ // mCodecSpecificInfo.
+ mCallback->OnEncodedImage(unit, &mCodecSpecificInfo);
+}
+
+// Decoder.
+WebrtcGmpVideoDecoder::WebrtcGmpVideoDecoder(std::string aPCHandle,
+ TrackingId aTrackingId)
+ : mGMP(nullptr),
+ mInitting(false),
+ mHost(nullptr),
+ mCallbackMutex("WebrtcGmpVideoDecoder decoded callback mutex"),
+ mCallback(nullptr),
+ mDecoderStatus(GMPNoErr),
+ mPCHandle(std::move(aPCHandle)),
+ mTrackingId(std::move(aTrackingId)) {
+ MOZ_ASSERT(!mPCHandle.empty());
+}
+
+WebrtcGmpVideoDecoder::~WebrtcGmpVideoDecoder() {
+ // We should not have been destroyed if we never closed our GMP
+ MOZ_ASSERT(!mGMP);
+}
+
+bool WebrtcGmpVideoDecoder::Configure(
+ const webrtc::VideoDecoder::Settings& settings) {
+ if (!mMPS) {
+ mMPS = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+ }
+ MOZ_ASSERT(mMPS);
+
+ if (!mGMPThread) {
+ if (NS_WARN_IF(NS_FAILED(mMPS->GetThread(getter_AddRefs(mGMPThread))))) {
+ return false;
+ }
+ }
+
+ RefPtr<GmpInitDoneRunnable> initDone(new GmpInitDoneRunnable(mPCHandle));
+ mGMPThread->Dispatch(
+ WrapRunnableNM(&WebrtcGmpVideoDecoder::Configure_g,
+ RefPtr<WebrtcGmpVideoDecoder>(this), settings, initDone),
+ NS_DISPATCH_NORMAL);
+
+ return true;
+}
+
+/* static */
+void WebrtcGmpVideoDecoder::Configure_g(
+ const RefPtr<WebrtcGmpVideoDecoder>& aThis,
+ const webrtc::VideoDecoder::Settings& settings, // unused
+ const RefPtr<GmpInitDoneRunnable>& aInitDone) {
+ nsTArray<nsCString> tags;
+ tags.AppendElement("h264"_ns);
+ UniquePtr<GetGMPVideoDecoderCallback> callback(
+ new InitDoneCallback(aThis, aInitDone));
+ aThis->mInitting = true;
+ nsresult rv = aThis->mMPS->GetGMPVideoDecoder(nullptr, &tags, ""_ns,
+ std::move(callback));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ GMP_LOG_DEBUG("GMP Decode: GetGMPVideoDecoder failed");
+ aThis->Close_g();
+ aInitDone->Dispatch(WEBRTC_VIDEO_CODEC_ERROR,
+ "GMP Decode: GetGMPVideoDecoder failed.");
+ }
+}
+
+int32_t WebrtcGmpVideoDecoder::GmpInitDone(GMPVideoDecoderProxy* aGMP,
+ GMPVideoHost* aHost,
+ std::string* aErrorOut) {
+ if (!mInitting || !aGMP || !aHost) {
+ *aErrorOut =
+ "GMP Decode: Either init was aborted, "
+ "or init failed to supply either a GMP decoder or GMP host.";
+ if (aGMP) {
+ // This could destroy us, since aGMP may be the last thing holding a ref
+ // Return immediately.
+ aGMP->Close();
+ }
+ return WEBRTC_VIDEO_CODEC_ERROR;
+ }
+
+ mInitting = false;
+
+ if (mGMP && mGMP != aGMP) {
+ Close_g();
+ }
+
+ mGMP = aGMP;
+ mHost = aHost;
+ mCachedPluginId = Some(mGMP->GetPluginId());
+ mInitPluginEvent.Notify(*mCachedPluginId);
+ // Bug XXXXXX: transfer settings from codecSettings to codec.
+ GMPVideoCodec codec;
+ memset(&codec, 0, sizeof(codec));
+ codec.mGMPApiVersion = kGMPVersion34;
+ codec.mLogLevel = GetGMPLibraryLogLevel();
+
+ // XXX this is currently a hack
+ // GMPVideoCodecUnion codecSpecific;
+ // memset(&codecSpecific, 0, sizeof(codecSpecific));
+ nsTArray<uint8_t> codecSpecific;
+ nsresult rv = mGMP->InitDecode(codec, codecSpecific, this, 1);
+ if (NS_FAILED(rv)) {
+ *aErrorOut = "GMP Decode: InitDecode failed";
+ mQueuedFrames.Clear();
+ return WEBRTC_VIDEO_CODEC_ERROR;
+ }
+
+ // now release any frames that got queued waiting for InitDone
+ if (!mQueuedFrames.IsEmpty()) {
+ // So we're safe to call Decode_g(), which asserts it's empty
+ nsTArray<UniquePtr<GMPDecodeData>> temp = std::move(mQueuedFrames);
+ for (auto& queued : temp) {
+ Decode_g(RefPtr<WebrtcGmpVideoDecoder>(this), std::move(queued));
+ }
+ }
+
+ // This is an ugly solution to asynchronous decoding errors
+ // from Decode_g() not being returned to the synchronous Decode() method.
+ // If we don't return an error code at this point, our caller ultimately won't
+ // know to request a PLI and the video stream will remain frozen unless an IDR
+ // happens to arrive for other reasons. Bug 1492852 tracks implementing a
+ // proper solution.
+ if (mDecoderStatus != GMPNoErr) {
+ GMP_LOG_ERROR("%s: Decoder status is bad (%u)!", __PRETTY_FUNCTION__,
+ static_cast<unsigned>(mDecoderStatus));
+ return WEBRTC_VIDEO_CODEC_ERROR;
+ }
+
+ return WEBRTC_VIDEO_CODEC_OK;
+}
+
+void WebrtcGmpVideoDecoder::Close_g() {
+ GMPVideoDecoderProxy* gmp(mGMP);
+ mGMP = nullptr;
+ mHost = nullptr;
+ mInitting = false;
+
+ if (mCachedPluginId) {
+ mReleasePluginEvent.Notify(*mCachedPluginId);
+ }
+ mCachedPluginId = Nothing();
+
+ if (gmp) {
+ // Do this last, since this could cause us to be destroyed
+ gmp->Close();
+ }
+}
+
+int32_t WebrtcGmpVideoDecoder::Decode(const webrtc::EncodedImage& aInputImage,
+ bool aMissingFrames,
+ int64_t aRenderTimeMs) {
+ MOZ_ASSERT(mGMPThread);
+ MOZ_ASSERT(!NS_IsMainThread());
+ if (!aInputImage.size()) {
+ return WEBRTC_VIDEO_CODEC_ERROR;
+ }
+
+ MediaInfoFlag flag = MediaInfoFlag::None;
+ flag |= (aInputImage._frameType == webrtc::VideoFrameType::kVideoFrameKey
+ ? MediaInfoFlag::KeyFrame
+ : MediaInfoFlag::NonKeyFrame);
+ flag |= MediaInfoFlag::SoftwareDecoding;
+ flag |= MediaInfoFlag::VIDEO_H264;
+ mPerformanceRecorder.Start((aInputImage.Timestamp() * 1000ll) / 90,
+ "WebrtcGmpVideoDecoder"_ns, mTrackingId, flag);
+
+ // This is an ugly solution to asynchronous decoding errors
+ // from Decode_g() not being returned to the synchronous Decode() method.
+ // If we don't return an error code at this point, our caller ultimately won't
+ // know to request a PLI and the video stream will remain frozen unless an IDR
+ // happens to arrive for other reasons. Bug 1492852 tracks implementing a
+ // proper solution.
+ auto decodeData =
+ MakeUnique<GMPDecodeData>(aInputImage, aMissingFrames, aRenderTimeMs);
+
+ mGMPThread->Dispatch(WrapRunnableNM(&WebrtcGmpVideoDecoder::Decode_g,
+ RefPtr<WebrtcGmpVideoDecoder>(this),
+ std::move(decodeData)),
+ NS_DISPATCH_NORMAL);
+
+ if (mDecoderStatus != GMPNoErr) {
+ GMP_LOG_ERROR("%s: Decoder status is bad (%u)!", __PRETTY_FUNCTION__,
+ static_cast<unsigned>(mDecoderStatus));
+ return WEBRTC_VIDEO_CODEC_ERROR;
+ }
+
+ return WEBRTC_VIDEO_CODEC_OK;
+}
+
+/* static */
+void WebrtcGmpVideoDecoder::Decode_g(const RefPtr<WebrtcGmpVideoDecoder>& aThis,
+ UniquePtr<GMPDecodeData>&& aDecodeData) {
+ if (!aThis->mGMP) {
+ if (aThis->mInitting) {
+ // InitDone hasn't been called yet (race)
+ aThis->mQueuedFrames.AppendElement(std::move(aDecodeData));
+ return;
+ }
+ // destroyed via Terminate(), failed to init, or just not initted yet
+ GMP_LOG_DEBUG("GMP Decode: not initted yet");
+
+ aThis->mDecoderStatus = GMPDecodeErr;
+ return;
+ }
+
+ MOZ_ASSERT(aThis->mQueuedFrames.IsEmpty());
+ MOZ_ASSERT(aThis->mHost);
+
+ GMPVideoFrame* ftmp = nullptr;
+ GMPErr err = aThis->mHost->CreateFrame(kGMPEncodedVideoFrame, &ftmp);
+ if (err != GMPNoErr) {
+ GMP_LOG_ERROR("%s: CreateFrame failed (%u)!", __PRETTY_FUNCTION__,
+ static_cast<unsigned>(err));
+ aThis->mDecoderStatus = err;
+ return;
+ }
+
+ GMPUniquePtr<GMPVideoEncodedFrame> frame(
+ static_cast<GMPVideoEncodedFrame*>(ftmp));
+ err = frame->CreateEmptyFrame(aDecodeData->mImage.size());
+ if (err != GMPNoErr) {
+ GMP_LOG_ERROR("%s: CreateEmptyFrame failed (%u)!", __PRETTY_FUNCTION__,
+ static_cast<unsigned>(err));
+ aThis->mDecoderStatus = err;
+ return;
+ }
+
+ // XXX At this point, we only will get mode1 data (a single length and a
+ // buffer) Session_info.cc/etc code needs to change to support mode 0.
+ *(reinterpret_cast<uint32_t*>(frame->Buffer())) = frame->Size();
+
+ // XXX It'd be wonderful not to have to memcpy the encoded data!
+ memcpy(frame->Buffer() + 4, aDecodeData->mImage.data() + 4,
+ frame->Size() - 4);
+
+ frame->SetEncodedWidth(aDecodeData->mImage._encodedWidth);
+ frame->SetEncodedHeight(aDecodeData->mImage._encodedHeight);
+ frame->SetTimeStamp((aDecodeData->mImage.Timestamp() * 1000ll) /
+ 90); // rounds down
+ frame->SetCompleteFrame(
+ true); // upstream no longer deals with incomplete frames
+ frame->SetBufferType(GMP_BufferLength32);
+
+ GMPVideoFrameType ft;
+ int32_t ret =
+ WebrtcFrameTypeToGmpFrameType(aDecodeData->mImage._frameType, &ft);
+ if (ret != WEBRTC_VIDEO_CODEC_OK) {
+ GMP_LOG_ERROR("%s: WebrtcFrameTypeToGmpFrameType failed (%u)!",
+ __PRETTY_FUNCTION__, static_cast<unsigned>(ret));
+ aThis->mDecoderStatus = GMPDecodeErr;
+ return;
+ }
+
+ // Bug XXXXXX: Set codecSpecific info
+ GMPCodecSpecificInfo info;
+ memset(&info, 0, sizeof(info));
+ info.mCodecType = kGMPVideoCodecH264;
+ info.mCodecSpecific.mH264.mSimulcastIdx = 0;
+ nsTArray<uint8_t> codecSpecificInfo;
+ codecSpecificInfo.AppendElements((uint8_t*)&info,
+ sizeof(GMPCodecSpecificInfo));
+
+ GMP_LOG_DEBUG("GMP Decode: %" PRIu64 ", len %zu%s", frame->TimeStamp(),
+ aDecodeData->mImage.size(),
+ ft == kGMPKeyFrame ? ", KeyFrame" : "");
+
+ nsresult rv =
+ aThis->mGMP->Decode(std::move(frame), aDecodeData->mMissingFrames,
+ codecSpecificInfo, aDecodeData->mRenderTimeMs);
+ if (NS_FAILED(rv)) {
+ GMP_LOG_ERROR("%s: Decode failed (rv=%u)!", __PRETTY_FUNCTION__,
+ static_cast<unsigned>(rv));
+ aThis->mDecoderStatus = GMPDecodeErr;
+ return;
+ }
+
+ aThis->mDecoderStatus = GMPNoErr;
+}
+
+int32_t WebrtcGmpVideoDecoder::RegisterDecodeCompleteCallback(
+ webrtc::DecodedImageCallback* aCallback) {
+ MutexAutoLock lock(mCallbackMutex);
+ mCallback = aCallback;
+
+ return WEBRTC_VIDEO_CODEC_OK;
+}
+
+/* static */
+void WebrtcGmpVideoDecoder::ReleaseGmp_g(
+ const RefPtr<WebrtcGmpVideoDecoder>& aDecoder) {
+ aDecoder->Close_g();
+}
+
+int32_t WebrtcGmpVideoDecoder::ReleaseGmp() {
+ GMP_LOG_DEBUG("GMP Released:");
+ RegisterDecodeCompleteCallback(nullptr);
+
+ if (mGMPThread) {
+ mGMPThread->Dispatch(WrapRunnableNM(&WebrtcGmpVideoDecoder::ReleaseGmp_g,
+ RefPtr<WebrtcGmpVideoDecoder>(this)),
+ NS_DISPATCH_NORMAL);
+ }
+ return WEBRTC_VIDEO_CODEC_OK;
+}
+
+void WebrtcGmpVideoDecoder::Terminated() {
+ GMP_LOG_DEBUG("GMP Decoder Terminated: %p", (void*)this);
+
+ GMPVideoDecoderProxy* gmp(mGMP);
+ mGMP = nullptr;
+ mHost = nullptr;
+ mInitting = false;
+
+ if (gmp) {
+ // Do this last, since this could cause us to be destroyed
+ gmp->Close();
+ }
+
+ // Could now notify that it's dead
+}
+
+void WebrtcGmpVideoDecoder::Decoded(GMPVideoi420Frame* aDecodedFrame) {
+ // we have two choices here: wrap the frame with a callback that frees
+ // the data later (risking running out of shmems), or copy the data out
+ // always. Also, we can only Destroy() the frame on the gmp thread, so
+ // copying is simplest if expensive.
+ // I420 size including rounding...
+ CheckedInt32 length =
+ (CheckedInt32(aDecodedFrame->Stride(kGMPYPlane)) *
+ aDecodedFrame->Height()) +
+ (aDecodedFrame->Stride(kGMPVPlane) + aDecodedFrame->Stride(kGMPUPlane)) *
+ ((aDecodedFrame->Height() + 1) / 2);
+ int32_t size = length.value();
+ MOZ_RELEASE_ASSERT(length.isValid() && size > 0);
+
+ // Don't use MakeUniqueFallible here, because UniquePtr isn't copyable, and
+ // the closure below in WrapI420Buffer uses std::function which _is_ copyable.
+ // We'll alloc the buffer here, so we preserve the "fallible" nature, and
+ // then hand a shared_ptr, which is copyable, to WrapI420Buffer.
+ auto falliblebuffer = new (std::nothrow) uint8_t[size];
+ if (falliblebuffer) {
+ auto buffer = std::shared_ptr<uint8_t>(falliblebuffer);
+
+ // This is 3 separate buffers currently anyways, no use in trying to
+ // see if we can use a single memcpy.
+ uint8_t* buffer_y = buffer.get();
+ memcpy(buffer_y, aDecodedFrame->Buffer(kGMPYPlane),
+ aDecodedFrame->Stride(kGMPYPlane) * aDecodedFrame->Height());
+ // Should this be aligned, making it non-contiguous? Assume no, this is
+ // already factored into the strides.
+ uint8_t* buffer_u =
+ buffer_y + aDecodedFrame->Stride(kGMPYPlane) * aDecodedFrame->Height();
+ memcpy(buffer_u, aDecodedFrame->Buffer(kGMPUPlane),
+ aDecodedFrame->Stride(kGMPUPlane) *
+ ((aDecodedFrame->Height() + 1) / 2));
+ uint8_t* buffer_v = buffer_u + aDecodedFrame->Stride(kGMPUPlane) *
+ ((aDecodedFrame->Height() + 1) / 2);
+ memcpy(buffer_v, aDecodedFrame->Buffer(kGMPVPlane),
+ aDecodedFrame->Stride(kGMPVPlane) *
+ ((aDecodedFrame->Height() + 1) / 2));
+
+ MutexAutoLock lock(mCallbackMutex);
+ if (mCallback) {
+ // Note: the last parameter to WrapI420Buffer is named no_longer_used,
+ // but is currently called in the destructor of WrappedYuvBuffer when
+ // the buffer is "no_longer_used".
+ rtc::scoped_refptr<webrtc::I420BufferInterface> video_frame_buffer =
+ webrtc::WrapI420Buffer(
+ aDecodedFrame->Width(), aDecodedFrame->Height(), buffer_y,
+ aDecodedFrame->Stride(kGMPYPlane), buffer_u,
+ aDecodedFrame->Stride(kGMPUPlane), buffer_v,
+ aDecodedFrame->Stride(kGMPVPlane), [buffer] {});
+
+ GMP_LOG_DEBUG("GMP Decoded: %" PRIu64, aDecodedFrame->Timestamp());
+ auto videoFrame =
+ webrtc::VideoFrame::Builder()
+ .set_video_frame_buffer(video_frame_buffer)
+ .set_timestamp_rtp(
+ // round up
+ (aDecodedFrame->UpdatedTimestamp() * 90ll + 999) / 1000)
+ .build();
+ mPerformanceRecorder.Record(
+ static_cast<int64_t>(aDecodedFrame->Timestamp()),
+ [&](DecodeStage& aStage) {
+ aStage.SetImageFormat(DecodeStage::YUV420P);
+ aStage.SetResolution(aDecodedFrame->Width(),
+ aDecodedFrame->Height());
+ aStage.SetColorDepth(gfx::ColorDepth::COLOR_8);
+ });
+ mCallback->Decoded(videoFrame);
+ }
+ }
+ aDecodedFrame->Destroy();
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/libwebrtcglue/WebrtcGmpVideoCodec.h b/dom/media/webrtc/libwebrtcglue/WebrtcGmpVideoCodec.h
new file mode 100644
index 0000000000..ef3e1bd5af
--- /dev/null
+++ b/dom/media/webrtc/libwebrtcglue/WebrtcGmpVideoCodec.h
@@ -0,0 +1,507 @@
+/*
+ * Copyright (c) 2012, The WebRTC project authors. 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 Google 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
+ * HOLDER 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 WEBRTCGMPVIDEOCODEC_H_
+#define WEBRTCGMPVIDEOCODEC_H_
+
+#include <queue>
+#include <string>
+
+#include "nsThreadUtils.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/Telemetry.h"
+
+#include "mozIGeckoMediaPluginService.h"
+#include "MediaConduitInterface.h"
+#include "AudioConduit.h"
+#include "PerformanceRecorder.h"
+#include "VideoConduit.h"
+#include "api/video/video_frame_type.h"
+#include "modules/video_coding/include/video_codec_interface.h"
+#include "common_video/h264/h264_bitstream_parser.h"
+
+#include "gmp-video-host.h"
+#include "GMPVideoDecoderProxy.h"
+#include "GMPVideoEncoderProxy.h"
+
+#include "jsapi/PeerConnectionImpl.h"
+
+namespace mozilla {
+
+class GmpInitDoneRunnable : public Runnable {
+ public:
+ explicit GmpInitDoneRunnable(std::string aPCHandle)
+ : Runnable("GmpInitDoneRunnable"),
+ mResult(WEBRTC_VIDEO_CODEC_OK),
+ mPCHandle(std::move(aPCHandle)) {}
+
+ NS_IMETHOD Run() override {
+ Telemetry::Accumulate(Telemetry::WEBRTC_GMP_INIT_SUCCESS,
+ mResult == WEBRTC_VIDEO_CODEC_OK);
+ if (mResult == WEBRTC_VIDEO_CODEC_OK) {
+ // Might be useful to notify the PeerConnection about successful init
+ // someday.
+ return NS_OK;
+ }
+
+ PeerConnectionWrapper wrapper(mPCHandle);
+ if (wrapper.impl()) {
+ wrapper.impl()->OnMediaError(mError);
+ }
+ return NS_OK;
+ }
+
+ void Dispatch(int32_t aResult, const std::string& aError = "") {
+ mResult = aResult;
+ mError = aError;
+ nsCOMPtr<nsIThread> mainThread(do_GetMainThread());
+ if (mainThread) {
+ // For some reason, the compiler on CI is treating |this| as a const
+ // pointer, despite the fact that we're in a non-const function. And,
+ // interestingly enough, correcting this doesn't require a const_cast.
+ mainThread->Dispatch(do_AddRef(static_cast<nsIRunnable*>(this)),
+ NS_DISPATCH_NORMAL);
+ }
+ }
+
+ int32_t Result() { return mResult; }
+
+ private:
+ int32_t mResult;
+ const std::string mPCHandle;
+ std::string mError;
+};
+
+// Hold a frame for later decode
+class GMPDecodeData {
+ public:
+ GMPDecodeData(const webrtc::EncodedImage& aInputImage, bool aMissingFrames,
+ int64_t aRenderTimeMs)
+ : mImage(aInputImage),
+ mMissingFrames(aMissingFrames),
+ mRenderTimeMs(aRenderTimeMs) {
+ // We want to use this for queuing, and the calling code recycles the
+ // buffer on return from Decode()
+ MOZ_RELEASE_ASSERT(aInputImage.size() <
+ (std::numeric_limits<size_t>::max() >> 1));
+ }
+
+ ~GMPDecodeData() = default;
+
+ const webrtc::EncodedImage mImage;
+ const bool mMissingFrames;
+ const int64_t mRenderTimeMs;
+};
+
+class RefCountedWebrtcVideoEncoder {
+ public:
+ NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
+
+ // Implement sort of WebrtcVideoEncoder interface and support refcounting.
+ // (We cannot use |Release|, since that's needed for nsRefPtr)
+ virtual int32_t InitEncode(
+ const webrtc::VideoCodec* aCodecSettings,
+ const webrtc::VideoEncoder::Settings& aSettings) = 0;
+
+ virtual int32_t Encode(
+ const webrtc::VideoFrame& aInputImage,
+ const std::vector<webrtc::VideoFrameType>* aFrameTypes) = 0;
+
+ virtual int32_t RegisterEncodeCompleteCallback(
+ webrtc::EncodedImageCallback* aCallback) = 0;
+
+ virtual int32_t Shutdown() = 0;
+
+ virtual int32_t SetRates(
+ const webrtc::VideoEncoder::RateControlParameters& aParameters) = 0;
+
+ virtual MediaEventSource<uint64_t>* InitPluginEvent() = 0;
+
+ virtual MediaEventSource<uint64_t>* ReleasePluginEvent() = 0;
+
+ virtual WebrtcVideoEncoder::EncoderInfo GetEncoderInfo() const = 0;
+
+ protected:
+ virtual ~RefCountedWebrtcVideoEncoder() = default;
+};
+
+class WebrtcGmpVideoEncoder : public GMPVideoEncoderCallbackProxy,
+ public RefCountedWebrtcVideoEncoder {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebrtcGmpVideoEncoder, final);
+
+ WebrtcGmpVideoEncoder(const webrtc::SdpVideoFormat& aFormat,
+ std::string aPCHandle);
+
+ // Implement VideoEncoder interface, sort of.
+ // (We cannot use |Release|, since that's needed for nsRefPtr)
+ int32_t InitEncode(const webrtc::VideoCodec* aCodecSettings,
+ const webrtc::VideoEncoder::Settings& aSettings) override;
+
+ int32_t Encode(
+ const webrtc::VideoFrame& aInputImage,
+ const std::vector<webrtc::VideoFrameType>* aFrameTypes) override;
+
+ int32_t RegisterEncodeCompleteCallback(
+ webrtc::EncodedImageCallback* aCallback) override;
+
+ int32_t Shutdown() override;
+
+ int32_t SetRates(
+ const webrtc::VideoEncoder::RateControlParameters& aParameters) override;
+
+ WebrtcVideoEncoder::EncoderInfo GetEncoderInfo() const override;
+
+ MediaEventSource<uint64_t>* InitPluginEvent() override {
+ return &mInitPluginEvent;
+ }
+
+ MediaEventSource<uint64_t>* ReleasePluginEvent() override {
+ return &mReleasePluginEvent;
+ }
+
+ // GMPVideoEncoderCallback virtual functions.
+ virtual void Terminated() override;
+
+ virtual void Encoded(GMPVideoEncodedFrame* aEncodedFrame,
+ const nsTArray<uint8_t>& aCodecSpecificInfo) override;
+
+ virtual void Error(GMPErr aError) override {}
+
+ private:
+ virtual ~WebrtcGmpVideoEncoder();
+
+ static void InitEncode_g(const RefPtr<WebrtcGmpVideoEncoder>& aThis,
+ const GMPVideoCodec& aCodecParams,
+ int32_t aNumberOfCores, uint32_t aMaxPayloadSize,
+ const RefPtr<GmpInitDoneRunnable>& aInitDone);
+ int32_t GmpInitDone(GMPVideoEncoderProxy* aGMP, GMPVideoHost* aHost,
+ const GMPVideoCodec& aCodecParams,
+ std::string* aErrorOut);
+ int32_t GmpInitDone(GMPVideoEncoderProxy* aGMP, GMPVideoHost* aHost,
+ std::string* aErrorOut);
+ int32_t InitEncoderForSize(unsigned short aWidth, unsigned short aHeight,
+ std::string* aErrorOut);
+ static void ReleaseGmp_g(const RefPtr<WebrtcGmpVideoEncoder>& aEncoder);
+ void Close_g();
+
+ class InitDoneCallback : public GetGMPVideoEncoderCallback {
+ public:
+ InitDoneCallback(const RefPtr<WebrtcGmpVideoEncoder>& aEncoder,
+ const RefPtr<GmpInitDoneRunnable>& aInitDone,
+ const GMPVideoCodec& aCodecParams)
+ : mEncoder(aEncoder),
+ mInitDone(aInitDone),
+ mCodecParams(aCodecParams) {}
+
+ virtual void Done(GMPVideoEncoderProxy* aGMP,
+ GMPVideoHost* aHost) override {
+ std::string errorOut;
+ int32_t result =
+ mEncoder->GmpInitDone(aGMP, aHost, mCodecParams, &errorOut);
+
+ mInitDone->Dispatch(result, errorOut);
+ }
+
+ private:
+ const RefPtr<WebrtcGmpVideoEncoder> mEncoder;
+ const RefPtr<GmpInitDoneRunnable> mInitDone;
+ const GMPVideoCodec mCodecParams;
+ };
+
+ static void Encode_g(const RefPtr<WebrtcGmpVideoEncoder>& aEncoder,
+ webrtc::VideoFrame aInputImage,
+ std::vector<webrtc::VideoFrameType> aFrameTypes);
+ void RegetEncoderForResolutionChange(
+ uint32_t aWidth, uint32_t aHeight,
+ const RefPtr<GmpInitDoneRunnable>& aInitDone);
+
+ class InitDoneForResolutionChangeCallback
+ : public GetGMPVideoEncoderCallback {
+ public:
+ InitDoneForResolutionChangeCallback(
+ const RefPtr<WebrtcGmpVideoEncoder>& aEncoder,
+ const RefPtr<GmpInitDoneRunnable>& aInitDone, uint32_t aWidth,
+ uint32_t aHeight)
+ : mEncoder(aEncoder),
+ mInitDone(aInitDone),
+ mWidth(aWidth),
+ mHeight(aHeight) {}
+
+ virtual void Done(GMPVideoEncoderProxy* aGMP,
+ GMPVideoHost* aHost) override {
+ std::string errorOut;
+ int32_t result = mEncoder->GmpInitDone(aGMP, aHost, &errorOut);
+ if (result != WEBRTC_VIDEO_CODEC_OK) {
+ mInitDone->Dispatch(result, errorOut);
+ return;
+ }
+
+ result = mEncoder->InitEncoderForSize(mWidth, mHeight, &errorOut);
+ mInitDone->Dispatch(result, errorOut);
+ }
+
+ private:
+ const RefPtr<WebrtcGmpVideoEncoder> mEncoder;
+ const RefPtr<GmpInitDoneRunnable> mInitDone;
+ const uint32_t mWidth;
+ const uint32_t mHeight;
+ };
+
+ static int32_t SetRates_g(RefPtr<WebrtcGmpVideoEncoder> aThis,
+ uint32_t aNewBitRateKbps, Maybe<double> aFrameRate);
+
+ nsCOMPtr<mozIGeckoMediaPluginService> mMPS;
+ nsCOMPtr<nsIThread> mGMPThread;
+ GMPVideoEncoderProxy* mGMP;
+ // Used to handle a race where Release() is called while init is in progress
+ bool mInitting;
+ GMPVideoHost* mHost;
+ GMPVideoCodec mCodecParams;
+ uint32_t mMaxPayloadSize;
+ const webrtc::SdpVideoFormat::Parameters mFormatParams;
+ webrtc::CodecSpecificInfo mCodecSpecificInfo;
+ webrtc::H264BitstreamParser mH264BitstreamParser;
+ // Protects mCallback
+ Mutex mCallbackMutex MOZ_UNANNOTATED;
+ webrtc::EncodedImageCallback* mCallback;
+ Maybe<uint64_t> mCachedPluginId;
+ const std::string mPCHandle;
+
+ struct InputImageData {
+ int64_t timestamp_us;
+ };
+ // Map rtp time -> input image data
+ DataMutex<std::map<uint32_t, InputImageData>> mInputImageMap;
+
+ MediaEventProducer<uint64_t> mInitPluginEvent;
+ MediaEventProducer<uint64_t> mReleasePluginEvent;
+};
+
+// Basically a strong ref to a RefCountedWebrtcVideoEncoder, that also
+// translates from Release() to RefCountedWebrtcVideoEncoder::Shutdown(),
+// since we need RefCountedWebrtcVideoEncoder::Release() for managing the
+// refcount. The webrtc.org code gets one of these, so it doesn't unilaterally
+// delete the "real" encoder.
+class WebrtcVideoEncoderProxy : public WebrtcVideoEncoder {
+ public:
+ explicit WebrtcVideoEncoderProxy(
+ RefPtr<RefCountedWebrtcVideoEncoder> aEncoder)
+ : mEncoderImpl(std::move(aEncoder)) {}
+
+ virtual ~WebrtcVideoEncoderProxy() {
+ RegisterEncodeCompleteCallback(nullptr);
+ }
+
+ MediaEventSource<uint64_t>* InitPluginEvent() override {
+ return mEncoderImpl->InitPluginEvent();
+ }
+
+ MediaEventSource<uint64_t>* ReleasePluginEvent() override {
+ return mEncoderImpl->ReleasePluginEvent();
+ }
+
+ int32_t InitEncode(const webrtc::VideoCodec* aCodecSettings,
+ const WebrtcVideoEncoder::Settings& aSettings) override {
+ return mEncoderImpl->InitEncode(aCodecSettings, aSettings);
+ }
+
+ int32_t Encode(
+ const webrtc::VideoFrame& aInputImage,
+ const std::vector<webrtc::VideoFrameType>* aFrameTypes) override {
+ return mEncoderImpl->Encode(aInputImage, aFrameTypes);
+ }
+
+ int32_t RegisterEncodeCompleteCallback(
+ webrtc::EncodedImageCallback* aCallback) override {
+ return mEncoderImpl->RegisterEncodeCompleteCallback(aCallback);
+ }
+
+ int32_t Release() override { return mEncoderImpl->Shutdown(); }
+
+ void SetRates(const RateControlParameters& aParameters) override {
+ mEncoderImpl->SetRates(aParameters);
+ }
+
+ EncoderInfo GetEncoderInfo() const override {
+ return mEncoderImpl->GetEncoderInfo();
+ }
+
+ private:
+ const RefPtr<RefCountedWebrtcVideoEncoder> mEncoderImpl;
+};
+
+class WebrtcGmpVideoDecoder : public GMPVideoDecoderCallbackProxy {
+ public:
+ WebrtcGmpVideoDecoder(std::string aPCHandle, TrackingId aTrackingId);
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebrtcGmpVideoDecoder, final);
+
+ // Implement VideoEncoder interface, sort of.
+ // (We cannot use |Release|, since that's needed for nsRefPtr)
+ virtual bool Configure(const webrtc::VideoDecoder::Settings& settings);
+ virtual int32_t Decode(const webrtc::EncodedImage& aInputImage,
+ bool aMissingFrames, int64_t aRenderTimeMs);
+ virtual int32_t RegisterDecodeCompleteCallback(
+ webrtc::DecodedImageCallback* aCallback);
+
+ virtual int32_t ReleaseGmp();
+
+ MediaEventSource<uint64_t>* InitPluginEvent() { return &mInitPluginEvent; }
+
+ MediaEventSource<uint64_t>* ReleasePluginEvent() {
+ return &mReleasePluginEvent;
+ }
+
+ // GMPVideoDecoderCallbackProxy
+ virtual void Terminated() override;
+
+ virtual void Decoded(GMPVideoi420Frame* aDecodedFrame) override;
+
+ virtual void ReceivedDecodedReferenceFrame(
+ const uint64_t aPictureId) override {
+ MOZ_CRASH();
+ }
+
+ virtual void ReceivedDecodedFrame(const uint64_t aPictureId) override {
+ MOZ_CRASH();
+ }
+
+ virtual void InputDataExhausted() override {}
+
+ virtual void DrainComplete() override {}
+
+ virtual void ResetComplete() override {}
+
+ virtual void Error(GMPErr aError) override { mDecoderStatus = aError; }
+
+ private:
+ virtual ~WebrtcGmpVideoDecoder();
+
+ static void Configure_g(const RefPtr<WebrtcGmpVideoDecoder>& aThis,
+ const webrtc::VideoDecoder::Settings& settings,
+ const RefPtr<GmpInitDoneRunnable>& aInitDone);
+ int32_t GmpInitDone(GMPVideoDecoderProxy* aGMP, GMPVideoHost* aHost,
+ std::string* aErrorOut);
+ static void ReleaseGmp_g(const RefPtr<WebrtcGmpVideoDecoder>& aDecoder);
+ void Close_g();
+
+ class InitDoneCallback : public GetGMPVideoDecoderCallback {
+ public:
+ explicit InitDoneCallback(const RefPtr<WebrtcGmpVideoDecoder>& aDecoder,
+ const RefPtr<GmpInitDoneRunnable>& aInitDone)
+ : mDecoder(aDecoder), mInitDone(aInitDone) {}
+
+ virtual void Done(GMPVideoDecoderProxy* aGMP,
+ GMPVideoHost* aHost) override {
+ std::string errorOut;
+ int32_t result = mDecoder->GmpInitDone(aGMP, aHost, &errorOut);
+
+ mInitDone->Dispatch(result, errorOut);
+ }
+
+ private:
+ const RefPtr<WebrtcGmpVideoDecoder> mDecoder;
+ const RefPtr<GmpInitDoneRunnable> mInitDone;
+ };
+
+ static void Decode_g(const RefPtr<WebrtcGmpVideoDecoder>& aThis,
+ UniquePtr<GMPDecodeData>&& aDecodeData);
+
+ nsCOMPtr<mozIGeckoMediaPluginService> mMPS;
+ nsCOMPtr<nsIThread> mGMPThread;
+ GMPVideoDecoderProxy* mGMP; // Addref is held for us
+ // Used to handle a race where Release() is called while init is in progress
+ bool mInitting;
+ // Frames queued for decode while mInitting is true
+ nsTArray<UniquePtr<GMPDecodeData>> mQueuedFrames;
+ GMPVideoHost* mHost;
+ // Protects mCallback
+ Mutex mCallbackMutex MOZ_UNANNOTATED;
+ webrtc::DecodedImageCallback* mCallback;
+ Maybe<uint64_t> mCachedPluginId;
+ Atomic<GMPErr, ReleaseAcquire> mDecoderStatus;
+ const std::string mPCHandle;
+ const TrackingId mTrackingId;
+ PerformanceRecorderMulti<DecodeStage> mPerformanceRecorder;
+
+ MediaEventProducer<uint64_t> mInitPluginEvent;
+ MediaEventProducer<uint64_t> mReleasePluginEvent;
+};
+
+// Basically a strong ref to a WebrtcGmpVideoDecoder, that also translates
+// from Release() to WebrtcGmpVideoDecoder::ReleaseGmp(), since we need
+// WebrtcGmpVideoDecoder::Release() for managing the refcount.
+// The webrtc.org code gets one of these, so it doesn't unilaterally delete
+// the "real" encoder.
+class WebrtcVideoDecoderProxy : public WebrtcVideoDecoder {
+ public:
+ explicit WebrtcVideoDecoderProxy(std::string aPCHandle,
+ TrackingId aTrackingId)
+ : mDecoderImpl(new WebrtcGmpVideoDecoder(std::move(aPCHandle),
+ std::move(aTrackingId))) {}
+
+ virtual ~WebrtcVideoDecoderProxy() {
+ RegisterDecodeCompleteCallback(nullptr);
+ }
+
+ MediaEventSource<uint64_t>* InitPluginEvent() override {
+ return mDecoderImpl->InitPluginEvent();
+ }
+
+ MediaEventSource<uint64_t>* ReleasePluginEvent() override {
+ return mDecoderImpl->ReleasePluginEvent();
+ }
+
+ bool Configure(const Settings& settings) override {
+ return mDecoderImpl->Configure(settings);
+ }
+
+ int32_t Decode(const webrtc::EncodedImage& aInputImage, bool aMissingFrames,
+ int64_t aRenderTimeMs) override {
+ return mDecoderImpl->Decode(aInputImage, aMissingFrames, aRenderTimeMs);
+ }
+
+ int32_t RegisterDecodeCompleteCallback(
+ webrtc::DecodedImageCallback* aCallback) override {
+ return mDecoderImpl->RegisterDecodeCompleteCallback(aCallback);
+ }
+
+ int32_t Release() override { return mDecoderImpl->ReleaseGmp(); }
+
+ private:
+ const RefPtr<WebrtcGmpVideoDecoder> mDecoderImpl;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/libwebrtcglue/WebrtcImageBuffer.h b/dom/media/webrtc/libwebrtcglue/WebrtcImageBuffer.h
new file mode 100644
index 0000000000..305f4df577
--- /dev/null
+++ b/dom/media/webrtc/libwebrtcglue/WebrtcImageBuffer.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WebrtcImageBuffer_h__
+#define WebrtcImageBuffer_h__
+
+#include "common_video/include/video_frame_buffer.h"
+
+namespace mozilla {
+namespace layers {
+class Image;
+}
+
+class ImageBuffer : public webrtc::VideoFrameBuffer {
+ public:
+ explicit ImageBuffer(RefPtr<layers::Image>&& aImage)
+ : mImage(std::move(aImage)) {}
+
+ rtc::scoped_refptr<webrtc::I420BufferInterface> ToI420() override {
+ RefPtr<layers::PlanarYCbCrImage> image = mImage->AsPlanarYCbCrImage();
+ MOZ_ASSERT(image);
+ if (!image) {
+ // TODO. YUV420 ReadBack, Image only provides a RGB readback.
+ return nullptr;
+ }
+ const layers::PlanarYCbCrData* data = image->GetData();
+ rtc::scoped_refptr<webrtc::I420BufferInterface> buf =
+ webrtc::WrapI420Buffer(
+ data->mPictureRect.width, data->mPictureRect.height,
+ data->mYChannel, data->mYStride, data->mCbChannel,
+ data->mCbCrStride, data->mCrChannel, data->mCbCrStride,
+ [image] { /* keep reference alive*/ });
+ return buf;
+ }
+
+ Type type() const override { return Type::kNative; }
+
+ int width() const override { return mImage->GetSize().width; }
+
+ int height() const override { return mImage->GetSize().height; }
+
+ RefPtr<layers::Image> GetNativeImage() const { return mImage; }
+
+ private:
+ const RefPtr<layers::Image> mImage;
+};
+
+} // namespace mozilla
+
+#endif // WebrtcImageBuffer_h__
diff --git a/dom/media/webrtc/libwebrtcglue/WebrtcMediaDataDecoderCodec.cpp b/dom/media/webrtc/libwebrtcglue/WebrtcMediaDataDecoderCodec.cpp
new file mode 100644
index 0000000000..f132e6a124
--- /dev/null
+++ b/dom/media/webrtc/libwebrtcglue/WebrtcMediaDataDecoderCodec.cpp
@@ -0,0 +1,209 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebrtcMediaDataDecoderCodec.h"
+
+#include "ImageContainer.h"
+#include "MediaDataDecoderProxy.h"
+#include "PDMFactory.h"
+#include "VideoUtils.h"
+#include "mozilla/layers/ImageBridgeChild.h"
+#include "mozilla/media/MediaUtils.h"
+#include "mozilla/StaticPrefs_media.h"
+
+namespace mozilla {
+
+WebrtcMediaDataDecoder::WebrtcMediaDataDecoder(nsACString& aCodecMimeType,
+ TrackingId aTrackingId)
+ : mThreadPool(GetMediaThreadPool(MediaThreadType::SUPERVISOR)),
+ mTaskQueue(TaskQueue::Create(do_AddRef(mThreadPool),
+ "WebrtcMediaDataDecoder::mTaskQueue")),
+ mImageContainer(MakeAndAddRef<layers::ImageContainer>(
+ layers::ImageContainer::ASYNCHRONOUS)),
+ mFactory(new PDMFactory()),
+ mTrackType(TrackInfo::kUndefinedTrack),
+ mCodecType(aCodecMimeType),
+ mTrackingId(std::move(aTrackingId)) {}
+
+WebrtcMediaDataDecoder::~WebrtcMediaDataDecoder() {}
+
+bool WebrtcMediaDataDecoder::Configure(
+ const webrtc::VideoDecoder::Settings& settings) {
+ nsCString codec;
+ mTrackType = TrackInfo::kVideoTrack;
+ mInfo = VideoInfo(settings.max_render_resolution().Width(),
+ settings.max_render_resolution().Height());
+ mInfo.mMimeType = mCodecType;
+
+#ifdef MOZ_WIDGET_GTK
+ if (mInfo.mMimeType.EqualsLiteral("video/vp8") &&
+ !StaticPrefs::media_navigator_mediadatadecoder_vp8_hardware_enabled()) {
+ mDisabledHardwareAcceleration = true;
+ }
+#endif
+
+ return WEBRTC_VIDEO_CODEC_OK == CreateDecoder();
+}
+
+int32_t WebrtcMediaDataDecoder::Decode(const webrtc::EncodedImage& aInputImage,
+ bool aMissingFrames,
+ int64_t aRenderTimeMs) {
+ if (!mCallback || !mDecoder) {
+ return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
+ }
+
+ if (!aInputImage.data() || !aInputImage.size()) {
+ return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
+ }
+
+ // Always start with a complete key frame.
+ if (mNeedKeyframe) {
+ if (aInputImage._frameType != webrtc::VideoFrameType::kVideoFrameKey)
+ return WEBRTC_VIDEO_CODEC_ERROR;
+ // We have a key frame - is it complete?
+ mNeedKeyframe = false;
+ }
+
+ auto disabledHardwareAcceleration =
+ MakeScopeExit([&] { mDisabledHardwareAcceleration = true; });
+
+ RefPtr<MediaRawData> compressedFrame =
+ new MediaRawData(aInputImage.data(), aInputImage.size());
+ if (!compressedFrame->Data()) {
+ return WEBRTC_VIDEO_CODEC_MEMORY;
+ }
+
+ compressedFrame->mTime =
+ media::TimeUnit::FromMicroseconds(aInputImage.Timestamp());
+ compressedFrame->mTimecode =
+ media::TimeUnit::FromMicroseconds(aRenderTimeMs * 1000);
+ compressedFrame->mKeyframe =
+ aInputImage._frameType == webrtc::VideoFrameType::kVideoFrameKey;
+ {
+ media::Await(
+ do_AddRef(mThreadPool), mDecoder->Decode(compressedFrame),
+ [&](const MediaDataDecoder::DecodedData& aResults) {
+ mResults = aResults.Clone();
+ mError = NS_OK;
+ },
+ [&](const MediaResult& aError) { mError = aError; });
+
+ for (auto& frame : mResults) {
+ MOZ_ASSERT(frame->mType == MediaData::Type::VIDEO_DATA);
+ RefPtr<VideoData> video = frame->As<VideoData>();
+ MOZ_ASSERT(video);
+ if (!video->mImage) {
+ // Nothing to display.
+ continue;
+ }
+ rtc::scoped_refptr<ImageBuffer> image(
+ new rtc::RefCountedObject<ImageBuffer>(std::move(video->mImage)));
+
+ auto videoFrame = webrtc::VideoFrame::Builder()
+ .set_video_frame_buffer(image)
+ .set_timestamp_rtp(aInputImage.Timestamp())
+ .set_rotation(aInputImage.rotation_)
+ .build();
+ mCallback->Decoded(videoFrame);
+ }
+ mResults.Clear();
+ }
+
+ if (NS_FAILED(mError) && mError != NS_ERROR_DOM_MEDIA_CANCELED) {
+ CreateDecoder();
+ return WEBRTC_VIDEO_CODEC_ERROR;
+ }
+
+ if (NS_FAILED(mError)) {
+ return WEBRTC_VIDEO_CODEC_ERROR;
+ }
+
+ disabledHardwareAcceleration.release();
+ return WEBRTC_VIDEO_CODEC_OK;
+}
+
+int32_t WebrtcMediaDataDecoder::RegisterDecodeCompleteCallback(
+ webrtc::DecodedImageCallback* aCallback) {
+ mCallback = aCallback;
+ return WEBRTC_VIDEO_CODEC_OK;
+}
+
+int32_t WebrtcMediaDataDecoder::Release() {
+ if (mDecoder) {
+ RefPtr<MediaDataDecoder> decoder = std::move(mDecoder);
+ decoder->Flush()->Then(mTaskQueue, __func__,
+ [decoder]() { decoder->Shutdown(); });
+ }
+
+ mNeedKeyframe = true;
+ mError = NS_OK;
+
+ return WEBRTC_VIDEO_CODEC_OK;
+}
+
+bool WebrtcMediaDataDecoder::OnTaskQueue() const {
+ return mTaskQueue->IsOnCurrentThread();
+}
+
+int32_t WebrtcMediaDataDecoder::CreateDecoder() {
+ RefPtr<layers::KnowsCompositor> knowsCompositor =
+ layers::ImageBridgeChild::GetSingleton();
+
+ if (mDecoder) {
+ Release();
+ }
+
+ RefPtr<TaskQueue> tq =
+ TaskQueue::Create(GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER),
+ "webrtc decode TaskQueue");
+ RefPtr<MediaDataDecoder> decoder;
+
+ media::Await(do_AddRef(mThreadPool), InvokeAsync(tq, __func__, [&] {
+ RefPtr<GenericPromise> p =
+ mFactory
+ ->CreateDecoder(
+ {mInfo,
+ CreateDecoderParams::OptionSet(
+ CreateDecoderParams::Option::LowLatency,
+ CreateDecoderParams::Option::FullH264Parsing,
+ CreateDecoderParams::Option::
+ ErrorIfNoInitializationData,
+ mDisabledHardwareAcceleration
+ ? CreateDecoderParams::Option::
+ HardwareDecoderNotAllowed
+ : CreateDecoderParams::Option::Default),
+ mTrackType, mImageContainer, knowsCompositor,
+ Some(mTrackingId)})
+ ->Then(
+ tq, __func__,
+ [&](RefPtr<MediaDataDecoder>&& aDecoder) {
+ decoder = std::move(aDecoder);
+ return GenericPromise::CreateAndResolve(
+ true, __func__);
+ },
+ [](const MediaResult& aResult) {
+ return GenericPromise::CreateAndReject(
+ NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+ });
+ return p;
+ }));
+
+ if (!decoder) {
+ return WEBRTC_VIDEO_CODEC_ERROR;
+ }
+
+ // We need to wrap our decoder in a MediaDataDecoderProxy so that it always
+ // run on an nsISerialEventTarget (which the webrtc code doesn't do)
+ mDecoder = new MediaDataDecoderProxy(decoder.forget(), tq.forget());
+
+ media::Await(
+ do_AddRef(mThreadPool), mDecoder->Init(),
+ [&](TrackInfo::TrackType) { mError = NS_OK; },
+ [&](const MediaResult& aError) { mError = aError; });
+
+ return NS_SUCCEEDED(mError) ? WEBRTC_VIDEO_CODEC_OK
+ : WEBRTC_VIDEO_CODEC_ERROR;
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/libwebrtcglue/WebrtcMediaDataDecoderCodec.h b/dom/media/webrtc/libwebrtcglue/WebrtcMediaDataDecoderCodec.h
new file mode 100644
index 0000000000..ccb54c692b
--- /dev/null
+++ b/dom/media/webrtc/libwebrtcglue/WebrtcMediaDataDecoderCodec.h
@@ -0,0 +1,70 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WebrtcMediaDataDecoderCodec_h__
+#define WebrtcMediaDataDecoderCodec_h__
+
+#include "MediaConduitInterface.h"
+#include "MediaInfo.h"
+#include "MediaResult.h"
+#include "PlatformDecoderModule.h"
+#include "VideoConduit.h"
+#include "WebrtcImageBuffer.h"
+#include "common_video/include/video_frame_buffer.h"
+#include "modules/video_coding/include/video_codec_interface.h"
+
+namespace webrtc {
+class DecodedImageCallback;
+}
+namespace mozilla {
+namespace layers {
+class Image;
+class ImageContainer;
+} // namespace layers
+
+class PDMFactory;
+class SharedThreadPool;
+class TaskQueue;
+
+class WebrtcMediaDataDecoder : public WebrtcVideoDecoder {
+ public:
+ WebrtcMediaDataDecoder(nsACString& aCodecMimeType, TrackingId aTrackingId);
+
+ bool Configure(const webrtc::VideoDecoder::Settings& settings) override;
+
+ int32_t Decode(const webrtc::EncodedImage& inputImage, bool missingFrames,
+ int64_t renderTimeMs = -1) override;
+
+ int32_t RegisterDecodeCompleteCallback(
+ webrtc::DecodedImageCallback* callback) override;
+
+ int32_t Release() override;
+
+ private:
+ ~WebrtcMediaDataDecoder();
+ void QueueFrame(MediaRawData* aFrame);
+ bool OnTaskQueue() const;
+ int32_t CreateDecoder();
+
+ const RefPtr<SharedThreadPool> mThreadPool;
+ const RefPtr<TaskQueue> mTaskQueue;
+ const RefPtr<layers::ImageContainer> mImageContainer;
+ const RefPtr<PDMFactory> mFactory;
+ RefPtr<MediaDataDecoder> mDecoder;
+ webrtc::DecodedImageCallback* mCallback = nullptr;
+ VideoInfo mInfo;
+ TrackInfo::TrackType mTrackType;
+ bool mNeedKeyframe = true;
+ MozPromiseRequestHolder<MediaDataDecoder::DecodePromise> mDecodeRequest;
+
+ MediaResult mError = NS_OK;
+ MediaDataDecoder::DecodedData mResults;
+ const nsCString mCodecType;
+ bool mDisabledHardwareAcceleration = false;
+ const TrackingId mTrackingId;
+};
+
+} // namespace mozilla
+
+#endif // WebrtcMediaDataDecoderCodec_h__
diff --git a/dom/media/webrtc/libwebrtcglue/WebrtcMediaDataEncoderCodec.cpp b/dom/media/webrtc/libwebrtcglue/WebrtcMediaDataEncoderCodec.cpp
new file mode 100644
index 0000000000..332d49b4e5
--- /dev/null
+++ b/dom/media/webrtc/libwebrtcglue/WebrtcMediaDataEncoderCodec.cpp
@@ -0,0 +1,518 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebrtcMediaDataEncoderCodec.h"
+
+#include "AnnexB.h"
+#include "api/video_codecs/h264_profile_level_id.h"
+#include "ImageContainer.h"
+#include "media/base/media_constants.h"
+#include "MediaData.h"
+#include "modules/video_coding/utility/vp8_header_parser.h"
+#include "modules/video_coding/utility/vp9_uncompressed_header_parser.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Span.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/media/MediaUtils.h"
+#include "mozilla/StaticPrefs_media.h"
+#include "PEMFactory.h"
+#include "system_wrappers/include/clock.h"
+#include "VideoUtils.h"
+
+namespace mozilla {
+
+extern LazyLogModule sPEMLog;
+
+#undef LOG
+#define LOG(msg, ...) \
+ MOZ_LOG(sPEMLog, LogLevel::Debug, \
+ ("WebrtcMediaDataEncoder=%p, " msg, this, ##__VA_ARGS__))
+
+#undef LOG_V
+#define LOG_V(msg, ...) \
+ MOZ_LOG(sPEMLog, LogLevel::Verbose, \
+ ("WebrtcMediaDataEncoder=%p, " msg, this, ##__VA_ARGS__))
+
+using namespace media;
+using namespace layers;
+using MimeTypeResult = Maybe<nsLiteralCString>;
+
+static MimeTypeResult ConvertWebrtcCodecTypeToMimeType(
+ const webrtc::VideoCodecType& aType) {
+ switch (aType) {
+ case webrtc::VideoCodecType::kVideoCodecVP8:
+ return Some("video/vp8"_ns);
+ case webrtc::VideoCodecType::kVideoCodecVP9:
+ return Some("video/vp9"_ns);
+ case webrtc::VideoCodecType::kVideoCodecH264:
+ return Some("video/avc"_ns);
+ default:
+ MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unsupported codec type");
+ }
+ return Nothing();
+}
+
+bool WebrtcMediaDataEncoder::CanCreate(
+ const webrtc::VideoCodecType aCodecType) {
+ auto factory = MakeRefPtr<PEMFactory>();
+ MimeTypeResult mimeType = ConvertWebrtcCodecTypeToMimeType(aCodecType);
+ return mimeType ? factory->SupportsMimeType(mimeType.ref()) : false;
+}
+
+static const char* PacketModeStr(const webrtc::CodecSpecificInfo& aInfo) {
+ MOZ_ASSERT(aInfo.codecType != webrtc::VideoCodecType::kVideoCodecGeneric);
+
+ if (aInfo.codecType != webrtc::VideoCodecType::kVideoCodecH264) {
+ return "N/A";
+ }
+ switch (aInfo.codecSpecific.H264.packetization_mode) {
+ case webrtc::H264PacketizationMode::SingleNalUnit:
+ return "SingleNalUnit";
+ case webrtc::H264PacketizationMode::NonInterleaved:
+ return "NonInterleaved";
+ default:
+ return "Unknown";
+ }
+}
+
+static MediaDataEncoder::H264Specific::ProfileLevel ConvertProfileLevel(
+ const webrtc::SdpVideoFormat::Parameters& aParameters) {
+ const absl::optional<webrtc::H264ProfileLevelId> profileLevel =
+ webrtc::ParseSdpForH264ProfileLevelId(aParameters);
+ if (profileLevel &&
+ (profileLevel->profile == webrtc::H264Profile::kProfileBaseline ||
+ profileLevel->profile ==
+ webrtc::H264Profile::kProfileConstrainedBaseline)) {
+ return MediaDataEncoder::H264Specific::ProfileLevel::BaselineAutoLevel;
+ }
+ return MediaDataEncoder::H264Specific::ProfileLevel::MainAutoLevel;
+}
+
+static MediaDataEncoder::VPXSpecific::Complexity MapComplexity(
+ webrtc::VideoCodecComplexity aComplexity) {
+ switch (aComplexity) {
+ case webrtc::VideoCodecComplexity::kComplexityNormal:
+ return MediaDataEncoder::VPXSpecific::Complexity::Normal;
+ case webrtc::VideoCodecComplexity::kComplexityHigh:
+ return MediaDataEncoder::VPXSpecific::Complexity::High;
+ case webrtc::VideoCodecComplexity::kComplexityHigher:
+ return MediaDataEncoder::VPXSpecific::Complexity::Higher;
+ case webrtc::VideoCodecComplexity::kComplexityMax:
+ return MediaDataEncoder::VPXSpecific::Complexity::Max;
+ default:
+ MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Bad complexity value");
+ }
+}
+
+WebrtcMediaDataEncoder::WebrtcMediaDataEncoder(
+ const webrtc::SdpVideoFormat& aFormat)
+ : mTaskQueue(
+ TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR),
+ "WebrtcMediaDataEncoder::mTaskQueue")),
+ mFactory(new PEMFactory()),
+ mCallbackMutex("WebrtcMediaDataEncoderCodec encoded callback mutex"),
+ mFormatParams(aFormat.parameters),
+ // Use the same lower and upper bound as h264_video_toolbox_encoder which
+ // is an encoder from webrtc's upstream codebase.
+ // 0.5 is set as a mininum to prevent overcompensating for large temporary
+ // overshoots. We don't want to degrade video quality too badly.
+ // 0.95 is set to prevent oscillations. When a lower bitrate is set on the
+ // encoder than previously set, its output seems to have a brief period of
+ // drastically reduced bitrate, so we want to avoid that. In steady state
+ // conditions, 0.95 seems to give us better overall bitrate over long
+ // periods of time.
+ mBitrateAdjuster(0.5, 0.95) {
+ PodZero(&mCodecSpecific.codecSpecific);
+}
+
+WebrtcMediaDataEncoder::~WebrtcMediaDataEncoder() = default;
+
+static void InitCodecSpecficInfo(
+ webrtc::CodecSpecificInfo& aInfo, const webrtc::VideoCodec* aCodecSettings,
+ const webrtc::SdpVideoFormat::Parameters& aParameters) {
+ MOZ_ASSERT(aCodecSettings);
+
+ aInfo.codecType = aCodecSettings->codecType;
+ switch (aCodecSettings->codecType) {
+ case webrtc::VideoCodecType::kVideoCodecH264: {
+ aInfo.codecSpecific.H264.packetization_mode =
+ aParameters.count(cricket::kH264FmtpPacketizationMode) == 1 &&
+ aParameters.at(cricket::kH264FmtpPacketizationMode) == "1"
+ ? webrtc::H264PacketizationMode::NonInterleaved
+ : webrtc::H264PacketizationMode::SingleNalUnit;
+ break;
+ }
+ case webrtc::VideoCodecType::kVideoCodecVP9: {
+ MOZ_ASSERT(aCodecSettings->VP9().numberOfSpatialLayers == 1);
+ aInfo.codecSpecific.VP9.flexible_mode =
+ aCodecSettings->VP9().flexibleMode;
+ aInfo.codecSpecific.VP9.first_frame_in_picture = true;
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+int32_t WebrtcMediaDataEncoder::InitEncode(
+ const webrtc::VideoCodec* aCodecSettings,
+ const webrtc::VideoEncoder::Settings& aSettings) {
+ MOZ_ASSERT(aCodecSettings);
+
+ if (aCodecSettings->numberOfSimulcastStreams > 1) {
+ LOG("Only one stream is supported. Falling back to simulcast adaptor");
+ return WEBRTC_VIDEO_CODEC_ERR_SIMULCAST_PARAMETERS_NOT_SUPPORTED;
+ }
+
+ // TODO: enable max output size setting when supported.
+ if (aCodecSettings->codecType == webrtc::VideoCodecType::kVideoCodecH264 &&
+ !(mFormatParams.count(cricket::kH264FmtpPacketizationMode) == 1 &&
+ mFormatParams.at(cricket::kH264FmtpPacketizationMode) == "1")) {
+ LOG("Some platform encoders don't support setting max output size."
+ " Falling back to SW");
+ return WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE;
+ }
+
+ if (mEncoder) {
+ // Clean existing encoder.
+ Shutdown();
+ }
+
+ RefPtr<MediaDataEncoder> encoder = CreateEncoder(aCodecSettings);
+ if (!encoder) {
+ LOG("Fail to create encoder. Falling back to SW");
+ return WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE;
+ }
+
+ InitCodecSpecficInfo(mCodecSpecific, aCodecSettings, mFormatParams);
+ LOG("Init encode, mimeType %s, mode %s", mInfo.mMimeType.get(),
+ PacketModeStr(mCodecSpecific));
+ if (!media::Await(do_AddRef(mTaskQueue), encoder->Init()).IsResolve()) {
+ LOG("Fail to init encoder. Falling back to SW");
+ return WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE;
+ }
+ mEncoder = std::move(encoder);
+ return WEBRTC_VIDEO_CODEC_OK;
+}
+
+bool WebrtcMediaDataEncoder::SetupConfig(
+ const webrtc::VideoCodec* aCodecSettings) {
+ MimeTypeResult mimeType =
+ ConvertWebrtcCodecTypeToMimeType(aCodecSettings->codecType);
+ if (!mimeType) {
+ LOG("Get incorrect mime type");
+ return false;
+ }
+ mInfo = VideoInfo(aCodecSettings->width, aCodecSettings->height);
+ mInfo.mMimeType = mimeType.extract();
+ mMaxFrameRate = aCodecSettings->maxFramerate;
+ // Those bitrates in codec setting are all kbps, so we have to covert them to
+ // bps.
+ mMaxBitrateBps = aCodecSettings->maxBitrate * 1000;
+ mMinBitrateBps = aCodecSettings->minBitrate * 1000;
+ mBitrateAdjuster.SetTargetBitrateBps(aCodecSettings->startBitrate * 1000);
+ return true;
+}
+
+already_AddRefed<MediaDataEncoder> WebrtcMediaDataEncoder::CreateEncoder(
+ const webrtc::VideoCodec* aCodecSettings) {
+ if (!SetupConfig(aCodecSettings)) {
+ return nullptr;
+ }
+ const bool swOnly = StaticPrefs::media_webrtc_platformencoder_sw_only();
+ LOG("Request platform encoder for %s, bitRate=%u bps, frameRate=%u"
+ ", sw-only=%d",
+ mInfo.mMimeType.get(), mBitrateAdjuster.GetTargetBitrateBps(),
+ aCodecSettings->maxFramerate, swOnly);
+
+ size_t keyframeInterval = 1;
+ switch (aCodecSettings->codecType) {
+ case webrtc::VideoCodecType::kVideoCodecH264: {
+ keyframeInterval = aCodecSettings->H264().keyFrameInterval;
+ break;
+ }
+ case webrtc::VideoCodecType::kVideoCodecVP8: {
+ keyframeInterval = aCodecSettings->VP8().keyFrameInterval;
+ break;
+ }
+ case webrtc::VideoCodecType::kVideoCodecVP9: {
+ keyframeInterval = aCodecSettings->VP9().keyFrameInterval;
+ break;
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unsupported codec type");
+ return nullptr;
+ }
+ CreateEncoderParams params(
+ mInfo, MediaDataEncoder::Usage::Realtime,
+ TaskQueue::Create(GetMediaThreadPool(MediaThreadType::PLATFORM_ENCODER),
+ "WebrtcMediaDataEncoder::mEncoder"),
+ MediaDataEncoder::PixelFormat::YUV420P, aCodecSettings->maxFramerate,
+ keyframeInterval, mBitrateAdjuster.GetTargetBitrateBps());
+ switch (aCodecSettings->codecType) {
+ case webrtc::VideoCodecType::kVideoCodecH264: {
+ params.SetCodecSpecific(
+ MediaDataEncoder::H264Specific(ConvertProfileLevel(mFormatParams)));
+ break;
+ }
+ case webrtc::VideoCodecType::kVideoCodecVP8: {
+ const webrtc::VideoCodecVP8& vp8 = aCodecSettings->VP8();
+ const webrtc::VideoCodecComplexity complexity =
+ aCodecSettings->GetVideoEncoderComplexity();
+ const bool frameDropEnabled = aCodecSettings->GetFrameDropEnabled();
+ params.SetCodecSpecific(MediaDataEncoder::VPXSpecific::VP8(
+ MapComplexity(complexity), false, vp8.numberOfTemporalLayers,
+ vp8.denoisingOn, vp8.automaticResizeOn, frameDropEnabled));
+ break;
+ }
+ case webrtc::VideoCodecType::kVideoCodecVP9: {
+ const webrtc::VideoCodecVP9& vp9 = aCodecSettings->VP9();
+ const webrtc::VideoCodecComplexity complexity =
+ aCodecSettings->GetVideoEncoderComplexity();
+ const bool frameDropEnabled = aCodecSettings->GetFrameDropEnabled();
+ params.SetCodecSpecific(MediaDataEncoder::VPXSpecific::VP9(
+ MapComplexity(complexity), false, vp9.numberOfTemporalLayers,
+ vp9.denoisingOn, vp9.automaticResizeOn, frameDropEnabled,
+ vp9.adaptiveQpMode, vp9.numberOfSpatialLayers, vp9.flexibleMode));
+ break;
+ }
+ default:
+ MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Unsupported codec type");
+ }
+ return mFactory->CreateEncoder(params, swOnly);
+}
+
+WebrtcVideoEncoder::EncoderInfo WebrtcMediaDataEncoder::GetEncoderInfo() const {
+ WebrtcVideoEncoder::EncoderInfo info;
+ info.supports_native_handle = false;
+ info.implementation_name = "MediaDataEncoder";
+ info.is_hardware_accelerated = false;
+ info.supports_simulcast = false;
+
+#ifdef MOZ_WIDGET_ANDROID
+ // Assume MediaDataEncoder is used mainly for hardware encoding. 16-alignment
+ // seems required on Android. This could be improved by querying the
+ // underlying encoder.
+ info.requested_resolution_alignment = 16;
+ info.apply_alignment_to_all_simulcast_layers = true;
+#endif
+ return info;
+}
+
+int32_t WebrtcMediaDataEncoder::RegisterEncodeCompleteCallback(
+ webrtc::EncodedImageCallback* aCallback) {
+ MutexAutoLock lock(mCallbackMutex);
+ mCallback = aCallback;
+ return WEBRTC_VIDEO_CODEC_OK;
+}
+
+int32_t WebrtcMediaDataEncoder::Shutdown() {
+ LOG("Release encoder");
+ {
+ MutexAutoLock lock(mCallbackMutex);
+ mCallback = nullptr;
+ mError = NS_OK;
+ }
+ if (mEncoder) {
+ media::Await(do_AddRef(mTaskQueue), mEncoder->Shutdown());
+ mEncoder = nullptr;
+ }
+ return WEBRTC_VIDEO_CODEC_OK;
+}
+
+static already_AddRefed<VideoData> CreateVideoDataFromWebrtcVideoFrame(
+ const webrtc::VideoFrame& aFrame, const bool aIsKeyFrame,
+ const TimeUnit aDuration) {
+ MOZ_ASSERT(aFrame.video_frame_buffer()->type() ==
+ webrtc::VideoFrameBuffer::Type::kI420,
+ "Only support YUV420!");
+ const webrtc::I420BufferInterface* i420 =
+ aFrame.video_frame_buffer()->GetI420();
+
+ PlanarYCbCrData yCbCrData;
+ yCbCrData.mYChannel = const_cast<uint8_t*>(i420->DataY());
+ yCbCrData.mYStride = i420->StrideY();
+ yCbCrData.mCbChannel = const_cast<uint8_t*>(i420->DataU());
+ yCbCrData.mCrChannel = const_cast<uint8_t*>(i420->DataV());
+ MOZ_ASSERT(i420->StrideU() == i420->StrideV());
+ yCbCrData.mCbCrStride = i420->StrideU();
+ yCbCrData.mPictureRect = gfx::IntRect(0, 0, i420->width(), i420->height());
+ yCbCrData.mChromaSubsampling = gfx::ChromaSubsampling::HALF_WIDTH_AND_HEIGHT;
+
+ RefPtr<PlanarYCbCrImage> image =
+ new RecyclingPlanarYCbCrImage(new BufferRecycleBin());
+ image->CopyData(yCbCrData);
+
+ // Although webrtc::VideoFrame::timestamp_rtp_ will likely be deprecated,
+ // webrtc::EncodedImage and the VPx encoders still use it in the imported
+ // version of libwebrtc. Not using the same timestamp values generates
+ // discontinuous time and confuses the video receiver when switching from
+ // platform to libwebrtc encoder.
+ TimeUnit timestamp =
+ media::TimeUnit(aFrame.timestamp(), cricket::kVideoCodecClockrate);
+ return VideoData::CreateFromImage(image->GetSize(), 0, timestamp, aDuration,
+ image, aIsKeyFrame, timestamp);
+}
+
+static void UpdateCodecSpecificInfo(webrtc::CodecSpecificInfo& aInfo,
+ const gfx::IntSize& aSize,
+ const bool aIsKeyframe) {
+ switch (aInfo.codecType) {
+ case webrtc::VideoCodecType::kVideoCodecVP8: {
+ // See webrtc::VP8EncoderImpl::PopulateCodecSpecific().
+ webrtc::CodecSpecificInfoVP8& vp8 = aInfo.codecSpecific.VP8;
+ vp8.keyIdx = webrtc::kNoKeyIdx;
+ // Cannot be 100% sure unless parsing significant portion of the
+ // bitstream. Treat all frames as referenced just to be safe.
+ vp8.nonReference = false;
+ // One temporal layer only.
+ vp8.temporalIdx = webrtc::kNoTemporalIdx;
+ vp8.layerSync = false;
+ break;
+ }
+ case webrtc::VideoCodecType::kVideoCodecVP9: {
+ // See webrtc::VP9EncoderImpl::PopulateCodecSpecific().
+ webrtc::CodecSpecificInfoVP9& vp9 = aInfo.codecSpecific.VP9;
+ vp9.inter_pic_predicted = !aIsKeyframe;
+ vp9.ss_data_available = aIsKeyframe && !vp9.flexible_mode;
+ // One temporal & spatial layer only.
+ vp9.temporal_idx = webrtc::kNoTemporalIdx;
+ vp9.temporal_up_switch = false;
+ vp9.num_spatial_layers = 1;
+ vp9.end_of_picture = true;
+ vp9.gof_idx = webrtc::kNoGofIdx;
+ vp9.width[0] = aSize.width;
+ vp9.height[0] = aSize.height;
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+static void GetVPXQp(const webrtc::VideoCodecType aType,
+ webrtc::EncodedImage& aImage) {
+ switch (aType) {
+ case webrtc::VideoCodecType::kVideoCodecVP8:
+ webrtc::vp8::GetQp(aImage.data(), aImage.size(), &(aImage.qp_));
+ break;
+ case webrtc::VideoCodecType::kVideoCodecVP9:
+ webrtc::vp9::GetQp(aImage.data(), aImage.size(), &(aImage.qp_));
+ break;
+ default:
+ break;
+ }
+}
+
+int32_t WebrtcMediaDataEncoder::Encode(
+ const webrtc::VideoFrame& aInputFrame,
+ const std::vector<webrtc::VideoFrameType>* aFrameTypes) {
+ if (!aInputFrame.size() || !aInputFrame.video_frame_buffer() ||
+ aFrameTypes->empty()) {
+ return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
+ }
+
+ if (!mEncoder) {
+ return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
+ }
+ {
+ MutexAutoLock lock(mCallbackMutex);
+ if (!mCallback) {
+ return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
+ }
+ if (NS_FAILED(mError)) {
+ return WEBRTC_VIDEO_CODEC_ERROR;
+ }
+ }
+
+ LOG_V("Encode frame, type %d size %u", static_cast<int>((*aFrameTypes)[0]),
+ aInputFrame.size());
+ MOZ_ASSERT(aInputFrame.video_frame_buffer()->type() ==
+ webrtc::VideoFrameBuffer::Type::kI420);
+ RefPtr<VideoData> data = CreateVideoDataFromWebrtcVideoFrame(
+ aInputFrame, (*aFrameTypes)[0] == webrtc::VideoFrameType::kVideoFrameKey,
+ TimeUnit::FromSeconds(1.0 / mMaxFrameRate));
+ const gfx::IntSize displaySize = data->mDisplay;
+
+ mEncoder->Encode(data)->Then(
+ mTaskQueue, __func__,
+ [self = RefPtr<WebrtcMediaDataEncoder>(this), this,
+ displaySize](MediaDataEncoder::EncodedData aFrames) {
+ LOG_V("Received encoded frame, nums %zu width %d height %d",
+ aFrames.Length(), displaySize.width, displaySize.height);
+ for (auto& frame : aFrames) {
+ MutexAutoLock lock(mCallbackMutex);
+ if (!mCallback) {
+ break;
+ }
+ webrtc::EncodedImage image;
+ image.SetEncodedData(
+ webrtc::EncodedImageBuffer::Create(frame->Data(), frame->Size()));
+ image._encodedWidth = displaySize.width;
+ image._encodedHeight = displaySize.height;
+ CheckedInt64 time =
+ TimeUnitToFrames(frame->mTime, cricket::kVideoCodecClockrate);
+ if (!time.isValid()) {
+ self->mError = MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+ "invalid timestamp from encoder");
+ break;
+ }
+ image.SetTimestamp(time.value());
+ image._frameType = frame->mKeyframe
+ ? webrtc::VideoFrameType::kVideoFrameKey
+ : webrtc::VideoFrameType::kVideoFrameDelta;
+ GetVPXQp(mCodecSpecific.codecType, image);
+ UpdateCodecSpecificInfo(mCodecSpecific, displaySize,
+ frame->mKeyframe);
+
+ LOG_V("Send encoded image");
+ self->mCallback->OnEncodedImage(image, &mCodecSpecific);
+ self->mBitrateAdjuster.Update(image.size());
+ }
+ },
+ [self = RefPtr<WebrtcMediaDataEncoder>(this)](const MediaResult aError) {
+ self->mError = aError;
+ });
+ return WEBRTC_VIDEO_CODEC_OK;
+}
+
+int32_t WebrtcMediaDataEncoder::SetRates(
+ const webrtc::VideoEncoder::RateControlParameters& aParameters) {
+ if (!aParameters.bitrate.HasBitrate(0, 0)) {
+ LOG("%s: no bitrate value to set.", __func__);
+ return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
+ }
+ MOZ_ASSERT(aParameters.bitrate.IsSpatialLayerUsed(0));
+ MOZ_ASSERT(!aParameters.bitrate.IsSpatialLayerUsed(1),
+ "No simulcast support for platform encoder");
+
+ const uint32_t newBitrateBps = aParameters.bitrate.GetBitrate(0, 0);
+ if (newBitrateBps < mMinBitrateBps || newBitrateBps > mMaxBitrateBps) {
+ LOG("%s: bitrate value out of range.", __func__);
+ return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
+ }
+
+ // We have already been in this bitrate.
+ if (mBitrateAdjuster.GetAdjustedBitrateBps() == newBitrateBps) {
+ return WEBRTC_VIDEO_CODEC_OK;
+ }
+
+ if (!mEncoder) {
+ return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
+ }
+ {
+ MutexAutoLock lock(mCallbackMutex);
+ if (NS_FAILED(mError)) {
+ return WEBRTC_VIDEO_CODEC_ERROR;
+ }
+ }
+ mBitrateAdjuster.SetTargetBitrateBps(newBitrateBps);
+ LOG("Set bitrate %u bps, minBitrate %u bps, maxBitrate %u bps", newBitrateBps,
+ mMinBitrateBps, mMaxBitrateBps);
+ auto rv =
+ media::Await(do_AddRef(mTaskQueue), mEncoder->SetBitrate(newBitrateBps));
+ return rv.IsResolve() ? WEBRTC_VIDEO_CODEC_OK : WEBRTC_VIDEO_CODEC_ERROR;
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/libwebrtcglue/WebrtcMediaDataEncoderCodec.h b/dom/media/webrtc/libwebrtcglue/WebrtcMediaDataEncoderCodec.h
new file mode 100644
index 0000000000..bc462cc382
--- /dev/null
+++ b/dom/media/webrtc/libwebrtcglue/WebrtcMediaDataEncoderCodec.h
@@ -0,0 +1,78 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WebrtcMediaDataEncoderCodec_h__
+#define WebrtcMediaDataEncoderCodec_h__
+
+#include "MediaConduitInterface.h"
+#include "MediaInfo.h"
+#include "MediaResult.h"
+#include "PlatformEncoderModule.h"
+#include "WebrtcGmpVideoCodec.h"
+#include "common_video/include/bitrate_adjuster.h"
+#include "modules/video_coding/include/video_codec_interface.h"
+
+namespace mozilla {
+
+class MediaData;
+class PEMFactory;
+class SharedThreadPool;
+class TaskQueue;
+
+class WebrtcMediaDataEncoder : public RefCountedWebrtcVideoEncoder {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebrtcMediaDataEncoder, final);
+
+ static bool CanCreate(const webrtc::VideoCodecType aCodecType);
+
+ explicit WebrtcMediaDataEncoder(const webrtc::SdpVideoFormat& aFormat);
+
+ int32_t InitEncode(const webrtc::VideoCodec* aCodecSettings,
+ const webrtc::VideoEncoder::Settings& aSettings) override;
+
+ int32_t RegisterEncodeCompleteCallback(
+ webrtc::EncodedImageCallback* aCallback) override;
+
+ int32_t Shutdown() override;
+
+ int32_t Encode(
+ const webrtc::VideoFrame& aFrame,
+ const std::vector<webrtc::VideoFrameType>* aFrameTypes) override;
+
+ int32_t SetRates(
+ const webrtc::VideoEncoder::RateControlParameters& aParameters) override;
+
+ WebrtcVideoEncoder::EncoderInfo GetEncoderInfo() const override;
+ MediaEventSource<uint64_t>* InitPluginEvent() override { return nullptr; }
+
+ MediaEventSource<uint64_t>* ReleasePluginEvent() override { return nullptr; }
+
+ private:
+ virtual ~WebrtcMediaDataEncoder();
+
+ bool SetupConfig(const webrtc::VideoCodec* aCodecSettings);
+ already_AddRefed<MediaDataEncoder> CreateEncoder(
+ const webrtc::VideoCodec* aCodecSettings);
+ bool InitEncoder();
+
+ const RefPtr<TaskQueue> mTaskQueue;
+ const RefPtr<PEMFactory> mFactory;
+ RefPtr<MediaDataEncoder> mEncoder;
+
+ Mutex mCallbackMutex MOZ_UNANNOTATED; // Protects mCallback and mError.
+ webrtc::EncodedImageCallback* mCallback = nullptr;
+ MediaResult mError = NS_OK;
+
+ VideoInfo mInfo;
+ webrtc::SdpVideoFormat::Parameters mFormatParams;
+ webrtc::CodecSpecificInfo mCodecSpecific;
+ webrtc::BitrateAdjuster mBitrateAdjuster;
+ uint32_t mMaxFrameRate;
+ uint32_t mMinBitrateBps;
+ uint32_t mMaxBitrateBps;
+};
+
+} // namespace mozilla
+
+#endif // WebrtcMediaDataEncoderCodec_h__
diff --git a/dom/media/webrtc/libwebrtcglue/WebrtcVideoCodecFactory.cpp b/dom/media/webrtc/libwebrtcglue/WebrtcVideoCodecFactory.cpp
new file mode 100644
index 0000000000..6acec07ea3
--- /dev/null
+++ b/dom/media/webrtc/libwebrtcglue/WebrtcVideoCodecFactory.cpp
@@ -0,0 +1,139 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebrtcVideoCodecFactory.h"
+
+#include "GmpVideoCodec.h"
+#include "MediaDataCodec.h"
+#include "VideoConduit.h"
+#include "mozilla/StaticPrefs_media.h"
+
+// libwebrtc includes
+#include "api/rtp_headers.h"
+#include "api/video_codecs/video_codec.h"
+#include "api/video_codecs/video_encoder_software_fallback_wrapper.h"
+#include "media/engine/encoder_simulcast_proxy.h"
+#include "modules/video_coding/codecs/vp8/include/vp8.h"
+#include "modules/video_coding/codecs/vp9/include/vp9.h"
+
+namespace mozilla {
+
+std::unique_ptr<webrtc::VideoDecoder>
+WebrtcVideoDecoderFactory::CreateVideoDecoder(
+ const webrtc::SdpVideoFormat& aFormat) {
+ std::unique_ptr<webrtc::VideoDecoder> decoder;
+ auto type = webrtc::PayloadStringToCodecType(aFormat.name);
+
+ // Attempt to create a decoder using MediaDataDecoder.
+ decoder.reset(MediaDataCodec::CreateDecoder(type, mTrackingId));
+ if (decoder) {
+ return decoder;
+ }
+
+ switch (type) {
+ case webrtc::VideoCodecType::kVideoCodecH264: {
+ // Get an external decoder
+ auto gmpDecoder =
+ WrapUnique(GmpVideoCodec::CreateDecoder(mPCHandle, mTrackingId));
+ mCreatedGmpPluginEvent.Forward(*gmpDecoder->InitPluginEvent());
+ mReleasedGmpPluginEvent.Forward(*gmpDecoder->ReleasePluginEvent());
+ decoder.reset(gmpDecoder.release());
+ break;
+ }
+
+ // Use libvpx decoders as fallbacks.
+ case webrtc::VideoCodecType::kVideoCodecVP8:
+ if (!decoder) {
+ decoder = webrtc::VP8Decoder::Create();
+ }
+ break;
+ case webrtc::VideoCodecType::kVideoCodecVP9:
+ decoder = webrtc::VP9Decoder::Create();
+ break;
+
+ default:
+ break;
+ }
+
+ return decoder;
+}
+
+std::unique_ptr<webrtc::VideoEncoder>
+WebrtcVideoEncoderFactory::CreateVideoEncoder(
+ const webrtc::SdpVideoFormat& aFormat) {
+ if (!mInternalFactory->Supports(aFormat)) {
+ return nullptr;
+ }
+ auto type = webrtc::PayloadStringToCodecType(aFormat.name);
+ switch (type) {
+ case webrtc::VideoCodecType::kVideoCodecVP8:
+ // XXX We might be able to use the simulcast proxy for more codecs, but
+ // that requires testing.
+ return std::make_unique<webrtc::EncoderSimulcastProxy>(
+ mInternalFactory.get(), aFormat);
+ default:
+ return mInternalFactory->CreateVideoEncoder(aFormat);
+ }
+}
+
+bool WebrtcVideoEncoderFactory::InternalFactory::Supports(
+ const webrtc::SdpVideoFormat& aFormat) {
+ switch (webrtc::PayloadStringToCodecType(aFormat.name)) {
+ case webrtc::VideoCodecType::kVideoCodecVP8:
+ case webrtc::VideoCodecType::kVideoCodecVP9:
+ case webrtc::VideoCodecType::kVideoCodecH264:
+ return true;
+ default:
+ return false;
+ }
+}
+
+std::unique_ptr<webrtc::VideoEncoder>
+WebrtcVideoEncoderFactory::InternalFactory::CreateVideoEncoder(
+ const webrtc::SdpVideoFormat& aFormat) {
+ MOZ_ASSERT(Supports(aFormat));
+
+ std::unique_ptr<webrtc::VideoEncoder> platformEncoder;
+ platformEncoder.reset(MediaDataCodec::CreateEncoder(aFormat));
+ const bool fallback = StaticPrefs::media_webrtc_software_encoder_fallback();
+ if (!fallback && platformEncoder) {
+ return platformEncoder;
+ }
+
+ std::unique_ptr<webrtc::VideoEncoder> encoder;
+ switch (webrtc::PayloadStringToCodecType(aFormat.name)) {
+ case webrtc::VideoCodecType::kVideoCodecH264: {
+ // get an external encoder
+ auto gmpEncoder =
+ WrapUnique(GmpVideoCodec::CreateEncoder(aFormat, mPCHandle));
+ mCreatedGmpPluginEvent.Forward(*gmpEncoder->InitPluginEvent());
+ mReleasedGmpPluginEvent.Forward(*gmpEncoder->ReleasePluginEvent());
+ encoder.reset(gmpEncoder.release());
+ break;
+ }
+ // libvpx fallbacks.
+ case webrtc::VideoCodecType::kVideoCodecVP8:
+ if (!encoder) {
+ encoder = webrtc::VP8Encoder::Create();
+ }
+ break;
+ case webrtc::VideoCodecType::kVideoCodecVP9:
+ encoder = webrtc::VP9Encoder::Create();
+ break;
+
+ default:
+ break;
+ }
+ if (fallback && encoder && platformEncoder) {
+ return webrtc::CreateVideoEncoderSoftwareFallbackWrapper(
+ std::move(encoder), std::move(platformEncoder), false);
+ }
+ if (platformEncoder) {
+ return platformEncoder;
+ }
+ return encoder;
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/libwebrtcglue/WebrtcVideoCodecFactory.h b/dom/media/webrtc/libwebrtcglue/WebrtcVideoCodecFactory.h
new file mode 100644
index 0000000000..ef5765043f
--- /dev/null
+++ b/dom/media/webrtc/libwebrtcglue/WebrtcVideoCodecFactory.h
@@ -0,0 +1,124 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DOM_MEDIA_WEBRTC_LIBWEBRTCGLUE_WEBRTCVIDEOCODECFACTORY_H_
+#define DOM_MEDIA_WEBRTC_LIBWEBRTCGLUE_WEBRTCVIDEOCODECFACTORY_H_
+
+#include "api/video_codecs/video_decoder_factory.h"
+#include "api/video_codecs/video_encoder_factory.h"
+#include "MediaEventSource.h"
+#include "PerformanceRecorder.h"
+
+namespace mozilla {
+class GmpPluginNotifierInterface {
+ virtual void DisconnectAll() = 0;
+ virtual MediaEventSource<uint64_t>& CreatedGmpPluginEvent() = 0;
+ virtual MediaEventSource<uint64_t>& ReleasedGmpPluginEvent() = 0;
+};
+
+class GmpPluginNotifier : public GmpPluginNotifierInterface {
+ public:
+ explicit GmpPluginNotifier(nsCOMPtr<nsISerialEventTarget> aOwningThread)
+ : mOwningThread(std::move(aOwningThread)),
+ mCreatedGmpPluginEvent(mOwningThread),
+ mReleasedGmpPluginEvent(mOwningThread) {}
+
+ ~GmpPluginNotifier() = default;
+
+ void DisconnectAll() override {
+ MOZ_ASSERT(mOwningThread->IsOnCurrentThread());
+ mCreatedGmpPluginEvent.DisconnectAll();
+ mReleasedGmpPluginEvent.DisconnectAll();
+ }
+
+ MediaEventSource<uint64_t>& CreatedGmpPluginEvent() override {
+ return mCreatedGmpPluginEvent;
+ }
+
+ MediaEventSource<uint64_t>& ReleasedGmpPluginEvent() override {
+ return mReleasedGmpPluginEvent;
+ }
+
+ protected:
+ const nsCOMPtr<nsISerialEventTarget> mOwningThread;
+ MediaEventForwarder<uint64_t> mCreatedGmpPluginEvent;
+ MediaEventForwarder<uint64_t> mReleasedGmpPluginEvent;
+};
+
+class WebrtcVideoDecoderFactory : public GmpPluginNotifier,
+ public webrtc::VideoDecoderFactory {
+ public:
+ WebrtcVideoDecoderFactory(nsCOMPtr<nsISerialEventTarget> aOwningThread,
+ std::string aPCHandle, TrackingId aTrackingId)
+ : GmpPluginNotifier(std::move(aOwningThread)),
+ mPCHandle(std::move(aPCHandle)),
+ mTrackingId(std::move(aTrackingId)) {}
+
+ std::vector<webrtc::SdpVideoFormat> GetSupportedFormats() const override {
+ MOZ_CRASH("Unexpected call");
+ return std::vector<webrtc::SdpVideoFormat>();
+ }
+
+ std::unique_ptr<webrtc::VideoDecoder> CreateVideoDecoder(
+ const webrtc::SdpVideoFormat& aFormat) override;
+
+ private:
+ const std::string mPCHandle;
+ const TrackingId mTrackingId;
+};
+
+class WebrtcVideoEncoderFactory : public GmpPluginNotifierInterface,
+ public webrtc::VideoEncoderFactory {
+ class InternalFactory : public GmpPluginNotifier,
+ public webrtc::VideoEncoderFactory {
+ public:
+ InternalFactory(nsCOMPtr<nsISerialEventTarget> aOwningThread,
+ std::string aPCHandle)
+ : GmpPluginNotifier(std::move(aOwningThread)),
+ mPCHandle(std::move(aPCHandle)) {}
+
+ std::vector<webrtc::SdpVideoFormat> GetSupportedFormats() const override {
+ MOZ_CRASH("Unexpected call");
+ return std::vector<webrtc::SdpVideoFormat>();
+ }
+
+ std::unique_ptr<webrtc::VideoEncoder> CreateVideoEncoder(
+ const webrtc::SdpVideoFormat& aFormat) override;
+
+ bool Supports(const webrtc::SdpVideoFormat& aFormat);
+
+ private:
+ const std::string mPCHandle;
+ };
+
+ public:
+ explicit WebrtcVideoEncoderFactory(
+ nsCOMPtr<nsISerialEventTarget> aOwningThread, std::string aPCHandle)
+ : mInternalFactory(MakeUnique<InternalFactory>(std::move(aOwningThread),
+ std::move(aPCHandle))) {}
+
+ std::vector<webrtc::SdpVideoFormat> GetSupportedFormats() const override {
+ MOZ_CRASH("Unexpected call");
+ return std::vector<webrtc::SdpVideoFormat>();
+ }
+
+ std::unique_ptr<webrtc::VideoEncoder> CreateVideoEncoder(
+ const webrtc::SdpVideoFormat& aFormat) override;
+
+ void DisconnectAll() override { mInternalFactory->DisconnectAll(); }
+
+ MediaEventSource<uint64_t>& CreatedGmpPluginEvent() override {
+ return mInternalFactory->CreatedGmpPluginEvent();
+ }
+ MediaEventSource<uint64_t>& ReleasedGmpPluginEvent() override {
+ return mInternalFactory->ReleasedGmpPluginEvent();
+ }
+
+ private:
+ const UniquePtr<InternalFactory> mInternalFactory;
+};
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/libwebrtcglue/moz.build b/dom/media/webrtc/libwebrtcglue/moz.build
new file mode 100644
index 0000000000..62bac2ffe6
--- /dev/null
+++ b/dom/media/webrtc/libwebrtcglue/moz.build
@@ -0,0 +1,35 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+include("/dom/media/webrtc/third_party_build/webrtc.mozbuild")
+
+LOCAL_INCLUDES += [
+ "!/ipc/ipdl/_ipdlheaders",
+ "/dom/media/gmp", # for GMPLog.h,
+ "/dom/media/webrtc",
+ "/ipc/chromium/src",
+ "/media/libyuv/libyuv/include",
+ "/media/webrtc",
+ "/third_party/libsrtp/src/include",
+ "/third_party/libwebrtc",
+ "/third_party/libwebrtc/third_party/abseil-cpp",
+]
+
+UNIFIED_SOURCES += [
+ "AudioConduit.cpp",
+ "GmpVideoCodec.cpp",
+ "MediaConduitInterface.cpp",
+ "MediaDataCodec.cpp",
+ "SystemTime.cpp",
+ "VideoConduit.cpp",
+ "VideoStreamFactory.cpp",
+ "WebrtcCallWrapper.cpp",
+ "WebrtcGmpVideoCodec.cpp",
+ "WebrtcMediaDataDecoderCodec.cpp",
+ "WebrtcMediaDataEncoderCodec.cpp",
+ "WebrtcVideoCodecFactory.cpp",
+]
+
+FINAL_LIBRARY = "xul"
diff --git a/dom/media/webrtc/metrics.yaml b/dom/media/webrtc/metrics.yaml
new file mode 100644
index 0000000000..77d9112543
--- /dev/null
+++ b/dom/media/webrtc/metrics.yaml
@@ -0,0 +1,358 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# Adding a new metric? We have docs for that!
+# https://firefox-source-docs.mozilla.org/toolkit/components/glean/user/new_definitions_file.html
+
+---
+$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0
+$tags:
+ - 'Core :: WebRTC'
+
+rtcrtpsender:
+ count:
+ type: counter
+ description: >
+ The number of RTCRtpSenders created.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - webrtc-telemetry-alerts@mozilla.com
+ expires: 120
+
+ count_setparameters_compat:
+ type: counter
+ description: >
+ The number of RTCRtpSenders created that use the compatibility mode for
+ setParameters.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - webrtc-telemetry-alerts@mozilla.com
+ expires: 120
+
+ used_sendencodings:
+ type: rate
+ description: >
+ The proportion of RTCRtpSenders that were created by an addTransceivers
+ call that was passed a sendEncodings.
+ denominator_metric: rtcrtpsender.count
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - webrtc-telemetry-alerts@mozilla.com
+ expires: 120
+
+rtcrtpsender.setparameters:
+ warn_no_getparameters:
+ type: rate
+ description: >
+ The proportion of RTCRtpSenders configured with the setParameters compat
+ mode that have warned at least once about a setParameters call because
+ [[LastReturnedParameters]] was not set. (ie; there was not a recent
+ enough call to getParameters)
+ denominator_metric: rtcrtpsender.count_setparameters_compat
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - webrtc-telemetry-alerts@mozilla.com
+ expires: 120
+
+ blame_no_getparameters:
+ type: labeled_counter
+ description: >
+ The number of RTCRtpSenders that have warned at least once about
+ a `setParameters` call because `[[LastReturnedParameters]]` was not set,
+ broken down by the eTLD+1 of the site. (ie; there was not a recent
+ enough call to `getParameters`) Collected only on EARLY_BETA_OR_EARLIER.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1831343
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1831343
+ data_sensitivity:
+ - web_activity
+ notification_emails:
+ - webrtc-telemetry-alerts@mozilla.com
+ expires: 120
+
+ warn_length_changed:
+ type: rate
+ description: >
+ The proportion of RTCRtpSenders configured with the setParameters compat
+ mode that have warned at least once about a setParameters call that
+ attempted to change the number of encodings.
+ denominator_metric: rtcrtpsender.count_setparameters_compat
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - webrtc-telemetry-alerts@mozilla.com
+ expires: 120
+
+ blame_length_changed:
+ type: labeled_counter
+ description: >
+ The number of RTCRtpSenders that have warned at least once about a
+ `setParameters` call that attempted to change the number of encodings,
+ broken down by the eTLD+1 of the site. Collected only on
+ EARLY_BETA_OR_EARLIER.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1831343
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1831343
+ data_sensitivity:
+ - web_activity
+ notification_emails:
+ - webrtc-telemetry-alerts@mozilla.com
+ expires: 120
+
+ warn_rid_changed:
+ type: rate
+ description: >
+ The proportion of RTCRtpSenders configured with the setParameters compat
+ mode that have warned at least once about a setParameters call that
+ attempted to change the rid on an encoding (note that we only check this
+ if the encoding count did not change, see warn_length_changed).
+ denominator_metric: rtcrtpsender.count_setparameters_compat
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - webrtc-telemetry-alerts@mozilla.com
+ expires: 116
+
+ warn_no_transactionid:
+ type: rate
+ description: >
+ The proportion of RTCRtpSenders configured with the setParameters compat
+ mode that have warned at least once about a setParameters call that did
+ not set the transactionId field.
+ denominator_metric: rtcrtpsender.count_setparameters_compat
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - webrtc-telemetry-alerts@mozilla.com
+ expires: 120
+
+ blame_no_transactionid:
+ type: labeled_counter
+ description: >
+ The number of RTCRtpSenders that have warned at least once about a
+ `setParameters` call that did not set the transactionId field, broken down
+ by the eTLD+1 of the site. Collected only on EARLY_BETA_OR_EARLIER.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1831343
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1831343
+ data_sensitivity:
+ - web_activity
+ notification_emails:
+ - webrtc-telemetry-alerts@mozilla.com
+ expires: 120
+
+ warn_stale_transactionid:
+ type: rate
+ description: >
+ The proportion of RTCRtpSenders configured with the setParameters compat
+ mode that have warned at least once about a setParameters call that used
+ a stale transaction id.
+ denominator_metric: rtcrtpsender.count_setparameters_compat
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - webrtc-telemetry-alerts@mozilla.com
+ expires: 120
+
+ blame_stale_transactionid:
+ type: labeled_counter
+ description: >
+ The number of RTCRtpSenders that have warned at least once about a
+ `setParameters` call that used a stale transaction id, broken down by the
+ eTLD+1 of the site. Collected only on EARLY_BETA_OR_EARLIER.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1831343
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1831343
+ data_sensitivity:
+ - web_activity
+ notification_emails:
+ - webrtc-telemetry-alerts@mozilla.com
+ expires: 120
+
+ fail_length_changed:
+ type: rate
+ description: >
+ The proportion of RTCRtpSenders that have thrown an error at least once
+ about a setParameters call that attempted to change the number of
+ encodings.
+ denominator_metric: rtcrtpsender.count
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - webrtc-telemetry-alerts@mozilla.com
+ expires: 120
+
+ fail_rid_changed:
+ type: rate
+ description: >
+ The proportion of RTCRtpSenders that have thrown an error at least once
+ about a setParameters call that attempted to change the rid on an
+ encoding (note that we only check this if the encoding count did not
+ change, see fail_length_changed).
+ denominator_metric: rtcrtpsender.count
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - webrtc-telemetry-alerts@mozilla.com
+ expires: 120
+
+ fail_no_getparameters:
+ type: rate
+ description: >
+ The proportion of RTCRtpSenders that have thrown an error at least once
+ about a setParameters call because [[LastReturnedParameters]] was not set.
+ (ie; there was not a recent enough call to getParameters)
+ denominator_metric: rtcrtpsender.count
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - webrtc-telemetry-alerts@mozilla.com
+ expires: 120
+
+ fail_no_transactionid:
+ type: rate
+ description: >
+ The proportion of RTCRtpSenders that have thrown an error at least once
+ about a setParameters call that did not set the transactionId field.
+ denominator_metric: rtcrtpsender.count
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - webrtc-telemetry-alerts@mozilla.com
+ expires: 120
+
+ fail_stale_transactionid:
+ type: rate
+ description: >
+ The proportion of RTCRtpSenders that have thrown an error at least once
+ about a setParameters call that used a stale transaction id.
+ denominator_metric: rtcrtpsender.count
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - webrtc-telemetry-alerts@mozilla.com
+ expires: 120
+
+ fail_no_encodings:
+ type: rate
+ description: >
+ The proportion of RTCRtpSenders configured with the setParameters compat
+ mode that have thrown an error at least once about a setParameters call
+ that had no encodings (we do not measure this against the general
+ population of RTCRtpSenders, since without the compat mode this failure
+ is never observed, because it fails the length change check).
+ denominator_metric: rtcrtpsender.count_setparameters_compat
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - webrtc-telemetry-alerts@mozilla.com
+ expires: 120
+
+ fail_other:
+ type: rate
+ description: >
+ The proportion of RTCRtpSenders that have thrown an error at least once
+ about a setParameters call that had no encodings.
+ denominator_metric: rtcrtpsender.count
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1401592
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1832459
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - webrtc-telemetry-alerts@mozilla.com
+ expires: 120
diff --git a/dom/media/webrtc/moz.build b/dom/media/webrtc/moz.build
new file mode 100644
index 0000000000..cb8e363faa
--- /dev/null
+++ b/dom/media/webrtc/moz.build
@@ -0,0 +1,132 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+include("/dom/media/webrtc/third_party_build/webrtc.mozbuild")
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "WebRTC: Audio/Video")
+
+with Files("PeerIdentity.*"):
+ BUG_COMPONENT = ("Core", "WebRTC: Signaling")
+
+with Files("common/**"):
+ BUG_COMPONENT = ("Core", "WebRTC: Signaling")
+with Files("jsep/**"):
+ BUG_COMPONENT = ("Core", "WebRTC: Signaling")
+with Files("libwebrtcglue/**"):
+ BUG_COMPONENT = ("Core", "WebRTC: Signaling")
+with Files("transportbridge/**"):
+ BUG_COMPONENT = ("Core", "WebRTC: Signaling")
+with Files("jsapi/**"):
+ BUG_COMPONENT = ("Core", "WebRTC: Signaling")
+with Files("sdp/**"):
+ BUG_COMPONENT = ("Core", "WebRTC: Signaling")
+with Files("third_party_build/**"):
+ BUG_COMPONENT = ("Core", "WebRTC")
+
+
+EXPORTS += [
+ "CubebDeviceEnumerator.h",
+ "MediaEngine.h",
+ "MediaEngineFake.h",
+ "MediaEnginePrefs.h",
+ "MediaEngineSource.h",
+ "MediaTrackConstraints.h",
+ "SineWaveGenerator.h",
+]
+
+SOURCES += [
+ "CubebDeviceEnumerator.cpp",
+]
+
+if CONFIG["MOZ_WEBRTC"]:
+ EXPORTS += [
+ "MediaEngineRemoteVideoSource.h",
+ "MediaEngineWebRTC.h",
+ "MediaEngineWebRTCAudio.h",
+ ]
+ EXPORTS.mozilla.dom += ["RTCIdentityProviderRegistrar.h"]
+ UNIFIED_SOURCES += [
+ "MediaEngineRemoteVideoSource.cpp",
+ "MediaEngineWebRTCAudio.cpp",
+ "RTCCertificate.cpp",
+ "RTCIdentityProviderRegistrar.cpp",
+ ]
+ # MediaEngineWebRTC.cpp needs to be built separately.
+ SOURCES += [
+ "MediaEngineWebRTC.cpp",
+ ]
+ PREPROCESSED_IPDL_SOURCES += [
+ "PWebrtcGlobal.ipdl",
+ ]
+ LOCAL_INCLUDES += [
+ "..",
+ "/dom/base",
+ "/dom/media",
+ "/dom/media/webrtc/common",
+ "/dom/media/webrtc/common/browser_logging",
+ "/media/libyuv/libyuv/include",
+ "/third_party/libwebrtc",
+ "/third_party/libwebrtc/third_party/abseil-cpp",
+ ]
+
+if CONFIG["MOZ_WEBRTC_SIGNALING"]:
+ DIRS += [
+ "common",
+ "jsapi",
+ "jsep",
+ "libwebrtcglue",
+ "sdp",
+ "transportbridge",
+ "/third_party/libwebrtc",
+ ]
+
+ if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ DIRS += [
+ "/third_party/drm/",
+ "/third_party/drm/libdrm",
+ "/third_party/gbm/",
+ "/third_party/gbm/libgbm",
+ "/third_party/libepoxy/",
+ "/third_party/pipewire/libpipewire",
+ ]
+
+ # Avoid warnings from third-party code that we can not modify.
+ if CONFIG["CC_TYPE"] == "clang-cl":
+ CXXFLAGS += ["-Wno-invalid-source-encoding"]
+
+
+PREPROCESSED_IPDL_SOURCES += [
+ "PMediaTransport.ipdl",
+]
+
+UNIFIED_SOURCES += [
+ "MediaEngineFake.cpp",
+ "MediaEngineSource.cpp",
+ "MediaTrackConstraints.cpp",
+ "PeerIdentity.cpp",
+]
+
+EXPORTS.mozilla += [
+ "PeerIdentity.h",
+]
+EXPORTS.mozilla.dom += [
+ "RTCCertificate.h",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+# Suppress some GCC/clang warnings being treated as errors:
+# - about attributes on forward declarations for types that are already
+# defined, which complains about important MOZ_EXPORT attributes for
+# android API types
+CXXFLAGS += [
+ "-Wno-error=attributes",
+]
+
+FINAL_LIBRARY = "xul"
+
+# Add libFuzzer configuration directives
+include("/tools/fuzzing/libfuzzer-config.mozbuild")
diff --git a/dom/media/webrtc/sdp/HybridSdpParser.cpp b/dom/media/webrtc/sdp/HybridSdpParser.cpp
new file mode 100644
index 0000000000..d1f2d5c058
--- /dev/null
+++ b/dom/media/webrtc/sdp/HybridSdpParser.cpp
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "sdp/HybridSdpParser.h"
+#include "sdp/SdpLog.h"
+#include "sdp/SdpPref.h"
+#include "sdp/SdpTelemetry.h"
+#include "sdp/SipccSdpParser.h"
+#include "sdp/RsdparsaSdpParser.h"
+#include "sdp/ParsingResultComparer.h"
+
+#include "mozilla/Logging.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Telemetry.h"
+
+#include <unordered_map>
+
+namespace mozilla {
+
+using mozilla::LogLevel;
+
+const std::string& HybridSdpParser::ParserName() {
+ const static std::string PARSER_NAME = "hybrid";
+ return PARSER_NAME;
+}
+
+HybridSdpParser::HybridSdpParser()
+ : mStrictSuccess(SdpPref::StrictSuccess()),
+ mPrimary(SdpPref::Primary()),
+ mSecondary(SdpPref::Secondary()),
+ mFailover(SdpPref::Failover()) {
+ MOZ_ASSERT(!(mSecondary && mFailover),
+ "Can not have both a secondary and failover parser!");
+ MOZ_LOG(SdpLog, LogLevel::Info,
+ ("Primary SDP Parser: %s", mPrimary->Name().c_str()));
+ mSecondary.apply([](auto& parser) {
+ MOZ_LOG(SdpLog, LogLevel::Info,
+ ("Secondary SDP Logger: %s", parser->Name().c_str()));
+ });
+ mFailover.apply([](auto& parser) {
+ MOZ_LOG(SdpLog, LogLevel::Info,
+ ("Failover SDP Logger: %s", parser->Name().c_str()));
+ });
+}
+
+auto HybridSdpParser::Parse(const std::string& aText)
+ -> UniquePtr<SdpParser::Results> {
+ using Results = UniquePtr<SdpParser::Results>;
+ using Role = SdpTelemetry::Roles;
+ using Mode = SdpPref::AlternateParseModes;
+
+ Mode mode = Mode::Never;
+ auto results = mPrimary->Parse(aText);
+
+ auto successful = [&](Results& aRes) -> bool {
+ // In strict mode any reported error counts as failure
+ if (mStrictSuccess) {
+ return aRes->Ok();
+ }
+ return aRes->Sdp() != nullptr;
+ };
+ // Pass results on for comparison and return A if it was a success and B
+ // otherwise.
+ auto compare = [&](Results&& aResB) -> Results {
+ SdpTelemetry::RecordParse(aResB, mode, Role::Secondary);
+ ParsingResultComparer::Compare(results, aResB, aText, mode);
+ return std::move(successful(results) ? results : aResB);
+ };
+ // Run secondary parser, if there is one, and update selected results.
+ mSecondary.apply([&](auto& sec) {
+ mode = Mode::Parallel;
+ results = compare(std::move(sec->Parse(aText)));
+ });
+ // Run failover parser, if there is one, and update selected results.
+ mFailover.apply([&](auto& failover) { // Only run if primary parser failed
+ mode = Mode::Failover;
+ if (!successful(results)) {
+ results = compare(std::move(failover->Parse(aText)));
+ }
+ });
+
+ SdpTelemetry::RecordParse(results, mode, Role::Primary);
+ return results;
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/sdp/HybridSdpParser.h b/dom/media/webrtc/sdp/HybridSdpParser.h
new file mode 100644
index 0000000000..2b0ef399a5
--- /dev/null
+++ b/dom/media/webrtc/sdp/HybridSdpParser.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _HYBRIDSDPPARSER_H_
+#define _HYBRIDSDPPARSER_H_
+
+#include "sdp/SdpParser.h"
+#include "sdp/SdpTelemetry.h"
+
+namespace mozilla {
+
+// This shim parser delegates parsing to WEbRTC-SDP and SIPCC, based on
+// preference flags. Additionally it handles collecting telemetry and fallback
+// behavior between the parsers.
+class HybridSdpParser : public SdpParser {
+ static const std::string& ParserName();
+
+ public:
+ HybridSdpParser();
+ virtual ~HybridSdpParser() = default;
+
+ auto Name() const -> const std::string& override { return ParserName(); }
+ auto Parse(const std::string& aText)
+ -> UniquePtr<SdpParser::Results> override;
+
+ private:
+ const bool mStrictSuccess;
+ const UniquePtr<SdpParser> mPrimary;
+ const Maybe<UniquePtr<SdpParser>> mSecondary;
+ const Maybe<UniquePtr<SdpParser>> mFailover;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/sdp/ParsingResultComparer.cpp b/dom/media/webrtc/sdp/ParsingResultComparer.cpp
new file mode 100644
index 0000000000..8592fa52b3
--- /dev/null
+++ b/dom/media/webrtc/sdp/ParsingResultComparer.cpp
@@ -0,0 +1,331 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "sdp/Sdp.h"
+#include "sdp/ParsingResultComparer.h"
+#include "sdp/SipccSdpParser.h"
+#include "sdp/RsdparsaSdpParser.h"
+#include "sdp/SdpTelemetry.h"
+
+#include <string>
+#include <ostream>
+#include <regex>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Logging.h"
+
+using mozilla::LogLevel;
+static mozilla::LazyLogModule sSdpDiffLogger("sdpdiff_logger");
+
+#define LOGD(msg) MOZ_LOG(sSdpDiffLogger, LogLevel::Debug, msg)
+#define LOGE(msg) MOZ_LOG(sSdpDiffLogger, LogLevel::Error, msg)
+
+#define LOG_EXPECT(result, expect, msg) \
+ { \
+ if (((expect) == SdpComparisonResult::Equal) == (result)) { \
+ LOGD(msg); \
+ } else { \
+ LOGE(("UNEXPECTED COMPARISON RESULT: vvvvvv")); \
+ LOGE(msg); \
+ } \
+ }
+
+namespace mozilla {
+
+using AttributeType = SdpAttribute::AttributeType;
+
+template <typename T>
+std::string ToString(const T& serializable) {
+ std::ostringstream os;
+
+ os << serializable;
+ return os.str();
+}
+bool ParsingResultComparer::Compare(const Results& aResA, const Results& aResB,
+ const std::string& aOriginalSdp,
+ const SdpPref::AlternateParseModes& aMode) {
+ MOZ_ASSERT(aResA, "aResA must not be a nullptr");
+ MOZ_ASSERT(aResB, "aResB must not be a nullptr");
+ MOZ_ASSERT(aResA->ParserName() != aResB->ParserName(),
+ "aResA and aResB must be from different parsers");
+ SdpTelemetry::RecordCompare(aResA, aResB, aMode);
+
+ ParsingResultComparer comparer;
+ if (!aResA->Sdp() || !aResB->Sdp()) {
+ return !aResA->Sdp() && !aResB->Sdp();
+ }
+ if (SipccSdpParser::IsNamed(aResA->ParserName())) {
+ MOZ_ASSERT(RsdparsaSdpParser::IsNamed(aResB->ParserName()));
+ return comparer.Compare(*aResB->Sdp(), *aResA->Sdp(), aOriginalSdp,
+ SdpComparisonResult::Equal);
+ }
+ MOZ_ASSERT(SipccSdpParser::IsNamed(aResB->ParserName()));
+ MOZ_ASSERT(RsdparsaSdpParser::IsNamed(aResA->ParserName()));
+ return comparer.Compare(*aResA->Sdp(), *aResB->Sdp(), aOriginalSdp,
+ SdpComparisonResult::Equal);
+}
+
+bool ParsingResultComparer::Compare(const Sdp& rsdparsaSdp, const Sdp& sipccSdp,
+ const std::string& originalSdp,
+ const SdpComparisonResult expect) {
+ mOriginalSdp = originalSdp;
+ const std::string sipccSdpStr = sipccSdp.ToString();
+ const std::string rsdparsaSdpStr = rsdparsaSdp.ToString();
+
+ bool result = rsdparsaSdpStr == sipccSdpStr;
+ LOG_EXPECT(result, expect, ("The original sdp: \n%s", mOriginalSdp.c_str()));
+ if (result) {
+ Telemetry::ScalarAdd(Telemetry::ScalarID::WEBRTC_SDP_PARSER_DIFF,
+ u"serialization_is_equal"_ns, 1);
+ LOG_EXPECT(result, expect, ("Serialization is equal"));
+ return result;
+ }
+ // Do a deep comparison
+ result = true;
+
+ Telemetry::ScalarAdd(Telemetry::ScalarID::WEBRTC_SDP_PARSER_DIFF,
+ u"serialization_is_not_equal"_ns, 1);
+ LOG_EXPECT(result, expect,
+ ("Serialization is not equal\n"
+ " --- Sipcc SDP ---\n"
+ "%s\n"
+ "--- Rsdparsa SDP ---\n"
+ "%s\n",
+ sipccSdpStr.c_str(), rsdparsaSdpStr.c_str()));
+
+ const std::string rsdparsaOriginStr = ToString(rsdparsaSdp.GetOrigin());
+ const std::string sipccOriginStr = ToString(sipccSdp.GetOrigin());
+
+ // Compare the session level
+ if (rsdparsaOriginStr != sipccOriginStr) {
+ Telemetry::ScalarAdd(Telemetry::ScalarID::WEBRTC_SDP_PARSER_DIFF, u"o="_ns,
+ 1);
+ result = false;
+ LOG_EXPECT(result, expect,
+ ("origin is not equal\nrust origin: %s\nsipcc origin: %s",
+ rsdparsaOriginStr.c_str(), sipccOriginStr.c_str()));
+ }
+
+ if (MOZ_LOG_TEST(sSdpDiffLogger, LogLevel::Debug)) {
+ const auto rust_sess_attr_count = rsdparsaSdp.GetAttributeList().Count();
+ const auto sipcc_sess_attr_count = sipccSdp.GetAttributeList().Count();
+
+ if (rust_sess_attr_count != sipcc_sess_attr_count) {
+ LOG_EXPECT(false, expect,
+ ("Session level attribute count is NOT equal, rsdparsa: %u, "
+ "sipcc: %u\n",
+ rust_sess_attr_count, sipcc_sess_attr_count));
+ }
+ }
+
+ result &= CompareAttrLists(rsdparsaSdp.GetAttributeList(),
+ sipccSdp.GetAttributeList(), -1);
+
+ const uint32_t sipccMediaSecCount =
+ static_cast<uint32_t>(sipccSdp.GetMediaSectionCount());
+ const uint32_t rsdparsaMediaSecCount =
+ static_cast<uint32_t>(rsdparsaSdp.GetMediaSectionCount());
+
+ if (sipccMediaSecCount != rsdparsaMediaSecCount) {
+ result = false;
+ Telemetry::ScalarAdd(Telemetry::ScalarID::WEBRTC_SDP_PARSER_DIFF,
+ u"inequal_msec_count"_ns, 1);
+ LOG_EXPECT(result, expect,
+ ("Media section count is NOT equal, rsdparsa: %d, sipcc: %d \n",
+ rsdparsaMediaSecCount, sipccMediaSecCount));
+ }
+
+ for (size_t i = 0; i < std::min(sipccMediaSecCount, rsdparsaMediaSecCount);
+ i++) {
+ result &= CompareMediaSections(rsdparsaSdp.GetMediaSection(i),
+ sipccSdp.GetMediaSection(i));
+ }
+
+ return result;
+}
+
+bool ParsingResultComparer::CompareMediaSections(
+ const SdpMediaSection& rustMediaSection,
+ const SdpMediaSection& sipccMediaSection,
+ const SdpComparisonResult expect) const {
+ bool result = true;
+ auto trackMediaLineMismatch = [&result, &expect](
+ auto rustValue, auto sipccValue,
+ const nsString& valueDescription) {
+ result = false;
+ nsString typeStr = u"m="_ns;
+ typeStr += valueDescription;
+ Telemetry::ScalarAdd(Telemetry::ScalarID::WEBRTC_SDP_PARSER_DIFF, typeStr,
+ 1);
+ LOG_EXPECT(result, expect,
+ ("The media line values %s are not equal\n"
+ "rsdparsa value: %s\n"
+ "sipcc value: %s\n",
+ NS_LossyConvertUTF16toASCII(valueDescription).get(),
+ ToString(rustValue).c_str(), ToString(sipccValue).c_str()));
+ };
+
+ auto compareMediaLineValue = [trackMediaLineMismatch](
+ auto rustValue, auto sipccValue,
+ const nsString& valueDescription) {
+ if (rustValue != sipccValue) {
+ trackMediaLineMismatch(rustValue, sipccValue, valueDescription);
+ }
+ };
+
+ auto compareSimpleMediaLineValue =
+ [&rustMediaSection, &sipccMediaSection, compareMediaLineValue](
+ auto valGetFuncPtr, const nsString& valueDescription) {
+ compareMediaLineValue((rustMediaSection.*valGetFuncPtr)(),
+ (sipccMediaSection.*valGetFuncPtr)(),
+ valueDescription);
+ };
+
+ compareSimpleMediaLineValue(&SdpMediaSection::GetMediaType, u"media_type"_ns);
+ compareSimpleMediaLineValue(&SdpMediaSection::GetPort, u"port"_ns);
+ compareSimpleMediaLineValue(&SdpMediaSection::GetPortCount, u"port_count"_ns);
+ compareSimpleMediaLineValue(&SdpMediaSection::GetProtocol, u"protocol"_ns);
+ compareSimpleMediaLineValue(&SdpMediaSection::IsReceiving,
+ u"is_receiving"_ns);
+ compareSimpleMediaLineValue(&SdpMediaSection::IsSending, u"is_sending"_ns);
+ compareSimpleMediaLineValue(&SdpMediaSection::GetDirection, u"direction"_ns);
+ compareSimpleMediaLineValue(&SdpMediaSection::GetLevel, u"level"_ns);
+
+ compareMediaLineValue(ToString(rustMediaSection.GetConnection()),
+ ToString(sipccMediaSection.GetConnection()),
+ u"connection"_ns);
+
+ result &= CompareAttrLists(rustMediaSection.GetAttributeList(),
+ sipccMediaSection.GetAttributeList(),
+ static_cast<int>(rustMediaSection.GetLevel()));
+ return result;
+}
+
+bool ParsingResultComparer::CompareAttrLists(
+ const SdpAttributeList& rustAttrlist, const SdpAttributeList& sipccAttrlist,
+ int level, const SdpComparisonResult expect) const {
+ bool result = true;
+
+ for (size_t i = AttributeType::kFirstAttribute;
+ i <= static_cast<size_t>(AttributeType::kLastAttribute); i++) {
+ const AttributeType type = static_cast<AttributeType>(i);
+ std::string attrStr;
+ if (type != AttributeType::kDirectionAttribute) {
+ attrStr = "a=" + SdpAttribute::GetAttributeTypeString(type);
+ } else {
+ attrStr = "a=_direction_attribute_";
+ }
+
+ if (sipccAttrlist.HasAttribute(type, false)) {
+ auto sipccAttrStr = ToString(*sipccAttrlist.GetAttribute(type, false));
+
+ if (!rustAttrlist.HasAttribute(type, false)) {
+ result = false;
+ nsString typeStr;
+ typeStr.AssignASCII(attrStr.c_str());
+ typeStr += u"_missing"_ns;
+ Telemetry::ScalarAdd(Telemetry::ScalarID::WEBRTC_SDP_PARSER_DIFF,
+ typeStr, 1);
+ LOG_EXPECT(result, expect,
+ ("Rust is missing the attribute: %s\n", attrStr.c_str()));
+ LOG_EXPECT(result, expect,
+ ("Rust is missing: %s\n", sipccAttrStr.c_str()));
+
+ continue;
+ }
+
+ auto rustAttrStr = ToString(*rustAttrlist.GetAttribute(type, false));
+
+ if (rustAttrStr != sipccAttrStr) {
+ if (type == AttributeType::kFmtpAttribute) {
+ if (rustAttrlist.GetFmtp() == sipccAttrlist.GetFmtp()) {
+ continue;
+ }
+ }
+
+ std::string originalAttrStr = GetAttributeLines(attrStr, level);
+ if (rustAttrStr != originalAttrStr) {
+ result = false;
+ nsString typeStr;
+ typeStr.AssignASCII(attrStr.c_str());
+ typeStr += u"_inequal"_ns;
+ Telemetry::ScalarAdd(Telemetry::ScalarID::WEBRTC_SDP_PARSER_DIFF,
+ typeStr, 1);
+ LOG_EXPECT(result, expect,
+ ("%s is neither equal to sipcc nor to the orginal sdp\n"
+ "--------------rsdparsa attribute---------------\n"
+ "%s"
+ "--------------sipcc attribute---------------\n"
+ "%s"
+ "--------------original attribute---------------\n"
+ "%s\n",
+ attrStr.c_str(), rustAttrStr.c_str(),
+ sipccAttrStr.c_str(), originalAttrStr.c_str()));
+ } else {
+ LOG_EXPECT(
+ result, expect,
+ ("But the rust serialization is equal to the orignal sdp\n"));
+ }
+ }
+ } else {
+ if (rustAttrlist.HasAttribute(type, false)) {
+ nsString typeStr;
+ typeStr.AssignASCII(attrStr.c_str());
+ typeStr += u"_unexpected"_ns;
+ Telemetry::ScalarAdd(Telemetry::ScalarID::WEBRTC_SDP_PARSER_DIFF,
+ typeStr, 1);
+ }
+ }
+ }
+
+ return result;
+}
+
+std::vector<std::string> SplitLines(const std::string& sdp) {
+ std::stringstream ss(sdp);
+ std::string to;
+ std::vector<std::string> lines;
+
+ while (std::getline(ss, to, '\n')) {
+ lines.push_back(to);
+ }
+
+ return lines;
+}
+
+std::string ParsingResultComparer::GetAttributeLines(
+ const std::string& attrType, int level) const {
+ std::vector<std::string> lines = SplitLines(mOriginalSdp);
+ std::string attrToFind = attrType + ":";
+ std::string attrLines;
+ int currentLevel = -1;
+ // Filters rtcp-fb lines that contain "x-..." types
+ // This is because every SDP from Edge contains these rtcp-fb x- types
+ // for example: a=rtcp-fb:121 x-foo
+ std::regex customRtcpFbLines(R"(a\=rtcp\-fb\:(\d+|\*).* x\-.*)");
+
+ for (auto& line : lines) {
+ if (line.find("m=") == 0) {
+ if (level > currentLevel) {
+ attrLines.clear();
+ currentLevel++;
+ } else {
+ break;
+ }
+ } else if (line.find(attrToFind) == 0) {
+ if (std::regex_match(line, customRtcpFbLines)) {
+ continue;
+ }
+
+ attrLines += (line + '\n');
+ }
+ }
+
+ return attrLines;
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/sdp/ParsingResultComparer.h b/dom/media/webrtc/sdp/ParsingResultComparer.h
new file mode 100644
index 0000000000..5a1c2ac635
--- /dev/null
+++ b/dom/media/webrtc/sdp/ParsingResultComparer.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _PARSINGRESULTCOMPARER_H_
+#define _PARSINGRESULTCOMPARER_H_
+
+#include "sdp/SdpParser.h"
+#include "sdp/SdpPref.h"
+
+#include <string>
+
+namespace mozilla {
+
+class Sdp;
+class SdpMediaSection;
+class SdpAttributeList;
+
+enum class SdpComparisonResult {
+ Inequal = false,
+ Equal = true,
+};
+
+class ParsingResultComparer {
+ public:
+ using Results = UniquePtr<SdpParser::Results>;
+
+ ParsingResultComparer() = default;
+
+ static bool Compare(const Results& aResA, const Results& aResB,
+ const std::string& aOrignalSdp,
+ const SdpPref::AlternateParseModes& aMode);
+ bool Compare(const Sdp& rsdparsaSdp, const Sdp& sipccSdp,
+ const std::string& aOriginalSdp,
+ const SdpComparisonResult expect = SdpComparisonResult::Equal);
+ bool CompareMediaSections(
+ const SdpMediaSection& rustMediaSection,
+ const SdpMediaSection& sipccMediaSection,
+ const SdpComparisonResult expect = SdpComparisonResult::Equal) const;
+ bool CompareAttrLists(
+ const SdpAttributeList& rustAttrlist,
+ const SdpAttributeList& sipccAttrlist, int level,
+ const SdpComparisonResult expect = SdpComparisonResult::Equal) const;
+ void TrackRustParsingFailed(size_t sipccErrorCount) const;
+ void TrackSipccParsingFailed(size_t rustErrorCount) const;
+
+ private:
+ std::string mOriginalSdp;
+
+ std::string GetAttributeLines(const std::string& attrType, int level) const;
+};
+
+} // namespace mozilla
+
+#endif // _PARSINGRESULTCOMPARER_H_
diff --git a/dom/media/webrtc/sdp/RsdparsaSdp.cpp b/dom/media/webrtc/sdp/RsdparsaSdp.cpp
new file mode 100644
index 0000000000..0e653dc17e
--- /dev/null
+++ b/dom/media/webrtc/sdp/RsdparsaSdp.cpp
@@ -0,0 +1,126 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "sdp/RsdparsaSdp.h"
+
+#include <cstdlib>
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Assertions.h"
+#include "nsError.h"
+#include <iostream>
+
+#include "sdp/RsdparsaSdpInc.h"
+#include "sdp/RsdparsaSdpMediaSection.h"
+
+#ifdef CRLF
+# undef CRLF
+#endif
+#define CRLF "\r\n"
+
+namespace mozilla {
+
+RsdparsaSdp::RsdparsaSdp(RsdparsaSessionHandle session, const SdpOrigin& origin)
+ : mSession(std::move(session)), mOrigin(origin) {
+ RsdparsaSessionHandle attributeSession(sdp_new_reference(mSession.get()));
+ mAttributeList.reset(
+ new RsdparsaSdpAttributeList(std::move(attributeSession)));
+
+ size_t section_count = sdp_media_section_count(mSession.get());
+ for (size_t level = 0; level < section_count; level++) {
+ RustMediaSection* mediaSection =
+ sdp_get_media_section(mSession.get(), level);
+ if (!mediaSection) {
+ MOZ_ASSERT(false,
+ "sdp_get_media_section failed because level was out of"
+ " bounds, but we did a bounds check!");
+ break;
+ }
+ RsdparsaSessionHandle newSession(sdp_new_reference(mSession.get()));
+ RsdparsaSdpMediaSection* sdpMediaSection;
+ sdpMediaSection = new RsdparsaSdpMediaSection(
+ level, std::move(newSession), mediaSection, mAttributeList.get());
+ mMediaSections.emplace_back(sdpMediaSection);
+ }
+}
+
+RsdparsaSdp::RsdparsaSdp(const RsdparsaSdp& aOrig)
+ : RsdparsaSdp(RsdparsaSessionHandle(create_sdp_clone(aOrig.mSession.get())),
+ aOrig.mOrigin) {}
+
+Sdp* RsdparsaSdp::Clone() const { return new RsdparsaSdp(*this); }
+
+const SdpOrigin& RsdparsaSdp::GetOrigin() const { return mOrigin; }
+
+uint32_t RsdparsaSdp::GetBandwidth(const std::string& type) const {
+ return get_sdp_bandwidth(mSession.get(), type.c_str());
+}
+
+const SdpMediaSection& RsdparsaSdp::GetMediaSection(size_t level) const {
+ if (level > mMediaSections.size()) {
+ MOZ_CRASH();
+ }
+ return *mMediaSections[level];
+}
+
+SdpMediaSection& RsdparsaSdp::GetMediaSection(size_t level) {
+ if (level > mMediaSections.size()) {
+ MOZ_CRASH();
+ }
+ return *mMediaSections[level];
+}
+
+SdpMediaSection& RsdparsaSdp::AddMediaSection(
+ SdpMediaSection::MediaType mediaType, SdpDirectionAttribute::Direction dir,
+ uint16_t port, SdpMediaSection::Protocol protocol, sdp::AddrType addrType,
+ const std::string& addr) {
+ StringView rustAddr{addr.c_str(), addr.size()};
+ auto nr = sdp_add_media_section(mSession.get(), mediaType, dir, port,
+ protocol, addrType, rustAddr);
+
+ if (NS_SUCCEEDED(nr)) {
+ size_t level = mMediaSections.size();
+ RsdparsaSessionHandle newSessHandle(sdp_new_reference(mSession.get()));
+
+ auto rustMediaSection = sdp_get_media_section(mSession.get(), level);
+ auto mediaSection =
+ new RsdparsaSdpMediaSection(level, std::move(newSessHandle),
+ rustMediaSection, mAttributeList.get());
+ mMediaSections.emplace_back(mediaSection);
+
+ return *mediaSection;
+ } else {
+ // Return the last media section if the construction of this one fails
+ return GetMediaSection(mMediaSections.size() - 1);
+ }
+}
+
+void RsdparsaSdp::Serialize(std::ostream& os) const {
+ os << "v=0" << CRLF << mOrigin << "s=-" << CRLF;
+
+ // We don't support creating i=, u=, e=, p=
+ // We don't generate c= at the session level (only in media)
+
+ BandwidthVec* bwVec = sdp_get_session_bandwidth_vec(mSession.get());
+ char* bwString = sdp_serialize_bandwidth(bwVec);
+ if (bwString) {
+ os << bwString;
+ sdp_free_string(bwString);
+ }
+
+ os << "t=0 0" << CRLF;
+
+ // We don't support r= or z=
+
+ // attributes
+ os << *mAttributeList;
+
+ // media sections
+ for (const auto& msection : mMediaSections) {
+ os << *msection;
+ }
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/sdp/RsdparsaSdp.h b/dom/media/webrtc/sdp/RsdparsaSdp.h
new file mode 100644
index 0000000000..4942abb574
--- /dev/null
+++ b/dom/media/webrtc/sdp/RsdparsaSdp.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _RSDPARSA_SDP_H_
+#define _RSDPARSA_SDP_H_
+
+#include "mozilla/UniquePtr.h"
+
+#include "sdp/Sdp.h"
+
+#include "sdp/RsdparsaSdpMediaSection.h"
+#include "sdp/RsdparsaSdpAttributeList.h"
+#include "sdp/RsdparsaSdpInc.h"
+#include "sdp/RsdparsaSdpGlue.h"
+
+namespace mozilla {
+
+class RsdparsaSdpParser;
+class SdpParser;
+
+class RsdparsaSdp final : public Sdp {
+ friend class RsdparsaSdpParser;
+
+ public:
+ explicit RsdparsaSdp(RsdparsaSessionHandle session, const SdpOrigin& origin);
+
+ Sdp* Clone() const override;
+
+ const SdpOrigin& GetOrigin() const override;
+
+ // Note: connection information is always retrieved from media sections
+ uint32_t GetBandwidth(const std::string& type) const override;
+
+ size_t GetMediaSectionCount() const override {
+ return sdp_media_section_count(mSession.get());
+ }
+
+ const SdpAttributeList& GetAttributeList() const override {
+ return *mAttributeList;
+ }
+
+ SdpAttributeList& GetAttributeList() override { return *mAttributeList; }
+
+ const SdpMediaSection& GetMediaSection(size_t level) const override;
+
+ SdpMediaSection& GetMediaSection(size_t level) override;
+
+ SdpMediaSection& AddMediaSection(SdpMediaSection::MediaType media,
+ SdpDirectionAttribute::Direction dir,
+ uint16_t port,
+ SdpMediaSection::Protocol proto,
+ sdp::AddrType addrType,
+ const std::string& addr) override;
+
+ void Serialize(std::ostream&) const override;
+
+ private:
+ RsdparsaSdp() : mOrigin("", 0, 0, sdp::kIPv4, "") {}
+ RsdparsaSdp(const RsdparsaSdp& aOrig);
+
+ RsdparsaSessionHandle mSession;
+ SdpOrigin mOrigin;
+ UniquePtr<RsdparsaSdpAttributeList> mAttributeList;
+ std::vector<UniquePtr<RsdparsaSdpMediaSection>> mMediaSections;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/sdp/RsdparsaSdpAttributeList.cpp b/dom/media/webrtc/sdp/RsdparsaSdpAttributeList.cpp
new file mode 100644
index 0000000000..0b76757c44
--- /dev/null
+++ b/dom/media/webrtc/sdp/RsdparsaSdpAttributeList.cpp
@@ -0,0 +1,1301 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCRT.h"
+
+#include "sdp/RsdparsaSdpAttributeList.h"
+#include "sdp/RsdparsaSdpInc.h"
+#include "sdp/RsdparsaSdpGlue.h"
+
+#include <ostream>
+#include "mozilla/Assertions.h"
+
+#include <limits>
+
+namespace mozilla {
+
+const std::string RsdparsaSdpAttributeList::kEmptyString = "";
+
+RsdparsaSdpAttributeList::~RsdparsaSdpAttributeList() {
+ for (size_t i = 0; i < kNumAttributeTypes; ++i) {
+ delete mAttributes[i];
+ }
+}
+
+bool RsdparsaSdpAttributeList::HasAttribute(AttributeType type,
+ bool sessionFallback) const {
+ return !!GetAttribute(type, sessionFallback);
+}
+
+const SdpAttribute* RsdparsaSdpAttributeList::GetAttribute(
+ AttributeType type, bool sessionFallback) const {
+ const SdpAttribute* value = mAttributes[static_cast<size_t>(type)];
+ // Only do fallback when the attribute can appear at both the media and
+ // session level
+ if (!value && !AtSessionLevel() && sessionFallback &&
+ SdpAttribute::IsAllowedAtSessionLevel(type) &&
+ SdpAttribute::IsAllowedAtMediaLevel(type)) {
+ return mSessionAttributes->GetAttribute(type, false);
+ }
+ return value;
+}
+
+void RsdparsaSdpAttributeList::RemoveAttribute(AttributeType type) {
+ delete mAttributes[static_cast<size_t>(type)];
+ mAttributes[static_cast<size_t>(type)] = nullptr;
+}
+
+void RsdparsaSdpAttributeList::Clear() {
+ for (size_t i = 0; i < kNumAttributeTypes; ++i) {
+ RemoveAttribute(static_cast<AttributeType>(i));
+ }
+}
+
+uint32_t RsdparsaSdpAttributeList::Count() const {
+ uint32_t count = 0;
+ for (size_t i = 0; i < kNumAttributeTypes; ++i) {
+ if (mAttributes[i]) {
+ count++;
+ }
+ }
+ return count;
+}
+
+void RsdparsaSdpAttributeList::SetAttribute(SdpAttribute* attr) {
+ if (!IsAllowedHere(attr->GetType())) {
+ MOZ_ASSERT(false, "This type of attribute is not allowed here");
+ delete attr;
+ return;
+ }
+ RemoveAttribute(attr->GetType());
+ mAttributes[attr->GetType()] = attr;
+}
+
+const std::vector<std::string>& RsdparsaSdpAttributeList::GetCandidate() const {
+ if (!HasAttribute(SdpAttribute::kCandidateAttribute)) {
+ MOZ_CRASH();
+ }
+
+ return static_cast<const SdpMultiStringAttribute*>(
+ GetAttribute(SdpAttribute::kCandidateAttribute))
+ ->mValues;
+}
+
+const SdpConnectionAttribute& RsdparsaSdpAttributeList::GetConnection() const {
+ if (!HasAttribute(SdpAttribute::kConnectionAttribute)) {
+ MOZ_CRASH();
+ }
+
+ return *static_cast<const SdpConnectionAttribute*>(
+ GetAttribute(SdpAttribute::kConnectionAttribute));
+}
+
+SdpDirectionAttribute::Direction RsdparsaSdpAttributeList::GetDirection()
+ const {
+ if (!HasAttribute(SdpAttribute::kDirectionAttribute)) {
+ MOZ_CRASH();
+ }
+
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kDirectionAttribute);
+ return static_cast<const SdpDirectionAttribute*>(attr)->mValue;
+}
+
+const SdpDtlsMessageAttribute& RsdparsaSdpAttributeList::GetDtlsMessage()
+ const {
+ if (!HasAttribute(SdpAttribute::kDtlsMessageAttribute)) {
+ MOZ_CRASH();
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kDtlsMessageAttribute);
+ return *static_cast<const SdpDtlsMessageAttribute*>(attr);
+}
+
+const SdpExtmapAttributeList& RsdparsaSdpAttributeList::GetExtmap() const {
+ if (!HasAttribute(SdpAttribute::kExtmapAttribute)) {
+ MOZ_CRASH();
+ }
+
+ return *static_cast<const SdpExtmapAttributeList*>(
+ GetAttribute(SdpAttribute::kExtmapAttribute));
+}
+
+const SdpFingerprintAttributeList& RsdparsaSdpAttributeList::GetFingerprint()
+ const {
+ if (!HasAttribute(SdpAttribute::kFingerprintAttribute)) {
+ MOZ_CRASH();
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kFingerprintAttribute);
+ return *static_cast<const SdpFingerprintAttributeList*>(attr);
+}
+
+const SdpFmtpAttributeList& RsdparsaSdpAttributeList::GetFmtp() const {
+ if (!HasAttribute(SdpAttribute::kFmtpAttribute)) {
+ MOZ_CRASH();
+ }
+
+ return *static_cast<const SdpFmtpAttributeList*>(
+ GetAttribute(SdpAttribute::kFmtpAttribute));
+}
+
+const SdpGroupAttributeList& RsdparsaSdpAttributeList::GetGroup() const {
+ if (!HasAttribute(SdpAttribute::kGroupAttribute)) {
+ MOZ_CRASH();
+ }
+
+ return *static_cast<const SdpGroupAttributeList*>(
+ GetAttribute(SdpAttribute::kGroupAttribute));
+}
+
+const SdpOptionsAttribute& RsdparsaSdpAttributeList::GetIceOptions() const {
+ if (!HasAttribute(SdpAttribute::kIceOptionsAttribute)) {
+ MOZ_CRASH();
+ }
+
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kIceOptionsAttribute);
+ return *static_cast<const SdpOptionsAttribute*>(attr);
+}
+
+const std::string& RsdparsaSdpAttributeList::GetIcePwd() const {
+ if (!HasAttribute(SdpAttribute::kIcePwdAttribute)) {
+ return kEmptyString;
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kIcePwdAttribute);
+ return static_cast<const SdpStringAttribute*>(attr)->mValue;
+}
+
+const std::string& RsdparsaSdpAttributeList::GetIceUfrag() const {
+ if (!HasAttribute(SdpAttribute::kIceUfragAttribute)) {
+ return kEmptyString;
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kIceUfragAttribute);
+ return static_cast<const SdpStringAttribute*>(attr)->mValue;
+}
+
+const std::string& RsdparsaSdpAttributeList::GetIdentity() const {
+ if (!HasAttribute(SdpAttribute::kIdentityAttribute)) {
+ return kEmptyString;
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kIdentityAttribute);
+ return static_cast<const SdpStringAttribute*>(attr)->mValue;
+}
+
+const SdpImageattrAttributeList& RsdparsaSdpAttributeList::GetImageattr()
+ const {
+ if (!HasAttribute(SdpAttribute::kImageattrAttribute)) {
+ MOZ_CRASH();
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kImageattrAttribute);
+ return *static_cast<const SdpImageattrAttributeList*>(attr);
+}
+
+const SdpSimulcastAttribute& RsdparsaSdpAttributeList::GetSimulcast() const {
+ if (!HasAttribute(SdpAttribute::kSimulcastAttribute)) {
+ MOZ_CRASH();
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kSimulcastAttribute);
+ return *static_cast<const SdpSimulcastAttribute*>(attr);
+}
+
+const std::string& RsdparsaSdpAttributeList::GetLabel() const {
+ if (!HasAttribute(SdpAttribute::kLabelAttribute)) {
+ return kEmptyString;
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kLabelAttribute);
+ return static_cast<const SdpStringAttribute*>(attr)->mValue;
+}
+
+uint32_t RsdparsaSdpAttributeList::GetMaxptime() const {
+ if (!HasAttribute(SdpAttribute::kMaxptimeAttribute)) {
+ MOZ_CRASH();
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kMaxptimeAttribute);
+ return static_cast<const SdpNumberAttribute*>(attr)->mValue;
+}
+
+const std::string& RsdparsaSdpAttributeList::GetMid() const {
+ if (!HasAttribute(SdpAttribute::kMidAttribute)) {
+ return kEmptyString;
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kMidAttribute);
+ return static_cast<const SdpStringAttribute*>(attr)->mValue;
+}
+
+const SdpMsidAttributeList& RsdparsaSdpAttributeList::GetMsid() const {
+ if (!HasAttribute(SdpAttribute::kMsidAttribute)) {
+ MOZ_CRASH();
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kMsidAttribute);
+ return *static_cast<const SdpMsidAttributeList*>(attr);
+}
+
+const SdpMsidSemanticAttributeList& RsdparsaSdpAttributeList::GetMsidSemantic()
+ const {
+ if (!HasAttribute(SdpAttribute::kMsidSemanticAttribute)) {
+ MOZ_CRASH();
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kMsidSemanticAttribute);
+ return *static_cast<const SdpMsidSemanticAttributeList*>(attr);
+}
+
+const SdpRidAttributeList& RsdparsaSdpAttributeList::GetRid() const {
+ if (!HasAttribute(SdpAttribute::kRidAttribute)) {
+ MOZ_CRASH();
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kRidAttribute);
+ return *static_cast<const SdpRidAttributeList*>(attr);
+}
+
+uint32_t RsdparsaSdpAttributeList::GetPtime() const {
+ if (!HasAttribute(SdpAttribute::kPtimeAttribute)) {
+ MOZ_CRASH();
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kPtimeAttribute);
+ return static_cast<const SdpNumberAttribute*>(attr)->mValue;
+}
+
+const SdpRtcpAttribute& RsdparsaSdpAttributeList::GetRtcp() const {
+ if (!HasAttribute(SdpAttribute::kRtcpAttribute)) {
+ MOZ_CRASH();
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kRtcpAttribute);
+ return *static_cast<const SdpRtcpAttribute*>(attr);
+}
+
+const SdpRtcpFbAttributeList& RsdparsaSdpAttributeList::GetRtcpFb() const {
+ if (!HasAttribute(SdpAttribute::kRtcpFbAttribute)) {
+ MOZ_CRASH();
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kRtcpFbAttribute);
+ return *static_cast<const SdpRtcpFbAttributeList*>(attr);
+}
+
+const SdpRemoteCandidatesAttribute&
+RsdparsaSdpAttributeList::GetRemoteCandidates() const {
+ MOZ_CRASH("Not yet implemented");
+}
+
+const SdpRtpmapAttributeList& RsdparsaSdpAttributeList::GetRtpmap() const {
+ if (!HasAttribute(SdpAttribute::kRtpmapAttribute)) {
+ MOZ_CRASH();
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kRtpmapAttribute);
+ return *static_cast<const SdpRtpmapAttributeList*>(attr);
+}
+
+const SdpSctpmapAttributeList& RsdparsaSdpAttributeList::GetSctpmap() const {
+ if (!HasAttribute(SdpAttribute::kSctpmapAttribute)) {
+ MOZ_CRASH();
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kSctpmapAttribute);
+ return *static_cast<const SdpSctpmapAttributeList*>(attr);
+}
+
+uint32_t RsdparsaSdpAttributeList::GetSctpPort() const {
+ if (!HasAttribute(SdpAttribute::kSctpPortAttribute)) {
+ MOZ_CRASH();
+ }
+
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kSctpPortAttribute);
+ return static_cast<const SdpNumberAttribute*>(attr)->mValue;
+}
+
+uint32_t RsdparsaSdpAttributeList::GetMaxMessageSize() const {
+ if (!HasAttribute(SdpAttribute::kMaxMessageSizeAttribute)) {
+ MOZ_CRASH();
+ }
+
+ const SdpAttribute* attr =
+ GetAttribute(SdpAttribute::kMaxMessageSizeAttribute);
+ return static_cast<const SdpNumberAttribute*>(attr)->mValue;
+}
+
+const SdpSetupAttribute& RsdparsaSdpAttributeList::GetSetup() const {
+ if (!HasAttribute(SdpAttribute::kSetupAttribute)) {
+ MOZ_CRASH();
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kSetupAttribute);
+ return *static_cast<const SdpSetupAttribute*>(attr);
+}
+
+const SdpSsrcAttributeList& RsdparsaSdpAttributeList::GetSsrc() const {
+ if (!HasAttribute(SdpAttribute::kSsrcAttribute)) {
+ MOZ_CRASH();
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kSsrcAttribute);
+ return *static_cast<const SdpSsrcAttributeList*>(attr);
+}
+
+const SdpSsrcGroupAttributeList& RsdparsaSdpAttributeList::GetSsrcGroup()
+ const {
+ if (!HasAttribute(SdpAttribute::kSsrcGroupAttribute)) {
+ MOZ_CRASH();
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kSsrcGroupAttribute);
+ return *static_cast<const SdpSsrcGroupAttributeList*>(attr);
+}
+
+void RsdparsaSdpAttributeList::LoadAttribute(RustAttributeList* attributeList,
+ AttributeType type) {
+ if (!mAttributes[type]) {
+ switch (type) {
+ case SdpAttribute::kIceUfragAttribute:
+ LoadIceUfrag(attributeList);
+ return;
+ case SdpAttribute::kIcePwdAttribute:
+ LoadIcePwd(attributeList);
+ return;
+ case SdpAttribute::kIceOptionsAttribute:
+ LoadIceOptions(attributeList);
+ return;
+ case SdpAttribute::kDtlsMessageAttribute:
+ LoadDtlsMessage(attributeList);
+ return;
+ case SdpAttribute::kFingerprintAttribute:
+ LoadFingerprint(attributeList);
+ return;
+ case SdpAttribute::kIdentityAttribute:
+ LoadIdentity(attributeList);
+ return;
+ case SdpAttribute::kSetupAttribute:
+ LoadSetup(attributeList);
+ return;
+ case SdpAttribute::kSsrcAttribute:
+ LoadSsrc(attributeList);
+ return;
+ case SdpAttribute::kRtpmapAttribute:
+ LoadRtpmap(attributeList);
+ return;
+ case SdpAttribute::kFmtpAttribute:
+ LoadFmtp(attributeList);
+ return;
+ case SdpAttribute::kPtimeAttribute:
+ LoadPtime(attributeList);
+ return;
+ case SdpAttribute::kIceLiteAttribute:
+ case SdpAttribute::kRtcpMuxAttribute:
+ case SdpAttribute::kRtcpRsizeAttribute:
+ case SdpAttribute::kBundleOnlyAttribute:
+ case SdpAttribute::kEndOfCandidatesAttribute:
+ LoadFlags(attributeList);
+ return;
+ case SdpAttribute::kMaxMessageSizeAttribute:
+ LoadMaxMessageSize(attributeList);
+ return;
+ case SdpAttribute::kMidAttribute:
+ LoadMid(attributeList);
+ return;
+ case SdpAttribute::kMsidAttribute:
+ LoadMsid(attributeList);
+ return;
+ case SdpAttribute::kMsidSemanticAttribute:
+ LoadMsidSemantics(attributeList);
+ return;
+ case SdpAttribute::kGroupAttribute:
+ LoadGroup(attributeList);
+ return;
+ case SdpAttribute::kRtcpAttribute:
+ LoadRtcp(attributeList);
+ return;
+ case SdpAttribute::kRtcpFbAttribute:
+ LoadRtcpFb(attributeList);
+ return;
+ case SdpAttribute::kImageattrAttribute:
+ LoadImageattr(attributeList);
+ return;
+ case SdpAttribute::kSctpmapAttribute:
+ LoadSctpmaps(attributeList);
+ return;
+ case SdpAttribute::kDirectionAttribute:
+ LoadDirection(attributeList);
+ return;
+ case SdpAttribute::kRemoteCandidatesAttribute:
+ LoadRemoteCandidates(attributeList);
+ return;
+ case SdpAttribute::kRidAttribute:
+ LoadRids(attributeList);
+ return;
+ case SdpAttribute::kSctpPortAttribute:
+ LoadSctpPort(attributeList);
+ return;
+ case SdpAttribute::kExtmapAttribute:
+ LoadExtmap(attributeList);
+ return;
+ case SdpAttribute::kSimulcastAttribute:
+ LoadSimulcast(attributeList);
+ return;
+ case SdpAttribute::kMaxptimeAttribute:
+ LoadMaxPtime(attributeList);
+ return;
+ case SdpAttribute::kCandidateAttribute:
+ LoadCandidate(attributeList);
+ return;
+ case SdpAttribute::kSsrcGroupAttribute:
+ LoadSsrcGroup(attributeList);
+ return;
+ case SdpAttribute::kConnectionAttribute:
+ case SdpAttribute::kIceMismatchAttribute:
+ case SdpAttribute::kLabelAttribute:
+ // These attributes are unused
+ return;
+ }
+ }
+}
+
+void RsdparsaSdpAttributeList::LoadAll(RustAttributeList* attributeList) {
+ for (int i = SdpAttribute::kFirstAttribute; i <= SdpAttribute::kLastAttribute;
+ i++) {
+ LoadAttribute(attributeList, static_cast<SdpAttribute::AttributeType>(i));
+ }
+}
+
+void RsdparsaSdpAttributeList::LoadIceUfrag(RustAttributeList* attributeList) {
+ StringView ufragStr;
+ nsresult nr = sdp_get_iceufrag(attributeList, &ufragStr);
+ if (NS_SUCCEEDED(nr)) {
+ std::string iceufrag = convertStringView(ufragStr);
+ SetAttribute(
+ new SdpStringAttribute(SdpAttribute::kIceUfragAttribute, iceufrag));
+ }
+}
+
+void RsdparsaSdpAttributeList::LoadIcePwd(RustAttributeList* attributeList) {
+ StringView pwdStr;
+ nsresult nr = sdp_get_icepwd(attributeList, &pwdStr);
+ if (NS_SUCCEEDED(nr)) {
+ std::string icePwd = convertStringView(pwdStr);
+ SetAttribute(
+ new SdpStringAttribute(SdpAttribute::kIcePwdAttribute, icePwd));
+ }
+}
+
+void RsdparsaSdpAttributeList::LoadIdentity(RustAttributeList* attributeList) {
+ StringView identityStr;
+ nsresult nr = sdp_get_identity(attributeList, &identityStr);
+ if (NS_SUCCEEDED(nr)) {
+ std::string identity = convertStringView(identityStr);
+ SetAttribute(
+ new SdpStringAttribute(SdpAttribute::kIdentityAttribute, identity));
+ }
+}
+
+void RsdparsaSdpAttributeList::LoadIceOptions(
+ RustAttributeList* attributeList) {
+ StringVec* options;
+ nsresult nr = sdp_get_iceoptions(attributeList, &options);
+ if (NS_SUCCEEDED(nr)) {
+ std::vector<std::string> optionsVec;
+ auto optionsAttr =
+ MakeUnique<SdpOptionsAttribute>(SdpAttribute::kIceOptionsAttribute);
+ for (size_t i = 0; i < string_vec_len(options); i++) {
+ StringView optionStr;
+ string_vec_get_view(options, i, &optionStr);
+ optionsAttr->PushEntry(convertStringView(optionStr));
+ }
+ SetAttribute(optionsAttr.release());
+ }
+}
+
+void RsdparsaSdpAttributeList::LoadFingerprint(
+ RustAttributeList* attributeList) {
+ size_t nFp = sdp_get_fingerprint_count(attributeList);
+ if (nFp == 0) {
+ return;
+ }
+ auto rustFingerprints = MakeUnique<RustSdpAttributeFingerprint[]>(nFp);
+ sdp_get_fingerprints(attributeList, nFp, rustFingerprints.get());
+ auto fingerprints = MakeUnique<SdpFingerprintAttributeList>();
+ for (size_t i = 0; i < nFp; i++) {
+ const RustSdpAttributeFingerprint& fingerprint = rustFingerprints[i];
+ std::string algorithm;
+ switch (fingerprint.hashAlgorithm) {
+ case RustSdpAttributeFingerprintHashAlgorithm::kSha1:
+ algorithm = "sha-1";
+ break;
+ case RustSdpAttributeFingerprintHashAlgorithm::kSha224:
+ algorithm = "sha-224";
+ break;
+ case RustSdpAttributeFingerprintHashAlgorithm::kSha256:
+ algorithm = "sha-256";
+ break;
+ case RustSdpAttributeFingerprintHashAlgorithm::kSha384:
+ algorithm = "sha-384";
+ break;
+ case RustSdpAttributeFingerprintHashAlgorithm::kSha512:
+ algorithm = "sha-512";
+ break;
+ }
+
+ std::vector<uint8_t> fingerprintBytes =
+ convertU8Vec(fingerprint.fingerprint);
+
+ fingerprints->PushEntry(algorithm, fingerprintBytes);
+ }
+ SetAttribute(fingerprints.release());
+}
+
+void RsdparsaSdpAttributeList::LoadDtlsMessage(
+ RustAttributeList* attributeList) {
+ RustSdpAttributeDtlsMessage rustDtlsMessage;
+ nsresult nr = sdp_get_dtls_message(attributeList, &rustDtlsMessage);
+ if (NS_SUCCEEDED(nr)) {
+ SdpDtlsMessageAttribute::Role role;
+ if (rustDtlsMessage.role == RustSdpAttributeDtlsMessageType::kClient) {
+ role = SdpDtlsMessageAttribute::kClient;
+ } else {
+ role = SdpDtlsMessageAttribute::kServer;
+ }
+
+ std::string value = convertStringView(rustDtlsMessage.value);
+
+ SetAttribute(new SdpDtlsMessageAttribute(role, value));
+ }
+}
+
+void RsdparsaSdpAttributeList::LoadSetup(RustAttributeList* attributeList) {
+ RustSdpSetup rustSetup;
+ nsresult nr = sdp_get_setup(attributeList, &rustSetup);
+ if (NS_SUCCEEDED(nr)) {
+ SdpSetupAttribute::Role setupEnum;
+ switch (rustSetup) {
+ case RustSdpSetup::kRustActive:
+ setupEnum = SdpSetupAttribute::kActive;
+ break;
+ case RustSdpSetup::kRustActpass:
+ setupEnum = SdpSetupAttribute::kActpass;
+ break;
+ case RustSdpSetup::kRustHoldconn:
+ setupEnum = SdpSetupAttribute::kHoldconn;
+ break;
+ case RustSdpSetup::kRustPassive:
+ setupEnum = SdpSetupAttribute::kPassive;
+ break;
+ }
+ SetAttribute(new SdpSetupAttribute(setupEnum));
+ }
+}
+
+void RsdparsaSdpAttributeList::LoadSsrc(RustAttributeList* attributeList) {
+ size_t numSsrc = sdp_get_ssrc_count(attributeList);
+ if (numSsrc == 0) {
+ return;
+ }
+ auto rustSsrcs = MakeUnique<RustSdpAttributeSsrc[]>(numSsrc);
+ sdp_get_ssrcs(attributeList, numSsrc, rustSsrcs.get());
+ auto ssrcs = MakeUnique<SdpSsrcAttributeList>();
+ for (size_t i = 0; i < numSsrc; i++) {
+ RustSdpAttributeSsrc& ssrc = rustSsrcs[i];
+ std::string attribute = convertStringView(ssrc.attribute);
+ std::string value = convertStringView(ssrc.value);
+ if (value.length() == 0) {
+ ssrcs->PushEntry(ssrc.id, attribute);
+ } else {
+ ssrcs->PushEntry(ssrc.id, attribute + ":" + value);
+ }
+ }
+ SetAttribute(ssrcs.release());
+}
+
+void RsdparsaSdpAttributeList::LoadSsrcGroup(RustAttributeList* attributeList) {
+ size_t numSsrcGroups = sdp_get_ssrc_group_count(attributeList);
+ if (numSsrcGroups == 0) {
+ return;
+ }
+ auto rustSsrcGroups = MakeUnique<RustSdpAttributeSsrcGroup[]>(numSsrcGroups);
+ sdp_get_ssrc_groups(attributeList, numSsrcGroups, rustSsrcGroups.get());
+ auto ssrcGroups = MakeUnique<SdpSsrcGroupAttributeList>();
+ for (size_t i = 0; i < numSsrcGroups; i++) {
+ RustSdpAttributeSsrcGroup& ssrcGroup = rustSsrcGroups[i];
+ SdpSsrcGroupAttributeList::Semantics semantic;
+ switch (ssrcGroup.semantic) {
+ case RustSdpAttributeSsrcGroupSemantic ::kRustDup:
+ semantic = SdpSsrcGroupAttributeList::kDup;
+ break;
+ case RustSdpAttributeSsrcGroupSemantic ::kRustFec:
+ semantic = SdpSsrcGroupAttributeList::kFec;
+ break;
+ case RustSdpAttributeSsrcGroupSemantic ::kRustFecFr:
+ semantic = SdpSsrcGroupAttributeList::kFecFr;
+ break;
+ case RustSdpAttributeSsrcGroupSemantic ::kRustFid:
+ semantic = SdpSsrcGroupAttributeList::kFid;
+ break;
+ case RustSdpAttributeSsrcGroupSemantic ::kRustSim:
+ semantic = SdpSsrcGroupAttributeList::kSim;
+ break;
+ }
+ std::vector<uint32_t> ssrcs;
+ for (size_t i = 0; i < ssrc_vec_len(ssrcGroup.ssrcs); ++i) {
+ uint32_t ssrc;
+ ssrc_vec_get_id(ssrcGroup.ssrcs, i, &ssrc);
+ ssrcs.push_back(ssrc);
+ }
+ ssrcGroups->PushEntry(semantic, ssrcs);
+ }
+ SetAttribute(ssrcGroups.release());
+}
+
+struct FmtDefaults {
+ uint32_t minimumChannels = 0;
+};
+
+std::tuple<SdpRtpmapAttributeList::CodecType, FmtDefaults> strToCodecType(
+ const std::string& name) {
+ auto codec = SdpRtpmapAttributeList::kOtherCodec;
+ FmtDefaults defaults = {0}; // This is tracked to match SIPCC behavior only
+ if (!nsCRT::strcasecmp(name.c_str(), "opus")) {
+ codec = SdpRtpmapAttributeList::kOpus;
+ defaults = {0};
+ } else if (!nsCRT::strcasecmp(name.c_str(), "G722")) {
+ codec = SdpRtpmapAttributeList::kG722;
+ defaults = {1};
+ } else if (!nsCRT::strcasecmp(name.c_str(), "PCMU")) {
+ codec = SdpRtpmapAttributeList::kPCMU;
+ defaults = {1};
+ } else if (!nsCRT::strcasecmp(name.c_str(), "PCMA")) {
+ codec = SdpRtpmapAttributeList::kPCMA;
+ defaults = {1};
+ } else if (!nsCRT::strcasecmp(name.c_str(), "VP8")) {
+ codec = SdpRtpmapAttributeList::kVP8;
+ defaults = {0};
+ } else if (!nsCRT::strcasecmp(name.c_str(), "VP9")) {
+ codec = SdpRtpmapAttributeList::kVP9;
+ defaults = {0};
+ } else if (!nsCRT::strcasecmp(name.c_str(), "iLBC")) {
+ codec = SdpRtpmapAttributeList::kiLBC;
+ defaults = {1};
+ } else if (!nsCRT::strcasecmp(name.c_str(), "iSAC")) {
+ codec = SdpRtpmapAttributeList::kiSAC;
+ defaults = {1};
+ } else if (!nsCRT::strcasecmp(name.c_str(), "H264")) {
+ codec = SdpRtpmapAttributeList::kH264;
+ defaults = {0};
+ } else if (!nsCRT::strcasecmp(name.c_str(), "red")) {
+ codec = SdpRtpmapAttributeList::kRed;
+ defaults = {0};
+ } else if (!nsCRT::strcasecmp(name.c_str(), "ulpfec")) {
+ codec = SdpRtpmapAttributeList::kUlpfec;
+ defaults = {0};
+ } else if (!nsCRT::strcasecmp(name.c_str(), "telephone-event")) {
+ codec = SdpRtpmapAttributeList::kTelephoneEvent;
+ defaults = {1};
+ } else if (!nsCRT::strcasecmp(name.c_str(), "rtx")) {
+ codec = SdpRtpmapAttributeList::kRtx;
+ defaults = {0};
+ }
+ return std::make_tuple(codec, defaults);
+}
+
+void RsdparsaSdpAttributeList::LoadRtpmap(RustAttributeList* attributeList) {
+ size_t numRtpmap = sdp_get_rtpmap_count(attributeList);
+ if (numRtpmap == 0) {
+ return;
+ }
+ auto rustRtpmaps = MakeUnique<RustSdpAttributeRtpmap[]>(numRtpmap);
+ sdp_get_rtpmaps(attributeList, numRtpmap, rustRtpmaps.get());
+ auto rtpmapList = MakeUnique<SdpRtpmapAttributeList>();
+ for (size_t i = 0; i < numRtpmap; i++) {
+ RustSdpAttributeRtpmap& rtpmap = rustRtpmaps[i];
+ std::string payloadType = std::to_string(rtpmap.payloadType);
+ std::string name = convertStringView(rtpmap.codecName);
+ auto [codec, defaults] = strToCodecType(name);
+ uint32_t channels = rtpmap.channels;
+ if (channels == 0) {
+ channels = defaults.minimumChannels;
+ }
+ rtpmapList->PushEntry(payloadType, codec, name, rtpmap.frequency, channels);
+ }
+ SetAttribute(rtpmapList.release());
+}
+
+void RsdparsaSdpAttributeList::LoadFmtp(RustAttributeList* attributeList) {
+ size_t numFmtp = sdp_get_fmtp_count(attributeList);
+ if (numFmtp == 0) {
+ return;
+ }
+ auto rustFmtps = MakeUnique<RustSdpAttributeFmtp[]>(numFmtp);
+ size_t numValidFmtp = sdp_get_fmtp(attributeList, numFmtp, rustFmtps.get());
+ auto fmtpList = MakeUnique<SdpFmtpAttributeList>();
+ for (size_t i = 0; i < numValidFmtp; i++) {
+ const RustSdpAttributeFmtp& fmtp = rustFmtps[i];
+ uint8_t payloadType = fmtp.payloadType;
+ std::string codecName = convertStringView(fmtp.codecName);
+ const RustSdpAttributeFmtpParameters& rustFmtpParameters = fmtp.parameters;
+
+ UniquePtr<SdpFmtpAttributeList::Parameters> fmtpParameters;
+
+ // use the upper case version of the codec name
+ std::transform(codecName.begin(), codecName.end(), codecName.begin(),
+ ::toupper);
+
+ if (codecName == "H264") {
+ SdpFmtpAttributeList::H264Parameters h264Parameters;
+
+ h264Parameters.packetization_mode = rustFmtpParameters.packetization_mode;
+ h264Parameters.level_asymmetry_allowed =
+ rustFmtpParameters.level_asymmetry_allowed;
+ h264Parameters.profile_level_id = rustFmtpParameters.profile_level_id;
+ h264Parameters.max_mbps = rustFmtpParameters.max_mbps;
+ h264Parameters.max_fs = rustFmtpParameters.max_fs;
+ h264Parameters.max_cpb = rustFmtpParameters.max_cpb;
+ h264Parameters.max_dpb = rustFmtpParameters.max_dpb;
+ h264Parameters.max_br = rustFmtpParameters.max_br;
+
+ // TODO(bug 1466859): Support sprop-parameter-sets
+
+ fmtpParameters.reset(
+ new SdpFmtpAttributeList::H264Parameters(std::move(h264Parameters)));
+ } else if (codecName == "OPUS") {
+ SdpFmtpAttributeList::OpusParameters opusParameters;
+
+ opusParameters.maxplaybackrate = rustFmtpParameters.maxplaybackrate;
+ opusParameters.maxAverageBitrate = rustFmtpParameters.maxaveragebitrate;
+ opusParameters.useDTX = rustFmtpParameters.usedtx;
+ opusParameters.stereo = rustFmtpParameters.stereo;
+ opusParameters.useInBandFec = rustFmtpParameters.useinbandfec;
+ opusParameters.frameSizeMs = rustFmtpParameters.ptime;
+ opusParameters.minFrameSizeMs = rustFmtpParameters.minptime;
+ opusParameters.maxFrameSizeMs = rustFmtpParameters.maxptime;
+ opusParameters.useCbr = rustFmtpParameters.cbr;
+
+ fmtpParameters.reset(
+ new SdpFmtpAttributeList::OpusParameters(std::move(opusParameters)));
+ } else if ((codecName == "VP8") || (codecName == "VP9")) {
+ SdpFmtpAttributeList::VP8Parameters vp8Parameters(
+ codecName == "VP8" ? SdpRtpmapAttributeList::kVP8
+ : SdpRtpmapAttributeList::kVP9);
+
+ vp8Parameters.max_fs = rustFmtpParameters.max_fs;
+ vp8Parameters.max_fr = rustFmtpParameters.max_fr;
+
+ fmtpParameters.reset(
+ new SdpFmtpAttributeList::VP8Parameters(std::move(vp8Parameters)));
+ } else if (codecName == "TELEPHONE-EVENT") {
+ SdpFmtpAttributeList::TelephoneEventParameters telephoneEventParameters;
+
+ telephoneEventParameters.dtmfTones =
+ convertStringView(rustFmtpParameters.dtmf_tones);
+
+ fmtpParameters.reset(new SdpFmtpAttributeList::TelephoneEventParameters(
+ std::move(telephoneEventParameters)));
+ } else if (codecName == "RED") {
+ SdpFmtpAttributeList::RedParameters redParameters;
+
+ redParameters.encodings = convertU8Vec(rustFmtpParameters.encodings);
+
+ fmtpParameters.reset(
+ new SdpFmtpAttributeList::RedParameters(std::move(redParameters)));
+ } else if (codecName == "RTX") {
+ SdpFmtpAttributeList::RtxParameters rtxParameters;
+
+ rtxParameters.apt = rustFmtpParameters.rtx.apt;
+ if (rustFmtpParameters.rtx.has_rtx_time) {
+ rtxParameters.rtx_time = Some(rustFmtpParameters.rtx.rtx_time);
+ }
+
+ fmtpParameters.reset(
+ new SdpFmtpAttributeList::RtxParameters(rtxParameters));
+ } else {
+ // The parameter set is unknown so skip it
+ continue;
+ }
+ fmtpList->PushEntry(std::to_string(payloadType), *fmtpParameters);
+ }
+ SetAttribute(fmtpList.release());
+}
+
+void RsdparsaSdpAttributeList::LoadPtime(RustAttributeList* attributeList) {
+ int64_t ptime = sdp_get_ptime(attributeList);
+ if (ptime >= 0) {
+ SetAttribute(new SdpNumberAttribute(SdpAttribute::kPtimeAttribute,
+ static_cast<uint32_t>(ptime)));
+ }
+}
+
+void RsdparsaSdpAttributeList::LoadFlags(RustAttributeList* attributeList) {
+ RustSdpAttributeFlags flags = sdp_get_attribute_flags(attributeList);
+ if (flags.iceLite) {
+ SetAttribute(new SdpFlagAttribute(SdpAttribute::kIceLiteAttribute));
+ }
+ if (flags.rtcpMux) {
+ SetAttribute(new SdpFlagAttribute(SdpAttribute::kRtcpMuxAttribute));
+ }
+ if (flags.rtcpRsize) {
+ SetAttribute(new SdpFlagAttribute(SdpAttribute::kRtcpRsizeAttribute));
+ }
+ if (flags.bundleOnly) {
+ SetAttribute(new SdpFlagAttribute(SdpAttribute::kBundleOnlyAttribute));
+ }
+ if (flags.endOfCandidates) {
+ SetAttribute(new SdpFlagAttribute(SdpAttribute::kEndOfCandidatesAttribute));
+ }
+}
+
+void RsdparsaSdpAttributeList::LoadMaxMessageSize(
+ RustAttributeList* attributeList) {
+ int64_t max_msg_size = sdp_get_max_msg_size(attributeList);
+ if (max_msg_size >= 0) {
+ SetAttribute(new SdpNumberAttribute(SdpAttribute::kMaxMessageSizeAttribute,
+ static_cast<uint32_t>(max_msg_size)));
+ }
+}
+
+void RsdparsaSdpAttributeList::LoadMid(RustAttributeList* attributeList) {
+ StringView rustMid;
+ if (NS_SUCCEEDED(sdp_get_mid(attributeList, &rustMid))) {
+ std::string mid = convertStringView(rustMid);
+ SetAttribute(new SdpStringAttribute(SdpAttribute::kMidAttribute, mid));
+ }
+}
+
+void RsdparsaSdpAttributeList::LoadMsid(RustAttributeList* attributeList) {
+ size_t numMsid = sdp_get_msid_count(attributeList);
+ if (numMsid == 0) {
+ return;
+ }
+ auto rustMsid = MakeUnique<RustSdpAttributeMsid[]>(numMsid);
+ sdp_get_msids(attributeList, numMsid, rustMsid.get());
+ auto msids = MakeUnique<SdpMsidAttributeList>();
+ for (size_t i = 0; i < numMsid; i++) {
+ RustSdpAttributeMsid& msid = rustMsid[i];
+ std::string id = convertStringView(msid.id);
+ std::string appdata = convertStringView(msid.appdata);
+ msids->PushEntry(id, appdata);
+ }
+ SetAttribute(msids.release());
+}
+
+void RsdparsaSdpAttributeList::LoadMsidSemantics(
+ RustAttributeList* attributeList) {
+ size_t numMsidSemantic = sdp_get_msid_semantic_count(attributeList);
+ if (numMsidSemantic == 0) {
+ return;
+ }
+ auto rustMsidSemantics =
+ MakeUnique<RustSdpAttributeMsidSemantic[]>(numMsidSemantic);
+ sdp_get_msid_semantics(attributeList, numMsidSemantic,
+ rustMsidSemantics.get());
+ auto msidSemantics = MakeUnique<SdpMsidSemanticAttributeList>();
+ for (size_t i = 0; i < numMsidSemantic; i++) {
+ RustSdpAttributeMsidSemantic& rustMsidSemantic = rustMsidSemantics[i];
+ std::string semantic = convertStringView(rustMsidSemantic.semantic);
+ std::vector<std::string> msids = convertStringVec(rustMsidSemantic.msids);
+ msidSemantics->PushEntry(semantic, msids);
+ }
+ SetAttribute(msidSemantics.release());
+}
+
+void RsdparsaSdpAttributeList::LoadGroup(RustAttributeList* attributeList) {
+ size_t numGroup = sdp_get_group_count(attributeList);
+ if (numGroup == 0) {
+ return;
+ }
+ auto rustGroups = MakeUnique<RustSdpAttributeGroup[]>(numGroup);
+ sdp_get_groups(attributeList, numGroup, rustGroups.get());
+ auto groups = MakeUnique<SdpGroupAttributeList>();
+ for (size_t i = 0; i < numGroup; i++) {
+ RustSdpAttributeGroup& group = rustGroups[i];
+ SdpGroupAttributeList::Semantics semantic;
+ switch (group.semantic) {
+ case RustSdpAttributeGroupSemantic ::kRustLipSynchronization:
+ semantic = SdpGroupAttributeList::kLs;
+ break;
+ case RustSdpAttributeGroupSemantic ::kRustFlowIdentification:
+ semantic = SdpGroupAttributeList::kFid;
+ break;
+ case RustSdpAttributeGroupSemantic ::kRustSingleReservationFlow:
+ semantic = SdpGroupAttributeList::kSrf;
+ break;
+ case RustSdpAttributeGroupSemantic ::kRustAlternateNetworkAddressType:
+ semantic = SdpGroupAttributeList::kAnat;
+ break;
+ case RustSdpAttributeGroupSemantic ::kRustForwardErrorCorrection:
+ semantic = SdpGroupAttributeList::kFec;
+ break;
+ case RustSdpAttributeGroupSemantic ::kRustDecodingDependency:
+ semantic = SdpGroupAttributeList::kDdp;
+ break;
+ case RustSdpAttributeGroupSemantic ::kRustBundle:
+ semantic = SdpGroupAttributeList::kBundle;
+ break;
+ }
+ std::vector<std::string> tags = convertStringVec(group.tags);
+ groups->PushEntry(semantic, tags);
+ }
+ SetAttribute(groups.release());
+}
+
+void RsdparsaSdpAttributeList::LoadRtcp(RustAttributeList* attributeList) {
+ RustSdpAttributeRtcp rtcp;
+ if (NS_SUCCEEDED(sdp_get_rtcp(attributeList, &rtcp))) {
+ if (rtcp.has_address) {
+ auto address = convertExplicitlyTypedAddress(&rtcp.unicastAddr);
+ SetAttribute(new SdpRtcpAttribute(rtcp.port, sdp::kInternet,
+ address.first, address.second));
+ } else {
+ SetAttribute(new SdpRtcpAttribute(rtcp.port));
+ }
+ }
+}
+
+void RsdparsaSdpAttributeList::LoadRtcpFb(RustAttributeList* attributeList) {
+ auto rtcpfbsCount = sdp_get_rtcpfb_count(attributeList);
+ if (!rtcpfbsCount) {
+ return;
+ }
+
+ auto rustRtcpfbs = MakeUnique<RustSdpAttributeRtcpFb[]>(rtcpfbsCount);
+ sdp_get_rtcpfbs(attributeList, rtcpfbsCount, rustRtcpfbs.get());
+
+ auto rtcpfbList = MakeUnique<SdpRtcpFbAttributeList>();
+ for (size_t i = 0; i < rtcpfbsCount; i++) {
+ RustSdpAttributeRtcpFb& rtcpfb = rustRtcpfbs[i];
+ uint32_t payloadTypeU32 = rtcpfb.payloadType;
+
+ std::stringstream ss;
+ if (payloadTypeU32 == std::numeric_limits<uint32_t>::max()) {
+ ss << "*";
+ } else {
+ ss << payloadTypeU32;
+ }
+
+ uint32_t feedbackType = rtcpfb.feedbackType;
+ std::string parameter = convertStringView(rtcpfb.parameter);
+ std::string extra = convertStringView(rtcpfb.extra);
+
+ rtcpfbList->PushEntry(
+ ss.str(), static_cast<SdpRtcpFbAttributeList::Type>(feedbackType),
+ parameter, extra);
+ }
+
+ SetAttribute(rtcpfbList.release());
+}
+
+SdpSimulcastAttribute::Versions LoadSimulcastVersions(
+ const RustSdpAttributeSimulcastVersionVec* rustVersionList) {
+ size_t rustVersionCount = sdp_simulcast_get_version_count(rustVersionList);
+ auto rustVersionArray =
+ MakeUnique<RustSdpAttributeSimulcastVersion[]>(rustVersionCount);
+ sdp_simulcast_get_versions(rustVersionList, rustVersionCount,
+ rustVersionArray.get());
+
+ SdpSimulcastAttribute::Versions versions;
+
+ for (size_t i = 0; i < rustVersionCount; i++) {
+ const RustSdpAttributeSimulcastVersion& rustVersion = rustVersionArray[i];
+ size_t rustIdCount = sdp_simulcast_get_ids_count(rustVersion.ids);
+ if (!rustIdCount) {
+ continue;
+ }
+
+ SdpSimulcastAttribute::Version version;
+ auto rustIdArray = MakeUnique<RustSdpAttributeSimulcastId[]>(rustIdCount);
+ sdp_simulcast_get_ids(rustVersion.ids, rustIdCount, rustIdArray.get());
+
+ for (size_t j = 0; j < rustIdCount; j++) {
+ const RustSdpAttributeSimulcastId& rustId = rustIdArray[j];
+ std::string id = convertStringView(rustId.id);
+ // TODO: Bug 1225877. Added support for 'paused'-state
+ version.choices.push_back(
+ SdpSimulcastAttribute::Encoding(id, rustId.paused));
+ }
+
+ versions.push_back(version);
+ }
+
+ return versions;
+}
+
+void RsdparsaSdpAttributeList::LoadSimulcast(RustAttributeList* attributeList) {
+ RustSdpAttributeSimulcast rustSimulcast;
+ if (NS_SUCCEEDED(sdp_get_simulcast(attributeList, &rustSimulcast))) {
+ auto simulcast = MakeUnique<SdpSimulcastAttribute>();
+
+ simulcast->sendVersions = LoadSimulcastVersions(rustSimulcast.send);
+ simulcast->recvVersions = LoadSimulcastVersions(rustSimulcast.recv);
+
+ SetAttribute(simulcast.release());
+ }
+}
+
+SdpImageattrAttributeList::XYRange LoadImageattrXYRange(
+ const RustSdpAttributeImageAttrXYRange& rustXYRange) {
+ SdpImageattrAttributeList::XYRange xyRange;
+
+ if (!rustXYRange.discrete_values) {
+ xyRange.min = rustXYRange.min;
+ xyRange.max = rustXYRange.max;
+ xyRange.step = rustXYRange.step;
+
+ } else {
+ xyRange.discreteValues = convertU32Vec(rustXYRange.discrete_values);
+ }
+
+ return xyRange;
+}
+
+std::vector<SdpImageattrAttributeList::Set> LoadImageattrSets(
+ const RustSdpAttributeImageAttrSetVec* rustSets) {
+ std::vector<SdpImageattrAttributeList::Set> sets;
+
+ size_t rustSetCount = sdp_imageattr_get_set_count(rustSets);
+ if (!rustSetCount) {
+ return sets;
+ }
+
+ auto rustSetArray = MakeUnique<RustSdpAttributeImageAttrSet[]>(rustSetCount);
+ sdp_imageattr_get_sets(rustSets, rustSetCount, rustSetArray.get());
+
+ for (size_t i = 0; i < rustSetCount; i++) {
+ const RustSdpAttributeImageAttrSet& rustSet = rustSetArray[i];
+ SdpImageattrAttributeList::Set set;
+
+ set.xRange = LoadImageattrXYRange(rustSet.x);
+ set.yRange = LoadImageattrXYRange(rustSet.y);
+
+ if (rustSet.has_sar) {
+ if (!rustSet.sar.discrete_values) {
+ set.sRange.min = rustSet.sar.min;
+ set.sRange.max = rustSet.sar.max;
+ } else {
+ set.sRange.discreteValues = convertF32Vec(rustSet.sar.discrete_values);
+ }
+ }
+
+ if (rustSet.has_par) {
+ set.pRange.min = rustSet.par.min;
+ set.pRange.max = rustSet.par.max;
+ }
+
+ set.qValue = rustSet.q;
+
+ sets.push_back(set);
+ }
+
+ return sets;
+}
+
+void RsdparsaSdpAttributeList::LoadImageattr(RustAttributeList* attributeList) {
+ size_t numImageattrs = sdp_get_imageattr_count(attributeList);
+ if (numImageattrs == 0) {
+ return;
+ }
+ auto rustImageattrs = MakeUnique<RustSdpAttributeImageAttr[]>(numImageattrs);
+ sdp_get_imageattrs(attributeList, numImageattrs, rustImageattrs.get());
+ auto imageattrList = MakeUnique<SdpImageattrAttributeList>();
+ for (size_t i = 0; i < numImageattrs; i++) {
+ const RustSdpAttributeImageAttr& rustImageAttr = rustImageattrs[i];
+
+ SdpImageattrAttributeList::Imageattr imageAttr;
+
+ if (rustImageAttr.payloadType != std::numeric_limits<uint32_t>::max()) {
+ imageAttr.pt = Some(rustImageAttr.payloadType);
+ }
+
+ if (rustImageAttr.send.sets) {
+ imageAttr.sendSets = LoadImageattrSets(rustImageAttr.send.sets);
+ } else {
+ imageAttr.sendAll = true;
+ }
+
+ if (rustImageAttr.recv.sets) {
+ imageAttr.recvSets = LoadImageattrSets(rustImageAttr.recv.sets);
+ } else {
+ imageAttr.recvAll = true;
+ }
+
+ imageattrList->mImageattrs.push_back(imageAttr);
+ }
+ SetAttribute(imageattrList.release());
+}
+
+void RsdparsaSdpAttributeList::LoadSctpmaps(RustAttributeList* attributeList) {
+ size_t numSctpmaps = sdp_get_sctpmap_count(attributeList);
+ if (numSctpmaps == 0) {
+ return;
+ }
+ auto rustSctpmaps = MakeUnique<RustSdpAttributeSctpmap[]>(numSctpmaps);
+ sdp_get_sctpmaps(attributeList, numSctpmaps, rustSctpmaps.get());
+ auto sctpmapList = MakeUnique<SdpSctpmapAttributeList>();
+ for (size_t i = 0; i < numSctpmaps; i++) {
+ RustSdpAttributeSctpmap& sctpmap = rustSctpmaps[i];
+ sctpmapList->PushEntry(std::to_string(sctpmap.port), "webrtc-datachannel",
+ sctpmap.channels);
+ }
+ SetAttribute(sctpmapList.release());
+}
+
+void RsdparsaSdpAttributeList::LoadDirection(RustAttributeList* attributeList) {
+ SdpDirectionAttribute::Direction dir;
+ RustDirection rustDir = sdp_get_direction(attributeList);
+ switch (rustDir) {
+ case RustDirection::kRustRecvonly:
+ dir = SdpDirectionAttribute::kRecvonly;
+ break;
+ case RustDirection::kRustSendonly:
+ dir = SdpDirectionAttribute::kSendonly;
+ break;
+ case RustDirection::kRustSendrecv:
+ dir = SdpDirectionAttribute::kSendrecv;
+ break;
+ case RustDirection::kRustInactive:
+ dir = SdpDirectionAttribute::kInactive;
+ break;
+ }
+ SetAttribute(new SdpDirectionAttribute(dir));
+}
+
+void RsdparsaSdpAttributeList::LoadRemoteCandidates(
+ RustAttributeList* attributeList) {
+ size_t nC = sdp_get_remote_candidate_count(attributeList);
+ if (nC == 0) {
+ return;
+ }
+ auto rustCandidates = MakeUnique<RustSdpAttributeRemoteCandidate[]>(nC);
+ sdp_get_remote_candidates(attributeList, nC, rustCandidates.get());
+ std::vector<SdpRemoteCandidatesAttribute::Candidate> candidates;
+ for (size_t i = 0; i < nC; i++) {
+ RustSdpAttributeRemoteCandidate& rustCandidate = rustCandidates[i];
+ SdpRemoteCandidatesAttribute::Candidate candidate;
+ candidate.port = rustCandidate.port;
+ candidate.id = std::to_string(rustCandidate.component);
+ candidate.address = convertAddress(&rustCandidate.address);
+ candidates.push_back(candidate);
+ }
+ SdpRemoteCandidatesAttribute* candidatesList;
+ candidatesList = new SdpRemoteCandidatesAttribute(candidates);
+ SetAttribute(candidatesList);
+}
+
+void RsdparsaSdpAttributeList::LoadRids(RustAttributeList* attributeList) {
+ size_t numRids = sdp_get_rid_count(attributeList);
+ if (numRids == 0) {
+ return;
+ }
+
+ auto rustRids = MakeUnique<RustSdpAttributeRid[]>(numRids);
+ sdp_get_rids(attributeList, numRids, rustRids.get());
+
+ auto ridList = MakeUnique<SdpRidAttributeList>();
+ for (size_t i = 0; i < numRids; i++) {
+ const RustSdpAttributeRid& rid = rustRids[i];
+
+ std::string id = convertStringView(rid.id);
+ auto direction = static_cast<sdp::Direction>(rid.direction);
+ std::vector<uint16_t> formats = convertU16Vec(rid.formats);
+
+ EncodingConstraints parameters;
+ parameters.maxWidth = rid.params.max_width;
+ parameters.maxHeight = rid.params.max_height;
+ // Right now, we treat max-fps=0 and the absence of max-fps as no limit.
+ // We will eventually want to treat max-fps=0 as 0 frames per second, and
+ // the absence of max-fps as no limit (bug 1762632).
+ if (rid.params.max_fps) {
+ parameters.maxFps = Some(rid.params.max_fps);
+ }
+ parameters.maxFs = rid.params.max_fs;
+ parameters.maxBr = rid.params.max_br;
+ parameters.maxPps = rid.params.max_pps;
+
+ std::vector<std::string> depends = convertStringVec(rid.depends);
+
+ ridList->PushEntry(id, direction, formats, parameters, depends);
+ }
+
+ SetAttribute(ridList.release());
+}
+
+void RsdparsaSdpAttributeList::LoadSctpPort(RustAttributeList* attributeList) {
+ int64_t port = sdp_get_sctp_port(attributeList);
+ if (port >= 0) {
+ SetAttribute(new SdpNumberAttribute(SdpAttribute::kSctpPortAttribute,
+ static_cast<uint32_t>(port)));
+ }
+}
+
+void RsdparsaSdpAttributeList::LoadExtmap(RustAttributeList* attributeList) {
+ size_t numExtmap = sdp_get_extmap_count(attributeList);
+ if (numExtmap == 0) {
+ return;
+ }
+ auto rustExtmaps = MakeUnique<RustSdpAttributeExtmap[]>(numExtmap);
+ sdp_get_extmaps(attributeList, numExtmap, rustExtmaps.get());
+ auto extmaps = MakeUnique<SdpExtmapAttributeList>();
+ for (size_t i = 0; i < numExtmap; i++) {
+ RustSdpAttributeExtmap& rustExtmap = rustExtmaps[i];
+ std::string name = convertStringView(rustExtmap.url);
+ SdpDirectionAttribute::Direction direction;
+ bool directionSpecified = rustExtmap.direction_specified;
+ switch (rustExtmap.direction) {
+ case RustDirection::kRustRecvonly:
+ direction = SdpDirectionAttribute::kRecvonly;
+ break;
+ case RustDirection::kRustSendonly:
+ direction = SdpDirectionAttribute::kSendonly;
+ break;
+ case RustDirection::kRustSendrecv:
+ direction = SdpDirectionAttribute::kSendrecv;
+ break;
+ case RustDirection::kRustInactive:
+ direction = SdpDirectionAttribute::kInactive;
+ break;
+ }
+ std::string extensionAttributes;
+ extensionAttributes = convertStringView(rustExtmap.extensionAttributes);
+ extmaps->PushEntry((uint16_t)rustExtmap.id, direction, directionSpecified,
+ name, extensionAttributes);
+ }
+ SetAttribute(extmaps.release());
+}
+
+void RsdparsaSdpAttributeList::LoadMaxPtime(RustAttributeList* attributeList) {
+ uint64_t maxPtime = 0;
+ nsresult nr = sdp_get_maxptime(attributeList, &maxPtime);
+ if (NS_SUCCEEDED(nr)) {
+ SetAttribute(
+ new SdpNumberAttribute(SdpAttribute::kMaxptimeAttribute, maxPtime));
+ }
+}
+
+void RsdparsaSdpAttributeList::LoadCandidate(RustAttributeList* attributeList) {
+ size_t candidatesCount = sdp_get_candidate_count(attributeList);
+ if (!candidatesCount) {
+ return;
+ }
+
+ StringVec* rustCandidatesStrings;
+ sdp_get_candidates(attributeList, candidatesCount, &rustCandidatesStrings);
+
+ std::vector<std::string> candidatesStrings =
+ convertStringVec(rustCandidatesStrings);
+ free_boxed_string_vec(rustCandidatesStrings);
+
+ auto candidates =
+ MakeUnique<SdpMultiStringAttribute>(SdpAttribute::kCandidateAttribute);
+ candidates->mValues = candidatesStrings;
+
+ SetAttribute(candidates.release());
+}
+
+bool RsdparsaSdpAttributeList::IsAllowedHere(SdpAttribute::AttributeType type) {
+ if (AtSessionLevel() && !SdpAttribute::IsAllowedAtSessionLevel(type)) {
+ return false;
+ }
+
+ if (!AtSessionLevel() && !SdpAttribute::IsAllowedAtMediaLevel(type)) {
+ return false;
+ }
+
+ return true;
+}
+
+void RsdparsaSdpAttributeList::Serialize(std::ostream& os) const {
+ for (size_t i = 0; i < kNumAttributeTypes; ++i) {
+ if (mAttributes[i]) {
+ os << *mAttributes[i];
+ }
+ }
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/sdp/RsdparsaSdpAttributeList.h b/dom/media/webrtc/sdp/RsdparsaSdpAttributeList.h
new file mode 100644
index 0000000000..812f15e112
--- /dev/null
+++ b/dom/media/webrtc/sdp/RsdparsaSdpAttributeList.h
@@ -0,0 +1,157 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _RSDPARSA_SDP_ATTRIBUTE_LIST_H_
+#define _RSDPARSA_SDP_ATTRIBUTE_LIST_H_
+
+#include "sdp/RsdparsaSdpGlue.h"
+#include "sdp/RsdparsaSdpInc.h"
+#include "sdp/SdpAttributeList.h"
+
+namespace mozilla {
+
+class RsdparsaSdp;
+class RsdparsaSdpMediaSection;
+class SdpParser;
+
+class RsdparsaSdpAttributeList : public SdpAttributeList {
+ friend class RsdparsaSdpMediaSection;
+ friend class RsdparsaSdp;
+
+ public:
+ // Make sure we don't hide the default arg thunks
+ using SdpAttributeList::GetAttribute;
+ using SdpAttributeList::HasAttribute;
+
+ bool HasAttribute(AttributeType type, bool sessionFallback) const override;
+ const SdpAttribute* GetAttribute(AttributeType type,
+ bool sessionFallback) const override;
+ void SetAttribute(SdpAttribute* attr) override;
+ void RemoveAttribute(AttributeType type) override;
+ void Clear() override;
+ uint32_t Count() const override;
+
+ const SdpConnectionAttribute& GetConnection() const override;
+ const SdpFingerprintAttributeList& GetFingerprint() const override;
+ const SdpGroupAttributeList& GetGroup() const override;
+ const SdpOptionsAttribute& GetIceOptions() const override;
+ const SdpRtcpAttribute& GetRtcp() const override;
+ const SdpRemoteCandidatesAttribute& GetRemoteCandidates() const override;
+ const SdpSetupAttribute& GetSetup() const override;
+ const SdpSsrcAttributeList& GetSsrc() const override;
+ const SdpSsrcGroupAttributeList& GetSsrcGroup() const override;
+ const SdpDtlsMessageAttribute& GetDtlsMessage() const override;
+
+ // These attributes can appear multiple times, so the returned
+ // classes actually represent a collection of values.
+ const std::vector<std::string>& GetCandidate() const override;
+ const SdpExtmapAttributeList& GetExtmap() const override;
+ const SdpFmtpAttributeList& GetFmtp() const override;
+ const SdpImageattrAttributeList& GetImageattr() const override;
+ const SdpSimulcastAttribute& GetSimulcast() const override;
+ const SdpMsidAttributeList& GetMsid() const override;
+ const SdpMsidSemanticAttributeList& GetMsidSemantic() const override;
+ const SdpRidAttributeList& GetRid() const override;
+ const SdpRtcpFbAttributeList& GetRtcpFb() const override;
+ const SdpRtpmapAttributeList& GetRtpmap() const override;
+ const SdpSctpmapAttributeList& GetSctpmap() const override;
+
+ // These attributes are effectively simple types, so we'll make life
+ // easy by just returning their value.
+ uint32_t GetSctpPort() const override;
+ uint32_t GetMaxMessageSize() const override;
+ const std::string& GetIcePwd() const override;
+ const std::string& GetIceUfrag() const override;
+ const std::string& GetIdentity() const override;
+ const std::string& GetLabel() const override;
+ unsigned int GetMaxptime() const override;
+ const std::string& GetMid() const override;
+ unsigned int GetPtime() const override;
+
+ SdpDirectionAttribute::Direction GetDirection() const override;
+
+ void Serialize(std::ostream&) const override;
+
+ virtual ~RsdparsaSdpAttributeList();
+
+ private:
+ explicit RsdparsaSdpAttributeList(RsdparsaSessionHandle session)
+ : mSession(std::move(session)),
+ mSessionAttributes(nullptr),
+ mIsVideo(false),
+ mAttributes() {
+ RustAttributeList* attributes = get_sdp_session_attributes(mSession.get());
+ LoadAll(attributes);
+ }
+
+ RsdparsaSdpAttributeList(RsdparsaSessionHandle session,
+ const RustMediaSection* const msection,
+ const RsdparsaSdpAttributeList* sessionAttributes)
+ : mSession(std::move(session)),
+ mSessionAttributes(sessionAttributes),
+ mAttributes() {
+ mIsVideo =
+ sdp_rust_get_media_type(msection) == RustSdpMediaValue::kRustVideo;
+ RustAttributeList* attributes = sdp_get_media_attribute_list(msection);
+ LoadAll(attributes);
+ }
+
+ static const std::string kEmptyString;
+ static const size_t kNumAttributeTypes = SdpAttribute::kLastAttribute + 1;
+
+ const RsdparsaSessionHandle mSession;
+ const RsdparsaSdpAttributeList* mSessionAttributes;
+ bool mIsVideo;
+
+ bool AtSessionLevel() const { return !mSessionAttributes; }
+
+ bool IsAllowedHere(SdpAttribute::AttributeType type);
+ void LoadAll(RustAttributeList* attributeList);
+ void LoadAttribute(RustAttributeList* attributeList, AttributeType type);
+ void LoadIceUfrag(RustAttributeList* attributeList);
+ void LoadIcePwd(RustAttributeList* attributeList);
+ void LoadIdentity(RustAttributeList* attributeList);
+ void LoadIceOptions(RustAttributeList* attributeList);
+ void LoadFingerprint(RustAttributeList* attributeList);
+ void LoadDtlsMessage(RustAttributeList* attributeList);
+ void LoadSetup(RustAttributeList* attributeList);
+ void LoadSsrc(RustAttributeList* attributeList);
+ void LoadSsrcGroup(RustAttributeList* attributeList);
+ void LoadRtpmap(RustAttributeList* attributeList);
+ void LoadFmtp(RustAttributeList* attributeList);
+ void LoadPtime(RustAttributeList* attributeList);
+ void LoadFlags(RustAttributeList* attributeList);
+ void LoadMaxMessageSize(RustAttributeList* attributeList);
+ void LoadMid(RustAttributeList* attributeList);
+ void LoadMsid(RustAttributeList* attributeList);
+ void LoadMsidSemantics(RustAttributeList* attributeList);
+ void LoadGroup(RustAttributeList* attributeList);
+ void LoadRtcp(RustAttributeList* attributeList);
+ void LoadRtcpFb(RustAttributeList* attributeList);
+ void LoadSctpPort(RustAttributeList* attributeList);
+ void LoadSimulcast(RustAttributeList* attributeList);
+ void LoadImageattr(RustAttributeList* attributeList);
+ void LoadSctpmaps(RustAttributeList* attributeList);
+ void LoadDirection(RustAttributeList* attributeList);
+ void LoadRemoteCandidates(RustAttributeList* attributeList);
+ void LoadRids(RustAttributeList* attributeList);
+ void LoadExtmap(RustAttributeList* attributeList);
+ void LoadMaxPtime(RustAttributeList* attributeList);
+ void LoadCandidate(RustAttributeList* attributeList);
+
+ void WarnAboutMisplacedAttribute(SdpAttribute::AttributeType type,
+ uint32_t lineNumber, SdpParser& errorHolder);
+
+ SdpAttribute* mAttributes[kNumAttributeTypes];
+
+ RsdparsaSdpAttributeList(const RsdparsaSdpAttributeList& orig) = delete;
+ RsdparsaSdpAttributeList& operator=(const RsdparsaSdpAttributeList& rhs) =
+ delete;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/sdp/RsdparsaSdpGlue.cpp b/dom/media/webrtc/sdp/RsdparsaSdpGlue.cpp
new file mode 100644
index 0000000000..01a1a1d817
--- /dev/null
+++ b/dom/media/webrtc/sdp/RsdparsaSdpGlue.cpp
@@ -0,0 +1,106 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include <string>
+#include <cstdint>
+
+#include "sdp/RsdparsaSdpInc.h"
+#include "sdp/RsdparsaSdpGlue.h"
+namespace mozilla {
+
+std::string convertStringView(StringView str) {
+ if (nullptr == str.buf) {
+ return std::string();
+ } else {
+ return std::string(str.buf, str.len);
+ }
+}
+
+std::vector<std::string> convertStringVec(StringVec* vec) {
+ std::vector<std::string> ret;
+ size_t len = string_vec_len(vec);
+ for (size_t i = 0; i < len; i++) {
+ StringView view;
+ string_vec_get_view(vec, i, &view);
+ ret.push_back(convertStringView(view));
+ }
+ return ret;
+}
+
+sdp::AddrType convertAddressType(RustSdpAddressType addrType) {
+ switch (addrType) {
+ case RustSdpAddressType::kRustAddrIp4:
+ return sdp::kIPv4;
+ case RustSdpAddressType::kRustAddrIp6:
+ return sdp::kIPv6;
+ }
+
+ MOZ_CRASH("unknown address type");
+}
+
+std::string convertAddress(RustAddress* address) {
+ return address->isFqdn ? convertStringView(address->fqdn)
+ : std::string(address->ipAddress);
+}
+
+std::pair<sdp::AddrType, std::string> convertExplicitlyTypedAddress(
+ RustExplicitlyTypedAddress* address) {
+ return std::make_pair(convertAddressType(address->addressType),
+ convertAddress(&address->address));
+}
+
+std::vector<uint8_t> convertU8Vec(U8Vec* vec) {
+ std::vector<std::uint8_t> ret;
+
+ size_t len = u8_vec_len(vec);
+ for (size_t i = 0; i < len; i++) {
+ uint8_t byte;
+ u8_vec_get(vec, i, &byte);
+ ret.push_back(byte);
+ }
+
+ return ret;
+}
+
+std::vector<uint16_t> convertU16Vec(U16Vec* vec) {
+ std::vector<std::uint16_t> ret;
+
+ size_t len = u16_vec_len(vec);
+ for (size_t i = 0; i < len; i++) {
+ uint16_t word;
+ u16_vec_get(vec, i, &word);
+ ret.push_back(word);
+ }
+
+ return ret;
+}
+
+std::vector<uint32_t> convertU32Vec(U32Vec* vec) {
+ std::vector<std::uint32_t> ret;
+
+ size_t len = u32_vec_len(vec);
+ for (size_t i = 0; i < len; i++) {
+ uint32_t num;
+ u32_vec_get(vec, i, &num);
+ ret.push_back(num);
+ }
+
+ return ret;
+}
+
+std::vector<float> convertF32Vec(F32Vec* vec) {
+ std::vector<float> ret;
+
+ size_t len = f32_vec_len(vec);
+ for (size_t i = 0; i < len; i++) {
+ float flt;
+ f32_vec_get(vec, i, &flt);
+ ret.push_back(flt);
+ }
+
+ return ret;
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/sdp/RsdparsaSdpGlue.h b/dom/media/webrtc/sdp/RsdparsaSdpGlue.h
new file mode 100644
index 0000000000..8ed275be75
--- /dev/null
+++ b/dom/media/webrtc/sdp/RsdparsaSdpGlue.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef _RUSTSDPGLUE_H_
+#define _RUSTSDPGLUE_H_
+
+#include <string>
+#include <vector>
+#include <utility>
+#include "SdpEnum.h"
+#include "mozilla/UniquePtr.h"
+#include "sdp/RsdparsaSdpInc.h"
+
+namespace mozilla {
+
+struct FreeRustSdpSession {
+ void operator()(RustSdpSession* aSess) { sdp_free_session(aSess); }
+};
+
+typedef UniquePtr<RustSdpSession, FreeRustSdpSession> RsdparsaSessionHandle;
+
+std::string convertStringView(StringView str);
+std::vector<std::string> convertStringVec(StringVec* vec);
+std::string convertAddress(RustAddress* address);
+std::pair<sdp::AddrType, std::string> convertExplicitlyTypedAddress(
+ RustExplicitlyTypedAddress* addr);
+std::vector<uint8_t> convertU8Vec(U8Vec* vec);
+std::vector<uint16_t> convertU16Vec(U16Vec* vec);
+std::vector<uint32_t> convertU32Vec(U32Vec* vec);
+std::vector<float> convertF32Vec(F32Vec* vec);
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/sdp/RsdparsaSdpInc.h b/dom/media/webrtc/sdp/RsdparsaSdpInc.h
new file mode 100644
index 0000000000..1237d2fdad
--- /dev/null
+++ b/dom/media/webrtc/sdp/RsdparsaSdpInc.h
@@ -0,0 +1,510 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef _RUSTSDPINC_H_
+#define _RUSTSDPINC_H_
+
+#include "nsError.h"
+#include "mozilla/Maybe.h"
+
+#include <stdint.h>
+#include <stdbool.h>
+
+struct BandwidthVec;
+struct RustSdpSession;
+struct RustSdpError;
+struct RustMediaSection;
+struct RustAttributeList;
+struct StringVec;
+struct U8Vec;
+struct U32Vec;
+struct U16Vec;
+struct F32Vec;
+struct SsrcVec;
+struct RustHeapString;
+
+enum class RustSdpAddressType { kRustAddrIp4, kRustAddrIp6 };
+
+struct StringView {
+ const char* buf;
+ size_t len;
+};
+
+struct RustAddress {
+ char ipAddress[50];
+ StringView fqdn;
+ bool isFqdn;
+};
+
+struct RustExplicitlyTypedAddress {
+ RustSdpAddressType addressType;
+ RustAddress address;
+};
+
+struct RustSdpConnection {
+ RustExplicitlyTypedAddress addr;
+ uint8_t ttl;
+ uint64_t amount;
+};
+
+struct RustSdpOrigin {
+ StringView username;
+ uint64_t sessionId;
+ uint64_t sessionVersion;
+ RustExplicitlyTypedAddress addr; // TODO address
+};
+
+enum class RustSdpMediaValue { kRustAudio, kRustVideo, kRustApplication };
+
+enum class RustSdpProtocolValue {
+ kRustRtpSavpf,
+ kRustUdpTlsRtpSavp,
+ kRustTcpDtlsRtpSavp,
+ kRustUdpTlsRtpSavpf,
+ kRustTcpDtlsRtpSavpf,
+ kRustDtlsSctp,
+ kRustUdpDtlsSctp,
+ kRustTcpDtlsSctp,
+ kRustRtpAvp,
+ kRustRtpAvpf,
+ kRustRtpSavp,
+};
+
+enum class RustSdpFormatType { kRustIntegers, kRustStrings };
+
+enum class RustSdpAttributeFingerprintHashAlgorithm : uint16_t {
+ kSha1,
+ kSha224,
+ kSha256,
+ kSha384,
+ kSha512,
+};
+
+struct RustSdpAttributeFingerprint {
+ RustSdpAttributeFingerprintHashAlgorithm hashAlgorithm;
+ U8Vec* fingerprint;
+};
+
+enum class RustSdpSetup {
+ kRustActive,
+ kRustActpass,
+ kRustHoldconn,
+ kRustPassive
+};
+
+enum class RustSdpAttributeDtlsMessageType : uint8_t {
+ kClient,
+ kServer,
+};
+
+struct RustSdpAttributeDtlsMessage {
+ RustSdpAttributeDtlsMessageType role;
+ StringView value;
+};
+
+struct RustSdpAttributeSsrc {
+ uint32_t id;
+ StringView attribute;
+ StringView value;
+};
+
+enum class RustSdpAttributeSsrcGroupSemantic {
+ kRustDup,
+ kRustFid,
+ kRustFec,
+ kRustFecFr,
+ kRustSim,
+};
+
+struct RustSdpAttributeSsrcGroup {
+ RustSdpAttributeSsrcGroupSemantic semantic;
+ SsrcVec* ssrcs;
+};
+
+struct RustSdpAttributeRtpmap {
+ uint8_t payloadType;
+ StringView codecName;
+ uint32_t frequency;
+ uint32_t channels;
+};
+
+struct RustSdpAttributeRtcpFb {
+ uint32_t payloadType;
+ uint32_t feedbackType;
+ StringView parameter;
+ StringView extra;
+};
+
+struct RustSdpAttributeRidParameters {
+ uint32_t max_width;
+ uint32_t max_height;
+ uint32_t max_fps;
+ uint32_t max_fs;
+ uint32_t max_br;
+ uint32_t max_pps;
+ StringVec* unknown;
+};
+
+struct RustSdpAttributeRid {
+ StringView id;
+ uint32_t direction;
+ U16Vec* formats;
+ RustSdpAttributeRidParameters params;
+ StringVec* depends;
+};
+
+struct RustSdpAttributeImageAttrXYRange {
+ uint32_t min;
+ uint32_t max;
+ uint32_t step;
+ U32Vec* discrete_values;
+};
+
+struct RustSdpAttributeImageAttrSRange {
+ float min;
+ float max;
+ F32Vec* discrete_values;
+};
+
+struct RustSdpAttributeImageAttrPRange {
+ float min;
+ float max;
+};
+
+struct RustSdpAttributeImageAttrSet {
+ RustSdpAttributeImageAttrXYRange x;
+ RustSdpAttributeImageAttrXYRange y;
+ bool has_sar;
+ RustSdpAttributeImageAttrSRange sar;
+ bool has_par;
+ RustSdpAttributeImageAttrPRange par;
+ float q;
+};
+
+struct RustSdpAttributeImageAttrSetVec;
+struct RustSdpAttributeImageAttrSetList {
+ RustSdpAttributeImageAttrSetVec* sets;
+};
+
+struct RustSdpAttributeImageAttr {
+ uint32_t payloadType;
+ RustSdpAttributeImageAttrSetList send;
+ RustSdpAttributeImageAttrSetList recv;
+};
+
+struct RustRtxFmtpParameters {
+ uint8_t apt;
+ bool has_rtx_time;
+ uint32_t rtx_time;
+};
+
+struct RustSdpAttributeFmtpParameters {
+ // H264
+ uint32_t packetization_mode;
+ bool level_asymmetry_allowed;
+ uint32_t profile_level_id;
+ uint32_t max_fs;
+ uint32_t max_cpb;
+ uint32_t max_dpb;
+ uint32_t max_br;
+ uint32_t max_mbps;
+
+ // VP8 and VP9
+ // max_fs, already defined in H264
+ uint32_t max_fr;
+
+ // Opus
+ uint32_t maxplaybackrate;
+ uint32_t maxaveragebitrate;
+ bool usedtx;
+ bool stereo;
+ bool useinbandfec;
+ bool cbr;
+ uint32_t ptime;
+ uint32_t minptime;
+ uint32_t maxptime;
+
+ // telephone-event
+ StringView dtmf_tones;
+
+ // RTX
+ RustRtxFmtpParameters rtx;
+
+ // Red codecs
+ U8Vec* encodings;
+
+ // Unknown
+ StringVec* unknown_tokens;
+};
+
+struct RustSdpAttributeFmtp {
+ uint8_t payloadType;
+ StringView codecName;
+ RustSdpAttributeFmtpParameters parameters;
+};
+
+struct RustSdpAttributeFlags {
+ bool iceLite;
+ bool rtcpMux;
+ bool rtcpRsize;
+ bool bundleOnly;
+ bool endOfCandidates;
+};
+
+struct RustSdpAttributeMsid {
+ StringView id;
+ StringView appdata;
+};
+
+struct RustSdpAttributeMsidSemantic {
+ StringView semantic;
+ StringVec* msids;
+};
+
+enum class RustSdpAttributeGroupSemantic {
+ kRustLipSynchronization,
+ kRustFlowIdentification,
+ kRustSingleReservationFlow,
+ kRustAlternateNetworkAddressType,
+ kRustForwardErrorCorrection,
+ kRustDecodingDependency,
+ kRustBundle,
+};
+
+struct RustSdpAttributeGroup {
+ RustSdpAttributeGroupSemantic semantic;
+ StringVec* tags;
+};
+
+struct RustSdpAttributeRtcp {
+ uint32_t port;
+ RustExplicitlyTypedAddress unicastAddr;
+ bool has_address;
+};
+
+struct RustSdpAttributeSctpmap {
+ uint32_t port;
+ uint32_t channels;
+};
+
+struct RustSdpAttributeSimulcastId {
+ StringView id;
+ bool paused;
+};
+
+struct RustSdpAttributeSimulcastIdVec;
+struct RustSdpAttributeSimulcastVersion {
+ RustSdpAttributeSimulcastIdVec* ids;
+};
+
+struct RustSdpAttributeSimulcastVersionVec;
+struct RustSdpAttributeSimulcast {
+ RustSdpAttributeSimulcastVersionVec* send;
+ RustSdpAttributeSimulcastVersionVec* recv;
+};
+
+enum class RustDirection {
+ kRustRecvonly,
+ kRustSendonly,
+ kRustSendrecv,
+ kRustInactive
+};
+
+struct RustSdpAttributeRemoteCandidate {
+ uint32_t component;
+ RustAddress address;
+ uint32_t port;
+};
+
+struct RustSdpAttributeExtmap {
+ uint16_t id;
+ bool direction_specified;
+ RustDirection direction;
+ StringView url;
+ StringView extensionAttributes;
+};
+
+extern "C" {
+
+size_t string_vec_len(const StringVec* vec);
+nsresult string_vec_get_view(const StringVec* vec, size_t index,
+ StringView* str);
+nsresult free_boxed_string_vec(StringVec* vec);
+
+size_t f32_vec_len(const F32Vec* vec);
+nsresult f32_vec_get(const F32Vec* vec, size_t index, float* ret);
+
+size_t u32_vec_len(const U32Vec* vec);
+nsresult u32_vec_get(const U32Vec* vec, size_t index, uint32_t* ret);
+
+size_t u16_vec_len(const U16Vec* vec);
+nsresult u16_vec_get(const U16Vec* vec, size_t index, uint16_t* ret);
+
+size_t u8_vec_len(const U8Vec* vec);
+nsresult u8_vec_get(const U8Vec* vec, size_t index, uint8_t* ret);
+
+size_t ssrc_vec_len(const SsrcVec* vec);
+nsresult ssrc_vec_get_id(const SsrcVec* vec, size_t index, uint32_t* ret);
+
+void sdp_free_string(char* string);
+
+nsresult parse_sdp(StringView sdp, bool fail_on_warning, RustSdpSession** ret,
+ RustSdpError** err);
+RustSdpSession* sdp_new_reference(RustSdpSession* aSess);
+RustSdpSession* create_sdp_clone(const RustSdpSession* aSess);
+void sdp_free_session(RustSdpSession* ret);
+size_t sdp_get_error_line_num(const RustSdpError* err);
+char* sdp_get_error_message(const RustSdpError* err);
+void sdp_free_error_message(char* message);
+void sdp_free_error(RustSdpError* err);
+
+RustSdpOrigin sdp_get_origin(const RustSdpSession* aSess);
+
+uint32_t get_sdp_bandwidth(const RustSdpSession* aSess,
+ const char* aBandwidthType);
+BandwidthVec* sdp_get_session_bandwidth_vec(const RustSdpSession* aSess);
+BandwidthVec* sdp_get_media_bandwidth_vec(const RustMediaSection* aMediaSec);
+char* sdp_serialize_bandwidth(const BandwidthVec* bandwidths);
+bool sdp_session_has_connection(const RustSdpSession* aSess);
+nsresult sdp_get_session_connection(const RustSdpSession* aSess,
+ RustSdpConnection* ret);
+RustAttributeList* get_sdp_session_attributes(const RustSdpSession* aSess);
+
+size_t sdp_media_section_count(const RustSdpSession* aSess);
+RustMediaSection* sdp_get_media_section(const RustSdpSession* aSess,
+ size_t aLevel);
+nsresult sdp_add_media_section(RustSdpSession* aSess, uint32_t aMediaType,
+ uint32_t aDirection, uint16_t aPort,
+ uint32_t aProtocol, uint32_t aAddrType,
+ StringView aAddr);
+RustSdpMediaValue sdp_rust_get_media_type(const RustMediaSection* aMediaSec);
+RustSdpProtocolValue sdp_get_media_protocol(const RustMediaSection* aMediaSec);
+RustSdpFormatType sdp_get_format_type(const RustMediaSection* aMediaSec);
+StringVec* sdp_get_format_string_vec(const RustMediaSection* aMediaSec);
+U32Vec* sdp_get_format_u32_vec(const RustMediaSection* aMediaSec);
+void sdp_set_media_port(const RustMediaSection* aMediaSec, uint32_t aPort);
+uint32_t sdp_get_media_port(const RustMediaSection* aMediaSec);
+uint32_t sdp_get_media_port_count(const RustMediaSection* aMediaSec);
+uint32_t sdp_get_media_bandwidth(const RustMediaSection* aMediaSec,
+ const char* aBandwidthType);
+bool sdp_media_has_connection(const RustMediaSection* aMediaSec);
+nsresult sdp_get_media_connection(const RustMediaSection* aMediaSec,
+ RustSdpConnection* ret);
+
+RustAttributeList* sdp_get_media_attribute_list(
+ const RustMediaSection* aMediaSec);
+
+nsresult sdp_media_add_codec(const RustMediaSection* aMediaSec, uint8_t aPT,
+ StringView aCodecName, uint32_t aClockrate,
+ uint16_t channels);
+void sdp_media_clear_codecs(const RustMediaSection* aMediaSec);
+nsresult sdp_media_add_datachannel(const RustMediaSection* aMediaSec,
+ StringView aName, uint16_t aPort,
+ uint16_t streams, uint32_t aMessageSize);
+
+nsresult sdp_get_iceufrag(const RustAttributeList* aList, StringView* ret);
+nsresult sdp_get_icepwd(const RustAttributeList* aList, StringView* ret);
+nsresult sdp_get_identity(const RustAttributeList* aList, StringView* ret);
+nsresult sdp_get_iceoptions(const RustAttributeList* aList, StringVec** ret);
+
+nsresult sdp_get_dtls_message(const RustAttributeList* aList,
+ RustSdpAttributeDtlsMessage* ret);
+
+size_t sdp_get_fingerprint_count(const RustAttributeList* aList);
+void sdp_get_fingerprints(const RustAttributeList* aList, size_t listSize,
+ RustSdpAttributeFingerprint* ret);
+
+nsresult sdp_get_setup(const RustAttributeList* aList, RustSdpSetup* ret);
+
+size_t sdp_get_ssrc_count(const RustAttributeList* aList);
+void sdp_get_ssrcs(const RustAttributeList* aList, size_t listSize,
+ RustSdpAttributeSsrc* ret);
+
+size_t sdp_get_ssrc_group_count(const RustAttributeList* aList);
+void sdp_get_ssrc_groups(const RustAttributeList* aList, size_t listSize,
+ RustSdpAttributeSsrcGroup* ret);
+
+size_t sdp_get_rtpmap_count(const RustAttributeList* aList);
+void sdp_get_rtpmaps(const RustAttributeList* aList, size_t listSize,
+ RustSdpAttributeRtpmap* ret);
+
+size_t sdp_get_fmtp_count(const RustAttributeList* aList);
+size_t sdp_get_fmtp(const RustAttributeList* aList, size_t listSize,
+ RustSdpAttributeFmtp* ret);
+
+int64_t sdp_get_ptime(const RustAttributeList* aList);
+int64_t sdp_get_max_msg_size(const RustAttributeList* aList);
+int64_t sdp_get_sctp_port(const RustAttributeList* aList);
+nsresult sdp_get_maxptime(const RustAttributeList* aList, uint64_t* aMaxPtime);
+
+RustSdpAttributeFlags sdp_get_attribute_flags(const RustAttributeList* aList);
+
+nsresult sdp_get_mid(const RustAttributeList* aList, StringView* ret);
+
+size_t sdp_get_msid_count(const RustAttributeList* aList);
+void sdp_get_msids(const RustAttributeList* aList, size_t listSize,
+ RustSdpAttributeMsid* ret);
+
+size_t sdp_get_msid_semantic_count(RustAttributeList* aList);
+void sdp_get_msid_semantics(const RustAttributeList* aList, size_t listSize,
+ RustSdpAttributeMsidSemantic* ret);
+
+size_t sdp_get_group_count(const RustAttributeList* aList);
+void sdp_get_groups(const RustAttributeList* aList, size_t listSize,
+ RustSdpAttributeGroup* ret);
+
+nsresult sdp_get_rtcp(const RustAttributeList* aList,
+ RustSdpAttributeRtcp* ret);
+
+size_t sdp_get_rtcpfb_count(const RustAttributeList* aList);
+void sdp_get_rtcpfbs(const RustAttributeList* aList, size_t listSize,
+ RustSdpAttributeRtcpFb* ret);
+
+size_t sdp_get_imageattr_count(const RustAttributeList* aList);
+void sdp_get_imageattrs(const RustAttributeList* aList, size_t listSize,
+ RustSdpAttributeImageAttr* ret);
+
+size_t sdp_imageattr_get_set_count(const RustSdpAttributeImageAttrSetVec* sets);
+void sdp_imageattr_get_sets(const RustSdpAttributeImageAttrSetVec* sets,
+ size_t listSize, RustSdpAttributeImageAttrSet* ret);
+
+size_t sdp_get_sctpmap_count(const RustAttributeList* aList);
+void sdp_get_sctpmaps(const RustAttributeList* aList, size_t listSize,
+ RustSdpAttributeSctpmap* ret);
+
+nsresult sdp_get_simulcast(const RustAttributeList* aList,
+ RustSdpAttributeSimulcast* ret);
+
+size_t sdp_simulcast_get_version_count(
+ const RustSdpAttributeSimulcastVersionVec* aVersionList);
+void sdp_simulcast_get_versions(
+ const RustSdpAttributeSimulcastVersionVec* aversionList, size_t listSize,
+ RustSdpAttributeSimulcastVersion* ret);
+
+size_t sdp_simulcast_get_ids_count(const RustSdpAttributeSimulcastIdVec* aAlts);
+void sdp_simulcast_get_ids(const RustSdpAttributeSimulcastIdVec* aAlts,
+ size_t listSize, RustSdpAttributeSimulcastId* ret);
+
+RustDirection sdp_get_direction(const RustAttributeList* aList);
+
+size_t sdp_get_remote_candidate_count(const RustAttributeList* aList);
+void sdp_get_remote_candidates(const RustAttributeList* aList, size_t listSize,
+ RustSdpAttributeRemoteCandidate* ret);
+
+size_t sdp_get_candidate_count(const RustAttributeList* aList);
+void sdp_get_candidates(const RustAttributeList* aLisst, size_t listSize,
+ StringVec** ret);
+
+size_t sdp_get_rid_count(const RustAttributeList* aList);
+void sdp_get_rids(const RustAttributeList* aList, size_t listSize,
+ RustSdpAttributeRid* ret);
+
+size_t sdp_get_extmap_count(const RustAttributeList* aList);
+void sdp_get_extmaps(const RustAttributeList* aList, size_t listSize,
+ RustSdpAttributeExtmap* ret);
+
+} // extern "C"
+
+#endif
diff --git a/dom/media/webrtc/sdp/RsdparsaSdpMediaSection.cpp b/dom/media/webrtc/sdp/RsdparsaSdpMediaSection.cpp
new file mode 100644
index 0000000000..25084db2ad
--- /dev/null
+++ b/dom/media/webrtc/sdp/RsdparsaSdpMediaSection.cpp
@@ -0,0 +1,253 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "sdp/SdpMediaSection.h"
+#include "sdp/RsdparsaSdpMediaSection.h"
+
+#include "sdp/RsdparsaSdpGlue.h"
+#include "sdp/RsdparsaSdpInc.h"
+
+#include <ostream>
+
+#ifdef CRLF
+# undef CRLF
+#endif
+#define CRLF "\r\n"
+
+namespace mozilla {
+
+RsdparsaSdpMediaSection::RsdparsaSdpMediaSection(
+ size_t level, RsdparsaSessionHandle session,
+ const RustMediaSection* const section,
+ const RsdparsaSdpAttributeList* sessionLevel)
+ : SdpMediaSection(level), mSession(std::move(session)), mSection(section) {
+ switch (sdp_rust_get_media_type(section)) {
+ case RustSdpMediaValue::kRustAudio:
+ mMediaType = kAudio;
+ break;
+ case RustSdpMediaValue::kRustVideo:
+ mMediaType = kVideo;
+ break;
+ case RustSdpMediaValue::kRustApplication:
+ mMediaType = kApplication;
+ break;
+ }
+
+ RsdparsaSessionHandle attributeSession(sdp_new_reference(mSession.get()));
+ mAttributeList.reset(new RsdparsaSdpAttributeList(std::move(attributeSession),
+ section, sessionLevel));
+
+ LoadFormats();
+ LoadConnection();
+}
+
+unsigned int RsdparsaSdpMediaSection::GetPort() const {
+ return sdp_get_media_port(mSection);
+}
+
+void RsdparsaSdpMediaSection::SetPort(unsigned int port) {
+ sdp_set_media_port(mSection, port);
+}
+
+unsigned int RsdparsaSdpMediaSection::GetPortCount() const {
+ return sdp_get_media_port_count(mSection);
+}
+
+SdpMediaSection::Protocol RsdparsaSdpMediaSection::GetProtocol() const {
+ switch (sdp_get_media_protocol(mSection)) {
+ case RustSdpProtocolValue::kRustRtpSavpf:
+ return kRtpSavpf;
+ case RustSdpProtocolValue::kRustUdpTlsRtpSavp:
+ return kUdpTlsRtpSavp;
+ case RustSdpProtocolValue::kRustTcpDtlsRtpSavp:
+ return kTcpDtlsRtpSavp;
+ case RustSdpProtocolValue::kRustUdpTlsRtpSavpf:
+ return kUdpTlsRtpSavpf;
+ case RustSdpProtocolValue::kRustTcpDtlsRtpSavpf:
+ return kTcpDtlsRtpSavpf;
+ case RustSdpProtocolValue::kRustDtlsSctp:
+ return kDtlsSctp;
+ case RustSdpProtocolValue::kRustUdpDtlsSctp:
+ return kUdpDtlsSctp;
+ case RustSdpProtocolValue::kRustTcpDtlsSctp:
+ return kTcpDtlsSctp;
+ case RustSdpProtocolValue::kRustRtpAvp:
+ return kRtpAvp;
+ case RustSdpProtocolValue::kRustRtpAvpf:
+ return kRtpAvpf;
+ case RustSdpProtocolValue::kRustRtpSavp:
+ return kRtpSavp;
+ }
+ MOZ_CRASH("invalid media protocol");
+}
+
+const SdpConnection& RsdparsaSdpMediaSection::GetConnection() const {
+ MOZ_ASSERT(mConnection);
+ return *mConnection;
+}
+
+SdpConnection& RsdparsaSdpMediaSection::GetConnection() {
+ MOZ_ASSERT(mConnection);
+ return *mConnection;
+}
+
+uint32_t RsdparsaSdpMediaSection::GetBandwidth(const std::string& type) const {
+ return sdp_get_media_bandwidth(mSection, type.c_str());
+}
+
+const std::vector<std::string>& RsdparsaSdpMediaSection::GetFormats() const {
+ return mFormats;
+}
+
+const SdpAttributeList& RsdparsaSdpMediaSection::GetAttributeList() const {
+ return *mAttributeList;
+}
+
+SdpAttributeList& RsdparsaSdpMediaSection::GetAttributeList() {
+ return *mAttributeList;
+}
+
+SdpDirectionAttribute RsdparsaSdpMediaSection::GetDirectionAttribute() const {
+ return SdpDirectionAttribute(mAttributeList->GetDirection());
+}
+
+void RsdparsaSdpMediaSection::AddCodec(const std::string& pt,
+ const std::string& name,
+ uint32_t clockrate, uint16_t channels) {
+ StringView rustName{name.c_str(), name.size()};
+
+ // call the rust interface
+ auto nr = sdp_media_add_codec(mSection, std::stoul(pt), rustName, clockrate,
+ channels);
+
+ if (NS_SUCCEEDED(nr)) {
+ // If the rust call was successful, adjust the shadow C++ structures
+ mFormats.push_back(pt);
+
+ // Add a rtpmap in mAttributeList
+ SdpRtpmapAttributeList* rtpmap = new SdpRtpmapAttributeList();
+ if (mAttributeList->HasAttribute(SdpAttribute::kRtpmapAttribute)) {
+ const SdpRtpmapAttributeList& old = mAttributeList->GetRtpmap();
+ for (auto it = old.mRtpmaps.begin(); it != old.mRtpmaps.end(); ++it) {
+ rtpmap->mRtpmaps.push_back(*it);
+ }
+ }
+
+ SdpRtpmapAttributeList::CodecType codec =
+ SdpRtpmapAttributeList::kOtherCodec;
+ if (name == "opus") {
+ codec = SdpRtpmapAttributeList::kOpus;
+ } else if (name == "VP8") {
+ codec = SdpRtpmapAttributeList::kVP8;
+ } else if (name == "VP9") {
+ codec = SdpRtpmapAttributeList::kVP9;
+ } else if (name == "H264") {
+ codec = SdpRtpmapAttributeList::kH264;
+ }
+
+ rtpmap->PushEntry(pt, codec, name, clockrate, channels);
+ mAttributeList->SetAttribute(rtpmap);
+ }
+}
+
+void RsdparsaSdpMediaSection::ClearCodecs() {
+ // Clear the codecs in rust
+ sdp_media_clear_codecs(mSection);
+
+ mFormats.clear();
+ mAttributeList->RemoveAttribute(SdpAttribute::kRtpmapAttribute);
+ mAttributeList->RemoveAttribute(SdpAttribute::kFmtpAttribute);
+ mAttributeList->RemoveAttribute(SdpAttribute::kSctpmapAttribute);
+ mAttributeList->RemoveAttribute(SdpAttribute::kRtcpFbAttribute);
+}
+
+void RsdparsaSdpMediaSection::AddDataChannel(const std::string& name,
+ uint16_t port, uint16_t streams,
+ uint32_t message_size) {
+ StringView rustName{name.c_str(), name.size()};
+ auto nr = sdp_media_add_datachannel(mSection, rustName, port, streams,
+ message_size);
+ if (NS_SUCCEEDED(nr)) {
+ // Update the formats
+ mFormats.clear();
+ LoadFormats();
+
+ // Update the attribute list
+ RsdparsaSessionHandle sessHandle(sdp_new_reference(mSession.get()));
+ auto sessAttributes = mAttributeList->mSessionAttributes;
+ mAttributeList.reset(new RsdparsaSdpAttributeList(
+ std::move(sessHandle), mSection, sessAttributes));
+ }
+}
+
+void RsdparsaSdpMediaSection::Serialize(std::ostream& os) const {
+ os << "m=" << mMediaType << " " << GetPort();
+ if (GetPortCount()) {
+ os << "/" << GetPortCount();
+ }
+ os << " " << GetProtocol();
+ for (auto i = mFormats.begin(); i != mFormats.end(); ++i) {
+ os << " " << (*i);
+ }
+ os << CRLF;
+
+ // We dont do i=
+
+ if (mConnection) {
+ os << *mConnection;
+ }
+
+ BandwidthVec* bwVec = sdp_get_media_bandwidth_vec(mSection);
+ char* bwString = sdp_serialize_bandwidth(bwVec);
+ if (bwString) {
+ os << bwString;
+ sdp_free_string(bwString);
+ }
+
+ // We dont do k= because they're evil
+
+ os << *mAttributeList;
+}
+
+void RsdparsaSdpMediaSection::LoadFormats() {
+ RustSdpFormatType formatType = sdp_get_format_type(mSection);
+ if (formatType == RustSdpFormatType::kRustIntegers) {
+ U32Vec* vec = sdp_get_format_u32_vec(mSection);
+ size_t len = u32_vec_len(vec);
+ for (size_t i = 0; i < len; i++) {
+ uint32_t val;
+ u32_vec_get(vec, i, &val);
+ mFormats.push_back(std::to_string(val));
+ }
+ } else {
+ StringVec* vec = sdp_get_format_string_vec(mSection);
+ mFormats = convertStringVec(vec);
+ }
+}
+
+UniquePtr<SdpConnection> convertRustConnection(RustSdpConnection conn) {
+ auto address = convertExplicitlyTypedAddress(&conn.addr);
+ return MakeUnique<SdpConnection>(address.first, address.second, conn.ttl,
+ conn.amount);
+}
+
+void RsdparsaSdpMediaSection::LoadConnection() {
+ RustSdpConnection conn;
+ nsresult nr;
+ if (sdp_media_has_connection(mSection)) {
+ nr = sdp_get_media_connection(mSection, &conn);
+ if (NS_SUCCEEDED(nr)) {
+ mConnection = convertRustConnection(conn);
+ }
+ } else if (sdp_session_has_connection(mSession.get())) {
+ nr = sdp_get_session_connection(mSession.get(), &conn);
+ if (NS_SUCCEEDED(nr)) {
+ mConnection = convertRustConnection(conn);
+ }
+ }
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/sdp/RsdparsaSdpMediaSection.h b/dom/media/webrtc/sdp/RsdparsaSdpMediaSection.h
new file mode 100644
index 0000000000..3c193bd99c
--- /dev/null
+++ b/dom/media/webrtc/sdp/RsdparsaSdpMediaSection.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _RUSTSDPMEDIASECTION_H_
+#define _RUSTSDPMEDIASECTION_H_
+
+#include "mozilla/UniquePtr.h"
+#include "sdp/RsdparsaSdpInc.h"
+#include "sdp/RsdparsaSdpGlue.h"
+#include "sdp/SdpMediaSection.h"
+#include "sdp/RsdparsaSdpAttributeList.h"
+
+namespace mozilla {
+
+class RsdparsaSdp;
+class SdpParser;
+
+class RsdparsaSdpMediaSection final : public SdpMediaSection {
+ friend class RsdparsaSdp;
+
+ public:
+ ~RsdparsaSdpMediaSection() {}
+
+ MediaType GetMediaType() const override { return mMediaType; }
+
+ unsigned int GetPort() const override;
+ void SetPort(unsigned int port) override;
+ unsigned int GetPortCount() const override;
+ Protocol GetProtocol() const override;
+ const SdpConnection& GetConnection() const override;
+ SdpConnection& GetConnection() override;
+ uint32_t GetBandwidth(const std::string& type) const override;
+ const std::vector<std::string>& GetFormats() const override;
+
+ const SdpAttributeList& GetAttributeList() const override;
+ SdpAttributeList& GetAttributeList() override;
+ SdpDirectionAttribute GetDirectionAttribute() const override;
+
+ void AddCodec(const std::string& pt, const std::string& name,
+ uint32_t clockrate, uint16_t channels) override;
+ void ClearCodecs() override;
+
+ void AddDataChannel(const std::string& name, uint16_t port, uint16_t streams,
+ uint32_t message_size) override;
+
+ void Serialize(std::ostream&) const override;
+
+ private:
+ RsdparsaSdpMediaSection(size_t level, RsdparsaSessionHandle session,
+ const RustMediaSection* const section,
+ const RsdparsaSdpAttributeList* sessionLevel);
+
+ void LoadFormats();
+ void LoadConnection();
+
+ RsdparsaSessionHandle mSession;
+ const RustMediaSection* mSection;
+
+ MediaType mMediaType;
+ std::vector<std::string> mFormats;
+
+ UniquePtr<SdpConnection> mConnection;
+
+ UniquePtr<RsdparsaSdpAttributeList> mAttributeList;
+};
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/sdp/RsdparsaSdpParser.cpp b/dom/media/webrtc/sdp/RsdparsaSdpParser.cpp
new file mode 100644
index 0000000000..b955bcd46b
--- /dev/null
+++ b/dom/media/webrtc/sdp/RsdparsaSdpParser.cpp
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsError.h"
+
+#include "mozilla/UniquePtr.h"
+
+#include "sdp/Sdp.h"
+#include "sdp/RsdparsaSdp.h"
+#include "sdp/RsdparsaSdpParser.h"
+#include "sdp/RsdparsaSdpInc.h"
+#include "sdp/RsdparsaSdpGlue.h"
+
+namespace mozilla {
+
+const std::string& RsdparsaSdpParser::ParserName() {
+ static const std::string& WEBRTC_SDP_NAME = "WEBRTCSDP";
+ return WEBRTC_SDP_NAME;
+}
+
+UniquePtr<SdpParser::Results> RsdparsaSdpParser::Parse(
+ const std::string& aText) {
+ UniquePtr<SdpParser::InternalResults> results(
+ new SdpParser::InternalResults(Name()));
+ RustSdpSession* result = nullptr;
+ RustSdpError* err = nullptr;
+ StringView sdpTextView{aText.c_str(), aText.length()};
+ nsresult rv = parse_sdp(sdpTextView, false, &result, &err);
+ if (rv != NS_OK) {
+ size_t line = sdp_get_error_line_num(err);
+ char* cString = sdp_get_error_message(err);
+ if (cString) {
+ std::string errMsg(cString);
+ sdp_free_error_message(cString);
+ sdp_free_error(err);
+ results->AddParseError(line, errMsg);
+ } else {
+ results->AddParseError(line, "Unable to retreive parse error.");
+ }
+ return results;
+ }
+
+ if (err) {
+ size_t line = sdp_get_error_line_num(err);
+ char* cString = sdp_get_error_message(err);
+ if (cString) {
+ std::string warningMsg(cString);
+ results->AddParseWarning(line, warningMsg);
+ sdp_free_error_message(cString);
+ sdp_free_error(err);
+ } else {
+ results->AddParseWarning(line, "Unable to retreive parse warning.");
+ }
+ }
+
+ RsdparsaSessionHandle uniqueResult(result);
+ RustSdpOrigin rustOrigin = sdp_get_origin(uniqueResult.get());
+ auto address = convertExplicitlyTypedAddress(&rustOrigin.addr);
+ SdpOrigin origin(convertStringView(rustOrigin.username), rustOrigin.sessionId,
+ rustOrigin.sessionVersion, address.first, address.second);
+
+ results->SetSdp(MakeUnique<RsdparsaSdp>(std::move(uniqueResult), origin));
+ return results;
+}
+
+bool RsdparsaSdpParser::IsNamed(const std::string& aName) {
+ return aName == ParserName();
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/sdp/RsdparsaSdpParser.h b/dom/media/webrtc/sdp/RsdparsaSdpParser.h
new file mode 100644
index 0000000000..443aa46557
--- /dev/null
+++ b/dom/media/webrtc/sdp/RsdparsaSdpParser.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _RUSTSDPPARSER_H_
+#define _RUSTSDPPARSER_H_
+
+#include <string>
+
+#include "mozilla/UniquePtr.h"
+
+#include "sdp/SdpParser.h"
+
+namespace mozilla {
+
+class RsdparsaSdpParser final : public SdpParser {
+ static const std::string& ParserName();
+
+ public:
+ RsdparsaSdpParser() = default;
+ virtual ~RsdparsaSdpParser() = default;
+
+ const std::string& Name() const override { return ParserName(); }
+
+ UniquePtr<SdpParser::Results> Parse(const std::string& text) override;
+
+ static bool IsNamed(const std::string& aName);
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/sdp/Sdp.h b/dom/media/webrtc/sdp/Sdp.h
new file mode 100644
index 0000000000..7576cac46f
--- /dev/null
+++ b/dom/media/webrtc/sdp/Sdp.h
@@ -0,0 +1,166 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+
+ ,-----. ,--. ,--.
+ ' .--./ ,--,--.,--.,--.,-' '-.`--' ,---. ,--,--,
+ | | ' ,-. || || |'-. .-',--.| .-. || `
+ ' '--'\\ '-' |' '' ' | | | |' '-' '| || |
+ `-----' `--`--' `----' `--' `--' `---' `--''--'
+
+ :+o+-
+ -dNNNNNd.
+ yNNNNNNNs
+ :mNNNNNm-
+ `/sso/``-://-
+ .:+sydNNNNNNms: `://`
+ `-/+shmNNNNNNNNNNNNNNNms- :mNNNm/
+ `-/oydmNNNNNNNNNNNNNNNNNNNNNNNNdo- +NNNNNN+
+ .shmNNNNNNNNNNNmdyo/:dNNNNNNNNNNNNNNNNdo. `sNNNNNm+
+ hNNNNNNNNmhs+:-` .dNNNNNNNNNNNNNNNNNNNNh+-` `hNNNNNm:
+ -yddyo/:. -dNNNNm::ymNNNNNNNNNNNNNNNmdy+/dNNNNNd.
+ :mNNNNd. `/ymNNNNNNNNNNNNNNNNNNNNNNh`
+ +NNNNNh` `+hNNNNNNNNNNNNNNNNNNNs
+ sNNNNNy` .yNNNNNm`-/oymNNNm+
+ `yNNNNNo oNNNNNm` `-.
+ .dNNNNm/ oNNNNNm`
+ oNNNNm: +NNNNNm`
+ `+yho. +NNNNNm`
+ +NNNNNNs.
+ `yNNNNNNmy-
+ -smNNNNNNh:
+ .smNNNNNNh/
+ `omNNNNNNd:
+ `+dNNNNNd
+ ````......```` /hmdy-
+ `.:/+osyhddmNNMMMMMMMMMMMMMMMMMMMMNNmddhyso+/:.`
+ `-+shmNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMNmhs+-`
+ -smMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMds-
+ hMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMh
+ yMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMs
+ .ohNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMNh+.
+ ./oydmMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMmhyo:.
+ `.:/+osyyhddmmNNMMMMMMMMMMMMMMNNmmddhyyso+/:.`
+
+ ,--------.,--. ,--. ,--.
+ '--. .--'| ,---. `--' ,---. | | ,---.
+ | | | .-. |,--.( .-' | |( .-'
+ | | | | | || |.-' `) | |.-' `)
+ `--' `--' `--'`--'`----' `--'`----'
+ ,--.
+ ,---. ,------. ,------. ,--. | |
+ ' .-' | .-. \ | .--. ' ,--,--.,--.--.,-' '-. ,--,--.| |
+ `. `-. | | \ :| '--' |' ,-. || .--''-. .-'' ,-. || |
+ .-' || '--' /| | --' \ '-' || | | | \ '-' |`--'
+ `-----' `-------' `--' `--`--'`--' `--' `--`--'.--.
+ '__'
+*/
+
+#ifndef _SDP_H_
+#define _SDP_H_
+
+#include <ostream>
+#include <vector>
+#include <sstream>
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Maybe.h"
+#include "sdp/SdpMediaSection.h"
+#include "sdp/SdpAttributeList.h"
+#include "sdp/SdpEnum.h"
+
+namespace mozilla {
+
+class SdpOrigin;
+class SdpEncryptionKey;
+class SdpMediaSection;
+
+/**
+ * Base class for an SDP
+ */
+class Sdp {
+ public:
+ Sdp() = default;
+ virtual ~Sdp() = default;
+
+ virtual Sdp* Clone() const = 0;
+
+ virtual const SdpOrigin& GetOrigin() const = 0;
+ // Note: connection information is always retrieved from media sections
+ virtual uint32_t GetBandwidth(const std::string& type) const = 0;
+
+ virtual const SdpAttributeList& GetAttributeList() const = 0;
+ virtual SdpAttributeList& GetAttributeList() = 0;
+
+ virtual size_t GetMediaSectionCount() const = 0;
+ virtual const SdpMediaSection& GetMediaSection(size_t level) const = 0;
+ virtual SdpMediaSection& GetMediaSection(size_t level) = 0;
+
+ virtual SdpMediaSection& AddMediaSection(SdpMediaSection::MediaType media,
+ SdpDirectionAttribute::Direction dir,
+ uint16_t port,
+ SdpMediaSection::Protocol proto,
+ sdp::AddrType addrType,
+ const std::string& addr) = 0;
+
+ virtual void Serialize(std::ostream&) const = 0;
+
+ std::string ToString() const;
+};
+
+inline std::ostream& operator<<(std::ostream& os, const Sdp& sdp) {
+ sdp.Serialize(os);
+ return os;
+}
+
+inline std::string Sdp::ToString() const {
+ std::stringstream s;
+ s << *this;
+ return s.str();
+}
+
+class SdpOrigin {
+ public:
+ SdpOrigin(const std::string& username, uint64_t sessId, uint64_t sessVer,
+ sdp::AddrType addrType, const std::string& addr)
+ : mUsername(username),
+ mSessionId(sessId),
+ mSessionVersion(sessVer),
+ mAddrType(addrType),
+ mAddress(addr) {}
+
+ const std::string& GetUsername() const { return mUsername; }
+
+ uint64_t GetSessionId() const { return mSessionId; }
+
+ uint64_t GetSessionVersion() const { return mSessionVersion; }
+
+ sdp::AddrType GetAddrType() const { return mAddrType; }
+
+ const std::string& GetAddress() const { return mAddress; }
+
+ void Serialize(std::ostream& os) const {
+ sdp::NetType netType = sdp::kInternet;
+ os << "o=" << mUsername << " " << mSessionId << " " << mSessionVersion
+ << " " << netType << " " << mAddrType << " " << mAddress << "\r\n";
+ }
+
+ private:
+ std::string mUsername;
+ uint64_t mSessionId;
+ uint64_t mSessionVersion;
+ sdp::AddrType mAddrType;
+ std::string mAddress;
+};
+
+inline std::ostream& operator<<(std::ostream& os, const SdpOrigin& origin) {
+ origin.Serialize(os);
+ return os;
+}
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/sdp/SdpAttribute.cpp b/dom/media/webrtc/sdp/SdpAttribute.cpp
new file mode 100644
index 0000000000..cacb25e6b1
--- /dev/null
+++ b/dom/media/webrtc/sdp/SdpAttribute.cpp
@@ -0,0 +1,1562 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "sdp/SdpAttribute.h"
+#include "sdp/SdpHelper.h"
+#include <iomanip>
+#include <bitset>
+
+#ifdef CRLF
+# undef CRLF
+#endif
+#define CRLF "\r\n"
+
+namespace mozilla {
+
+static unsigned char PeekChar(std::istream& is, std::string* error) {
+ int next = is.peek();
+ if (next == EOF) {
+ *error = "Truncated";
+ return 0;
+ }
+
+ return next;
+}
+
+static std::string ParseToken(std::istream& is, const std::string& delims,
+ std::string* error) {
+ std::string token;
+ while (is) {
+ unsigned char c = PeekChar(is, error);
+ if (!c || (delims.find(c) != std::string::npos)) {
+ break;
+ }
+ token.push_back(std::tolower(is.get()));
+ }
+ return token;
+}
+
+static bool SkipChar(std::istream& is, unsigned char c, std::string* error) {
+ if (PeekChar(is, error) != c) {
+ *error = "Expected \'";
+ error->push_back(c);
+ error->push_back('\'');
+ return false;
+ }
+
+ is.get();
+ return true;
+}
+
+void SdpConnectionAttribute::Serialize(std::ostream& os) const {
+ os << "a=" << mType << ":" << mValue << CRLF;
+}
+
+void SdpDirectionAttribute::Serialize(std::ostream& os) const {
+ os << "a=" << mValue << CRLF;
+}
+
+void SdpDtlsMessageAttribute::Serialize(std::ostream& os) const {
+ os << "a=" << mType << ":" << mRole << " " << mValue << CRLF;
+}
+
+bool SdpDtlsMessageAttribute::Parse(std::istream& is, std::string* error) {
+ std::string roleToken = ParseToken(is, " ", error);
+ if (roleToken == "server") {
+ mRole = kServer;
+ } else if (roleToken == "client") {
+ mRole = kClient;
+ } else {
+ *error = "Invalid dtls-message role; must be either client or server";
+ return false;
+ }
+
+ is >> std::ws;
+
+ std::string s(std::istreambuf_iterator<char>(is), {});
+ mValue = s;
+
+ return true;
+}
+
+void SdpExtmapAttributeList::Serialize(std::ostream& os) const {
+ for (auto i = mExtmaps.begin(); i != mExtmaps.end(); ++i) {
+ os << "a=" << mType << ":" << i->entry;
+ if (i->direction_specified) {
+ os << "/" << i->direction;
+ }
+ os << " " << i->extensionname;
+ if (i->extensionattributes.length()) {
+ os << " " << i->extensionattributes;
+ }
+ os << CRLF;
+ }
+}
+
+void SdpFingerprintAttributeList::Serialize(std::ostream& os) const {
+ for (auto i = mFingerprints.begin(); i != mFingerprints.end(); ++i) {
+ os << "a=" << mType << ":" << i->hashFunc << " "
+ << FormatFingerprint(i->fingerprint) << CRLF;
+ }
+}
+
+// Format the fingerprint in RFC 4572 Section 5 attribute format
+std::string SdpFingerprintAttributeList::FormatFingerprint(
+ const std::vector<uint8_t>& fp) {
+ if (fp.empty()) {
+ MOZ_ASSERT(false, "Cannot format an empty fingerprint.");
+ return "";
+ }
+
+ std::ostringstream os;
+ for (auto i = fp.begin(); i != fp.end(); ++i) {
+ os << ":" << std::hex << std::uppercase << std::setw(2) << std::setfill('0')
+ << static_cast<uint32_t>(*i);
+ }
+ return os.str().substr(1);
+}
+
+static uint8_t FromUppercaseHex(char ch) {
+ if ((ch >= '0') && (ch <= '9')) {
+ return ch - '0';
+ }
+ if ((ch >= 'A') && (ch <= 'F')) {
+ return ch - 'A' + 10;
+ }
+ return 16; // invalid
+}
+
+// Parse the fingerprint from RFC 4572 Section 5 attribute format
+std::vector<uint8_t> SdpFingerprintAttributeList::ParseFingerprint(
+ const std::string& str) {
+ size_t targetSize = (str.length() + 1) / 3;
+ std::vector<uint8_t> fp(targetSize);
+ size_t fpIndex = 0;
+
+ if (str.length() % 3 != 2) {
+ fp.clear();
+ return fp;
+ }
+
+ for (size_t i = 0; i < str.length(); i += 3) {
+ uint8_t high = FromUppercaseHex(str[i]);
+ uint8_t low = FromUppercaseHex(str[i + 1]);
+ if (high > 0xf || low > 0xf ||
+ (i + 2 < str.length() && str[i + 2] != ':')) {
+ fp.clear(); // error
+ return fp;
+ }
+ fp[fpIndex++] = high << 4 | low;
+ }
+ return fp;
+}
+
+bool SdpFmtpAttributeList::operator==(const SdpFmtpAttributeList& other) const {
+ return mFmtps == other.mFmtps;
+}
+
+void SdpFmtpAttributeList::Serialize(std::ostream& os) const {
+ for (auto i = mFmtps.begin(); i != mFmtps.end(); ++i) {
+ if (i->parameters) {
+ os << "a=" << mType << ":" << i->format << " ";
+ i->parameters->Serialize(os);
+ os << CRLF;
+ }
+ }
+}
+
+void SdpGroupAttributeList::Serialize(std::ostream& os) const {
+ for (auto i = mGroups.begin(); i != mGroups.end(); ++i) {
+ os << "a=" << mType << ":" << i->semantics;
+ for (auto j = i->tags.begin(); j != i->tags.end(); ++j) {
+ os << " " << (*j);
+ }
+ os << CRLF;
+ }
+}
+
+// We're just using an SdpStringAttribute for this right now
+#if 0
+void SdpIdentityAttribute::Serialize(std::ostream& os) const
+{
+ os << "a=" << mType << ":" << mAssertion;
+ for (auto i = mExtensions.begin(); i != mExtensions.end(); i++) {
+ os << (i == mExtensions.begin() ? " " : ";") << (*i);
+ }
+ os << CRLF;
+}
+#endif
+
+// Class to help with omitting a leading delimiter for the first item in a list
+class SkipFirstDelimiter {
+ public:
+ explicit SkipFirstDelimiter(const std::string& delim)
+ : mDelim(delim), mFirst(true) {}
+
+ std::ostream& print(std::ostream& os) {
+ if (!mFirst) {
+ os << mDelim;
+ }
+ mFirst = false;
+ return os;
+ }
+
+ private:
+ std::string mDelim;
+ bool mFirst;
+};
+
+static std::ostream& operator<<(std::ostream& os, SkipFirstDelimiter& delim) {
+ return delim.print(os);
+}
+
+void SdpImageattrAttributeList::XYRange::Serialize(std::ostream& os) const {
+ if (discreteValues.empty()) {
+ os << "[" << min << ":";
+ if (step != 1) {
+ os << step << ":";
+ }
+ os << max << "]";
+ } else if (discreteValues.size() == 1) {
+ os << discreteValues.front();
+ } else {
+ os << "[";
+ SkipFirstDelimiter comma(",");
+ for (auto value : discreteValues) {
+ os << comma << value;
+ }
+ os << "]";
+ }
+}
+
+template <typename T>
+bool GetUnsigned(std::istream& is, T min, T max, T* value, std::string* error) {
+ if (PeekChar(is, error) == '-') {
+ *error = "Value is less than 0";
+ return false;
+ }
+
+ is >> std::noskipws >> *value;
+
+ if (is.fail()) {
+ *error = "Malformed";
+ return false;
+ }
+
+ if (*value < min) {
+ *error = "Value too small";
+ return false;
+ }
+
+ if (*value > max) {
+ *error = "Value too large";
+ return false;
+ }
+
+ return true;
+}
+
+static bool GetXYValue(std::istream& is, uint32_t* value, std::string* error) {
+ return GetUnsigned<uint32_t>(is, 1, 999999, value, error);
+}
+
+bool SdpImageattrAttributeList::XYRange::ParseDiscreteValues(
+ std::istream& is, std::string* error) {
+ do {
+ uint32_t value;
+ if (!GetXYValue(is, &value, error)) {
+ return false;
+ }
+ discreteValues.push_back(value);
+ } while (SkipChar(is, ',', error));
+
+ return SkipChar(is, ']', error);
+}
+
+bool SdpImageattrAttributeList::XYRange::ParseAfterMin(std::istream& is,
+ std::string* error) {
+ // We have already parsed "[320:", and now expect another uint
+ uint32_t value;
+ if (!GetXYValue(is, &value, error)) {
+ return false;
+ }
+
+ if (SkipChar(is, ':', error)) {
+ // Range with step eg [320:16:640]
+ step = value;
+ // Now |value| should be the max
+ if (!GetXYValue(is, &value, error)) {
+ return false;
+ }
+ }
+
+ max = value;
+ if (min >= max) {
+ *error = "Min is not smaller than max";
+ return false;
+ }
+
+ return SkipChar(is, ']', error);
+}
+
+bool SdpImageattrAttributeList::XYRange::ParseAfterBracket(std::istream& is,
+ std::string* error) {
+ // Either a range, or a list of discrete values
+ // [320:640], [320:16:640], or [320,640]
+ uint32_t value;
+ if (!GetXYValue(is, &value, error)) {
+ return false;
+ }
+
+ if (SkipChar(is, ':', error)) {
+ // Range - [640:480] or [640:16:480]
+ min = value;
+ return ParseAfterMin(is, error);
+ }
+
+ if (SkipChar(is, ',', error)) {
+ discreteValues.push_back(value);
+ return ParseDiscreteValues(is, error);
+ }
+
+ *error = "Expected \':\' or \',\'";
+ return false;
+}
+
+bool SdpImageattrAttributeList::XYRange::Parse(std::istream& is,
+ std::string* error) {
+ if (SkipChar(is, '[', error)) {
+ return ParseAfterBracket(is, error);
+ }
+
+ // Single discrete value
+ uint32_t value;
+ if (!GetXYValue(is, &value, error)) {
+ return false;
+ }
+ discreteValues.push_back(value);
+
+ return true;
+}
+
+static bool GetSPValue(std::istream& is, float* value, std::string* error) {
+ return GetUnsigned<float>(is, 0.1f, 9.9999f, value, error);
+}
+
+static bool GetQValue(std::istream& is, float* value, std::string* error) {
+ return GetUnsigned<float>(is, 0.0f, 1.0f, value, error);
+}
+
+bool SdpImageattrAttributeList::SRange::ParseDiscreteValues(
+ std::istream& is, std::string* error) {
+ do {
+ float value;
+ if (!GetSPValue(is, &value, error)) {
+ return false;
+ }
+ discreteValues.push_back(value);
+ } while (SkipChar(is, ',', error));
+
+ return SkipChar(is, ']', error);
+}
+
+bool SdpImageattrAttributeList::SRange::ParseAfterMin(std::istream& is,
+ std::string* error) {
+ if (!GetSPValue(is, &max, error)) {
+ return false;
+ }
+
+ if (min >= max) {
+ *error = "Min is not smaller than max";
+ return false;
+ }
+
+ return SkipChar(is, ']', error);
+}
+
+bool SdpImageattrAttributeList::SRange::ParseAfterBracket(std::istream& is,
+ std::string* error) {
+ // Either a range, or a list of discrete values
+ float value;
+ if (!GetSPValue(is, &value, error)) {
+ return false;
+ }
+
+ if (SkipChar(is, '-', error)) {
+ min = value;
+ return ParseAfterMin(is, error);
+ }
+
+ if (SkipChar(is, ',', error)) {
+ discreteValues.push_back(value);
+ return ParseDiscreteValues(is, error);
+ }
+
+ *error = "Expected either \'-\' or \',\'";
+ return false;
+}
+
+bool SdpImageattrAttributeList::SRange::Parse(std::istream& is,
+ std::string* error) {
+ if (SkipChar(is, '[', error)) {
+ return ParseAfterBracket(is, error);
+ }
+
+ // Single discrete value
+ float value;
+ if (!GetSPValue(is, &value, error)) {
+ return false;
+ }
+ discreteValues.push_back(value);
+ return true;
+}
+
+bool SdpImageattrAttributeList::PRange::Parse(std::istream& is,
+ std::string* error) {
+ if (!SkipChar(is, '[', error)) {
+ return false;
+ }
+
+ if (!GetSPValue(is, &min, error)) {
+ return false;
+ }
+
+ if (!SkipChar(is, '-', error)) {
+ return false;
+ }
+
+ if (!GetSPValue(is, &max, error)) {
+ return false;
+ }
+
+ if (min >= max) {
+ *error = "min must be smaller than max";
+ return false;
+ }
+
+ if (!SkipChar(is, ']', error)) {
+ return false;
+ }
+ return true;
+}
+
+void SdpImageattrAttributeList::SRange::Serialize(std::ostream& os) const {
+ os << std::setprecision(4) << std::fixed;
+ if (discreteValues.empty()) {
+ os << "[" << min << "-" << max << "]";
+ } else if (discreteValues.size() == 1) {
+ os << discreteValues.front();
+ } else {
+ os << "[";
+ SkipFirstDelimiter comma(",");
+ for (auto value : discreteValues) {
+ os << comma << value;
+ }
+ os << "]";
+ }
+}
+
+void SdpImageattrAttributeList::PRange::Serialize(std::ostream& os) const {
+ os << std::setprecision(4) << std::fixed;
+ os << "[" << min << "-" << max << "]";
+}
+
+static std::string ParseKey(std::istream& is, std::string* error) {
+ std::string token = ParseToken(is, "=", error);
+ if (!SkipChar(is, '=', error)) {
+ return "";
+ }
+ return token;
+}
+
+static bool SkipBraces(std::istream& is, std::string* error) {
+ if (PeekChar(is, error) != '[') {
+ *error = "Expected \'[\'";
+ return false;
+ }
+
+ size_t braceCount = 0;
+ do {
+ switch (PeekChar(is, error)) {
+ case '[':
+ ++braceCount;
+ break;
+ case ']':
+ --braceCount;
+ break;
+ default:
+ break;
+ }
+ is.get();
+ } while (braceCount && is);
+
+ if (!is) {
+ *error = "Expected closing brace";
+ return false;
+ }
+
+ return true;
+}
+
+// Assumptions:
+// 1. If the value contains '[' or ']', they are balanced.
+// 2. The value contains no ',' outside of brackets.
+static bool SkipValue(std::istream& is, std::string* error) {
+ while (is) {
+ switch (PeekChar(is, error)) {
+ case ',':
+ case ']':
+ return true;
+ case '[':
+ if (!SkipBraces(is, error)) {
+ return false;
+ }
+ break;
+ default:
+ is.get();
+ }
+ }
+
+ *error = "No closing \']\' on set";
+ return false;
+}
+
+bool SdpImageattrAttributeList::Set::Parse(std::istream& is,
+ std::string* error) {
+ if (!SkipChar(is, '[', error)) {
+ return false;
+ }
+
+ if (ParseKey(is, error) != "x") {
+ *error = "Expected x=";
+ return false;
+ }
+
+ if (!xRange.Parse(is, error)) {
+ return false;
+ }
+
+ if (!SkipChar(is, ',', error)) {
+ return false;
+ }
+
+ if (ParseKey(is, error) != "y") {
+ *error = "Expected y=";
+ return false;
+ }
+
+ if (!yRange.Parse(is, error)) {
+ return false;
+ }
+
+ qValue = 0.5f; // default
+
+ bool gotSar = false;
+ bool gotPar = false;
+ bool gotQ = false;
+
+ while (SkipChar(is, ',', error)) {
+ std::string key = ParseKey(is, error);
+ if (key.empty()) {
+ *error = "Expected key-value";
+ return false;
+ }
+
+ if (key == "sar") {
+ if (gotSar) {
+ *error = "Extra sar parameter";
+ return false;
+ }
+ gotSar = true;
+ if (!sRange.Parse(is, error)) {
+ return false;
+ }
+ } else if (key == "par") {
+ if (gotPar) {
+ *error = "Extra par parameter";
+ return false;
+ }
+ gotPar = true;
+ if (!pRange.Parse(is, error)) {
+ return false;
+ }
+ } else if (key == "q") {
+ if (gotQ) {
+ *error = "Extra q parameter";
+ return false;
+ }
+ gotQ = true;
+ if (!GetQValue(is, &qValue, error)) {
+ return false;
+ }
+ } else {
+ if (!SkipValue(is, error)) {
+ return false;
+ }
+ }
+ }
+
+ return SkipChar(is, ']', error);
+}
+
+void SdpImageattrAttributeList::Set::Serialize(std::ostream& os) const {
+ os << "[x=";
+ xRange.Serialize(os);
+ os << ",y=";
+ yRange.Serialize(os);
+ if (sRange.IsSet()) {
+ os << ",sar=";
+ sRange.Serialize(os);
+ }
+ if (pRange.IsSet()) {
+ os << ",par=";
+ pRange.Serialize(os);
+ }
+ if (qValue >= 0) {
+ os << std::setprecision(2) << std::fixed << ",q=" << qValue;
+ }
+ os << "]";
+}
+
+bool SdpImageattrAttributeList::Imageattr::ParseSets(std::istream& is,
+ std::string* error) {
+ std::string type = ParseToken(is, " \t", error);
+
+ bool* isAll = nullptr;
+ std::vector<Set>* sets = nullptr;
+
+ if (type == "send") {
+ isAll = &sendAll;
+ sets = &sendSets;
+ } else if (type == "recv") {
+ isAll = &recvAll;
+ sets = &recvSets;
+ } else {
+ *error = "Unknown type, must be either send or recv";
+ return false;
+ }
+
+ if (*isAll || !sets->empty()) {
+ *error = "Multiple send or recv set lists";
+ return false;
+ }
+
+ is >> std::ws;
+ if (SkipChar(is, '*', error)) {
+ *isAll = true;
+ return true;
+ }
+
+ do {
+ Set set;
+ if (!set.Parse(is, error)) {
+ return false;
+ }
+
+ sets->push_back(set);
+ is >> std::ws;
+ } while (PeekChar(is, error) == '[');
+
+ return true;
+}
+
+bool SdpImageattrAttributeList::Imageattr::Parse(std::istream& is,
+ std::string* error) {
+ if (!SkipChar(is, '*', error)) {
+ uint16_t value;
+ if (!GetUnsigned<uint16_t>(is, 0, UINT16_MAX, &value, error)) {
+ return false;
+ }
+ pt = Some(value);
+ }
+
+ is >> std::ws;
+ if (!ParseSets(is, error)) {
+ return false;
+ }
+
+ // There might be a second one
+ is >> std::ws;
+ if (is.eof()) {
+ return true;
+ }
+
+ if (!ParseSets(is, error)) {
+ return false;
+ }
+
+ is >> std::ws;
+ if (!is.eof()) {
+ *error = "Trailing characters";
+ return false;
+ }
+
+ return true;
+}
+
+void SdpImageattrAttributeList::Imageattr::Serialize(std::ostream& os) const {
+ if (pt.isSome()) {
+ os << *pt;
+ } else {
+ os << "*";
+ }
+
+ if (sendAll) {
+ os << " send *";
+ } else if (!sendSets.empty()) {
+ os << " send";
+ for (auto& set : sendSets) {
+ os << " ";
+ set.Serialize(os);
+ }
+ }
+
+ if (recvAll) {
+ os << " recv *";
+ } else if (!recvSets.empty()) {
+ os << " recv";
+ for (auto& set : recvSets) {
+ os << " ";
+ set.Serialize(os);
+ }
+ }
+}
+
+void SdpImageattrAttributeList::Serialize(std::ostream& os) const {
+ for (auto& imageattr : mImageattrs) {
+ os << "a=" << mType << ":";
+ imageattr.Serialize(os);
+ os << CRLF;
+ }
+}
+
+bool SdpImageattrAttributeList::PushEntry(const std::string& raw,
+ std::string* error,
+ size_t* errorPos) {
+ std::istringstream is(raw);
+
+ Imageattr imageattr;
+ if (!imageattr.Parse(is, error)) {
+ is.clear();
+ *errorPos = is.tellg();
+ return false;
+ }
+
+ mImageattrs.push_back(imageattr);
+ return true;
+}
+
+void SdpMsidAttributeList::Serialize(std::ostream& os) const {
+ for (auto i = mMsids.begin(); i != mMsids.end(); ++i) {
+ os << "a=" << mType << ":" << i->identifier;
+ if (i->appdata.length()) {
+ os << " " << i->appdata;
+ }
+ os << CRLF;
+ }
+}
+
+void SdpMsidSemanticAttributeList::Serialize(std::ostream& os) const {
+ for (auto i = mMsidSemantics.begin(); i != mMsidSemantics.end(); ++i) {
+ os << "a=" << mType << ":" << i->semantic;
+ for (auto j = i->msids.begin(); j != i->msids.end(); ++j) {
+ os << " " << *j;
+ }
+ os << CRLF;
+ }
+}
+
+void SdpRemoteCandidatesAttribute::Serialize(std::ostream& os) const {
+ if (mCandidates.empty()) {
+ return;
+ }
+
+ os << "a=" << mType;
+ for (auto i = mCandidates.begin(); i != mCandidates.end(); i++) {
+ os << (i == mCandidates.begin() ? ":" : " ") << i->id << " " << i->address
+ << " " << i->port;
+ }
+ os << CRLF;
+}
+
+// Remove this function. See Bug 1469702
+bool SdpRidAttributeList::Rid::ParseParameters(std::istream& is,
+ std::string* error) {
+ if (!PeekChar(is, error)) {
+ // No parameters
+ return true;
+ }
+
+ do {
+ is >> std::ws;
+ std::string key = ParseKey(is, error);
+ if (key.empty()) {
+ return false; // Illegal trailing cruft
+ }
+
+ // This allows pt= to appear anywhere, instead of only at the beginning, but
+ // this ends up being significantly less code.
+ if (key == "pt") {
+ if (!ParseFormats(is, error)) {
+ return false;
+ }
+ } else if (key == "max-width") {
+ if (!GetUnsigned<uint32_t>(is, 0, UINT32_MAX, &constraints.maxWidth,
+ error)) {
+ return false;
+ }
+ } else if (key == "max-height") {
+ if (!GetUnsigned<uint32_t>(is, 0, UINT32_MAX, &constraints.maxHeight,
+ error)) {
+ return false;
+ }
+ } else if (key == "max-fps") {
+ uint32_t maxFps;
+ if (!GetUnsigned<uint32_t>(is, 0, UINT32_MAX, &maxFps, error)) {
+ return false;
+ }
+ constraints.maxFps = Some(maxFps);
+ } else if (key == "max-fs") {
+ if (!GetUnsigned<uint32_t>(is, 0, UINT32_MAX, &constraints.maxFs,
+ error)) {
+ return false;
+ }
+ } else if (key == "max-br") {
+ if (!GetUnsigned<uint32_t>(is, 0, UINT32_MAX, &constraints.maxBr,
+ error)) {
+ return false;
+ }
+ } else if (key == "max-pps") {
+ if (!GetUnsigned<uint32_t>(is, 0, UINT32_MAX, &constraints.maxPps,
+ error)) {
+ return false;
+ }
+ } else if (key == "depend") {
+ if (!ParseDepend(is, error)) {
+ return false;
+ }
+ } else {
+ (void)ParseToken(is, ";", error);
+ }
+ } while (SkipChar(is, ';', error));
+ return true;
+}
+
+// Remove this function. See Bug 1469702
+bool SdpRidAttributeList::Rid::ParseDepend(std::istream& is,
+ std::string* error) {
+ do {
+ std::string id = ParseToken(is, ",;", error);
+ if (id.empty()) {
+ return false;
+ }
+ dependIds.push_back(id);
+ } while (SkipChar(is, ',', error));
+
+ return true;
+}
+
+// Remove this function. See Bug 1469702
+bool SdpRidAttributeList::Rid::ParseFormats(std::istream& is,
+ std::string* error) {
+ do {
+ uint16_t fmt;
+ if (!GetUnsigned<uint16_t>(is, 0, 127, &fmt, error)) {
+ return false;
+ }
+ formats.push_back(fmt);
+ } while (SkipChar(is, ',', error));
+
+ return true;
+}
+
+void SdpRidAttributeList::Rid::SerializeParameters(std::ostream& os) const {
+ if (!HasParameters()) {
+ return;
+ }
+
+ os << " ";
+
+ SkipFirstDelimiter semic(";");
+
+ if (!formats.empty()) {
+ os << semic << "pt=";
+ SkipFirstDelimiter comma(",");
+ for (uint16_t fmt : formats) {
+ os << comma << fmt;
+ }
+ }
+
+ if (constraints.maxWidth) {
+ os << semic << "max-width=" << constraints.maxWidth;
+ }
+
+ if (constraints.maxHeight) {
+ os << semic << "max-height=" << constraints.maxHeight;
+ }
+
+ if (constraints.maxFps) {
+ os << semic << "max-fps=" << constraints.maxFps;
+ }
+
+ if (constraints.maxFs) {
+ os << semic << "max-fs=" << constraints.maxFs;
+ }
+
+ if (constraints.maxBr) {
+ os << semic << "max-br=" << constraints.maxBr;
+ }
+
+ if (constraints.maxPps) {
+ os << semic << "max-pps=" << constraints.maxPps;
+ }
+
+ if (!dependIds.empty()) {
+ os << semic << "depend=";
+ SkipFirstDelimiter comma(",");
+ for (const std::string& id : dependIds) {
+ os << comma << id;
+ }
+ }
+}
+
+// Remove this function. See Bug 1469702
+bool SdpRidAttributeList::Rid::Parse(std::istream& is, std::string* error) {
+ id = ParseToken(is, " ", error);
+ if (!CheckRidValidity(id, error)) {
+ return false;
+ }
+
+ is >> std::ws;
+ std::string directionToken = ParseToken(is, " ", error);
+ if (directionToken == "send") {
+ direction = sdp::kSend;
+ } else if (directionToken == "recv") {
+ direction = sdp::kRecv;
+ } else {
+ *error = "Invalid direction, must be either send or recv";
+ return false;
+ }
+
+ return ParseParameters(is, error);
+}
+
+static std::bitset<256> GetAllowedRidCharacters() {
+ // From RFC 8851:
+ // rid-id = 1*(alpha-numeric / "-" / "_")
+ std::bitset<256> result;
+ for (unsigned char c = 'a'; c <= 'z'; ++c) {
+ result.set(c);
+ }
+ for (unsigned char c = 'A'; c <= 'Z'; ++c) {
+ result.set(c);
+ }
+ for (unsigned char c = '0'; c <= '9'; ++c) {
+ result.set(c);
+ }
+ // NOTE: RFC 8851 says these are allowed, but RFC 8852 says they are not
+ // https://www.rfc-editor.org/errata/eid7132
+ // result.set('-');
+ // result.set('_');
+ return result;
+}
+
+/* static */
+bool SdpRidAttributeList::CheckRidValidity(const std::string& aRid,
+ std::string* aError) {
+ if (aRid.empty()) {
+ *aError = "Rid must be non-empty (according to RFC 8851)";
+ return false;
+ }
+
+ // We need to check against a maximum length, but that is nowhere
+ // specified in webrtc-pc right now.
+ if (aRid.size() > 255) {
+ *aError = "Rid can be at most 255 characters long (according to RFC 8852)";
+ return false;
+ }
+
+ // TODO: Right now, if the rid is longer than kMaxRidLength, we don't treat it
+ // as a parse error, since the grammar does not have this restriction.
+ // Instead, our JSEP code ignores rids that exceed this limit. However, there
+ // is a possibility that the IETF grammar (in RFC 8852) will change the limit
+ // from 255 to 16, in which case we will need to revise this code.
+
+ static const std::bitset<256> allowed = GetAllowedRidCharacters();
+ for (unsigned char c : aRid) {
+ if (!allowed[c]) {
+ *aError =
+ "Rid can contain only alphanumeric characters (according to RFC "
+ "8852)";
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// This can be overridden if necessary
+size_t SdpRidAttributeList::kMaxRidLength = 255;
+
+void SdpRidAttributeList::Rid::Serialize(std::ostream& os) const {
+ os << id << " " << direction;
+ SerializeParameters(os);
+}
+
+bool SdpRidAttributeList::Rid::HasFormat(const std::string& format) const {
+ if (formats.empty()) {
+ return true;
+ }
+
+ uint16_t formatAsInt;
+ if (!SdpHelper::GetPtAsInt(format, &formatAsInt)) {
+ return false;
+ }
+
+ return (std::find(formats.begin(), formats.end(), formatAsInt) !=
+ formats.end());
+}
+
+void SdpRidAttributeList::Serialize(std::ostream& os) const {
+ for (const Rid& rid : mRids) {
+ os << "a=" << mType << ":";
+ rid.Serialize(os);
+ os << CRLF;
+ }
+}
+
+// Remove this function. See Bug 1469702
+bool SdpRidAttributeList::PushEntry(const std::string& raw, std::string* error,
+ size_t* errorPos) {
+ std::istringstream is(raw);
+
+ Rid rid;
+ if (!rid.Parse(is, error)) {
+ is.clear();
+ *errorPos = is.tellg();
+ return false;
+ }
+
+ mRids.push_back(rid);
+ return true;
+}
+
+void SdpRidAttributeList::PushEntry(const std::string& id, sdp::Direction dir,
+ const std::vector<uint16_t>& formats,
+ const EncodingConstraints& constraints,
+ const std::vector<std::string>& dependIds) {
+ SdpRidAttributeList::Rid rid;
+
+ rid.id = id;
+ rid.direction = dir;
+ rid.formats = formats;
+ rid.constraints = constraints;
+ rid.dependIds = dependIds;
+
+ mRids.push_back(std::move(rid));
+}
+
+void SdpRtcpAttribute::Serialize(std::ostream& os) const {
+ os << "a=" << mType << ":" << mPort;
+ if (!mAddress.empty()) {
+ os << " " << mNetType << " " << mAddrType << " " << mAddress;
+ }
+ os << CRLF;
+}
+
+const char* SdpRtcpFbAttributeList::pli = "pli";
+const char* SdpRtcpFbAttributeList::sli = "sli";
+const char* SdpRtcpFbAttributeList::rpsi = "rpsi";
+const char* SdpRtcpFbAttributeList::app = "app";
+
+const char* SdpRtcpFbAttributeList::fir = "fir";
+const char* SdpRtcpFbAttributeList::tmmbr = "tmmbr";
+const char* SdpRtcpFbAttributeList::tstr = "tstr";
+const char* SdpRtcpFbAttributeList::vbcm = "vbcm";
+
+void SdpRtcpFbAttributeList::Serialize(std::ostream& os) const {
+ for (auto i = mFeedbacks.begin(); i != mFeedbacks.end(); ++i) {
+ os << "a=" << mType << ":" << i->pt << " " << i->type;
+ if (i->parameter.length()) {
+ os << " " << i->parameter;
+ if (i->extra.length()) {
+ os << " " << i->extra;
+ }
+ }
+ os << CRLF;
+ }
+}
+
+static bool ShouldSerializeChannels(SdpRtpmapAttributeList::CodecType type) {
+ switch (type) {
+ case SdpRtpmapAttributeList::kOpus:
+ case SdpRtpmapAttributeList::kG722:
+ return true;
+ case SdpRtpmapAttributeList::kPCMU:
+ case SdpRtpmapAttributeList::kPCMA:
+ case SdpRtpmapAttributeList::kVP8:
+ case SdpRtpmapAttributeList::kVP9:
+ case SdpRtpmapAttributeList::kiLBC:
+ case SdpRtpmapAttributeList::kiSAC:
+ case SdpRtpmapAttributeList::kH264:
+ case SdpRtpmapAttributeList::kRed:
+ case SdpRtpmapAttributeList::kUlpfec:
+ case SdpRtpmapAttributeList::kTelephoneEvent:
+ case SdpRtpmapAttributeList::kRtx:
+ return false;
+ case SdpRtpmapAttributeList::kOtherCodec:
+ return true;
+ }
+ MOZ_CRASH();
+}
+
+void SdpRtpmapAttributeList::Serialize(std::ostream& os) const {
+ for (auto i = mRtpmaps.begin(); i != mRtpmaps.end(); ++i) {
+ os << "a=" << mType << ":" << i->pt << " " << i->name << "/" << i->clock;
+ if (i->channels && ShouldSerializeChannels(i->codec)) {
+ os << "/" << i->channels;
+ }
+ os << CRLF;
+ }
+}
+
+void SdpSctpmapAttributeList::Serialize(std::ostream& os) const {
+ for (auto i = mSctpmaps.begin(); i != mSctpmaps.end(); ++i) {
+ os << "a=" << mType << ":" << i->pt << " " << i->name << " " << i->streams
+ << CRLF;
+ }
+}
+
+void SdpSetupAttribute::Serialize(std::ostream& os) const {
+ os << "a=" << mType << ":" << mRole << CRLF;
+}
+
+void SdpSimulcastAttribute::Version::Serialize(std::ostream& os) const {
+ SkipFirstDelimiter comma(",");
+ for (const Encoding& choice : choices) {
+ os << comma;
+ if (choice.paused) {
+ os << '~';
+ }
+ os << choice.rid;
+ }
+}
+
+bool SdpSimulcastAttribute::Version::Parse(std::istream& is,
+ std::string* error) {
+ do {
+ bool paused = SkipChar(is, '~', error);
+ std::string value = ParseToken(is, ",; ", error);
+ if (value.empty()) {
+ *error = "Missing rid";
+ return false;
+ }
+ if (!SdpRidAttributeList::CheckRidValidity(value, error)) {
+ return false;
+ }
+ choices.push_back(Encoding(value, paused));
+ } while (SkipChar(is, ',', error));
+
+ return true;
+}
+
+void SdpSimulcastAttribute::Versions::Serialize(std::ostream& os) const {
+ SkipFirstDelimiter semic(";");
+ for (const Version& version : *this) {
+ if (!version.IsSet()) {
+ continue;
+ }
+ os << semic;
+ version.Serialize(os);
+ }
+}
+
+bool SdpSimulcastAttribute::Versions::Parse(std::istream& is,
+ std::string* error) {
+ do {
+ Version version;
+ if (!version.Parse(is, error)) {
+ return false;
+ }
+ push_back(version);
+ } while (SkipChar(is, ';', error));
+
+ return true;
+}
+
+void SdpSimulcastAttribute::Serialize(std::ostream& os) const {
+ MOZ_ASSERT(sendVersions.IsSet() || recvVersions.IsSet());
+
+ os << "a=" << mType << ":";
+
+ if (sendVersions.IsSet()) {
+ os << "send ";
+ sendVersions.Serialize(os);
+ }
+
+ if (recvVersions.IsSet()) {
+ if (sendVersions.IsSet()) {
+ os << " ";
+ }
+ os << "recv ";
+ recvVersions.Serialize(os);
+ }
+
+ os << CRLF;
+}
+
+bool SdpSimulcastAttribute::Parse(std::istream& is, std::string* error) {
+ bool gotRecv = false;
+ bool gotSend = false;
+
+ while (true) {
+ is >> std::ws;
+ std::string token = ParseToken(is, " \t", error);
+ if (token.empty()) {
+ break;
+ }
+
+ if (token == "send") {
+ if (gotSend) {
+ *error = "Already got a send list";
+ return false;
+ }
+ gotSend = true;
+
+ is >> std::ws;
+ if (!sendVersions.Parse(is, error)) {
+ return false;
+ }
+ } else if (token == "recv") {
+ if (gotRecv) {
+ *error = "Already got a recv list";
+ return false;
+ }
+ gotRecv = true;
+
+ is >> std::ws;
+ if (!recvVersions.Parse(is, error)) {
+ return false;
+ }
+ } else {
+ *error = "Type must be either 'send' or 'recv'";
+ return false;
+ }
+ }
+
+ if (!gotSend && !gotRecv) {
+ *error = "Empty simulcast attribute";
+ return false;
+ }
+
+ return true;
+}
+
+void SdpSsrcAttributeList::Serialize(std::ostream& os) const {
+ for (auto i = mSsrcs.begin(); i != mSsrcs.end(); ++i) {
+ os << "a=" << mType << ":" << i->ssrc << " " << i->attribute << CRLF;
+ }
+}
+
+void SdpSsrcGroupAttributeList::Serialize(std::ostream& os) const {
+ for (auto i = mSsrcGroups.begin(); i != mSsrcGroups.end(); ++i) {
+ os << "a=" << mType << ":" << i->semantics;
+ for (auto j = i->ssrcs.begin(); j != i->ssrcs.end(); ++j) {
+ os << " " << (*j);
+ }
+ os << CRLF;
+ }
+}
+
+void SdpMultiStringAttribute::Serialize(std::ostream& os) const {
+ for (auto i = mValues.begin(); i != mValues.end(); ++i) {
+ os << "a=" << mType << ":" << *i << CRLF;
+ }
+}
+
+void SdpOptionsAttribute::Serialize(std::ostream& os) const {
+ if (mValues.empty()) {
+ return;
+ }
+
+ os << "a=" << mType << ":";
+
+ for (auto i = mValues.begin(); i != mValues.end(); ++i) {
+ if (i != mValues.begin()) {
+ os << " ";
+ }
+ os << *i;
+ }
+ os << CRLF;
+}
+
+void SdpOptionsAttribute::Load(const std::string& value) {
+ size_t start = 0;
+ size_t end = value.find(' ');
+ while (end != std::string::npos) {
+ PushEntry(value.substr(start, end));
+ start = end + 1;
+ end = value.find(' ', start);
+ }
+ PushEntry(value.substr(start));
+}
+
+void SdpFlagAttribute::Serialize(std::ostream& os) const {
+ os << "a=" << mType << CRLF;
+}
+
+void SdpStringAttribute::Serialize(std::ostream& os) const {
+ os << "a=" << mType << ":" << mValue << CRLF;
+}
+
+void SdpNumberAttribute::Serialize(std::ostream& os) const {
+ os << "a=" << mType << ":" << mValue << CRLF;
+}
+
+bool SdpAttribute::IsAllowedAtMediaLevel(AttributeType type) {
+ switch (type) {
+ case kBundleOnlyAttribute:
+ return true;
+ case kCandidateAttribute:
+ return true;
+ case kConnectionAttribute:
+ return true;
+ case kDirectionAttribute:
+ return true;
+ case kDtlsMessageAttribute:
+ return false;
+ case kEndOfCandidatesAttribute:
+ return true;
+ case kExtmapAttribute:
+ return true;
+ case kFingerprintAttribute:
+ return true;
+ case kFmtpAttribute:
+ return true;
+ case kGroupAttribute:
+ return false;
+ case kIceLiteAttribute:
+ return false;
+ case kIceMismatchAttribute:
+ return true;
+ // RFC 5245 says this is session-level only, but
+ // draft-ietf-mmusic-ice-sip-sdp-03 updates this to allow at the media
+ // level.
+ case kIceOptionsAttribute:
+ return true;
+ case kIcePwdAttribute:
+ return true;
+ case kIceUfragAttribute:
+ return true;
+ case kIdentityAttribute:
+ return false;
+ case kImageattrAttribute:
+ return true;
+ case kLabelAttribute:
+ return true;
+ case kMaxptimeAttribute:
+ return true;
+ case kMidAttribute:
+ return true;
+ case kMsidAttribute:
+ return true;
+ case kMsidSemanticAttribute:
+ return false;
+ case kPtimeAttribute:
+ return true;
+ case kRemoteCandidatesAttribute:
+ return true;
+ case kRidAttribute:
+ return true;
+ case kRtcpAttribute:
+ return true;
+ case kRtcpFbAttribute:
+ return true;
+ case kRtcpMuxAttribute:
+ return true;
+ case kRtcpRsizeAttribute:
+ return true;
+ case kRtpmapAttribute:
+ return true;
+ case kSctpmapAttribute:
+ return true;
+ case kSetupAttribute:
+ return true;
+ case kSimulcastAttribute:
+ return true;
+ case kSsrcAttribute:
+ return true;
+ case kSsrcGroupAttribute:
+ return true;
+ case kSctpPortAttribute:
+ return true;
+ case kMaxMessageSizeAttribute:
+ return true;
+ }
+ MOZ_CRASH("Unknown attribute type");
+}
+
+bool SdpAttribute::IsAllowedAtSessionLevel(AttributeType type) {
+ switch (type) {
+ case kBundleOnlyAttribute:
+ return false;
+ case kCandidateAttribute:
+ return false;
+ case kConnectionAttribute:
+ return true;
+ case kDirectionAttribute:
+ return true;
+ case kDtlsMessageAttribute:
+ return true;
+ case kEndOfCandidatesAttribute:
+ return true;
+ case kExtmapAttribute:
+ return true;
+ case kFingerprintAttribute:
+ return true;
+ case kFmtpAttribute:
+ return false;
+ case kGroupAttribute:
+ return true;
+ case kIceLiteAttribute:
+ return true;
+ case kIceMismatchAttribute:
+ return false;
+ case kIceOptionsAttribute:
+ return true;
+ case kIcePwdAttribute:
+ return true;
+ case kIceUfragAttribute:
+ return true;
+ case kIdentityAttribute:
+ return true;
+ case kImageattrAttribute:
+ return false;
+ case kLabelAttribute:
+ return false;
+ case kMaxptimeAttribute:
+ return false;
+ case kMidAttribute:
+ return false;
+ case kMsidSemanticAttribute:
+ return true;
+ case kMsidAttribute:
+ return false;
+ case kPtimeAttribute:
+ return false;
+ case kRemoteCandidatesAttribute:
+ return false;
+ case kRidAttribute:
+ return false;
+ case kRtcpAttribute:
+ return false;
+ case kRtcpFbAttribute:
+ return false;
+ case kRtcpMuxAttribute:
+ return false;
+ case kRtcpRsizeAttribute:
+ return false;
+ case kRtpmapAttribute:
+ return false;
+ case kSctpmapAttribute:
+ return false;
+ case kSetupAttribute:
+ return true;
+ case kSimulcastAttribute:
+ return false;
+ case kSsrcAttribute:
+ return false;
+ case kSsrcGroupAttribute:
+ return false;
+ case kSctpPortAttribute:
+ return false;
+ case kMaxMessageSizeAttribute:
+ return false;
+ }
+ MOZ_CRASH("Unknown attribute type");
+}
+
+const std::string SdpAttribute::GetAttributeTypeString(AttributeType type) {
+ switch (type) {
+ case kBundleOnlyAttribute:
+ return "bundle-only";
+ case kCandidateAttribute:
+ return "candidate";
+ case kConnectionAttribute:
+ return "connection";
+ case kDtlsMessageAttribute:
+ return "dtls-message";
+ case kEndOfCandidatesAttribute:
+ return "end-of-candidates";
+ case kExtmapAttribute:
+ return "extmap";
+ case kFingerprintAttribute:
+ return "fingerprint";
+ case kFmtpAttribute:
+ return "fmtp";
+ case kGroupAttribute:
+ return "group";
+ case kIceLiteAttribute:
+ return "ice-lite";
+ case kIceMismatchAttribute:
+ return "ice-mismatch";
+ case kIceOptionsAttribute:
+ return "ice-options";
+ case kIcePwdAttribute:
+ return "ice-pwd";
+ case kIceUfragAttribute:
+ return "ice-ufrag";
+ case kIdentityAttribute:
+ return "identity";
+ case kImageattrAttribute:
+ return "imageattr";
+ case kLabelAttribute:
+ return "label";
+ case kMaxptimeAttribute:
+ return "maxptime";
+ case kMidAttribute:
+ return "mid";
+ case kMsidAttribute:
+ return "msid";
+ case kMsidSemanticAttribute:
+ return "msid-semantic";
+ case kPtimeAttribute:
+ return "ptime";
+ case kRemoteCandidatesAttribute:
+ return "remote-candidates";
+ case kRidAttribute:
+ return "rid";
+ case kRtcpAttribute:
+ return "rtcp";
+ case kRtcpFbAttribute:
+ return "rtcp-fb";
+ case kRtcpMuxAttribute:
+ return "rtcp-mux";
+ case kRtcpRsizeAttribute:
+ return "rtcp-rsize";
+ case kRtpmapAttribute:
+ return "rtpmap";
+ case kSctpmapAttribute:
+ return "sctpmap";
+ case kSetupAttribute:
+ return "setup";
+ case kSimulcastAttribute:
+ return "simulcast";
+ case kSsrcAttribute:
+ return "ssrc";
+ case kSsrcGroupAttribute:
+ return "ssrc-group";
+ case kSctpPortAttribute:
+ return "sctp-port";
+ case kMaxMessageSizeAttribute:
+ return "max-message-size";
+ case kDirectionAttribute:
+ MOZ_CRASH("kDirectionAttribute not valid here");
+ }
+ MOZ_CRASH("Unknown attribute type");
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/sdp/SdpAttribute.h b/dom/media/webrtc/sdp/SdpAttribute.h
new file mode 100644
index 0000000000..6af497ac18
--- /dev/null
+++ b/dom/media/webrtc/sdp/SdpAttribute.h
@@ -0,0 +1,1907 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _SDPATTRIBUTE_H_
+#define _SDPATTRIBUTE_H_
+
+#include <algorithm>
+#include <cctype>
+#include <vector>
+#include <ostream>
+#include <sstream>
+#include <cstring>
+#include <iomanip>
+#include <string>
+
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Maybe.h"
+
+#include "sdp/SdpEnum.h"
+#include "common/EncodingConstraints.h"
+
+namespace mozilla {
+
+/**
+ * Base class for SDP attributes
+ */
+class SdpAttribute {
+ public:
+ enum AttributeType {
+ kFirstAttribute = 0,
+ kBundleOnlyAttribute = 0,
+ kCandidateAttribute,
+ kConnectionAttribute,
+ kDirectionAttribute,
+ kDtlsMessageAttribute,
+ kEndOfCandidatesAttribute,
+ kExtmapAttribute,
+ kFingerprintAttribute,
+ kFmtpAttribute,
+ kGroupAttribute,
+ kIceLiteAttribute,
+ kIceMismatchAttribute,
+ kIceOptionsAttribute,
+ kIcePwdAttribute,
+ kIceUfragAttribute,
+ kIdentityAttribute,
+ kImageattrAttribute,
+ kLabelAttribute,
+ kMaxptimeAttribute,
+ kMidAttribute,
+ kMsidAttribute,
+ kMsidSemanticAttribute,
+ kPtimeAttribute,
+ kRemoteCandidatesAttribute,
+ kRidAttribute,
+ kRtcpAttribute,
+ kRtcpFbAttribute,
+ kRtcpMuxAttribute,
+ kRtcpRsizeAttribute,
+ kRtpmapAttribute,
+ kSctpmapAttribute,
+ kSetupAttribute,
+ kSimulcastAttribute,
+ kSsrcAttribute,
+ kSsrcGroupAttribute,
+ kSctpPortAttribute,
+ kMaxMessageSizeAttribute,
+ kLastAttribute = kMaxMessageSizeAttribute
+ };
+
+ explicit SdpAttribute(AttributeType type) : mType(type) {}
+ virtual ~SdpAttribute() {}
+
+ virtual SdpAttribute* Clone() const = 0;
+
+ AttributeType GetType() const { return mType; }
+
+ virtual void Serialize(std::ostream&) const = 0;
+
+ static bool IsAllowedAtSessionLevel(AttributeType type);
+ static bool IsAllowedAtMediaLevel(AttributeType type);
+ static const std::string GetAttributeTypeString(AttributeType type);
+
+ protected:
+ AttributeType mType;
+};
+
+inline std::ostream& operator<<(std::ostream& os, const SdpAttribute& attr) {
+ attr.Serialize(os);
+ return os;
+}
+
+inline std::ostream& operator<<(std::ostream& os,
+ const SdpAttribute::AttributeType type) {
+ os << SdpAttribute::GetAttributeTypeString(type);
+ return os;
+}
+
+///////////////////////////////////////////////////////////////////////////
+// a=candidate, RFC5245
+//-------------------------------------------------------------------------
+//
+// candidate-attribute = "candidate" ":" foundation SP component-id SP
+// transport SP
+// priority SP
+// connection-address SP ;from RFC 4566
+// port ;port from RFC 4566
+// SP cand-type
+// [SP rel-addr]
+// [SP rel-port]
+// *(SP extension-att-name SP
+// extension-att-value)
+// foundation = 1*32ice-char
+// component-id = 1*5DIGIT
+// transport = "UDP" / transport-extension
+// transport-extension = token ; from RFC 3261
+// priority = 1*10DIGIT
+// cand-type = "typ" SP candidate-types
+// candidate-types = "host" / "srflx" / "prflx" / "relay" / token
+// rel-addr = "raddr" SP connection-address
+// rel-port = "rport" SP port
+// extension-att-name = byte-string ;from RFC 4566
+// extension-att-value = byte-string
+// ice-char = ALPHA / DIGIT / "+" / "/"
+
+// We use a SdpMultiStringAttribute for candidates
+
+///////////////////////////////////////////////////////////////////////////
+// a=connection, RFC4145
+//-------------------------------------------------------------------------
+// connection-attr = "a=connection:" conn-value
+// conn-value = "new" / "existing"
+class SdpConnectionAttribute : public SdpAttribute {
+ public:
+ enum ConnValue { kNew, kExisting };
+
+ explicit SdpConnectionAttribute(SdpConnectionAttribute::ConnValue value)
+ : SdpAttribute(kConnectionAttribute), mValue(value) {}
+
+ SdpAttribute* Clone() const override {
+ return new SdpConnectionAttribute(*this);
+ }
+
+ virtual void Serialize(std::ostream& os) const override;
+
+ ConnValue mValue;
+};
+
+inline std::ostream& operator<<(std::ostream& os,
+ SdpConnectionAttribute::ConnValue c) {
+ switch (c) {
+ case SdpConnectionAttribute::kNew:
+ os << "new";
+ break;
+ case SdpConnectionAttribute::kExisting:
+ os << "existing";
+ break;
+ default:
+ MOZ_ASSERT(false);
+ os << "?";
+ }
+ return os;
+}
+
+///////////////////////////////////////////////////////////////////////////
+// a=sendrecv / a=sendonly / a=recvonly / a=inactive, RFC 4566
+//-------------------------------------------------------------------------
+class SdpDirectionAttribute : public SdpAttribute {
+ public:
+ enum Direction {
+ kInactive = 0,
+ kSendonly = sdp::kSend,
+ kRecvonly = sdp::kRecv,
+ kSendrecv = sdp::kSend | sdp::kRecv
+ };
+
+ explicit SdpDirectionAttribute(Direction value)
+ : SdpAttribute(kDirectionAttribute), mValue(value) {}
+
+ SdpAttribute* Clone() const override {
+ return new SdpDirectionAttribute(*this);
+ }
+
+ virtual void Serialize(std::ostream& os) const override;
+
+ Direction mValue;
+};
+
+inline std::ostream& operator<<(std::ostream& os,
+ SdpDirectionAttribute::Direction d) {
+ switch (d) {
+ case SdpDirectionAttribute::kSendonly:
+ os << "sendonly";
+ break;
+ case SdpDirectionAttribute::kRecvonly:
+ os << "recvonly";
+ break;
+ case SdpDirectionAttribute::kSendrecv:
+ os << "sendrecv";
+ break;
+ case SdpDirectionAttribute::kInactive:
+ os << "inactive";
+ break;
+ default:
+ MOZ_ASSERT(false);
+ os << "?";
+ }
+ return os;
+}
+
+inline SdpDirectionAttribute::Direction reverse(
+ SdpDirectionAttribute::Direction d) {
+ switch (d) {
+ case SdpDirectionAttribute::Direction::kInactive:
+ return SdpDirectionAttribute::Direction::kInactive;
+ case SdpDirectionAttribute::Direction::kSendonly:
+ return SdpDirectionAttribute::Direction::kRecvonly;
+ case SdpDirectionAttribute::Direction::kRecvonly:
+ return SdpDirectionAttribute::Direction::kSendonly;
+ case SdpDirectionAttribute::Direction::kSendrecv:
+ return SdpDirectionAttribute::Direction::kSendrecv;
+ }
+ MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Invalid direction!");
+ MOZ_RELEASE_ASSERT(false);
+}
+
+inline SdpDirectionAttribute::Direction operator|(
+ SdpDirectionAttribute::Direction d1, SdpDirectionAttribute::Direction d2) {
+ return (SdpDirectionAttribute::Direction)((unsigned)d1 | (unsigned)d2);
+}
+
+inline SdpDirectionAttribute::Direction operator&(
+ SdpDirectionAttribute::Direction d1, SdpDirectionAttribute::Direction d2) {
+ return (SdpDirectionAttribute::Direction)((unsigned)d1 & (unsigned)d2);
+}
+
+inline SdpDirectionAttribute::Direction operator|=(
+ SdpDirectionAttribute::Direction& d1, SdpDirectionAttribute::Direction d2) {
+ d1 = d1 | d2;
+ return d1;
+}
+
+inline SdpDirectionAttribute::Direction operator&=(
+ SdpDirectionAttribute::Direction& d1, SdpDirectionAttribute::Direction d2) {
+ d1 = d1 & d2;
+ return d1;
+}
+
+///////////////////////////////////////////////////////////////////////////
+// a=dtls-message, draft-rescorla-dtls-in-sdp
+//-------------------------------------------------------------------------
+// attribute =/ dtls-message-attribute
+//
+// dtls-message-attribute = "dtls-message" ":" role SP value
+//
+// role = "client" / "server"
+//
+// value = 1*(ALPHA / DIGIT / "+" / "/" / "=" )
+// ; base64 encoded message
+class SdpDtlsMessageAttribute : public SdpAttribute {
+ public:
+ enum Role { kClient, kServer };
+
+ explicit SdpDtlsMessageAttribute(Role role, const std::string& value)
+ : SdpAttribute(kDtlsMessageAttribute), mRole(role), mValue(value) {}
+
+ // TODO: remove this, Bug 1469702
+ explicit SdpDtlsMessageAttribute(const std::string& unparsed)
+ : SdpAttribute(kDtlsMessageAttribute), mRole(kClient) {
+ std::istringstream is(unparsed);
+ std::string error;
+ // We're not really worried about errors here if we don't parse;
+ // this attribute is a pure optimization.
+ Parse(is, &error);
+ }
+
+ SdpAttribute* Clone() const override {
+ return new SdpDtlsMessageAttribute(*this);
+ }
+
+ virtual void Serialize(std::ostream& os) const override;
+
+ // TODO: remove this, Bug 1469702
+ bool Parse(std::istream& is, std::string* error);
+
+ Role mRole;
+ std::string mValue;
+};
+
+inline std::ostream& operator<<(std::ostream& os,
+ SdpDtlsMessageAttribute::Role r) {
+ switch (r) {
+ case SdpDtlsMessageAttribute::kClient:
+ os << "client";
+ break;
+ case SdpDtlsMessageAttribute::kServer:
+ os << "server";
+ break;
+ default:
+ MOZ_ASSERT(false);
+ os << "?";
+ }
+ return os;
+}
+
+///////////////////////////////////////////////////////////////////////////
+// a=extmap, RFC5285
+//-------------------------------------------------------------------------
+// RFC5285
+// extmap = mapentry SP extensionname [SP extensionattributes]
+//
+// extensionname = URI
+//
+// direction = "sendonly" / "recvonly" / "sendrecv" / "inactive"
+//
+// mapentry = "extmap:" 1*5DIGIT ["/" direction]
+//
+// extensionattributes = byte-string
+//
+// URI = <Defined in RFC 3986>
+//
+// byte-string = <Defined in RFC 4566>
+//
+// SP = <Defined in RFC 5234>
+//
+// DIGIT = <Defined in RFC 5234>
+class SdpExtmapAttributeList : public SdpAttribute {
+ public:
+ SdpExtmapAttributeList() : SdpAttribute(kExtmapAttribute) {}
+
+ struct Extmap {
+ uint16_t entry;
+ SdpDirectionAttribute::Direction direction;
+ bool direction_specified;
+ std::string extensionname;
+ std::string extensionattributes;
+ };
+
+ void PushEntry(uint16_t entry, SdpDirectionAttribute::Direction direction,
+ bool direction_specified, const std::string& extensionname,
+ const std::string& extensionattributes = "") {
+ Extmap value = {entry, direction, direction_specified, extensionname,
+ extensionattributes};
+ mExtmaps.push_back(value);
+ }
+
+ SdpAttribute* Clone() const override {
+ return new SdpExtmapAttributeList(*this);
+ }
+
+ virtual void Serialize(std::ostream& os) const override;
+
+ std::vector<Extmap> mExtmaps;
+};
+
+///////////////////////////////////////////////////////////////////////////
+// a=fingerprint, RFC4572
+//-------------------------------------------------------------------------
+// fingerprint-attribute = "fingerprint" ":" hash-func SP fingerprint
+//
+// hash-func = "sha-1" / "sha-224" / "sha-256" /
+// "sha-384" / "sha-512" /
+// "md5" / "md2" / token
+// ; Additional hash functions can only come
+// ; from updates to RFC 3279
+//
+// fingerprint = 2UHEX *(":" 2UHEX)
+// ; Each byte in upper-case hex, separated
+// ; by colons.
+//
+// UHEX = DIGIT / %x41-46 ; A-F uppercase
+class SdpFingerprintAttributeList : public SdpAttribute {
+ public:
+ SdpFingerprintAttributeList() : SdpAttribute(kFingerprintAttribute) {}
+
+ enum HashAlgorithm {
+ kSha1,
+ kSha224,
+ kSha256,
+ kSha384,
+ kSha512,
+ kMd5,
+ kMd2,
+ kUnknownAlgorithm
+ };
+
+ struct Fingerprint {
+ HashAlgorithm hashFunc;
+ std::vector<uint8_t> fingerprint;
+ };
+
+ // For use by application programmers. Enforces that it's a known and
+ // reasonable algorithm.
+ void PushEntry(std::string algorithm_str,
+ const std::vector<uint8_t>& fingerprint,
+ bool enforcePlausible = true) {
+ std::transform(algorithm_str.begin(), algorithm_str.end(),
+ algorithm_str.begin(), ::tolower);
+
+ SdpFingerprintAttributeList::HashAlgorithm algorithm =
+ SdpFingerprintAttributeList::kUnknownAlgorithm;
+
+ if (algorithm_str == "sha-1") {
+ algorithm = SdpFingerprintAttributeList::kSha1;
+ } else if (algorithm_str == "sha-224") {
+ algorithm = SdpFingerprintAttributeList::kSha224;
+ } else if (algorithm_str == "sha-256") {
+ algorithm = SdpFingerprintAttributeList::kSha256;
+ } else if (algorithm_str == "sha-384") {
+ algorithm = SdpFingerprintAttributeList::kSha384;
+ } else if (algorithm_str == "sha-512") {
+ algorithm = SdpFingerprintAttributeList::kSha512;
+ } else if (algorithm_str == "md5") {
+ algorithm = SdpFingerprintAttributeList::kMd5;
+ } else if (algorithm_str == "md2") {
+ algorithm = SdpFingerprintAttributeList::kMd2;
+ }
+
+ if ((algorithm == SdpFingerprintAttributeList::kUnknownAlgorithm) ||
+ fingerprint.empty()) {
+ if (enforcePlausible) {
+ MOZ_ASSERT(false, "Unknown fingerprint algorithm");
+ } else {
+ return;
+ }
+ }
+
+ PushEntry(algorithm, fingerprint);
+ }
+
+ void PushEntry(HashAlgorithm hashFunc,
+ const std::vector<uint8_t>& fingerprint) {
+ Fingerprint value = {hashFunc, fingerprint};
+ mFingerprints.push_back(value);
+ }
+
+ SdpAttribute* Clone() const override {
+ return new SdpFingerprintAttributeList(*this);
+ }
+
+ virtual void Serialize(std::ostream& os) const override;
+
+ std::vector<Fingerprint> mFingerprints;
+
+ static std::string FormatFingerprint(const std::vector<uint8_t>& fp);
+ static std::vector<uint8_t> ParseFingerprint(const std::string& str);
+};
+
+inline std::ostream& operator<<(std::ostream& os,
+ SdpFingerprintAttributeList::HashAlgorithm a) {
+ switch (a) {
+ case SdpFingerprintAttributeList::kSha1:
+ os << "sha-1";
+ break;
+ case SdpFingerprintAttributeList::kSha224:
+ os << "sha-224";
+ break;
+ case SdpFingerprintAttributeList::kSha256:
+ os << "sha-256";
+ break;
+ case SdpFingerprintAttributeList::kSha384:
+ os << "sha-384";
+ break;
+ case SdpFingerprintAttributeList::kSha512:
+ os << "sha-512";
+ break;
+ case SdpFingerprintAttributeList::kMd5:
+ os << "md5";
+ break;
+ case SdpFingerprintAttributeList::kMd2:
+ os << "md2";
+ break;
+ default:
+ MOZ_ASSERT(false);
+ os << "?";
+ }
+ return os;
+}
+
+///////////////////////////////////////////////////////////////////////////
+// a=group, RFC5888
+//-------------------------------------------------------------------------
+// group-attribute = "a=group:" semantics
+// *(SP identification-tag)
+// semantics = "LS" / "FID" / semantics-extension
+// semantics-extension = token
+// identification-tag = token
+class SdpGroupAttributeList : public SdpAttribute {
+ public:
+ SdpGroupAttributeList() : SdpAttribute(kGroupAttribute) {}
+
+ enum Semantics {
+ kLs, // RFC5888
+ kFid, // RFC5888
+ kSrf, // RFC3524
+ kAnat, // RFC4091
+ kFec, // RFC5956
+ kFecFr, // RFC5956
+ kCs, // draft-mehta-rmt-flute-sdp-05
+ kDdp, // RFC5583
+ kDup, // RFC7104
+ kBundle // draft-ietf-mmusic-bundle
+ };
+
+ struct Group {
+ Semantics semantics;
+ std::vector<std::string> tags;
+ };
+
+ void PushEntry(Semantics semantics, const std::vector<std::string>& tags) {
+ Group value = {semantics, tags};
+ mGroups.push_back(value);
+ }
+
+ void RemoveMid(const std::string& mid) {
+ for (auto i = mGroups.begin(); i != mGroups.end();) {
+ auto tag = std::find(i->tags.begin(), i->tags.end(), mid);
+ if (tag != i->tags.end()) {
+ i->tags.erase(tag);
+ }
+
+ if (i->tags.empty()) {
+ i = mGroups.erase(i);
+ } else {
+ ++i;
+ }
+ }
+ }
+
+ SdpAttribute* Clone() const override {
+ return new SdpGroupAttributeList(*this);
+ }
+
+ virtual void Serialize(std::ostream& os) const override;
+
+ std::vector<Group> mGroups;
+};
+
+inline std::ostream& operator<<(std::ostream& os,
+ SdpGroupAttributeList::Semantics s) {
+ switch (s) {
+ case SdpGroupAttributeList::kLs:
+ os << "LS";
+ break;
+ case SdpGroupAttributeList::kFid:
+ os << "FID";
+ break;
+ case SdpGroupAttributeList::kSrf:
+ os << "SRF";
+ break;
+ case SdpGroupAttributeList::kAnat:
+ os << "ANAT";
+ break;
+ case SdpGroupAttributeList::kFec:
+ os << "FEC";
+ break;
+ case SdpGroupAttributeList::kFecFr:
+ os << "FEC-FR";
+ break;
+ case SdpGroupAttributeList::kCs:
+ os << "CS";
+ break;
+ case SdpGroupAttributeList::kDdp:
+ os << "DDP";
+ break;
+ case SdpGroupAttributeList::kDup:
+ os << "DUP";
+ break;
+ case SdpGroupAttributeList::kBundle:
+ os << "BUNDLE";
+ break;
+ default:
+ MOZ_ASSERT(false);
+ os << "?";
+ }
+ return os;
+}
+
+///////////////////////////////////////////////////////////////////////////
+// a=identity, draft-ietf-rtcweb-security-arch
+//-------------------------------------------------------------------------
+// identity-attribute = "identity:" identity-assertion
+// [ SP identity-extension
+// *(";" [ SP ] identity-extension) ]
+// identity-assertion = base64
+// base64 = 1*(ALPHA / DIGIT / "+" / "/" / "=" )
+// identity-extension = extension-att-name [ "=" extension-att-value ]
+// extension-att-name = token
+// extension-att-value = 1*(%x01-09 / %x0b-0c / %x0e-3a / %x3c-ff)
+// ; byte-string from [RFC4566] omitting ";"
+
+// We're just using an SdpStringAttribute for this right now
+#if 0
+class SdpIdentityAttribute : public SdpAttribute
+{
+public:
+ explicit SdpIdentityAttribute(const std::string &assertion,
+ const std::vector<std::string> &extensions =
+ std::vector<std::string>()) :
+ SdpAttribute(kIdentityAttribute),
+ mAssertion(assertion),
+ mExtensions(extensions) {}
+
+ virtual void Serialize(std::ostream& os) const override;
+
+ std::string mAssertion;
+ std::vector<std::string> mExtensions;
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////
+// a=imageattr, RFC6236
+//-------------------------------------------------------------------------
+// image-attr = "imageattr:" PT 1*2( 1*WSP ( "send" / "recv" )
+// 1*WSP attr-list )
+// PT = 1*DIGIT / "*"
+// attr-list = ( set *(1*WSP set) ) / "*"
+// ; WSP and DIGIT defined in [RFC5234]
+//
+// set= "[" "x=" xyrange "," "y=" xyrange *( "," key-value ) "]"
+// ; x is the horizontal image size range (pixel count)
+// ; y is the vertical image size range (pixel count)
+//
+// key-value = ( "sar=" srange )
+// / ( "par=" prange )
+// / ( "q=" qvalue )
+// ; Key-value MAY be extended with other keyword
+// ; parameters.
+// ; At most, one instance each of sar, par, or q
+// ; is allowed in a set.
+// ;
+// ; sar (sample aspect ratio) is the sample aspect ratio
+// ; associated with the set (optional, MAY be ignored)
+// ; par (picture aspect ratio) is the allowed
+// ; ratio between the display's x and y physical
+// ; size (optional)
+// ; q (optional, range [0.0..1.0], default value 0.5)
+// ; is the preference for the given set,
+// ; a higher value means a higher preference
+//
+// onetonine = "1" / "2" / "3" / "4" / "5" / "6" / "7" / "8" / "9"
+// ; Digit between 1 and 9
+// xyvalue = onetonine *5DIGIT
+// ; Digit between 1 and 9 that is
+// ; followed by 0 to 5 other digits
+// step = xyvalue
+// xyrange = ( "[" xyvalue ":" [ step ":" ] xyvalue "]" )
+// ; Range between a lower and an upper value
+// ; with an optional step, default step = 1
+// ; The rightmost occurrence of xyvalue MUST have a
+// ; higher value than the leftmost occurrence.
+// / ( "[" xyvalue 1*( "," xyvalue ) "]" )
+// ; Discrete values separated by ','
+// / ( xyvalue )
+// ; A single value
+// spvalue = ( "0" "." onetonine *3DIGIT )
+// ; Values between 0.1000 and 0.9999
+// / ( onetonine "." 1*4DIGIT )
+// ; Values between 1.0000 and 9.9999
+// srange = ( "[" spvalue 1*( "," spvalue ) "]" )
+// ; Discrete values separated by ','.
+// ; Each occurrence of spvalue MUST be
+// ; greater than the previous occurrence.
+// / ( "[" spvalue "-" spvalue "]" )
+// ; Range between a lower and an upper level (inclusive)
+// ; The second occurrence of spvalue MUST have a higher
+// ; value than the first
+// / ( spvalue )
+// ; A single value
+//
+// prange = ( "[" spvalue "-" spvalue "]" )
+// ; Range between a lower and an upper level (inclusive)
+// ; The second occurrence of spvalue MUST have a higher
+// ; value than the first
+//
+// qvalue = ( "0" "." 1*2DIGIT )
+// / ( "1" "." 1*2("0") )
+// ; Values between 0.00 and 1.00
+//
+// XXX TBD -- We don't use this yet, and it's a project unto itself.
+//
+
+class SdpImageattrAttributeList : public SdpAttribute {
+ public:
+ SdpImageattrAttributeList() : SdpAttribute(kImageattrAttribute) {}
+
+ class XYRange {
+ public:
+ XYRange() : min(0), max(0), step(1) {}
+ void Serialize(std::ostream& os) const;
+ // TODO: Remove this Bug 1469702
+ bool Parse(std::istream& is, std::string* error);
+ // TODO: Remove this Bug 1469702
+ bool ParseAfterBracket(std::istream& is, std::string* error);
+ // TODO: Remove this Bug 1469702
+ bool ParseAfterMin(std::istream& is, std::string* error);
+ // TODO: Remove this Bug 1469702
+ bool ParseDiscreteValues(std::istream& is, std::string* error);
+ std::vector<uint32_t> discreteValues;
+ // min/max are used iff discreteValues is empty
+ uint32_t min;
+ uint32_t max;
+ uint32_t step;
+ };
+
+ class SRange {
+ public:
+ SRange() : min(0), max(0) {}
+ void Serialize(std::ostream& os) const;
+ // TODO: Remove this Bug 1469702
+ bool Parse(std::istream& is, std::string* error);
+ // TODO: Remove this Bug 1469702
+ bool ParseAfterBracket(std::istream& is, std::string* error);
+ // TODO: Remove this Bug 1469702
+ bool ParseAfterMin(std::istream& is, std::string* error);
+ // TODO: Remove this Bug 1469702
+ bool ParseDiscreteValues(std::istream& is, std::string* error);
+ bool IsSet() const { return !discreteValues.empty() || (min && max); }
+ std::vector<float> discreteValues;
+ // min/max are used iff discreteValues is empty
+ float min;
+ float max;
+ };
+
+ class PRange {
+ public:
+ PRange() : min(0), max(0) {}
+ void Serialize(std::ostream& os) const;
+ // TODO: Remove this Bug 1469702
+ bool Parse(std::istream& is, std::string* error);
+ bool IsSet() const { return min && max; }
+ float min;
+ float max;
+ };
+
+ class Set {
+ public:
+ Set() : qValue(-1) {}
+ void Serialize(std::ostream& os) const;
+ // TODO: Remove this Bug 1469702
+ bool Parse(std::istream& is, std::string* error);
+ XYRange xRange;
+ XYRange yRange;
+ SRange sRange;
+ PRange pRange;
+ float qValue;
+ };
+
+ class Imageattr {
+ public:
+ Imageattr() : pt(), sendAll(false), recvAll(false) {}
+ void Serialize(std::ostream& os) const;
+ // TODO: Remove this Bug 1469702
+ bool Parse(std::istream& is, std::string* error);
+ // TODO: Remove this Bug 1469702
+ bool ParseSets(std::istream& is, std::string* error);
+ // If not set, this means all payload types
+ Maybe<uint16_t> pt;
+ bool sendAll;
+ std::vector<Set> sendSets;
+ bool recvAll;
+ std::vector<Set> recvSets;
+ };
+
+ SdpAttribute* Clone() const override {
+ return new SdpImageattrAttributeList(*this);
+ }
+
+ virtual void Serialize(std::ostream& os) const override;
+
+ // TODO: Remove this Bug 1469702
+ bool PushEntry(const std::string& raw, std::string* error, size_t* errorPos);
+
+ std::vector<Imageattr> mImageattrs;
+};
+
+///////////////////////////////////////////////////////////////////////////
+// a=msid, draft-ietf-mmusic-msid
+//-------------------------------------------------------------------------
+// msid-attr = "msid:" identifier [ SP appdata ]
+// identifier = 1*64token-char ; see RFC 4566
+// appdata = 1*64token-char ; see RFC 4566
+class SdpMsidAttributeList : public SdpAttribute {
+ public:
+ SdpMsidAttributeList() : SdpAttribute(kMsidAttribute) {}
+
+ struct Msid {
+ std::string identifier;
+ std::string appdata;
+ };
+
+ void PushEntry(const std::string& identifier,
+ const std::string& appdata = "") {
+ Msid value = {identifier, appdata};
+ mMsids.push_back(value);
+ }
+
+ SdpAttribute* Clone() const override {
+ return new SdpMsidAttributeList(*this);
+ }
+
+ virtual void Serialize(std::ostream& os) const override;
+
+ std::vector<Msid> mMsids;
+};
+
+///////////////////////////////////////////////////////////////////////////
+// a=msid-semantic, draft-ietf-mmusic-msid
+//-------------------------------------------------------------------------
+// msid-semantic-attr = "msid-semantic:" msid-semantic msid-list
+// msid-semantic = token ; see RFC 4566
+// msid-list = *(" " msid-id) / " *"
+class SdpMsidSemanticAttributeList : public SdpAttribute {
+ public:
+ SdpMsidSemanticAttributeList() : SdpAttribute(kMsidSemanticAttribute) {}
+
+ struct MsidSemantic {
+ // TODO: Once we have some more of these, we might want to make an enum
+ std::string semantic;
+ std::vector<std::string> msids;
+ };
+
+ void PushEntry(const std::string& semantic,
+ const std::vector<std::string>& msids) {
+ MsidSemantic value = {semantic, msids};
+ mMsidSemantics.push_back(value);
+ }
+
+ SdpAttribute* Clone() const override {
+ return new SdpMsidSemanticAttributeList(*this);
+ }
+
+ virtual void Serialize(std::ostream& os) const override;
+
+ std::vector<MsidSemantic> mMsidSemantics;
+};
+
+///////////////////////////////////////////////////////////////////////////
+// a=remote-candiate, RFC5245
+//-------------------------------------------------------------------------
+// remote-candidate-att = "remote-candidates" ":" remote-candidate
+// 0*(SP remote-candidate)
+// remote-candidate = component-ID SP connection-address SP port
+class SdpRemoteCandidatesAttribute : public SdpAttribute {
+ public:
+ struct Candidate {
+ std::string id;
+ std::string address;
+ uint16_t port;
+ };
+
+ explicit SdpRemoteCandidatesAttribute(
+ const std::vector<Candidate>& candidates)
+ : SdpAttribute(kRemoteCandidatesAttribute), mCandidates(candidates) {}
+
+ SdpAttribute* Clone() const override {
+ return new SdpRemoteCandidatesAttribute(*this);
+ }
+
+ virtual void Serialize(std::ostream& os) const override;
+
+ std::vector<Candidate> mCandidates;
+};
+
+/*
+a=rid, draft-pthatcher-mmusic-rid-01
+
+ rid-syntax = "a=rid:" rid-identifier SP rid-dir
+ [ rid-pt-param-list / rid-param-list ]
+
+ rid-identifier = 1*(alpha-numeric / "-" / "_")
+
+ rid-dir = "send" / "recv"
+
+ rid-pt-param-list = SP rid-fmt-list *(";" rid-param)
+
+ rid-param-list = SP rid-param *(";" rid-param)
+
+ rid-fmt-list = "pt=" fmt *( "," fmt )
+ ; fmt defined in {{RFC4566}}
+
+ rid-param = rid-width-param
+ / rid-height-param
+ / rid-fps-param
+ / rid-fs-param
+ / rid-br-param
+ / rid-pps-param
+ / rid-depend-param
+ / rid-param-other
+
+ rid-width-param = "max-width" [ "=" int-param-val ]
+
+ rid-height-param = "max-height" [ "=" int-param-val ]
+
+ rid-fps-param = "max-fps" [ "=" int-param-val ]
+
+ rid-fs-param = "max-fs" [ "=" int-param-val ]
+
+ rid-br-param = "max-br" [ "=" int-param-val ]
+
+ rid-pps-param = "max-pps" [ "=" int-param-val ]
+
+ rid-depend-param = "depend=" rid-list
+
+ rid-param-other = 1*(alpha-numeric / "-") [ "=" param-val ]
+
+ rid-list = rid-identifier *( "," rid-identifier )
+
+ int-param-val = 1*DIGIT
+
+ param-val = *( %x20-58 / %x60-7E )
+ ; Any printable character except semicolon
+*/
+class SdpRidAttributeList : public SdpAttribute {
+ public:
+ explicit SdpRidAttributeList() : SdpAttribute(kRidAttribute) {}
+
+ struct Rid {
+ Rid() : direction(sdp::kSend) {}
+
+ // Remove this function. See Bug 1469702
+ bool Parse(std::istream& is, std::string* error);
+ // Remove this function. See Bug 1469702
+ bool ParseParameters(std::istream& is, std::string* error);
+ // Remove this function. See Bug 1469702
+ bool ParseDepend(std::istream& is, std::string* error);
+ // Remove this function. See Bug 1469702
+ bool ParseFormats(std::istream& is, std::string* error);
+
+ void Serialize(std::ostream& os) const;
+ void SerializeParameters(std::ostream& os) const;
+ bool HasFormat(const std::string& format) const;
+ bool HasParameters() const {
+ return !formats.empty() || constraints.maxWidth ||
+ constraints.maxHeight || constraints.maxFps || constraints.maxFs ||
+ constraints.maxBr || constraints.maxPps || !dependIds.empty();
+ }
+
+ std::string id;
+ sdp::Direction direction;
+ std::vector<uint16_t> formats; // Empty implies all
+ EncodingConstraints constraints;
+ std::vector<std::string> dependIds;
+ };
+
+ SdpAttribute* Clone() const override {
+ return new SdpRidAttributeList(*this);
+ }
+
+ static bool CheckRidValidity(const std::string& aRid, std::string* aError);
+ static size_t kMaxRidLength;
+
+ virtual void Serialize(std::ostream& os) const override;
+
+ // Remove this function. See Bug 1469702
+ bool PushEntry(const std::string& raw, std::string* error, size_t* errorPos);
+
+ void PushEntry(const std::string& id, sdp::Direction dir,
+ const std::vector<uint16_t>& formats,
+ const EncodingConstraints& constraints,
+ const std::vector<std::string>& dependIds);
+
+ std::vector<Rid> mRids;
+};
+
+///////////////////////////////////////////////////////////////////////////
+// a=rtcp, RFC3605
+//-------------------------------------------------------------------------
+// rtcp-attribute = "a=rtcp:" port [nettype space addrtype space
+// connection-address] CRLF
+class SdpRtcpAttribute : public SdpAttribute {
+ public:
+ explicit SdpRtcpAttribute(uint16_t port)
+ : SdpAttribute(kRtcpAttribute),
+ mPort(port),
+ mNetType(sdp::kNetTypeNone),
+ mAddrType(sdp::kAddrTypeNone) {}
+
+ SdpRtcpAttribute(uint16_t port, sdp::NetType netType, sdp::AddrType addrType,
+ const std::string& address)
+ : SdpAttribute(kRtcpAttribute),
+ mPort(port),
+ mNetType(netType),
+ mAddrType(addrType),
+ mAddress(address) {
+ MOZ_ASSERT(netType != sdp::kNetTypeNone);
+ MOZ_ASSERT(addrType != sdp::kAddrTypeNone);
+ MOZ_ASSERT(!address.empty());
+ }
+
+ SdpAttribute* Clone() const override { return new SdpRtcpAttribute(*this); }
+
+ virtual void Serialize(std::ostream& os) const override;
+
+ uint16_t mPort;
+ sdp::NetType mNetType;
+ sdp::AddrType mAddrType;
+ std::string mAddress;
+};
+
+///////////////////////////////////////////////////////////////////////////
+// a=rtcp-fb, RFC4585
+//-------------------------------------------------------------------------
+// rtcp-fb-syntax = "a=rtcp-fb:" rtcp-fb-pt SP rtcp-fb-val CRLF
+//
+// rtcp-fb-pt = "*" ; wildcard: applies to all formats
+// / fmt ; as defined in SDP spec
+//
+// rtcp-fb-val = "ack" rtcp-fb-ack-param
+// / "nack" rtcp-fb-nack-param
+// / "trr-int" SP 1*DIGIT
+// / rtcp-fb-id rtcp-fb-param
+//
+// rtcp-fb-id = 1*(alpha-numeric / "-" / "_")
+//
+// rtcp-fb-param = SP "app" [SP byte-string]
+// / SP token [SP byte-string]
+// / ; empty
+//
+// rtcp-fb-ack-param = SP "rpsi"
+// / SP "app" [SP byte-string]
+// / SP token [SP byte-string]
+// / ; empty
+//
+// rtcp-fb-nack-param = SP "pli"
+// / SP "sli"
+// / SP "rpsi"
+// / SP "app" [SP byte-string]
+// / SP token [SP byte-string]
+// / ; empty
+//
+class SdpRtcpFbAttributeList : public SdpAttribute {
+ public:
+ SdpRtcpFbAttributeList() : SdpAttribute(kRtcpFbAttribute) {}
+
+ enum Type { kAck, kApp, kCcm, kNack, kTrrInt, kRemb, kTransportCC };
+
+ static const char* pli;
+ static const char* sli;
+ static const char* rpsi;
+ static const char* app;
+
+ static const char* fir;
+ static const char* tmmbr;
+ static const char* tstr;
+ static const char* vbcm;
+
+ struct Feedback {
+ std::string pt;
+ Type type;
+ std::string parameter;
+ std::string extra;
+ // TODO(bug 1744307): Use =default here once it is supported
+ bool operator==(const Feedback& aOther) const {
+ return pt == aOther.pt && type == aOther.type &&
+ parameter == aOther.parameter && extra == aOther.extra;
+ }
+ };
+
+ void PushEntry(const std::string& pt, Type type,
+ const std::string& parameter = "",
+ const std::string& extra = "") {
+ Feedback value = {pt, type, parameter, extra};
+ mFeedbacks.push_back(value);
+ }
+
+ SdpAttribute* Clone() const override {
+ return new SdpRtcpFbAttributeList(*this);
+ }
+
+ virtual void Serialize(std::ostream& os) const override;
+
+ std::vector<Feedback> mFeedbacks;
+};
+
+inline std::ostream& operator<<(std::ostream& os,
+ SdpRtcpFbAttributeList::Type type) {
+ switch (type) {
+ case SdpRtcpFbAttributeList::kAck:
+ os << "ack";
+ break;
+ case SdpRtcpFbAttributeList::kApp:
+ os << "app";
+ break;
+ case SdpRtcpFbAttributeList::kCcm:
+ os << "ccm";
+ break;
+ case SdpRtcpFbAttributeList::kNack:
+ os << "nack";
+ break;
+ case SdpRtcpFbAttributeList::kTrrInt:
+ os << "trr-int";
+ break;
+ case SdpRtcpFbAttributeList::kRemb:
+ os << "goog-remb";
+ break;
+ case SdpRtcpFbAttributeList::kTransportCC:
+ os << "transport-cc";
+ break;
+ default:
+ MOZ_ASSERT(false);
+ os << "?";
+ }
+ return os;
+}
+
+///////////////////////////////////////////////////////////////////////////
+// a=rtpmap, RFC4566
+//-------------------------------------------------------------------------
+// a=rtpmap:<payload type> <encoding name>/<clock rate> [/<encoding parameters>]
+class SdpRtpmapAttributeList : public SdpAttribute {
+ public:
+ SdpRtpmapAttributeList() : SdpAttribute(kRtpmapAttribute) {}
+
+ // Minimal set to get going
+ enum CodecType {
+ kOpus,
+ kG722,
+ kPCMU,
+ kPCMA,
+ kVP8,
+ kVP9,
+ kiLBC,
+ kiSAC,
+ kH264,
+ kRed,
+ kUlpfec,
+ kTelephoneEvent,
+ kRtx,
+ kOtherCodec
+ };
+
+ struct Rtpmap {
+ std::string pt;
+ CodecType codec;
+ std::string name;
+ uint32_t clock;
+ // Technically, this could mean something else in the future.
+ // In practice, that's probably not going to happen.
+ uint32_t channels;
+ };
+
+ void PushEntry(const std::string& pt, CodecType codec,
+ const std::string& name, uint32_t clock,
+ uint32_t channels = 0) {
+ Rtpmap value = {pt, codec, name, clock, channels};
+ mRtpmaps.push_back(value);
+ }
+
+ SdpAttribute* Clone() const override {
+ return new SdpRtpmapAttributeList(*this);
+ }
+
+ virtual void Serialize(std::ostream& os) const override;
+
+ bool HasEntry(const std::string& pt) const {
+ for (auto it = mRtpmaps.begin(); it != mRtpmaps.end(); ++it) {
+ if (it->pt == pt) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ const Rtpmap& GetEntry(const std::string& pt) const {
+ for (auto it = mRtpmaps.begin(); it != mRtpmaps.end(); ++it) {
+ if (it->pt == pt) {
+ return *it;
+ }
+ }
+ MOZ_CRASH();
+ }
+
+ std::vector<Rtpmap> mRtpmaps;
+};
+
+inline std::ostream& operator<<(std::ostream& os,
+ SdpRtpmapAttributeList::CodecType c) {
+ switch (c) {
+ case SdpRtpmapAttributeList::kOpus:
+ os << "opus";
+ break;
+ case SdpRtpmapAttributeList::kG722:
+ os << "G722";
+ break;
+ case SdpRtpmapAttributeList::kPCMU:
+ os << "PCMU";
+ break;
+ case SdpRtpmapAttributeList::kPCMA:
+ os << "PCMA";
+ break;
+ case SdpRtpmapAttributeList::kVP8:
+ os << "VP8";
+ break;
+ case SdpRtpmapAttributeList::kVP9:
+ os << "VP9";
+ break;
+ case SdpRtpmapAttributeList::kiLBC:
+ os << "iLBC";
+ break;
+ case SdpRtpmapAttributeList::kiSAC:
+ os << "iSAC";
+ break;
+ case SdpRtpmapAttributeList::kH264:
+ os << "H264";
+ break;
+ case SdpRtpmapAttributeList::kRed:
+ os << "red";
+ break;
+ case SdpRtpmapAttributeList::kUlpfec:
+ os << "ulpfec";
+ break;
+ case SdpRtpmapAttributeList::kTelephoneEvent:
+ os << "telephone-event";
+ break;
+ case SdpRtpmapAttributeList::kRtx:
+ os << "rtx";
+ break;
+ default:
+ MOZ_ASSERT(false);
+ os << "?";
+ }
+ return os;
+}
+
+///////////////////////////////////////////////////////////////////////////
+// a=fmtp, RFC4566, RFC5576
+//-------------------------------------------------------------------------
+// a=fmtp:<format> <format specific parameters>
+//
+class SdpFmtpAttributeList : public SdpAttribute {
+ public:
+ SdpFmtpAttributeList() : SdpAttribute(kFmtpAttribute) {}
+
+ // Base class for format parameters
+ class Parameters {
+ public:
+ explicit Parameters(SdpRtpmapAttributeList::CodecType aCodec)
+ : codec_type(aCodec) {}
+
+ virtual ~Parameters() {}
+ virtual Parameters* Clone() const = 0;
+ virtual void Serialize(std::ostream& os) const = 0;
+ virtual bool CompareEq(const Parameters& other) const = 0;
+
+ bool operator==(const Parameters& other) const {
+ return codec_type == other.codec_type && CompareEq(other);
+ }
+
+ SdpRtpmapAttributeList::CodecType codec_type;
+ };
+
+ class RedParameters : public Parameters {
+ public:
+ RedParameters() : Parameters(SdpRtpmapAttributeList::kRed) {}
+
+ virtual Parameters* Clone() const override {
+ return new RedParameters(*this);
+ }
+
+ virtual void Serialize(std::ostream& os) const override {
+ for (size_t i = 0; i < encodings.size(); ++i) {
+ os << (i != 0 ? "/" : "") << std::to_string(encodings[i]);
+ }
+ }
+
+ virtual bool CompareEq(const Parameters& other) const override {
+ return encodings == static_cast<const RedParameters&>(other).encodings;
+ }
+
+ std::vector<uint8_t> encodings;
+ };
+
+ class RtxParameters : public Parameters {
+ public:
+ uint8_t apt = 255; // Valid payload types are 0 - 127, use 255 to represent
+ // unset value.
+ Maybe<uint32_t> rtx_time;
+
+ RtxParameters() : Parameters(SdpRtpmapAttributeList::kRtx) {}
+
+ virtual ~RtxParameters() {}
+
+ virtual Parameters* Clone() const override {
+ return new RtxParameters(*this);
+ }
+
+ virtual void Serialize(std::ostream& os) const override {
+ if (apt <= 127) {
+ os << "apt=" << static_cast<uint32_t>(apt);
+ rtx_time.apply([&](const auto& time) { os << ";rtx-time=" << time; });
+ }
+ }
+
+ virtual bool CompareEq(const Parameters& aOther) const override {
+ if (aOther.codec_type != codec_type) {
+ return false;
+ }
+ auto other = static_cast<const RtxParameters&>(aOther);
+ return other.apt == apt && other.rtx_time == rtx_time;
+ }
+ };
+
+ class H264Parameters : public Parameters {
+ public:
+ static const uint32_t kDefaultProfileLevelId = 0x420010;
+
+ H264Parameters()
+ : Parameters(SdpRtpmapAttributeList::kH264),
+ packetization_mode(0),
+ level_asymmetry_allowed(false),
+ profile_level_id(kDefaultProfileLevelId),
+ max_mbps(0),
+ max_fs(0),
+ max_cpb(0),
+ max_dpb(0),
+ max_br(0) {
+ memset(sprop_parameter_sets, 0, sizeof(sprop_parameter_sets));
+ }
+
+ virtual Parameters* Clone() const override {
+ return new H264Parameters(*this);
+ }
+
+ virtual void Serialize(std::ostream& os) const override {
+ // Note: don't move this, since having an unconditional param up top
+ // lets us avoid a whole bunch of conditional streaming of ';' below
+ os << "profile-level-id=" << std::hex << std::setfill('0') << std::setw(6)
+ << profile_level_id << std::dec << std::setfill(' ');
+
+ os << ";level-asymmetry-allowed=" << (level_asymmetry_allowed ? 1 : 0);
+
+ if (strlen(sprop_parameter_sets)) {
+ os << ";sprop-parameter-sets=" << sprop_parameter_sets;
+ }
+
+ if (packetization_mode != 0) {
+ os << ";packetization-mode=" << packetization_mode;
+ }
+
+ if (max_mbps != 0) {
+ os << ";max-mbps=" << max_mbps;
+ }
+
+ if (max_fs != 0) {
+ os << ";max-fs=" << max_fs;
+ }
+
+ if (max_cpb != 0) {
+ os << ";max-cpb=" << max_cpb;
+ }
+
+ if (max_dpb != 0) {
+ os << ";max-dpb=" << max_dpb;
+ }
+
+ if (max_br != 0) {
+ os << ";max-br=" << max_br;
+ }
+ }
+
+ virtual bool CompareEq(const Parameters& other) const override {
+ const auto& otherH264 = static_cast<const H264Parameters&>(other);
+
+ // sprop is not comapred here as it does not get parsed in the rsdparsa
+ return packetization_mode == otherH264.packetization_mode &&
+ level_asymmetry_allowed == otherH264.level_asymmetry_allowed &&
+ profile_level_id == otherH264.profile_level_id &&
+ max_mbps == otherH264.max_mbps && max_fs == otherH264.max_fs &&
+ max_cpb == otherH264.max_cpb && max_dpb == otherH264.max_dpb &&
+ max_br == otherH264.max_br;
+ }
+
+ static const size_t max_sprop_len = 128;
+ char sprop_parameter_sets[max_sprop_len];
+ unsigned int packetization_mode;
+ bool level_asymmetry_allowed;
+ unsigned int profile_level_id;
+ unsigned int max_mbps;
+ unsigned int max_fs;
+ unsigned int max_cpb;
+ unsigned int max_dpb;
+ unsigned int max_br;
+ };
+
+ // Also used for VP9 since they share parameters
+ class VP8Parameters : public Parameters {
+ public:
+ explicit VP8Parameters(SdpRtpmapAttributeList::CodecType type)
+ : Parameters(type), max_fs(0), max_fr(0) {}
+
+ virtual Parameters* Clone() const override {
+ return new VP8Parameters(*this);
+ }
+
+ virtual void Serialize(std::ostream& os) const override {
+ // draft-ietf-payload-vp8-11 says these are mandatory, upper layer
+ // needs to ensure they're set properly.
+ os << "max-fs=" << max_fs;
+ os << ";max-fr=" << max_fr;
+ }
+
+ virtual bool CompareEq(const Parameters& other) const override {
+ const auto& otherVP8 = static_cast<const VP8Parameters&>(other);
+
+ return max_fs == otherVP8.max_fs && max_fr == otherVP8.max_fr;
+ }
+
+ unsigned int max_fs;
+ unsigned int max_fr;
+ };
+
+ class OpusParameters : public Parameters {
+ public:
+ enum {
+ kDefaultMaxPlaybackRate = 48000,
+ kDefaultStereo = 0,
+ kDefaultUseInBandFec = 0,
+ kDefaultMaxAverageBitrate = 0,
+ kDefaultUseDTX = 0,
+ kDefaultFrameSize = 0,
+ kDefaultMinFrameSize = 0,
+ kDefaultMaxFrameSize = 0,
+ kDefaultUseCbr = 0
+ };
+ OpusParameters()
+ : Parameters(SdpRtpmapAttributeList::kOpus),
+ maxplaybackrate(kDefaultMaxPlaybackRate),
+ stereo(kDefaultStereo),
+ useInBandFec(kDefaultUseInBandFec),
+ maxAverageBitrate(kDefaultMaxAverageBitrate),
+ useDTX(kDefaultUseDTX),
+ frameSizeMs(kDefaultFrameSize),
+ minFrameSizeMs(kDefaultMinFrameSize),
+ maxFrameSizeMs(kDefaultMaxFrameSize),
+ useCbr(kDefaultUseCbr) {}
+
+ Parameters* Clone() const override { return new OpusParameters(*this); }
+
+ void Serialize(std::ostream& os) const override {
+ os << "maxplaybackrate=" << maxplaybackrate << ";stereo=" << stereo
+ << ";useinbandfec=" << useInBandFec;
+
+ if (useDTX) {
+ os << ";usedtx=1";
+ }
+ if (maxAverageBitrate) {
+ os << ";maxaveragebitrate=" << maxAverageBitrate;
+ }
+ if (frameSizeMs) {
+ os << ";ptime=" << frameSizeMs;
+ }
+ if (minFrameSizeMs) {
+ os << ";minptime=" << minFrameSizeMs;
+ }
+ if (maxFrameSizeMs) {
+ os << ";maxptime=" << maxFrameSizeMs;
+ }
+ if (useCbr) {
+ os << ";cbr=1";
+ }
+ }
+
+ virtual bool CompareEq(const Parameters& other) const override {
+ const auto& otherOpus = static_cast<const OpusParameters&>(other);
+
+ bool maxplaybackrateIsEq = (maxplaybackrate == otherOpus.maxplaybackrate);
+
+ // This is due to a bug in sipcc that causes maxplaybackrate to
+ // always be 0 if it appears in the fmtp
+ if (((maxplaybackrate == 0) && (otherOpus.maxplaybackrate != 0)) ||
+ ((maxplaybackrate != 0) && (otherOpus.maxplaybackrate == 0))) {
+ maxplaybackrateIsEq = true;
+ }
+
+ return maxplaybackrateIsEq && stereo == otherOpus.stereo &&
+ useInBandFec == otherOpus.useInBandFec &&
+ maxAverageBitrate == otherOpus.maxAverageBitrate &&
+ useDTX == otherOpus.useDTX &&
+ frameSizeMs == otherOpus.frameSizeMs &&
+ minFrameSizeMs == otherOpus.minFrameSizeMs &&
+ maxFrameSizeMs == otherOpus.maxFrameSizeMs &&
+ useCbr == otherOpus.useCbr;
+ }
+
+ unsigned int maxplaybackrate;
+ unsigned int stereo;
+ unsigned int useInBandFec;
+ uint32_t maxAverageBitrate;
+ bool useDTX;
+ uint32_t frameSizeMs;
+ uint32_t minFrameSizeMs;
+ uint32_t maxFrameSizeMs;
+ bool useCbr;
+ };
+
+ class TelephoneEventParameters : public Parameters {
+ public:
+ TelephoneEventParameters()
+ : Parameters(SdpRtpmapAttributeList::kTelephoneEvent),
+ dtmfTones("0-15") {}
+
+ virtual Parameters* Clone() const override {
+ return new TelephoneEventParameters(*this);
+ }
+
+ void Serialize(std::ostream& os) const override { os << dtmfTones; }
+
+ virtual bool CompareEq(const Parameters& other) const override {
+ return dtmfTones ==
+ static_cast<const TelephoneEventParameters&>(other).dtmfTones;
+ }
+
+ std::string dtmfTones;
+ };
+
+ class Fmtp {
+ public:
+ Fmtp(const std::string& aFormat, const Parameters& aParameters)
+ : format(aFormat), parameters(aParameters.Clone()) {}
+
+ // TODO: Rip all of this out when we have move semantics in the stl.
+ Fmtp(const Fmtp& orig) { *this = orig; }
+
+ Fmtp& operator=(const Fmtp& rhs) {
+ if (this != &rhs) {
+ format = rhs.format;
+ parameters.reset(rhs.parameters ? rhs.parameters->Clone() : nullptr);
+ }
+ return *this;
+ }
+
+ bool operator==(const Fmtp& other) const {
+ return format == other.format && *parameters == *other.parameters;
+ }
+
+ // The contract around these is as follows:
+ // * |parameters| is only set if we recognized the media type and had
+ // a subclass of Parameters to represent that type of parameters
+ // * |parameters| is a best-effort representation; it might be missing
+ // stuff
+ // * Parameters::codec_type tells you the concrete class, eg
+ // kH264 -> H264Parameters
+ std::string format;
+ UniquePtr<Parameters> parameters;
+ };
+
+ bool operator==(const SdpFmtpAttributeList& other) const;
+
+ SdpAttribute* Clone() const override {
+ return new SdpFmtpAttributeList(*this);
+ }
+
+ virtual void Serialize(std::ostream& os) const override;
+
+ void PushEntry(const std::string& format, const Parameters& parameters) {
+ mFmtps.push_back(Fmtp(format, parameters));
+ }
+
+ std::vector<Fmtp> mFmtps;
+};
+
+///////////////////////////////////////////////////////////////////////////
+// a=sctpmap, draft-ietf-mmusic-sctp-sdp-05
+//-------------------------------------------------------------------------
+// sctpmap-attr = "a=sctpmap:" sctpmap-number media-subtypes
+// [streams]
+// sctpmap-number = 1*DIGIT
+// protocol = labelstring
+// labelstring = text
+// text = byte-string
+// streams = 1*DIGIT
+//
+// We're going to pretend that there are spaces where they make sense.
+class SdpSctpmapAttributeList : public SdpAttribute {
+ public:
+ SdpSctpmapAttributeList() : SdpAttribute(kSctpmapAttribute) {}
+
+ struct Sctpmap {
+ std::string pt;
+ std::string name;
+ uint32_t streams;
+ };
+
+ void PushEntry(const std::string& pt, const std::string& name,
+ uint32_t streams = 0) {
+ Sctpmap value = {pt, name, streams};
+ mSctpmaps.push_back(value);
+ }
+
+ SdpAttribute* Clone() const override {
+ return new SdpSctpmapAttributeList(*this);
+ }
+
+ virtual void Serialize(std::ostream& os) const override;
+
+ bool HasEntry(const std::string& pt) const {
+ for (auto it = mSctpmaps.begin(); it != mSctpmaps.end(); ++it) {
+ if (it->pt == pt) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ const Sctpmap& GetFirstEntry() const { return mSctpmaps[0]; }
+
+ std::vector<Sctpmap> mSctpmaps;
+};
+
+///////////////////////////////////////////////////////////////////////////
+// a=setup, RFC4145
+//-------------------------------------------------------------------------
+// setup-attr = "a=setup:" role
+// role = "active" / "passive" / "actpass" / "holdconn"
+class SdpSetupAttribute : public SdpAttribute {
+ public:
+ enum Role { kActive, kPassive, kActpass, kHoldconn };
+
+ explicit SdpSetupAttribute(Role role)
+ : SdpAttribute(kSetupAttribute), mRole(role) {}
+
+ SdpAttribute* Clone() const override { return new SdpSetupAttribute(*this); }
+
+ virtual void Serialize(std::ostream& os) const override;
+
+ Role mRole;
+};
+
+inline std::ostream& operator<<(std::ostream& os, SdpSetupAttribute::Role r) {
+ switch (r) {
+ case SdpSetupAttribute::kActive:
+ os << "active";
+ break;
+ case SdpSetupAttribute::kPassive:
+ os << "passive";
+ break;
+ case SdpSetupAttribute::kActpass:
+ os << "actpass";
+ break;
+ case SdpSetupAttribute::kHoldconn:
+ os << "holdconn";
+ break;
+ default:
+ MOZ_ASSERT(false);
+ os << "?";
+ }
+ return os;
+}
+
+// Old draft-04
+// sc-attr = "a=simulcast:" 1*2( WSP sc-str-list ) [WSP sc-pause-list]
+// sc-str-list = sc-dir WSP sc-id-type "=" sc-alt-list *( ";" sc-alt-list )
+// sc-pause-list = "paused=" sc-alt-list
+// sc-dir = "send" / "recv"
+// sc-id-type = "pt" / "rid" / token
+// sc-alt-list = sc-id *( "," sc-id )
+// sc-id = fmt / rid-identifier / token
+// ; WSP defined in [RFC5234]
+// ; fmt, token defined in [RFC4566]
+// ; rid-identifier defined in [I-D.pthatcher-mmusic-rid]
+//
+// New draft 14, need to parse this for now, will eventually emit it
+// sc-value = ( sc-send [SP sc-recv] ) / ( sc-recv [SP sc-send] )
+// sc-send = %s"send" SP sc-str-list
+// sc-recv = %s"recv" SP sc-str-list
+// sc-str-list = sc-alt-list *( ";" sc-alt-list )
+// sc-alt-list = sc-id *( "," sc-id )
+// sc-id-paused = "~"
+// sc-id = [sc-id-paused] rid-id
+// ; SP defined in [RFC5234]
+// ; rid-id defined in [I-D.ietf-mmusic-rid]
+
+class SdpSimulcastAttribute : public SdpAttribute {
+ public:
+ SdpSimulcastAttribute() : SdpAttribute(kSimulcastAttribute) {}
+
+ SdpAttribute* Clone() const override {
+ return new SdpSimulcastAttribute(*this);
+ }
+
+ void Serialize(std::ostream& os) const override;
+ bool Parse(std::istream& is, std::string* error);
+
+ class Encoding {
+ public:
+ Encoding(const std::string& aRid, bool aPaused)
+ : rid(aRid), paused(aPaused) {}
+ std::string rid;
+ bool paused = false;
+ };
+
+ class Version {
+ public:
+ void Serialize(std::ostream& os) const;
+ bool IsSet() const { return !choices.empty(); }
+ bool Parse(std::istream& is, std::string* error);
+
+ std::vector<Encoding> choices;
+ };
+
+ class Versions : public std::vector<Version> {
+ public:
+ void Serialize(std::ostream& os) const;
+ bool IsSet() const {
+ if (empty()) {
+ return false;
+ }
+
+ for (const Version& version : *this) {
+ if (version.IsSet()) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ bool Parse(std::istream& is, std::string* error);
+ };
+
+ Versions sendVersions;
+ Versions recvVersions;
+};
+
+///////////////////////////////////////////////////////////////////////////
+// a=ssrc, RFC5576
+//-------------------------------------------------------------------------
+// ssrc-attr = "ssrc:" ssrc-id SP attribute
+// ; The base definition of "attribute" is in RFC 4566.
+// ; (It is the content of "a=" lines.)
+//
+// ssrc-id = integer ; 0 .. 2**32 - 1
+//-------------------------------------------------------------------------
+// TODO -- In the future, it might be nice if we ran a parse on the
+// attribute section of this so that we could interpret it semantically.
+// For WebRTC, the key use case for a=ssrc is assocaiting SSRCs with
+// media sections, and we're not really going to care about the attribute
+// itself. So we're just going to store it as a string for the time being.
+// Issue 187.
+class SdpSsrcAttributeList : public SdpAttribute {
+ public:
+ SdpSsrcAttributeList() : SdpAttribute(kSsrcAttribute) {}
+
+ struct Ssrc {
+ uint32_t ssrc;
+ std::string attribute;
+ };
+
+ void PushEntry(uint32_t ssrc, const std::string& attribute) {
+ Ssrc value = {ssrc, attribute};
+ mSsrcs.push_back(value);
+ }
+
+ SdpAttribute* Clone() const override {
+ return new SdpSsrcAttributeList(*this);
+ }
+
+ virtual void Serialize(std::ostream& os) const override;
+
+ std::vector<Ssrc> mSsrcs;
+};
+
+///////////////////////////////////////////////////////////////////////////
+// a=ssrc-group, RFC5576
+//-------------------------------------------------------------------------
+// ssrc-group-attr = "ssrc-group:" semantics *(SP ssrc-id)
+//
+// semantics = "FEC" / "FID" / token
+//
+// ssrc-id = integer ; 0 .. 2**32 - 1
+class SdpSsrcGroupAttributeList : public SdpAttribute {
+ public:
+ enum Semantics {
+ kFec, // RFC5576
+ kFid, // RFC5576
+ kFecFr, // RFC5956
+ kDup, // RFC7104
+ kSim // non-standard, used by hangouts
+ };
+
+ struct SsrcGroup {
+ Semantics semantics;
+ std::vector<uint32_t> ssrcs;
+ };
+
+ SdpSsrcGroupAttributeList() : SdpAttribute(kSsrcGroupAttribute) {}
+
+ void PushEntry(Semantics semantics, const std::vector<uint32_t>& ssrcs) {
+ SsrcGroup value = {semantics, ssrcs};
+ mSsrcGroups.push_back(value);
+ }
+
+ SdpAttribute* Clone() const override {
+ return new SdpSsrcGroupAttributeList(*this);
+ }
+
+ virtual void Serialize(std::ostream& os) const override;
+
+ std::vector<SsrcGroup> mSsrcGroups;
+};
+
+inline std::ostream& operator<<(std::ostream& os,
+ SdpSsrcGroupAttributeList::Semantics s) {
+ switch (s) {
+ case SdpSsrcGroupAttributeList::kFec:
+ os << "FEC";
+ break;
+ case SdpSsrcGroupAttributeList::kFid:
+ os << "FID";
+ break;
+ case SdpSsrcGroupAttributeList::kFecFr:
+ os << "FEC-FR";
+ break;
+ case SdpSsrcGroupAttributeList::kDup:
+ os << "DUP";
+ break;
+ case SdpSsrcGroupAttributeList::kSim:
+ os << "SIM";
+ break;
+ default:
+ MOZ_ASSERT(false);
+ os << "?";
+ }
+ return os;
+}
+
+///////////////////////////////////////////////////////////////////////////
+class SdpMultiStringAttribute : public SdpAttribute {
+ public:
+ explicit SdpMultiStringAttribute(AttributeType type) : SdpAttribute(type) {}
+
+ void PushEntry(const std::string& entry) { mValues.push_back(entry); }
+
+ SdpAttribute* Clone() const override {
+ return new SdpMultiStringAttribute(*this);
+ }
+
+ virtual void Serialize(std::ostream& os) const override;
+
+ std::vector<std::string> mValues;
+};
+
+// otherwise identical to SdpMultiStringAttribute, this is used for
+// ice-options and other places where the value is serialized onto
+// a single line with space separating tokens
+class SdpOptionsAttribute : public SdpAttribute {
+ public:
+ explicit SdpOptionsAttribute(AttributeType type) : SdpAttribute(type) {}
+
+ void PushEntry(const std::string& entry) { mValues.push_back(entry); }
+
+ void Load(const std::string& value);
+
+ SdpAttribute* Clone() const override {
+ return new SdpOptionsAttribute(*this);
+ }
+
+ virtual void Serialize(std::ostream& os) const override;
+
+ std::vector<std::string> mValues;
+};
+
+// Used for attributes that take no value (eg; a=ice-lite)
+class SdpFlagAttribute : public SdpAttribute {
+ public:
+ explicit SdpFlagAttribute(AttributeType type) : SdpAttribute(type) {}
+
+ SdpAttribute* Clone() const override { return new SdpFlagAttribute(*this); }
+
+ virtual void Serialize(std::ostream& os) const override;
+};
+
+// Used for any other kind of single-valued attribute not otherwise specialized
+class SdpStringAttribute : public SdpAttribute {
+ public:
+ explicit SdpStringAttribute(AttributeType type, const std::string& value)
+ : SdpAttribute(type), mValue(value) {}
+
+ SdpAttribute* Clone() const override { return new SdpStringAttribute(*this); }
+
+ virtual void Serialize(std::ostream& os) const override;
+
+ std::string mValue;
+};
+
+// Used for any purely (non-negative) numeric attribute
+class SdpNumberAttribute : public SdpAttribute {
+ public:
+ explicit SdpNumberAttribute(AttributeType type, uint32_t value = 0)
+ : SdpAttribute(type), mValue(value) {}
+
+ SdpAttribute* Clone() const override { return new SdpNumberAttribute(*this); }
+
+ virtual void Serialize(std::ostream& os) const override;
+
+ uint32_t mValue;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/sdp/SdpAttributeList.h b/dom/media/webrtc/sdp/SdpAttributeList.h
new file mode 100644
index 0000000000..23088dc967
--- /dev/null
+++ b/dom/media/webrtc/sdp/SdpAttributeList.h
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _SDPATTRIBUTELIST_H_
+#define _SDPATTRIBUTELIST_H_
+
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Attributes.h"
+
+#include "sdp/SdpAttribute.h"
+
+namespace mozilla {
+
+class SdpAttributeList {
+ public:
+ virtual ~SdpAttributeList() {}
+ typedef SdpAttribute::AttributeType AttributeType;
+
+ // Avoid default params on virtual functions
+ bool HasAttribute(AttributeType type) const {
+ return HasAttribute(type, true);
+ }
+
+ const SdpAttribute* GetAttribute(AttributeType type) const {
+ return GetAttribute(type, true);
+ }
+
+ virtual bool HasAttribute(AttributeType type, bool sessionFallback) const = 0;
+ virtual const SdpAttribute* GetAttribute(AttributeType type,
+ bool sessionFallback) const = 0;
+ // The setter takes an attribute of any type, and takes ownership
+ virtual void SetAttribute(SdpAttribute* attr) = 0;
+ virtual void RemoveAttribute(AttributeType type) = 0;
+ virtual void Clear() = 0;
+ virtual uint32_t Count() const = 0;
+
+ virtual const SdpConnectionAttribute& GetConnection() const = 0;
+ virtual const SdpOptionsAttribute& GetIceOptions() const = 0;
+ virtual const SdpRtcpAttribute& GetRtcp() const = 0;
+ virtual const SdpRemoteCandidatesAttribute& GetRemoteCandidates() const = 0;
+ virtual const SdpSetupAttribute& GetSetup() const = 0;
+ virtual const SdpDtlsMessageAttribute& GetDtlsMessage() const = 0;
+
+ // These attributes can appear multiple times, so the returned
+ // classes actually represent a collection of values.
+ virtual const std::vector<std::string>& GetCandidate() const = 0;
+ virtual const SdpExtmapAttributeList& GetExtmap() const = 0;
+ virtual const SdpFingerprintAttributeList& GetFingerprint() const = 0;
+ virtual const SdpFmtpAttributeList& GetFmtp() const = 0;
+ virtual const SdpGroupAttributeList& GetGroup() const = 0;
+ virtual const SdpImageattrAttributeList& GetImageattr() const = 0;
+ virtual const SdpSimulcastAttribute& GetSimulcast() const = 0;
+ virtual const SdpMsidAttributeList& GetMsid() const = 0;
+ virtual const SdpMsidSemanticAttributeList& GetMsidSemantic() const = 0;
+ virtual const SdpRidAttributeList& GetRid() const = 0;
+ virtual const SdpRtcpFbAttributeList& GetRtcpFb() const = 0;
+ virtual const SdpRtpmapAttributeList& GetRtpmap() const = 0;
+ virtual const SdpSctpmapAttributeList& GetSctpmap() const = 0;
+ virtual uint32_t GetSctpPort() const = 0;
+ virtual uint32_t GetMaxMessageSize() const = 0;
+ virtual const SdpSsrcAttributeList& GetSsrc() const = 0;
+ virtual const SdpSsrcGroupAttributeList& GetSsrcGroup() const = 0;
+
+ // These attributes are effectively simple types, so we'll make life
+ // easy by just returning their value.
+ virtual const std::string& GetIcePwd() const = 0;
+ virtual const std::string& GetIceUfrag() const = 0;
+ virtual const std::string& GetIdentity() const = 0;
+ virtual const std::string& GetLabel() const = 0;
+ virtual unsigned int GetMaxptime() const = 0;
+ virtual const std::string& GetMid() const = 0;
+ virtual unsigned int GetPtime() const = 0;
+
+ // This is "special", because it's multiple things
+ virtual SdpDirectionAttribute::Direction GetDirection() const = 0;
+
+ virtual void Serialize(std::ostream&) const = 0;
+};
+
+inline std::ostream& operator<<(std::ostream& os, const SdpAttributeList& al) {
+ al.Serialize(os);
+ return os;
+}
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/sdp/SdpEnum.h b/dom/media/webrtc/sdp/SdpEnum.h
new file mode 100644
index 0000000000..c5d67352e0
--- /dev/null
+++ b/dom/media/webrtc/sdp/SdpEnum.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _SDPENUM_H_
+#define _SDPENUM_H_
+
+#include <ostream>
+
+#include "mozilla/Assertions.h"
+
+namespace mozilla::sdp {
+
+enum NetType { kNetTypeNone, kInternet };
+
+inline std::ostream& operator<<(std::ostream& os, sdp::NetType t) {
+ switch (t) {
+ case sdp::kNetTypeNone:
+ MOZ_ASSERT(false);
+ return os << "NONE";
+ case sdp::kInternet:
+ return os << "IN";
+ }
+ MOZ_CRASH("Unknown NetType");
+}
+
+enum AddrType { kAddrTypeNone, kIPv4, kIPv6 };
+
+inline std::ostream& operator<<(std::ostream& os, sdp::AddrType t) {
+ switch (t) {
+ case sdp::kAddrTypeNone:
+ MOZ_ASSERT(false);
+ return os << "NONE";
+ case sdp::kIPv4:
+ return os << "IP4";
+ case sdp::kIPv6:
+ return os << "IP6";
+ }
+ MOZ_CRASH("Unknown AddrType");
+}
+
+enum Direction {
+ // Start at 1 so these can be used as flags
+ kSend = 1,
+ kRecv = 2
+};
+
+inline std::ostream& operator<<(std::ostream& os, sdp::Direction d) {
+ switch (d) {
+ case sdp::kSend:
+ return os << "send";
+ case sdp::kRecv:
+ return os << "recv";
+ }
+ MOZ_CRASH("Unknown Direction");
+}
+
+enum SdpType { kOffer, kAnswer };
+
+} // namespace mozilla::sdp
+
+#endif
diff --git a/dom/media/webrtc/sdp/SdpHelper.cpp b/dom/media/webrtc/sdp/SdpHelper.cpp
new file mode 100644
index 0000000000..d24a7d199d
--- /dev/null
+++ b/dom/media/webrtc/sdp/SdpHelper.cpp
@@ -0,0 +1,801 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "sdp/SdpHelper.h"
+
+#include "sdp/Sdp.h"
+#include "sdp/SdpMediaSection.h"
+#include "transport/logging.h"
+
+#include "nsDebug.h"
+#include "nsError.h"
+#include "prprf.h"
+
+#include <string.h>
+#include <set>
+
+namespace mozilla {
+MOZ_MTLOG_MODULE("sdp")
+
+#define SDP_SET_ERROR(error) \
+ do { \
+ std::ostringstream os; \
+ os << error; \
+ mLastError = os.str(); \
+ MOZ_MTLOG(ML_ERROR, mLastError); \
+ } while (0);
+
+nsresult SdpHelper::CopyTransportParams(size_t numComponents,
+ const SdpMediaSection& oldLocal,
+ SdpMediaSection* newLocal) {
+ const SdpAttributeList& oldLocalAttrs = oldLocal.GetAttributeList();
+ // Copy over m-section details
+ if (!oldLocalAttrs.HasAttribute(SdpAttribute::kBundleOnlyAttribute)) {
+ // Do not copy port 0 from an offer with a=bundle-only; this could cause
+ // an answer msection to be erroneously rejected.
+ newLocal->SetPort(oldLocal.GetPort());
+ }
+ newLocal->GetConnection() = oldLocal.GetConnection();
+
+ SdpAttributeList& newLocalAttrs = newLocal->GetAttributeList();
+
+ // Now we copy over attributes that won't be added by the usual logic
+ if (oldLocalAttrs.HasAttribute(SdpAttribute::kCandidateAttribute) &&
+ numComponents) {
+ UniquePtr<SdpMultiStringAttribute> candidateAttrs(
+ new SdpMultiStringAttribute(SdpAttribute::kCandidateAttribute));
+ for (const std::string& candidate : oldLocalAttrs.GetCandidate()) {
+ size_t component;
+ nsresult rv = GetComponent(candidate, &component);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (numComponents >= component) {
+ candidateAttrs->mValues.push_back(candidate);
+ }
+ }
+ if (!candidateAttrs->mValues.empty()) {
+ newLocalAttrs.SetAttribute(candidateAttrs.release());
+ }
+ }
+
+ if (oldLocalAttrs.HasAttribute(SdpAttribute::kEndOfCandidatesAttribute)) {
+ newLocalAttrs.SetAttribute(
+ new SdpFlagAttribute(SdpAttribute::kEndOfCandidatesAttribute));
+ }
+
+ if (numComponents == 2 &&
+ oldLocalAttrs.HasAttribute(SdpAttribute::kRtcpAttribute)) {
+ // copy rtcp attribute if we had one that we are using
+ newLocalAttrs.SetAttribute(new SdpRtcpAttribute(oldLocalAttrs.GetRtcp()));
+ }
+
+ return NS_OK;
+}
+
+bool SdpHelper::AreOldTransportParamsValid(const Sdp& oldAnswer,
+ const Sdp& offerersPreviousSdp,
+ const Sdp& newOffer, size_t level) {
+ if (MsectionIsDisabled(oldAnswer.GetMediaSection(level)) ||
+ MsectionIsDisabled(newOffer.GetMediaSection(level))) {
+ // Obvious
+ return false;
+ }
+
+ if (!OwnsTransport(oldAnswer, level, sdp::kAnswer)) {
+ // The transport attributes on this m-section were thrown away, because it
+ // was bundled.
+ return false;
+ }
+
+ if (!OwnsTransport(newOffer, level, sdp::kOffer)) {
+ return false;
+ }
+
+ if (IceCredentialsDiffer(newOffer.GetMediaSection(level),
+ offerersPreviousSdp.GetMediaSection(level))) {
+ return false;
+ }
+
+ return true;
+}
+
+bool SdpHelper::IceCredentialsDiffer(const SdpMediaSection& msection1,
+ const SdpMediaSection& msection2) {
+ const SdpAttributeList& attrs1(msection1.GetAttributeList());
+ const SdpAttributeList& attrs2(msection2.GetAttributeList());
+
+ if ((attrs1.GetIceUfrag() != attrs2.GetIceUfrag()) ||
+ (attrs1.GetIcePwd() != attrs2.GetIcePwd())) {
+ return true;
+ }
+
+ return false;
+}
+
+nsresult SdpHelper::GetComponent(const std::string& candidate,
+ size_t* component) {
+ unsigned int temp;
+ int32_t result = PR_sscanf(candidate.c_str(), "%*s %u", &temp);
+ if (result == 1) {
+ *component = temp;
+ return NS_OK;
+ }
+ SDP_SET_ERROR("Malformed ICE candidate: " << candidate);
+ return NS_ERROR_INVALID_ARG;
+}
+
+bool SdpHelper::MsectionIsDisabled(const SdpMediaSection& msection) const {
+ return !msection.GetPort() && !msection.GetAttributeList().HasAttribute(
+ SdpAttribute::kBundleOnlyAttribute);
+}
+
+void SdpHelper::DisableMsection(Sdp* sdp, SdpMediaSection* msection) {
+ std::string mid;
+
+ // Make sure to remove the mid from any group attributes
+ if (msection->GetAttributeList().HasAttribute(SdpAttribute::kMidAttribute)) {
+ mid = msection->GetAttributeList().GetMid();
+ if (sdp->GetAttributeList().HasAttribute(SdpAttribute::kGroupAttribute)) {
+ UniquePtr<SdpGroupAttributeList> newGroupAttr(
+ new SdpGroupAttributeList(sdp->GetAttributeList().GetGroup()));
+ newGroupAttr->RemoveMid(mid);
+ sdp->GetAttributeList().SetAttribute(newGroupAttr.release());
+ }
+ }
+
+ // Clear out attributes.
+ msection->GetAttributeList().Clear();
+
+ auto* direction = new SdpDirectionAttribute(SdpDirectionAttribute::kInactive);
+ msection->GetAttributeList().SetAttribute(direction);
+ msection->SetPort(0);
+
+ // maintain the mid for easier identification on other side
+ if (!mid.empty()) {
+ msection->GetAttributeList().SetAttribute(
+ new SdpStringAttribute(SdpAttribute::kMidAttribute, mid));
+ }
+
+ msection->ClearCodecs();
+
+ auto mediaType = msection->GetMediaType();
+ switch (mediaType) {
+ case SdpMediaSection::kAudio:
+ msection->AddCodec("0", "PCMU", 8000, 1);
+ break;
+ case SdpMediaSection::kVideo:
+ msection->AddCodec("120", "VP8", 90000, 1);
+ break;
+ case SdpMediaSection::kApplication:
+ msection->AddDataChannel("webrtc-datachannel", 0, 0, 0);
+ break;
+ default:
+ // We need to have something here to fit the grammar, this seems safe
+ // and 19 is a reserved payload type which should not be used by anyone.
+ msection->AddCodec("19", "reserved", 8000, 1);
+ }
+}
+
+void SdpHelper::GetBundleGroups(
+ const Sdp& sdp,
+ std::vector<SdpGroupAttributeList::Group>* bundleGroups) const {
+ if (sdp.GetAttributeList().HasAttribute(SdpAttribute::kGroupAttribute)) {
+ for (auto& group : sdp.GetAttributeList().GetGroup().mGroups) {
+ if (group.semantics == SdpGroupAttributeList::kBundle) {
+ bundleGroups->push_back(group);
+ }
+ }
+ }
+}
+
+nsresult SdpHelper::GetBundledMids(const Sdp& sdp, BundledMids* bundledMids) {
+ std::vector<SdpGroupAttributeList::Group> bundleGroups;
+ GetBundleGroups(sdp, &bundleGroups);
+
+ for (SdpGroupAttributeList::Group& group : bundleGroups) {
+ if (group.tags.empty()) {
+ continue;
+ }
+
+ const SdpMediaSection* msection(FindMsectionByMid(sdp, group.tags[0]));
+
+ if (!msection) {
+ SDP_SET_ERROR(
+ "mid specified for bundle transport in group attribute"
+ " does not exist in the SDP. (mid="
+ << group.tags[0] << ")");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (MsectionIsDisabled(*msection)) {
+ SDP_SET_ERROR(
+ "mid specified for bundle transport in group attribute"
+ " points at a disabled m-section. (mid="
+ << group.tags[0] << ")");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ for (const std::string& mid : group.tags) {
+ if (bundledMids->count(mid)) {
+ SDP_SET_ERROR("mid \'" << mid
+ << "\' appears more than once in a "
+ "BUNDLE group");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ (*bundledMids)[mid] = msection;
+ }
+ }
+
+ return NS_OK;
+}
+
+bool SdpHelper::OwnsTransport(const Sdp& sdp, uint16_t level,
+ sdp::SdpType type) {
+ auto& msection = sdp.GetMediaSection(level);
+
+ BundledMids bundledMids;
+ nsresult rv = GetBundledMids(sdp, &bundledMids);
+ if (NS_FAILED(rv)) {
+ // Should have been caught sooner.
+ MOZ_ASSERT(false);
+ return true;
+ }
+
+ return OwnsTransport(msection, bundledMids, type);
+}
+
+bool SdpHelper::OwnsTransport(const SdpMediaSection& msection,
+ const BundledMids& bundledMids,
+ sdp::SdpType type) {
+ if (MsectionIsDisabled(msection)) {
+ return false;
+ }
+
+ if (!msection.GetAttributeList().HasAttribute(SdpAttribute::kMidAttribute)) {
+ // No mid, definitely no bundle for this m-section
+ return true;
+ }
+ std::string mid(msection.GetAttributeList().GetMid());
+ if (type != sdp::kOffer || msection.GetAttributeList().HasAttribute(
+ SdpAttribute::kBundleOnlyAttribute)) {
+ // If this is an answer, or this m-section is marked bundle-only, the group
+ // attribute is authoritative. Otherwise, we aren't sure.
+ if (bundledMids.count(mid) && &msection != bundledMids.at(mid)) {
+ // mid is bundled, and isn't the bundle m-section
+ return false;
+ }
+ }
+
+ return true;
+}
+
+nsresult SdpHelper::GetMidFromLevel(const Sdp& sdp, uint16_t level,
+ std::string* mid) {
+ if (level >= sdp.GetMediaSectionCount()) {
+ SDP_SET_ERROR("Index " << level << " out of range");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ const SdpMediaSection& msection = sdp.GetMediaSection(level);
+ const SdpAttributeList& attrList = msection.GetAttributeList();
+
+ // grab the mid and set the outparam
+ if (attrList.HasAttribute(SdpAttribute::kMidAttribute)) {
+ *mid = attrList.GetMid();
+ }
+
+ return NS_OK;
+}
+
+nsresult SdpHelper::AddCandidateToSdp(Sdp* sdp,
+ const std::string& candidateUntrimmed,
+ uint16_t level,
+ const std::string& ufrag) {
+ if (level >= sdp->GetMediaSectionCount()) {
+ SDP_SET_ERROR("Index " << level << " out of range");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ SdpMediaSection& msection = sdp->GetMediaSection(level);
+ SdpAttributeList& attrList = msection.GetAttributeList();
+
+ if (!ufrag.empty()) {
+ if (!attrList.HasAttribute(SdpAttribute::kIceUfragAttribute) ||
+ attrList.GetIceUfrag() != ufrag) {
+ SDP_SET_ERROR("Unknown ufrag (" << ufrag << ")");
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ if (candidateUntrimmed.empty()) {
+ SetIceGatheringComplete(sdp, level, ufrag);
+ return NS_OK;
+ }
+
+ // Trim off '[a=]candidate:'
+ size_t begin = candidateUntrimmed.find(':');
+ if (begin == std::string::npos) {
+ SDP_SET_ERROR("Invalid candidate, no ':' (" << candidateUntrimmed << ")");
+ return NS_ERROR_INVALID_ARG;
+ }
+ ++begin;
+
+ std::string candidate = candidateUntrimmed.substr(begin);
+
+ UniquePtr<SdpMultiStringAttribute> candidates;
+ if (!attrList.HasAttribute(SdpAttribute::kCandidateAttribute)) {
+ // Create new
+ candidates.reset(
+ new SdpMultiStringAttribute(SdpAttribute::kCandidateAttribute));
+ } else {
+ // Copy existing
+ candidates.reset(new SdpMultiStringAttribute(
+ *static_cast<const SdpMultiStringAttribute*>(
+ attrList.GetAttribute(SdpAttribute::kCandidateAttribute))));
+ }
+ candidates->PushEntry(candidate);
+ attrList.SetAttribute(candidates.release());
+
+ return NS_OK;
+}
+
+nsresult SdpHelper::SetIceGatheringComplete(Sdp* sdp,
+ const std::string& ufrag) {
+ for (uint16_t i = 0; i < sdp->GetMediaSectionCount(); ++i) {
+ nsresult rv = SetIceGatheringComplete(sdp, i, ufrag);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+nsresult SdpHelper::SetIceGatheringComplete(Sdp* sdp, uint16_t level,
+ const std::string& ufrag) {
+ if (level >= sdp->GetMediaSectionCount()) {
+ SDP_SET_ERROR("Index " << level << " out of range");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ SdpMediaSection& msection = sdp->GetMediaSection(level);
+ SdpAttributeList& attrList = msection.GetAttributeList();
+
+ if (!ufrag.empty()) {
+ if (!attrList.HasAttribute(SdpAttribute::kIceUfragAttribute) ||
+ attrList.GetIceUfrag() != ufrag) {
+ SDP_SET_ERROR("Unknown ufrag (" << ufrag << ")");
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+
+ attrList.SetAttribute(
+ new SdpFlagAttribute(SdpAttribute::kEndOfCandidatesAttribute));
+ // Remove trickle-ice option
+ attrList.RemoveAttribute(SdpAttribute::kIceOptionsAttribute);
+ return NS_OK;
+}
+
+void SdpHelper::SetDefaultAddresses(const std::string& defaultCandidateAddr,
+ uint16_t defaultCandidatePort,
+ const std::string& defaultRtcpCandidateAddr,
+ uint16_t defaultRtcpCandidatePort,
+ SdpMediaSection* msection) {
+ SdpAttributeList& attrList = msection->GetAttributeList();
+
+ msection->GetConnection().SetAddress(defaultCandidateAddr);
+ msection->SetPort(defaultCandidatePort);
+ if (!defaultRtcpCandidateAddr.empty()) {
+ sdp::AddrType ipVersion = sdp::kIPv4;
+ if (defaultRtcpCandidateAddr.find(':') != std::string::npos) {
+ ipVersion = sdp::kIPv6;
+ }
+ attrList.SetAttribute(new SdpRtcpAttribute(defaultRtcpCandidatePort,
+ sdp::kInternet, ipVersion,
+ defaultRtcpCandidateAddr));
+ }
+}
+
+nsresult SdpHelper::GetIdsFromMsid(const Sdp& sdp,
+ const SdpMediaSection& msection,
+ std::vector<std::string>* streamIds) {
+ std::vector<SdpMsidAttributeList::Msid> allMsids;
+ nsresult rv = GetMsids(msection, &allMsids);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (allMsids.empty()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ streamIds->clear();
+ for (const auto& msid : allMsids) {
+ // "-" means no stream, see draft-ietf-mmusic-msid
+ // Remove duplicates, but leave order the same
+ if (msid.identifier != "-" &&
+ !std::count(streamIds->begin(), streamIds->end(), msid.identifier)) {
+ streamIds->push_back(msid.identifier);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult SdpHelper::GetMsids(const SdpMediaSection& msection,
+ std::vector<SdpMsidAttributeList::Msid>* msids) {
+ if (msection.GetAttributeList().HasAttribute(SdpAttribute::kMsidAttribute)) {
+ *msids = msection.GetAttributeList().GetMsid().mMsids;
+ return NS_OK;
+ }
+
+ // If there are no a=msid, can we find msids in ssrc attributes?
+ // (Chrome does not put plain-old msid attributes in its SDP)
+ if (msection.GetAttributeList().HasAttribute(SdpAttribute::kSsrcAttribute)) {
+ auto& ssrcs = msection.GetAttributeList().GetSsrc().mSsrcs;
+
+ for (auto i = ssrcs.begin(); i != ssrcs.end(); ++i) {
+ if (i->attribute.find("msid:") == 0) {
+ std::string streamId;
+ std::string trackId;
+ nsresult rv = ParseMsid(i->attribute, &streamId, &trackId);
+ NS_ENSURE_SUCCESS(rv, rv);
+ msids->push_back({streamId, trackId});
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult SdpHelper::ParseMsid(const std::string& msidAttribute,
+ std::string* streamId, std::string* trackId) {
+ // Would be nice if SdpSsrcAttributeList could parse out the contained
+ // attribute, but at least the parse here is simple.
+ // We are being very forgiving here wrt whitespace; tabs are not actually
+ // allowed, nor is leading/trailing whitespace.
+ size_t streamIdStart = msidAttribute.find_first_not_of(" \t", 5);
+ // We do not assume the appdata token is here, since this is not
+ // necessarily a webrtc msid
+ if (streamIdStart == std::string::npos) {
+ SDP_SET_ERROR("Malformed source-level msid attribute: " << msidAttribute);
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ size_t streamIdEnd = msidAttribute.find_first_of(" \t", streamIdStart);
+ if (streamIdEnd == std::string::npos) {
+ streamIdEnd = msidAttribute.size();
+ }
+
+ size_t trackIdStart = msidAttribute.find_first_not_of(" \t", streamIdEnd);
+ if (trackIdStart == std::string::npos) {
+ trackIdStart = msidAttribute.size();
+ }
+
+ size_t trackIdEnd = msidAttribute.find_first_of(" \t", trackIdStart);
+ if (trackIdEnd == std::string::npos) {
+ trackIdEnd = msidAttribute.size();
+ }
+
+ size_t streamIdSize = streamIdEnd - streamIdStart;
+ size_t trackIdSize = trackIdEnd - trackIdStart;
+
+ *streamId = msidAttribute.substr(streamIdStart, streamIdSize);
+ *trackId = msidAttribute.substr(trackIdStart, trackIdSize);
+ return NS_OK;
+}
+
+void SdpHelper::SetupMsidSemantic(const std::vector<std::string>& msids,
+ Sdp* sdp) const {
+ if (!msids.empty()) {
+ UniquePtr<SdpMsidSemanticAttributeList> msidSemantics(
+ new SdpMsidSemanticAttributeList);
+ msidSemantics->PushEntry("WMS", msids);
+ sdp->GetAttributeList().SetAttribute(msidSemantics.release());
+ }
+}
+
+std::string SdpHelper::GetCNAME(const SdpMediaSection& msection) const {
+ if (msection.GetAttributeList().HasAttribute(SdpAttribute::kSsrcAttribute)) {
+ auto& ssrcs = msection.GetAttributeList().GetSsrc().mSsrcs;
+ for (auto i = ssrcs.begin(); i != ssrcs.end(); ++i) {
+ if (i->attribute.find("cname:") == 0) {
+ return i->attribute.substr(6);
+ }
+ }
+ }
+ return "";
+}
+
+const SdpMediaSection* SdpHelper::FindMsectionByMid(
+ const Sdp& sdp, const std::string& mid) const {
+ for (size_t i = 0; i < sdp.GetMediaSectionCount(); ++i) {
+ auto& attrs = sdp.GetMediaSection(i).GetAttributeList();
+ if (attrs.HasAttribute(SdpAttribute::kMidAttribute) &&
+ attrs.GetMid() == mid) {
+ return &sdp.GetMediaSection(i);
+ }
+ }
+ return nullptr;
+}
+
+SdpMediaSection* SdpHelper::FindMsectionByMid(Sdp& sdp,
+ const std::string& mid) const {
+ for (size_t i = 0; i < sdp.GetMediaSectionCount(); ++i) {
+ auto& attrs = sdp.GetMediaSection(i).GetAttributeList();
+ if (attrs.HasAttribute(SdpAttribute::kMidAttribute) &&
+ attrs.GetMid() == mid) {
+ return &sdp.GetMediaSection(i);
+ }
+ }
+ return nullptr;
+}
+
+nsresult SdpHelper::CopyStickyParams(const SdpMediaSection& source,
+ SdpMediaSection* dest) {
+ auto& sourceAttrs = source.GetAttributeList();
+ auto& destAttrs = dest->GetAttributeList();
+
+ // There's no reason to renegotiate rtcp-mux
+ if (sourceAttrs.HasAttribute(SdpAttribute::kRtcpMuxAttribute)) {
+ destAttrs.SetAttribute(
+ new SdpFlagAttribute(SdpAttribute::kRtcpMuxAttribute));
+ }
+
+ // mid should stay the same
+ if (sourceAttrs.HasAttribute(SdpAttribute::kMidAttribute)) {
+ destAttrs.SetAttribute(new SdpStringAttribute(SdpAttribute::kMidAttribute,
+ sourceAttrs.GetMid()));
+ }
+
+ // Keep RTCP mode setting
+ if (sourceAttrs.HasAttribute(SdpAttribute::kRtcpRsizeAttribute) &&
+ source.GetMediaType() == SdpMediaSection::kVideo) {
+ destAttrs.SetAttribute(
+ new SdpFlagAttribute(SdpAttribute::kRtcpRsizeAttribute));
+ }
+
+ return NS_OK;
+}
+
+bool SdpHelper::HasRtcp(SdpMediaSection::Protocol proto) const {
+ switch (proto) {
+ case SdpMediaSection::kRtpAvpf:
+ case SdpMediaSection::kDccpRtpAvpf:
+ case SdpMediaSection::kDccpRtpSavpf:
+ case SdpMediaSection::kRtpSavpf:
+ case SdpMediaSection::kUdpTlsRtpSavpf:
+ case SdpMediaSection::kTcpDtlsRtpSavpf:
+ case SdpMediaSection::kDccpTlsRtpSavpf:
+ return true;
+ case SdpMediaSection::kRtpAvp:
+ case SdpMediaSection::kUdp:
+ case SdpMediaSection::kVat:
+ case SdpMediaSection::kRtp:
+ case SdpMediaSection::kUdptl:
+ case SdpMediaSection::kTcp:
+ case SdpMediaSection::kTcpRtpAvp:
+ case SdpMediaSection::kRtpSavp:
+ case SdpMediaSection::kTcpBfcp:
+ case SdpMediaSection::kTcpTlsBfcp:
+ case SdpMediaSection::kTcpTls:
+ case SdpMediaSection::kFluteUdp:
+ case SdpMediaSection::kTcpMsrp:
+ case SdpMediaSection::kTcpTlsMsrp:
+ case SdpMediaSection::kDccp:
+ case SdpMediaSection::kDccpRtpAvp:
+ case SdpMediaSection::kDccpRtpSavp:
+ case SdpMediaSection::kUdpTlsRtpSavp:
+ case SdpMediaSection::kTcpDtlsRtpSavp:
+ case SdpMediaSection::kDccpTlsRtpSavp:
+ case SdpMediaSection::kUdpMbmsFecRtpAvp:
+ case SdpMediaSection::kUdpMbmsFecRtpSavp:
+ case SdpMediaSection::kUdpMbmsRepair:
+ case SdpMediaSection::kFecUdp:
+ case SdpMediaSection::kUdpFec:
+ case SdpMediaSection::kTcpMrcpv2:
+ case SdpMediaSection::kTcpTlsMrcpv2:
+ case SdpMediaSection::kPstn:
+ case SdpMediaSection::kUdpTlsUdptl:
+ case SdpMediaSection::kSctp:
+ case SdpMediaSection::kDtlsSctp:
+ case SdpMediaSection::kUdpDtlsSctp:
+ case SdpMediaSection::kTcpDtlsSctp:
+ return false;
+ }
+ MOZ_CRASH("Unknown protocol, probably corruption.");
+}
+
+SdpMediaSection::Protocol SdpHelper::GetProtocolForMediaType(
+ SdpMediaSection::MediaType type) {
+ if (type == SdpMediaSection::kApplication) {
+ return SdpMediaSection::kUdpDtlsSctp;
+ }
+
+ return SdpMediaSection::kUdpTlsRtpSavpf;
+}
+
+void SdpHelper::AppendSdpParseErrors(
+ const std::vector<std::pair<size_t, std::string> >& aErrors,
+ std::string* aErrorString) {
+ std::ostringstream os;
+ for (auto i = aErrors.begin(); i != aErrors.end(); ++i) {
+ os << "SDP Parse Error on line " << i->first << ": " + i->second
+ << std::endl;
+ }
+ *aErrorString += os.str();
+}
+
+/* static */
+bool SdpHelper::GetPtAsInt(const std::string& ptString, uint16_t* ptOutparam) {
+ char* end;
+ unsigned long pt = strtoul(ptString.c_str(), &end, 10);
+ size_t length = static_cast<size_t>(end - ptString.c_str());
+ if ((pt > UINT16_MAX) || (length != ptString.size())) {
+ return false;
+ }
+ *ptOutparam = pt;
+ return true;
+}
+
+void SdpHelper::NegotiateAndAddExtmaps(
+ const SdpMediaSection& remoteMsection,
+ std::vector<SdpExtmapAttributeList::Extmap>& localExtensions,
+ SdpMediaSection* localMsection) {
+ if (!remoteMsection.GetAttributeList().HasAttribute(
+ SdpAttribute::kExtmapAttribute)) {
+ return;
+ }
+
+ UniquePtr<SdpExtmapAttributeList> localExtmap(new SdpExtmapAttributeList);
+ auto& theirExtmap = remoteMsection.GetAttributeList().GetExtmap().mExtmaps;
+ for (const auto& theirExt : theirExtmap) {
+ for (auto& ourExt : localExtensions) {
+ if (theirExt.entry == 0) {
+ // 0 is invalid, ignore it
+ continue;
+ }
+
+ if (theirExt.extensionname != ourExt.extensionname) {
+ continue;
+ }
+
+ ourExt.direction = reverse(theirExt.direction) & ourExt.direction;
+ if (ourExt.direction == SdpDirectionAttribute::Direction::kInactive) {
+ continue;
+ }
+
+ // RFC 5285 says that ids >= 4096 can be used by the offerer to
+ // force the answerer to pick, otherwise the value in the offer is
+ // used.
+ if (theirExt.entry < 4096) {
+ ourExt.entry = theirExt.entry;
+ }
+
+ localExtmap->mExtmaps.push_back(ourExt);
+ }
+ }
+
+ if (!localExtmap->mExtmaps.empty()) {
+ localMsection->GetAttributeList().SetAttribute(localExtmap.release());
+ }
+}
+
+static bool AttributeListMatch(const SdpAttributeList& list1,
+ const SdpAttributeList& list2) {
+ // TODO: Consider adding telemetry in this function to record which
+ // attributes don't match. See Bug 1432955.
+ for (int i = SdpAttribute::kFirstAttribute; i <= SdpAttribute::kLastAttribute;
+ i++) {
+ auto attributeType = static_cast<SdpAttribute::AttributeType>(i);
+ // TODO: We should do more thorough checking here, e.g. serialize and
+ // compare strings. See Bug 1439690.
+ if (list1.HasAttribute(attributeType, false) !=
+ list2.HasAttribute(attributeType, false)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool MediaSectionMatch(const SdpMediaSection& mediaSection1,
+ const SdpMediaSection& mediaSection2) {
+ // TODO: We should do more thorough checking in this function.
+ // See Bug 1439690.
+ if (!AttributeListMatch(mediaSection1.GetAttributeList(),
+ mediaSection2.GetAttributeList())) {
+ return false;
+ }
+ if (mediaSection1.GetPort() != mediaSection2.GetPort()) {
+ return false;
+ }
+ const std::vector<std::string>& formats1 = mediaSection1.GetFormats();
+ const std::vector<std::string>& formats2 = mediaSection2.GetFormats();
+ auto formats1Set = std::set<std::string>(formats1.begin(), formats1.end());
+ auto formats2Set = std::set<std::string>(formats2.begin(), formats2.end());
+ if (formats1Set != formats2Set) {
+ return false;
+ }
+ return true;
+}
+
+bool SdpHelper::SdpMatch(const Sdp& sdp1, const Sdp& sdp2) {
+ if (sdp1.GetMediaSectionCount() != sdp2.GetMediaSectionCount()) {
+ return false;
+ }
+ if (!AttributeListMatch(sdp1.GetAttributeList(), sdp2.GetAttributeList())) {
+ return false;
+ }
+ for (size_t i = 0; i < sdp1.GetMediaSectionCount(); i++) {
+ const SdpMediaSection& mediaSection1 = sdp1.GetMediaSection(i);
+ const SdpMediaSection& mediaSection2 = sdp2.GetMediaSection(i);
+ if (!MediaSectionMatch(mediaSection1, mediaSection2)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+nsresult SdpHelper::ValidateTransportAttributes(const Sdp& aSdp,
+ sdp::SdpType aType) {
+ BundledMids bundledMids;
+ nsresult rv = GetBundledMids(aSdp, &bundledMids);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (size_t level = 0; level < aSdp.GetMediaSectionCount(); ++level) {
+ const auto& msection = aSdp.GetMediaSection(level);
+ if (OwnsTransport(msection, bundledMids, aType)) {
+ const auto& mediaAttrs = msection.GetAttributeList();
+ if (mediaAttrs.GetIceUfrag().empty()) {
+ SDP_SET_ERROR("Invalid description, no ice-ufrag attribute at level "
+ << level);
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (mediaAttrs.GetIcePwd().empty()) {
+ SDP_SET_ERROR("Invalid description, no ice-pwd attribute at level "
+ << level);
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (!mediaAttrs.HasAttribute(SdpAttribute::kFingerprintAttribute)) {
+ SDP_SET_ERROR("Invalid description, no fingerprint attribute at level "
+ << level);
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ const SdpFingerprintAttributeList& fingerprints(
+ mediaAttrs.GetFingerprint());
+ if (fingerprints.mFingerprints.empty()) {
+ SDP_SET_ERROR(
+ "Invalid description, no supported fingerprint algorithms present "
+ "at level "
+ << level);
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (mediaAttrs.HasAttribute(SdpAttribute::kSetupAttribute, true)) {
+ if (mediaAttrs.GetSetup().mRole == SdpSetupAttribute::kHoldconn) {
+ SDP_SET_ERROR(
+ "Invalid description, illegal setup attribute \"holdconn\" "
+ "at level "
+ << level);
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (aType == sdp::kAnswer &&
+ mediaAttrs.GetSetup().mRole == SdpSetupAttribute::kActpass) {
+ SDP_SET_ERROR(
+ "Invalid answer, illegal setup attribute \"actpass\" at level "
+ << level);
+ return NS_ERROR_INVALID_ARG;
+ }
+ } else if (aType == sdp::kOffer) {
+ SDP_SET_ERROR("Invalid offer, no setup attribute at level " << level);
+ return NS_ERROR_INVALID_ARG;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/sdp/SdpHelper.h b/dom/media/webrtc/sdp/SdpHelper.h
new file mode 100644
index 0000000000..3c65d01442
--- /dev/null
+++ b/dom/media/webrtc/sdp/SdpHelper.h
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _SDPHELPER_H_
+#define _SDPHELPER_H_
+
+#include "nsError.h"
+
+#include "sdp/SdpMediaSection.h"
+#include "sdp/SdpAttribute.h"
+
+#include "transport/m_cpp_utils.h"
+
+#include <string>
+#include <map>
+#include <vector>
+
+namespace mozilla {
+class SdpMediaSection;
+class Sdp;
+
+class SdpHelper {
+ public:
+ // Takes a std::string* into which error strings will be written for the
+ // lifetime of the SdpHelper.
+ explicit SdpHelper(std::string* errorDest) : mLastError(*errorDest) {}
+ ~SdpHelper() {}
+
+ nsresult GetComponent(const std::string& candidate, size_t* component);
+ nsresult CopyTransportParams(size_t numComponents,
+ const SdpMediaSection& source,
+ SdpMediaSection* dest);
+ bool AreOldTransportParamsValid(const Sdp& oldAnswer,
+ const Sdp& offerersPreviousSdp,
+ const Sdp& newOffer, size_t level);
+ bool IceCredentialsDiffer(const SdpMediaSection& msection1,
+ const SdpMediaSection& msection2);
+
+ bool MsectionIsDisabled(const SdpMediaSection& msection) const;
+ static void DisableMsection(Sdp* sdp, SdpMediaSection* msection);
+
+ // Maps each mid to the m-section that owns its bundle transport.
+ // Mids that do not appear in an a=group:BUNDLE do not appear here.
+ typedef std::map<std::string, const SdpMediaSection*> BundledMids;
+
+ nsresult GetBundledMids(const Sdp& sdp, BundledMids* bundledMids);
+
+ bool OwnsTransport(const Sdp& localSdp, uint16_t level, sdp::SdpType type);
+ bool OwnsTransport(const SdpMediaSection& msection,
+ const BundledMids& bundledMids, sdp::SdpType type);
+ void GetBundleGroups(const Sdp& sdp,
+ std::vector<SdpGroupAttributeList::Group>* groups) const;
+
+ nsresult GetMidFromLevel(const Sdp& sdp, uint16_t level, std::string* mid);
+ nsresult GetIdsFromMsid(const Sdp& sdp, const SdpMediaSection& msection,
+ std::vector<std::string>* streamId);
+ nsresult GetMsids(const SdpMediaSection& msection,
+ std::vector<SdpMsidAttributeList::Msid>* msids);
+ nsresult ParseMsid(const std::string& msidAttribute, std::string* streamId,
+ std::string* trackId);
+ nsresult AddCandidateToSdp(Sdp* sdp, const std::string& candidate,
+ uint16_t level, const std::string& ufrag);
+ nsresult SetIceGatheringComplete(Sdp* sdp, const std::string& ufrag);
+ nsresult SetIceGatheringComplete(Sdp* sdp, uint16_t level,
+ const std::string& ufrag);
+ void SetDefaultAddresses(const std::string& defaultCandidateAddr,
+ uint16_t defaultCandidatePort,
+ const std::string& defaultRtcpCandidateAddr,
+ uint16_t defaultRtcpCandidatePort,
+ SdpMediaSection* msection);
+ void SetupMsidSemantic(const std::vector<std::string>& msids, Sdp* sdp) const;
+
+ std::string GetCNAME(const SdpMediaSection& msection) const;
+
+ SdpMediaSection* FindMsectionByMid(Sdp& sdp, const std::string& mid) const;
+
+ const SdpMediaSection* FindMsectionByMid(const Sdp& sdp,
+ const std::string& mid) const;
+
+ nsresult CopyStickyParams(const SdpMediaSection& source,
+ SdpMediaSection* dest);
+ bool HasRtcp(SdpMediaSection::Protocol proto) const;
+ static SdpMediaSection::Protocol GetProtocolForMediaType(
+ SdpMediaSection::MediaType type);
+ void AppendSdpParseErrors(
+ const std::vector<std::pair<size_t, std::string> >& aErrors,
+ std::string* aErrorString);
+
+ static bool GetPtAsInt(const std::string& ptString, uint16_t* ptOutparam);
+
+ void NegotiateAndAddExtmaps(
+ const SdpMediaSection& remoteMsection,
+ std::vector<SdpExtmapAttributeList::Extmap>& localExtensions,
+ SdpMediaSection* localMsection);
+
+ bool SdpMatch(const Sdp& sdp1, const Sdp& sdp2);
+ nsresult ValidateTransportAttributes(const Sdp& aSdp, sdp::SdpType aType);
+
+ private:
+ std::string& mLastError;
+
+ DISALLOW_COPY_ASSIGN(SdpHelper);
+};
+} // namespace mozilla
+
+#endif // _SDPHELPER_H_
diff --git a/dom/media/webrtc/sdp/SdpLog.cpp b/dom/media/webrtc/sdp/SdpLog.cpp
new file mode 100644
index 0000000000..a841ac7412
--- /dev/null
+++ b/dom/media/webrtc/sdp/SdpLog.cpp
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include <type_traits>
+
+#include "sdp/SdpLog.h"
+#include "common/browser_logging/CSFLog.h"
+
+namespace mozilla {
+LazyLogModule SdpLog("sdp");
+} // namespace mozilla
+
+// For compile time enum comparison
+template <typename E, typename F>
+constexpr bool compareEnum(E e, F f) {
+ return static_cast<typename std::underlying_type<E>::type>(e) ==
+ static_cast<typename std::underlying_type<F>::type>(f);
+}
+
+CSFLogLevel SDPToCSFLogLevel(const SDPLogLevel priority) {
+ static_assert(compareEnum(SDP_LOG_ERROR, CSF_LOG_ERROR));
+ static_assert(compareEnum(SDP_LOG_WARNING, CSF_LOG_WARNING));
+ static_assert(compareEnum(SDP_LOG_INFO, CSF_LOG_INFO));
+ static_assert(compareEnum(SDP_LOG_DEBUG, CSF_LOG_DEBUG));
+ static_assert(compareEnum(SDP_LOG_VERBOSE, CSF_LOG_VERBOSE));
+
+ // Check that all SDP_LOG_* cases are covered. It compiles to nothing.
+ switch (priority) {
+ case SDP_LOG_ERROR:
+ case SDP_LOG_WARNING:
+ case SDP_LOG_INFO:
+ case SDP_LOG_DEBUG:
+ case SDP_LOG_VERBOSE:
+ break;
+ }
+
+ // Ditto for CSF_LOG_*
+ switch (static_cast<CSFLogLevel>(priority)) {
+ case CSF_LOG_ERROR:
+ case CSF_LOG_WARNING:
+ case CSF_LOG_INFO:
+ case CSF_LOG_DEBUG:
+ case CSF_LOG_VERBOSE:
+ break;
+ }
+
+ return static_cast<CSFLogLevel>(priority);
+}
+
+void SDPLog(SDPLogLevel priority, const char* sourceFile, int sourceLine,
+ const char* tag, const char* format, ...) {
+ va_list ap;
+ va_start(ap, format);
+ CSFLogV(SDPToCSFLogLevel(priority), sourceFile, sourceLine, tag, format, ap);
+ va_end(ap);
+}
+
+void SDPLogV(SDPLogLevel priority, const char* sourceFile, int sourceLine,
+ const char* tag, const char* format, va_list args) {
+ CSFLogV(SDPToCSFLogLevel(priority), sourceFile, sourceLine, tag, format,
+ args);
+}
+
+int SDPLogTestLevel(SDPLogLevel priority) {
+ return CSFLogTestLevel(SDPToCSFLogLevel(priority));
+}
diff --git a/dom/media/webrtc/sdp/SdpLog.h b/dom/media/webrtc/sdp/SdpLog.h
new file mode 100644
index 0000000000..63bceff889
--- /dev/null
+++ b/dom/media/webrtc/sdp/SdpLog.h
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _SDP_LOG_H_
+#define _SDP_LOG_H_
+
+#include "mozilla/Logging.h"
+#include "sdp_log.h"
+
+namespace mozilla {
+extern mozilla::LazyLogModule SdpLog;
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/sdp/SdpMediaSection.cpp b/dom/media/webrtc/sdp/SdpMediaSection.cpp
new file mode 100644
index 0000000000..dd8f6e54fe
--- /dev/null
+++ b/dom/media/webrtc/sdp/SdpMediaSection.cpp
@@ -0,0 +1,197 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "sdp/SdpMediaSection.h"
+
+namespace mozilla {
+const SdpFmtpAttributeList::Parameters* SdpMediaSection::FindFmtp(
+ const std::string& pt) const {
+ const SdpAttributeList& attrs = GetAttributeList();
+
+ if (attrs.HasAttribute(SdpAttribute::kFmtpAttribute)) {
+ for (auto& fmtpAttr : attrs.GetFmtp().mFmtps) {
+ if (fmtpAttr.format == pt && fmtpAttr.parameters) {
+ return fmtpAttr.parameters.get();
+ }
+ }
+ }
+ return nullptr;
+}
+
+void SdpMediaSection::SetFmtp(const SdpFmtpAttributeList::Fmtp& fmtpToSet) {
+ UniquePtr<SdpFmtpAttributeList> fmtps(new SdpFmtpAttributeList);
+
+ if (GetAttributeList().HasAttribute(SdpAttribute::kFmtpAttribute)) {
+ *fmtps = GetAttributeList().GetFmtp();
+ }
+
+ bool found = false;
+ for (SdpFmtpAttributeList::Fmtp& fmtp : fmtps->mFmtps) {
+ if (fmtp.format == fmtpToSet.format) {
+ fmtp = fmtpToSet;
+ found = true;
+ }
+ }
+
+ if (!found) {
+ fmtps->mFmtps.push_back(fmtpToSet);
+ }
+
+ GetAttributeList().SetAttribute(fmtps.release());
+}
+
+void SdpMediaSection::RemoveFmtp(const std::string& pt) {
+ UniquePtr<SdpFmtpAttributeList> fmtps(new SdpFmtpAttributeList);
+
+ SdpAttributeList& attrList = GetAttributeList();
+ if (attrList.HasAttribute(SdpAttribute::kFmtpAttribute)) {
+ *fmtps = attrList.GetFmtp();
+ }
+
+ for (size_t i = 0; i < fmtps->mFmtps.size(); ++i) {
+ if (pt == fmtps->mFmtps[i].format) {
+ fmtps->mFmtps.erase(fmtps->mFmtps.begin() + i);
+ break;
+ }
+ }
+
+ attrList.SetAttribute(fmtps.release());
+}
+
+const SdpRtpmapAttributeList::Rtpmap* SdpMediaSection::FindRtpmap(
+ const std::string& pt) const {
+ auto& attrs = GetAttributeList();
+ if (!attrs.HasAttribute(SdpAttribute::kRtpmapAttribute)) {
+ return nullptr;
+ }
+
+ const SdpRtpmapAttributeList& rtpmap = attrs.GetRtpmap();
+ if (!rtpmap.HasEntry(pt)) {
+ return nullptr;
+ }
+
+ return &rtpmap.GetEntry(pt);
+}
+
+const SdpSctpmapAttributeList::Sctpmap* SdpMediaSection::GetSctpmap() const {
+ auto& attrs = GetAttributeList();
+ if (!attrs.HasAttribute(SdpAttribute::kSctpmapAttribute)) {
+ return nullptr;
+ }
+
+ const SdpSctpmapAttributeList& sctpmap = attrs.GetSctpmap();
+ if (sctpmap.mSctpmaps.empty()) {
+ return nullptr;
+ }
+
+ return &sctpmap.GetFirstEntry();
+}
+
+uint32_t SdpMediaSection::GetSctpPort() const {
+ auto& attrs = GetAttributeList();
+ if (!attrs.HasAttribute(SdpAttribute::kSctpPortAttribute)) {
+ return 0;
+ }
+
+ return attrs.GetSctpPort();
+}
+
+bool SdpMediaSection::GetMaxMessageSize(uint32_t* size) const {
+ *size = 0;
+
+ auto& attrs = GetAttributeList();
+ if (!attrs.HasAttribute(SdpAttribute::kMaxMessageSizeAttribute)) {
+ return false;
+ }
+
+ *size = attrs.GetMaxMessageSize();
+ return true;
+}
+
+bool SdpMediaSection::HasRtcpFb(const std::string& pt,
+ SdpRtcpFbAttributeList::Type type,
+ const std::string& subType) const {
+ const SdpAttributeList& attrs(GetAttributeList());
+
+ if (!attrs.HasAttribute(SdpAttribute::kRtcpFbAttribute)) {
+ return false;
+ }
+
+ for (auto& rtcpfb : attrs.GetRtcpFb().mFeedbacks) {
+ if (rtcpfb.type == type) {
+ if (rtcpfb.pt == "*" || rtcpfb.pt == pt) {
+ if (rtcpfb.parameter == subType) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+SdpRtcpFbAttributeList SdpMediaSection::GetRtcpFbs() const {
+ SdpRtcpFbAttributeList result;
+ if (GetAttributeList().HasAttribute(SdpAttribute::kRtcpFbAttribute)) {
+ result = GetAttributeList().GetRtcpFb();
+ }
+ return result;
+}
+
+void SdpMediaSection::SetRtcpFbs(const SdpRtcpFbAttributeList& rtcpfbs) {
+ if (rtcpfbs.mFeedbacks.empty()) {
+ GetAttributeList().RemoveAttribute(SdpAttribute::kRtcpFbAttribute);
+ return;
+ }
+
+ GetAttributeList().SetAttribute(new SdpRtcpFbAttributeList(rtcpfbs));
+}
+
+void SdpMediaSection::SetSsrcs(const std::vector<uint32_t>& ssrcs,
+ const std::string& cname) {
+ if (ssrcs.empty()) {
+ GetAttributeList().RemoveAttribute(SdpAttribute::kSsrcAttribute);
+ return;
+ }
+
+ UniquePtr<SdpSsrcAttributeList> ssrcAttr(new SdpSsrcAttributeList);
+ for (auto ssrc : ssrcs) {
+ // When using ssrc attributes, we are required to at least have a cname.
+ // (See https://tools.ietf.org/html/rfc5576#section-6.1)
+ std::string cnameAttr("cname:");
+ cnameAttr += cname;
+ ssrcAttr->PushEntry(ssrc, cnameAttr);
+ }
+
+ GetAttributeList().SetAttribute(ssrcAttr.release());
+}
+
+void SdpMediaSection::AddMsid(const std::string& id,
+ const std::string& appdata) {
+ UniquePtr<SdpMsidAttributeList> msids(new SdpMsidAttributeList);
+ if (GetAttributeList().HasAttribute(SdpAttribute::kMsidAttribute)) {
+ msids->mMsids = GetAttributeList().GetMsid().mMsids;
+ }
+ msids->PushEntry(id, appdata);
+ GetAttributeList().SetAttribute(msids.release());
+}
+
+const SdpRidAttributeList::Rid* SdpMediaSection::FindRid(
+ const std::string& id) const {
+ if (!GetAttributeList().HasAttribute(SdpAttribute::kRidAttribute)) {
+ return nullptr;
+ }
+
+ for (const auto& rid : GetAttributeList().GetRid().mRids) {
+ if (rid.id == id) {
+ return &rid;
+ }
+ }
+
+ return nullptr;
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/sdp/SdpMediaSection.h b/dom/media/webrtc/sdp/SdpMediaSection.h
new file mode 100644
index 0000000000..a205551a1d
--- /dev/null
+++ b/dom/media/webrtc/sdp/SdpMediaSection.h
@@ -0,0 +1,317 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _SDPMEDIASECTION_H_
+#define _SDPMEDIASECTION_H_
+
+#include "mozilla/Maybe.h"
+#include "sdp/SdpEnum.h"
+#include "sdp/SdpAttributeList.h"
+#include <string>
+#include <vector>
+#include <sstream>
+
+namespace mozilla {
+
+class SdpAttributeList;
+
+class SdpConnection;
+
+class SdpMediaSection {
+ public:
+ enum MediaType { kAudio, kVideo, kText, kApplication, kMessage };
+ // don't add to enum to avoid warnings about unhandled enum values
+ static const size_t kMediaTypes = static_cast<size_t>(kMessage) + 1;
+
+ enum Protocol {
+ kRtpAvp, // RTP/AVP [RFC4566]
+ kUdp, // udp [RFC4566]
+ kVat, // vat [historic]
+ kRtp, // rtp [historic]
+ kUdptl, // udptl [ITU-T]
+ kTcp, // TCP [RFC4145]
+ kRtpAvpf, // RTP/AVPF [RFC4585]
+ kTcpRtpAvp, // TCP/RTP/AVP [RFC4571]
+ kRtpSavp, // RTP/SAVP [RFC3711]
+ kTcpBfcp, // TCP/BFCP [RFC4583]
+ kTcpTlsBfcp, // TCP/TLS/BFCP [RFC4583]
+ kTcpTls, // TCP/TLS [RFC4572]
+ kFluteUdp, // FLUTE/UDP [RFC-mehta-rmt-flute-sdp-05]
+ kTcpMsrp, // TCP/MSRP [RFC4975]
+ kTcpTlsMsrp, // TCP/TLS/MSRP [RFC4975]
+ kDccp, // DCCP [RFC5762]
+ kDccpRtpAvp, // DCCP/RTP/AVP [RFC5762]
+ kDccpRtpSavp, // DCCP/RTP/SAVP [RFC5762]
+ kDccpRtpAvpf, // DCCP/RTP/AVPF [RFC5762]
+ kDccpRtpSavpf, // DCCP/RTP/SAVPF [RFC5762]
+ kRtpSavpf, // RTP/SAVPF [RFC5124]
+ kUdpTlsRtpSavp, // UDP/TLS/RTP/SAVP [RFC5764]
+ kTcpDtlsRtpSavp, // TCP/DTLS/RTP/SAVP [RFC7850]
+ kDccpTlsRtpSavp, // DCCP/TLS/RTP/SAVP [RFC5764]
+ kUdpTlsRtpSavpf, // UDP/TLS/RTP/SAVPF [RFC5764]
+ kTcpDtlsRtpSavpf, // TCP/DTLS/RTP/SAVPF [RFC7850]
+ kDccpTlsRtpSavpf, // DCCP/TLS/RTP/SAVPF [RFC5764]
+ kUdpMbmsFecRtpAvp, // UDP/MBMS-FEC/RTP/AVP [RFC6064]
+ kUdpMbmsFecRtpSavp, // UDP/MBMS-FEC/RTP/SAVP [RFC6064]
+ kUdpMbmsRepair, // UDP/MBMS-REPAIR [RFC6064]
+ kFecUdp, // FEC/UDP [RFC6364]
+ kUdpFec, // UDP/FEC [RFC6364]
+ kTcpMrcpv2, // TCP/MRCPv2 [RFC6787]
+ kTcpTlsMrcpv2, // TCP/TLS/MRCPv2 [RFC6787]
+ kPstn, // PSTN [RFC7195]
+ kUdpTlsUdptl, // UDP/TLS/UDPTL [RFC7345]
+ kSctp, // SCTP [draft-ietf-mmusic-sctp-sdp-07]
+ kDtlsSctp, // DTLS/SCTP [draft-ietf-mmusic-sctp-sdp-07]
+ kUdpDtlsSctp, // UDP/DTLS/SCTP [draft-ietf-mmusic-sctp-sdp-21]
+ kTcpDtlsSctp // TCP/DTLS/SCTP [draft-ietf-mmusic-sctp-sdp-21]
+ };
+
+ explicit SdpMediaSection(size_t level) : mLevel(level) {}
+
+ virtual MediaType GetMediaType() const = 0;
+ virtual unsigned int GetPort() const = 0;
+ virtual void SetPort(unsigned int port) = 0;
+ virtual unsigned int GetPortCount() const = 0;
+ virtual Protocol GetProtocol() const = 0;
+ virtual const SdpConnection& GetConnection() const = 0;
+ virtual SdpConnection& GetConnection() = 0;
+ virtual uint32_t GetBandwidth(const std::string& type) const = 0;
+ virtual const std::vector<std::string>& GetFormats() const = 0;
+
+ std::vector<std::string> GetFormatsForSimulcastVersion(
+ size_t simulcastVersion, bool send, bool recv) const;
+ virtual const SdpAttributeList& GetAttributeList() const = 0;
+ virtual SdpAttributeList& GetAttributeList() = 0;
+
+ virtual SdpDirectionAttribute GetDirectionAttribute() const = 0;
+
+ virtual void Serialize(std::ostream&) const = 0;
+
+ virtual void AddCodec(const std::string& pt, const std::string& name,
+ uint32_t clockrate, uint16_t channels) = 0;
+ virtual void ClearCodecs() = 0;
+
+ virtual void AddDataChannel(const std::string& name, uint16_t port,
+ uint16_t streams, uint32_t message_size) = 0;
+
+ size_t GetLevel() const { return mLevel; }
+
+ inline bool IsReceiving() const { return GetDirection() & sdp::kRecv; }
+
+ inline bool IsSending() const { return GetDirection() & sdp::kSend; }
+
+ inline void SetReceiving(bool receiving) {
+ auto direction = GetDirection();
+ if (direction & sdp::kSend) {
+ SetDirection(receiving ? SdpDirectionAttribute::kSendrecv
+ : SdpDirectionAttribute::kSendonly);
+ } else {
+ SetDirection(receiving ? SdpDirectionAttribute::kRecvonly
+ : SdpDirectionAttribute::kInactive);
+ }
+ }
+
+ inline void SetSending(bool sending) {
+ auto direction = GetDirection();
+ if (direction & sdp::kRecv) {
+ SetDirection(sending ? SdpDirectionAttribute::kSendrecv
+ : SdpDirectionAttribute::kRecvonly);
+ } else {
+ SetDirection(sending ? SdpDirectionAttribute::kSendonly
+ : SdpDirectionAttribute::kInactive);
+ }
+ }
+
+ inline void SetDirection(SdpDirectionAttribute::Direction direction) {
+ GetAttributeList().SetAttribute(new SdpDirectionAttribute(direction));
+ }
+
+ inline SdpDirectionAttribute::Direction GetDirection() const {
+ return GetDirectionAttribute().mValue;
+ }
+
+ const SdpFmtpAttributeList::Parameters* FindFmtp(const std::string& pt) const;
+ void SetFmtp(const SdpFmtpAttributeList::Fmtp& fmtp);
+ void RemoveFmtp(const std::string& pt);
+ const SdpRtpmapAttributeList::Rtpmap* FindRtpmap(const std::string& pt) const;
+ const SdpSctpmapAttributeList::Sctpmap* GetSctpmap() const;
+ uint32_t GetSctpPort() const;
+ bool GetMaxMessageSize(uint32_t* size) const;
+ bool HasRtcpFb(const std::string& pt, SdpRtcpFbAttributeList::Type type,
+ const std::string& subType) const;
+ SdpRtcpFbAttributeList GetRtcpFbs() const;
+ void SetRtcpFbs(const SdpRtcpFbAttributeList& rtcpfbs);
+ bool HasFormat(const std::string& format) const {
+ return std::find(GetFormats().begin(), GetFormats().end(), format) !=
+ GetFormats().end();
+ }
+ void SetSsrcs(const std::vector<uint32_t>& ssrcs, const std::string& cname);
+ void AddMsid(const std::string& id, const std::string& appdata);
+ const SdpRidAttributeList::Rid* FindRid(const std::string& id) const;
+
+ private:
+ size_t mLevel;
+};
+
+inline std::ostream& operator<<(std::ostream& os, const SdpMediaSection& ms) {
+ ms.Serialize(os);
+ return os;
+}
+
+inline std::ostream& operator<<(std::ostream& os,
+ SdpMediaSection::MediaType t) {
+ switch (t) {
+ case SdpMediaSection::kAudio:
+ return os << "audio";
+ case SdpMediaSection::kVideo:
+ return os << "video";
+ case SdpMediaSection::kText:
+ return os << "text";
+ case SdpMediaSection::kApplication:
+ return os << "application";
+ case SdpMediaSection::kMessage:
+ return os << "message";
+ }
+ MOZ_ASSERT(false, "Unknown MediaType");
+ return os << "?";
+}
+
+inline std::ostream& operator<<(std::ostream& os, SdpMediaSection::Protocol p) {
+ switch (p) {
+ case SdpMediaSection::kRtpAvp:
+ return os << "RTP/AVP";
+ case SdpMediaSection::kUdp:
+ return os << "udp";
+ case SdpMediaSection::kVat:
+ return os << "vat";
+ case SdpMediaSection::kRtp:
+ return os << "rtp";
+ case SdpMediaSection::kUdptl:
+ return os << "udptl";
+ case SdpMediaSection::kTcp:
+ return os << "TCP";
+ case SdpMediaSection::kRtpAvpf:
+ return os << "RTP/AVPF";
+ case SdpMediaSection::kTcpRtpAvp:
+ return os << "TCP/RTP/AVP";
+ case SdpMediaSection::kRtpSavp:
+ return os << "RTP/SAVP";
+ case SdpMediaSection::kTcpBfcp:
+ return os << "TCP/BFCP";
+ case SdpMediaSection::kTcpTlsBfcp:
+ return os << "TCP/TLS/BFCP";
+ case SdpMediaSection::kTcpTls:
+ return os << "TCP/TLS";
+ case SdpMediaSection::kFluteUdp:
+ return os << "FLUTE/UDP";
+ case SdpMediaSection::kTcpMsrp:
+ return os << "TCP/MSRP";
+ case SdpMediaSection::kTcpTlsMsrp:
+ return os << "TCP/TLS/MSRP";
+ case SdpMediaSection::kDccp:
+ return os << "DCCP";
+ case SdpMediaSection::kDccpRtpAvp:
+ return os << "DCCP/RTP/AVP";
+ case SdpMediaSection::kDccpRtpSavp:
+ return os << "DCCP/RTP/SAVP";
+ case SdpMediaSection::kDccpRtpAvpf:
+ return os << "DCCP/RTP/AVPF";
+ case SdpMediaSection::kDccpRtpSavpf:
+ return os << "DCCP/RTP/SAVPF";
+ case SdpMediaSection::kRtpSavpf:
+ return os << "RTP/SAVPF";
+ case SdpMediaSection::kUdpTlsRtpSavp:
+ return os << "UDP/TLS/RTP/SAVP";
+ case SdpMediaSection::kTcpDtlsRtpSavp:
+ return os << "TCP/DTLS/RTP/SAVP";
+ case SdpMediaSection::kDccpTlsRtpSavp:
+ return os << "DCCP/TLS/RTP/SAVP";
+ case SdpMediaSection::kUdpTlsRtpSavpf:
+ return os << "UDP/TLS/RTP/SAVPF";
+ case SdpMediaSection::kTcpDtlsRtpSavpf:
+ return os << "TCP/DTLS/RTP/SAVPF";
+ case SdpMediaSection::kDccpTlsRtpSavpf:
+ return os << "DCCP/TLS/RTP/SAVPF";
+ case SdpMediaSection::kUdpMbmsFecRtpAvp:
+ return os << "UDP/MBMS-FEC/RTP/AVP";
+ case SdpMediaSection::kUdpMbmsFecRtpSavp:
+ return os << "UDP/MBMS-FEC/RTP/SAVP";
+ case SdpMediaSection::kUdpMbmsRepair:
+ return os << "UDP/MBMS-REPAIR";
+ case SdpMediaSection::kFecUdp:
+ return os << "FEC/UDP";
+ case SdpMediaSection::kUdpFec:
+ return os << "UDP/FEC";
+ case SdpMediaSection::kTcpMrcpv2:
+ return os << "TCP/MRCPv2";
+ case SdpMediaSection::kTcpTlsMrcpv2:
+ return os << "TCP/TLS/MRCPv2";
+ case SdpMediaSection::kPstn:
+ return os << "PSTN";
+ case SdpMediaSection::kUdpTlsUdptl:
+ return os << "UDP/TLS/UDPTL";
+ case SdpMediaSection::kSctp:
+ return os << "SCTP";
+ case SdpMediaSection::kDtlsSctp:
+ return os << "DTLS/SCTP";
+ case SdpMediaSection::kUdpDtlsSctp:
+ return os << "UDP/DTLS/SCTP";
+ case SdpMediaSection::kTcpDtlsSctp:
+ return os << "TCP/DTLS/SCTP";
+ }
+ MOZ_ASSERT(false, "Unknown Protocol");
+ return os << "?";
+}
+
+class SdpConnection {
+ public:
+ SdpConnection(sdp::AddrType addrType, std::string addr, uint8_t ttl = 0,
+ uint32_t count = 0)
+ : mAddrType(addrType), mAddr(addr), mTtl(ttl), mCount(count) {}
+ ~SdpConnection() {}
+
+ sdp::AddrType GetAddrType() const { return mAddrType; }
+ const std::string& GetAddress() const { return mAddr; }
+ void SetAddress(const std::string& address) {
+ mAddr = address;
+ if (mAddr.find(':') != std::string::npos) {
+ mAddrType = sdp::kIPv6;
+ } else {
+ mAddrType = sdp::kIPv4;
+ }
+ }
+ uint8_t GetTtl() const { return mTtl; }
+ uint32_t GetCount() const { return mCount; }
+
+ void Serialize(std::ostream& os) const {
+ sdp::NetType netType = sdp::kInternet;
+
+ os << "c=" << netType << " " << mAddrType << " " << mAddr;
+
+ if (mTtl) {
+ os << "/" << static_cast<uint32_t>(mTtl);
+ if (mCount) {
+ os << "/" << mCount;
+ }
+ }
+ os << "\r\n";
+ }
+
+ private:
+ sdp::AddrType mAddrType;
+ std::string mAddr;
+ uint8_t mTtl; // 0-255; 0 when unset
+ uint32_t mCount; // 0 when unset
+};
+
+inline std::ostream& operator<<(std::ostream& os, const SdpConnection& c) {
+ c.Serialize(os);
+ return os;
+}
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/sdp/SdpParser.h b/dom/media/webrtc/sdp/SdpParser.h
new file mode 100644
index 0000000000..4b23e93705
--- /dev/null
+++ b/dom/media/webrtc/sdp/SdpParser.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _SDPPARSER_H_
+#define _SDPPARSER_H_
+
+#include <vector>
+#include <string>
+#include "sdp/Sdp.h"
+#include "sdp/SdpLog.h"
+
+#include "mozilla/Telemetry.h"
+
+namespace mozilla {
+
+class SdpParser {
+ public:
+ SdpParser() = default;
+ virtual ~SdpParser() = default;
+
+ class Results {
+ public:
+ typedef std::pair<size_t, std::string> Anomaly;
+ typedef std::vector<Anomaly> AnomalyVec;
+ virtual ~Results() = default;
+ UniquePtr<mozilla::Sdp>& Sdp() { return mSdp; }
+ AnomalyVec& Errors() { return mErrors; }
+ AnomalyVec& Warnings() { return mWarnings; }
+ virtual const std::string& ParserName() const = 0;
+ bool Ok() const { return mErrors.empty(); }
+
+ protected:
+ UniquePtr<mozilla::Sdp> mSdp;
+ AnomalyVec mErrors;
+ AnomalyVec mWarnings;
+ };
+
+ // The name of the parser implementation
+ virtual const std::string& Name() const = 0;
+
+ /**
+ * This parses the provided text into an SDP object.
+ * This returns a nullptr-valued pointer if things go poorly.
+ */
+ virtual UniquePtr<SdpParser::Results> Parse(const std::string& aText) = 0;
+
+ class InternalResults : public Results {
+ public:
+ explicit InternalResults(const std::string& aParserName)
+ : mParserName(aParserName) {}
+ virtual ~InternalResults() = default;
+
+ void SetSdp(UniquePtr<mozilla::Sdp>&& aSdp) { mSdp = std::move(aSdp); }
+
+ void AddParseError(size_t line, const std::string& message) {
+ MOZ_LOG(SdpLog, LogLevel::Error,
+ ("%s: parser error %s, at line %zu", mParserName.c_str(),
+ message.c_str(), line));
+ mErrors.push_back(std::make_pair(line, message));
+ }
+
+ void AddParseWarning(size_t line, const std::string& message) {
+ MOZ_LOG(SdpLog, LogLevel::Warning,
+ ("%s: parser warning %s, at line %zu", mParserName.c_str(),
+ message.c_str(), line));
+ mWarnings.push_back(std::make_pair(line, message));
+ }
+
+ const std::string& ParserName() const override { return mParserName; }
+
+ private:
+ const std::string mParserName;
+ };
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/sdp/SdpPref.cpp b/dom/media/webrtc/sdp/SdpPref.cpp
new file mode 100644
index 0000000000..d0fc03c4d0
--- /dev/null
+++ b/dom/media/webrtc/sdp/SdpPref.cpp
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "sdp/SdpPref.h"
+#include "sdp/RsdparsaSdpParser.h"
+#include "sdp/SipccSdpParser.h"
+
+namespace mozilla {
+
+const std::string SdpPref::PRIMARY_PREF = "media.peerconnection.sdp.parser";
+const std::string SdpPref::ALTERNATE_PREF =
+ "media.peerconnection.sdp.alternate_parse_mode";
+const std::string SdpPref::STRICT_SUCCESS_PREF =
+ "media.peerconnection.sdp.strict_success";
+const std::string SdpPref::DEFAULT = "default";
+
+auto SdpPref::ToString(const Parsers& aParser) -> std::string {
+ switch (aParser) {
+ case Parsers::Sipcc:
+ return "sipcc";
+ case Parsers::WebRtcSdp:
+ return "webrtc-sdp";
+ };
+ MOZ_CRASH("ALL Parsers CASES ARE NOT COVERED");
+ return "";
+}
+
+auto SdpPref::ToString(const AlternateParseModes& aMode) -> std::string {
+ switch (aMode) {
+ case AlternateParseModes::Parallel:
+ return "parallel";
+ case AlternateParseModes::Failover:
+ return "failover";
+ case AlternateParseModes::Never:
+ return "never";
+ };
+ MOZ_CRASH("ALL AlternateParseModes CASES ARE NOT COVERED");
+ return "";
+}
+
+auto SdpPref::Parser() -> Parsers {
+ static const auto values = std::unordered_map<std::string, Parsers>{
+ {"sipcc", Parsers::Sipcc},
+ {"webrtc-sdp", Parsers::WebRtcSdp},
+ {DEFAULT, Parsers::Sipcc},
+ };
+ return Pref(PRIMARY_PREF, values);
+}
+
+auto SdpPref::AlternateParseMode() -> AlternateParseModes {
+ static const auto values =
+ std::unordered_map<std::string, AlternateParseModes>{
+ {"parallel", AlternateParseModes::Parallel},
+ {"failover", AlternateParseModes::Failover},
+ {"never", AlternateParseModes::Never},
+ {DEFAULT, AlternateParseModes::Parallel},
+ };
+ return Pref(ALTERNATE_PREF, values);
+}
+
+auto SdpPref::Primary() -> UniquePtr<SdpParser> {
+ switch (Parser()) {
+ case Parsers::Sipcc:
+ return UniquePtr<SdpParser>(new SipccSdpParser());
+ case Parsers::WebRtcSdp:
+ return UniquePtr<SdpParser>(new RsdparsaSdpParser());
+ }
+ MOZ_CRASH("ALL Parsers CASES ARE NOT COVERED");
+ return nullptr;
+}
+
+auto SdpPref::Secondary() -> Maybe<UniquePtr<SdpParser>> {
+ if (AlternateParseMode() != AlternateParseModes::Parallel) {
+ return Nothing();
+ }
+ switch (Parser()) { // Choose whatever the primary parser isn't
+ case Parsers::Sipcc:
+ return Some(UniquePtr<SdpParser>(new RsdparsaSdpParser()));
+ case Parsers::WebRtcSdp:
+ return Some(UniquePtr<SdpParser>(new SipccSdpParser()));
+ }
+ MOZ_CRASH("ALL Parsers CASES ARE NOT COVERED");
+ return Nothing();
+}
+
+auto SdpPref::Failover() -> Maybe<UniquePtr<SdpParser>> {
+ if (AlternateParseMode() != AlternateParseModes::Failover) {
+ return Nothing();
+ }
+ switch (Parser()) {
+ case Parsers::Sipcc:
+ return Some(UniquePtr<SdpParser>(new RsdparsaSdpParser()));
+ case Parsers::WebRtcSdp:
+ return Some(UniquePtr<SdpParser>(new SipccSdpParser()));
+ }
+ MOZ_CRASH("ALL Parsers CASES ARE NOT COVERED");
+ return Nothing();
+}
+
+auto SdpPref::StrictSuccess() -> bool {
+ return Preferences::GetBool(STRICT_SUCCESS_PREF.c_str(), false);
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/sdp/SdpPref.h b/dom/media/webrtc/sdp/SdpPref.h
new file mode 100644
index 0000000000..5f24fb12a7
--- /dev/null
+++ b/dom/media/webrtc/sdp/SdpPref.h
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _SDPPREF_H_
+#define _SDPPREF_H_
+
+#include "mozilla/Maybe.h"
+#include "mozilla/Preferences.h"
+
+#include <string>
+#include <unordered_map>
+
+namespace mozilla {
+
+class SdpParser;
+
+// Interprets about:config SDP parsing preferences
+class SdpPref {
+ private:
+ static const std::string PRIMARY_PREF;
+ static const std::string ALTERNATE_PREF;
+ static const std::string STRICT_SUCCESS_PREF;
+ static const std::string DEFAULT;
+
+ public:
+ // Supported Parsers
+ enum class Parsers {
+ Sipcc,
+ WebRtcSdp,
+ };
+ static auto ToString(const Parsers& aParser) -> std::string;
+
+ // How is the alternate used
+ enum class AlternateParseModes {
+ Parallel, // Alternate is always run, if A succedes it is used, otherwise B
+ // is used
+ Failover, // Alternate is only run on failure of the primary to parse
+ Never, // Alternate is never run; this is effectively a kill switch
+ };
+ static auto ToString(const AlternateParseModes& aMode) -> std::string;
+
+ private:
+ // Finds the mapping between a pref string and pref value, if none exists the
+ // default is used
+ template <class T>
+ static auto Pref(const std::string& aPrefName,
+ const std::unordered_map<std::string, T>& aMap) -> T {
+ MOZ_ASSERT(aMap.find(DEFAULT) != aMap.end());
+
+ nsCString value;
+ if (NS_FAILED(Preferences::GetCString(aPrefName.c_str(), value))) {
+ return aMap.at(DEFAULT);
+ }
+ const auto found = aMap.find(value.get());
+ if (found != aMap.end()) {
+ return found->second;
+ }
+ return aMap.at(DEFAULT);
+ }
+ // The value of the parser pref
+ static auto Parser() -> Parsers;
+
+ // The value of the alternate parse mode pref
+ static auto AlternateParseMode() -> AlternateParseModes;
+
+ public:
+ // Do non-fatal parsing errors count as failure
+ static auto StrictSuccess() -> bool;
+ // Functions to create the primary, secondary and failover parsers.
+
+ // Reads about:config to choose the primary Parser
+ static auto Primary() -> UniquePtr<SdpParser>;
+ static auto Secondary() -> Maybe<UniquePtr<SdpParser>>;
+ static auto Failover() -> Maybe<UniquePtr<SdpParser>>;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/sdp/SdpTelemetry.cpp b/dom/media/webrtc/sdp/SdpTelemetry.cpp
new file mode 100644
index 0000000000..7b3a6cec18
--- /dev/null
+++ b/dom/media/webrtc/sdp/SdpTelemetry.cpp
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "sdp/SdpTelemetry.h"
+
+#include "mozilla/Telemetry.h"
+
+namespace mozilla {
+
+auto SdpTelemetry::RecordParse(const SdpTelemetry::Results& aResult,
+ const SdpTelemetry::Modes& aMode,
+ const SdpTelemetry::Roles& aRole) -> void {
+ Telemetry::ScalarAdd(Telemetry::ScalarID::WEBRTC_SDP_PARSER_DIFF,
+ BucketNameFragment(aResult, aMode, aRole), 1);
+}
+
+auto SdpTelemetry::RecordCompare(const SdpTelemetry::Results& aFirst,
+ const SdpTelemetry::Results& aSecond,
+ const SdpTelemetry::Modes& aMode) -> void {
+ const nsAutoString bucket =
+ BucketNameFragment(aFirst, aMode, Roles::Primary) +
+ NS_ConvertASCIItoUTF16("__") +
+ BucketNameFragment(aSecond, aMode, Roles::Secondary);
+ Telemetry::ScalarAdd(Telemetry::ScalarID::WEBRTC_SDP_PARSER_DIFF, bucket, 1);
+}
+
+auto SdpTelemetry::BucketNameFragment(const SdpTelemetry::Results& aResult,
+ const SdpTelemetry::Modes& aMode,
+ const SdpTelemetry::Roles& aRole)
+ -> nsAutoString {
+ auto mode = [&]() -> std::string {
+ switch (aMode) {
+ case Modes::Parallel:
+ return "parallel";
+ case Modes::Failover:
+ return "failover";
+ case Modes::Never:
+ return "standalone";
+ }
+ MOZ_CRASH("Unknown SDP Parse Mode!");
+ };
+ auto role = [&]() -> std::string {
+ switch (aRole) {
+ case Roles::Primary:
+ return "primary";
+ case Roles::Secondary:
+ return "secondary";
+ }
+ MOZ_CRASH("Unknown SDP Parse Role!");
+ };
+ auto success = [&]() -> std::string {
+ return aResult->Ok() ? "success" : "failure";
+ };
+ nsAutoString name;
+ name.AssignASCII(nsCString(aResult->ParserName() + "_" + mode() + "_" +
+ role() + "_" + success()));
+ return name;
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/sdp/SdpTelemetry.h b/dom/media/webrtc/sdp/SdpTelemetry.h
new file mode 100644
index 0000000000..ce308a04ec
--- /dev/null
+++ b/dom/media/webrtc/sdp/SdpTelemetry.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _SDPTELEMETRY_H_
+#define _SDPTELEMETRY_H_
+
+#include "sdp/SdpParser.h"
+#include "sdp/SdpPref.h"
+
+namespace mozilla {
+
+class SdpTelemetry {
+ public:
+ SdpTelemetry() = delete;
+
+ using Results = UniquePtr<SdpParser::Results>;
+ using Modes = SdpPref::AlternateParseModes;
+
+ enum class Roles {
+ Primary,
+ Secondary,
+ };
+
+ static auto RecordParse(const Results& aResults, const Modes& aMode,
+ const Roles& aRole) -> void;
+
+ static auto RecordSecondaryParse(const Results& aResult, const Modes& aMode)
+ -> void;
+
+ static auto RecordCompare(const Results& aFirst, const Results& aSecond,
+ const Modes& aMode) -> void;
+
+ private:
+ static auto BucketNameFragment(const Results& aResult, const Modes& aModes,
+ const Roles& aRoles) -> nsAutoString;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/sdp/SipccSdp.cpp b/dom/media/webrtc/sdp/SipccSdp.cpp
new file mode 100644
index 0000000000..7e36f9e930
--- /dev/null
+++ b/dom/media/webrtc/sdp/SipccSdp.cpp
@@ -0,0 +1,173 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "sdp/SipccSdp.h"
+
+#include <cstdlib>
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Assertions.h"
+#include "sdp/SdpParser.h"
+
+#ifdef CRLF
+# undef CRLF
+#endif
+#define CRLF "\r\n"
+
+namespace mozilla {
+
+SipccSdp::SipccSdp(const SipccSdp& aOrig)
+ : mOrigin(aOrig.mOrigin),
+ mBandwidths(aOrig.mBandwidths),
+ mAttributeList(aOrig.mAttributeList, nullptr) {
+ for (const auto& msection : aOrig.mMediaSections) {
+ mMediaSections.emplace_back(
+ new SipccSdpMediaSection(*msection, &mAttributeList));
+ }
+}
+
+Sdp* SipccSdp::Clone() const { return new SipccSdp(*this); }
+
+const SdpOrigin& SipccSdp::GetOrigin() const { return mOrigin; }
+
+uint32_t SipccSdp::GetBandwidth(const std::string& type) const {
+ auto found = mBandwidths.find(type);
+ if (found == mBandwidths.end()) {
+ return 0;
+ }
+ return found->second;
+}
+
+const SdpMediaSection& SipccSdp::GetMediaSection(size_t level) const {
+ if (level > mMediaSections.size()) {
+ MOZ_CRASH();
+ }
+ return *mMediaSections[level];
+}
+
+SdpMediaSection& SipccSdp::GetMediaSection(size_t level) {
+ if (level > mMediaSections.size()) {
+ MOZ_CRASH();
+ }
+ return *mMediaSections[level];
+}
+
+SdpMediaSection& SipccSdp::AddMediaSection(SdpMediaSection::MediaType mediaType,
+ SdpDirectionAttribute::Direction dir,
+ uint16_t port,
+ SdpMediaSection::Protocol protocol,
+ sdp::AddrType addrType,
+ const std::string& addr) {
+ size_t level = mMediaSections.size();
+ SipccSdpMediaSection* media =
+ new SipccSdpMediaSection(level, &mAttributeList);
+ media->mMediaType = mediaType;
+ media->mPort = port;
+ media->mPortCount = 0;
+ media->mProtocol = protocol;
+ media->mConnection = MakeUnique<SdpConnection>(addrType, addr);
+ media->GetAttributeList().SetAttribute(new SdpDirectionAttribute(dir));
+ mMediaSections.emplace_back(media);
+ return *media;
+}
+
+bool SipccSdp::LoadOrigin(sdp_t* sdp, InternalResults& results) {
+ std::string username = sdp_get_owner_username(sdp);
+ uint64_t sessId = strtoull(sdp_get_owner_sessionid(sdp), nullptr, 10);
+ uint64_t sessVer = strtoull(sdp_get_owner_version(sdp), nullptr, 10);
+
+ sdp_nettype_e type = sdp_get_owner_network_type(sdp);
+ if (type != SDP_NT_INTERNET) {
+ results.AddParseError(2, "Unsupported network type");
+ return false;
+ }
+
+ sdp::AddrType addrType;
+ switch (sdp_get_owner_address_type(sdp)) {
+ case SDP_AT_IP4:
+ addrType = sdp::kIPv4;
+ break;
+ case SDP_AT_IP6:
+ addrType = sdp::kIPv6;
+ break;
+ default:
+ results.AddParseError(2, "Unsupported address type");
+ return false;
+ }
+
+ std::string address = sdp_get_owner_address(sdp);
+ mOrigin = SdpOrigin(username, sessId, sessVer, addrType, address);
+ return true;
+}
+
+bool SipccSdp::Load(sdp_t* sdp, InternalResults& results) {
+ // Believe it or not, SDP_SESSION_LEVEL is 0xFFFF
+ if (!mAttributeList.Load(sdp, SDP_SESSION_LEVEL, results)) {
+ return false;
+ }
+
+ if (!LoadOrigin(sdp, results)) {
+ return false;
+ }
+
+ if (!mBandwidths.Load(sdp, SDP_SESSION_LEVEL, results)) {
+ return false;
+ }
+
+ for (int i = 0; i < sdp_get_num_media_lines(sdp); ++i) {
+ // note that we pass a "level" here that is one higher
+ // sipcc counts media sections from 1, using 0xFFFF as the "session"
+ UniquePtr<SipccSdpMediaSection> section(
+ new SipccSdpMediaSection(i, &mAttributeList));
+ if (!section->Load(sdp, i + 1, results)) {
+ return false;
+ }
+ mMediaSections.push_back(std::move(section));
+ }
+ return true;
+}
+
+void SipccSdp::Serialize(std::ostream& os) const {
+ os << "v=0" << CRLF << mOrigin << "s=-" << CRLF;
+
+ // We don't support creating i=, u=, e=, p=
+ // We don't generate c= at the session level (only in media)
+
+ mBandwidths.Serialize(os);
+ os << "t=0 0" << CRLF;
+
+ // We don't support r= or z=
+
+ // attributes
+ os << mAttributeList;
+
+ // media sections
+ for (const auto& msection : mMediaSections) {
+ os << *msection;
+ }
+}
+
+bool SipccSdpBandwidths::Load(sdp_t* sdp, uint16_t level,
+ InternalResults& results) {
+ size_t count = sdp_get_num_bw_lines(sdp, level);
+ for (size_t i = 1; i <= count; ++i) {
+ sdp_bw_modifier_e bwtype = sdp_get_bw_modifier(sdp, level, i);
+ uint32_t bandwidth = sdp_get_bw_value(sdp, level, i);
+ if (bwtype != SDP_BW_MODIFIER_UNSUPPORTED) {
+ const char* typeName = sdp_get_bw_modifier_name(bwtype);
+ (*this)[typeName] = bandwidth;
+ }
+ }
+
+ return true;
+}
+
+void SipccSdpBandwidths::Serialize(std::ostream& os) const {
+ for (auto i = begin(); i != end(); ++i) {
+ os << "b=" << i->first << ":" << i->second << CRLF;
+ }
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/sdp/SipccSdp.h b/dom/media/webrtc/sdp/SipccSdp.h
new file mode 100644
index 0000000000..4915821cee
--- /dev/null
+++ b/dom/media/webrtc/sdp/SipccSdp.h
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _SIPCCSDP_H_
+#define _SIPCCSDP_H_
+
+#include <map>
+#include <vector>
+#include "mozilla/Attributes.h"
+
+#include "sdp/Sdp.h"
+#include "sdp/SdpParser.h"
+#include "sdp/SipccSdpMediaSection.h"
+#include "sdp/SipccSdpAttributeList.h"
+extern "C" {
+#include "sipcc_sdp.h"
+}
+
+namespace mozilla {
+
+class SipccSdpParser;
+
+class SipccSdp final : public Sdp {
+ friend class SipccSdpParser;
+
+ public:
+ explicit SipccSdp(const SdpOrigin& origin)
+ : mOrigin(origin), mAttributeList(nullptr) {}
+ SipccSdp(const SipccSdp& aOrig);
+
+ virtual Sdp* Clone() const override;
+
+ virtual const SdpOrigin& GetOrigin() const override;
+
+ // Note: connection information is always retrieved from media sections
+ virtual uint32_t GetBandwidth(const std::string& type) const override;
+
+ virtual size_t GetMediaSectionCount() const override {
+ return mMediaSections.size();
+ }
+
+ virtual const SdpAttributeList& GetAttributeList() const override {
+ return mAttributeList;
+ }
+
+ virtual SdpAttributeList& GetAttributeList() override {
+ return mAttributeList;
+ }
+
+ virtual const SdpMediaSection& GetMediaSection(size_t level) const override;
+
+ virtual SdpMediaSection& GetMediaSection(size_t level) override;
+
+ virtual SdpMediaSection& AddMediaSection(SdpMediaSection::MediaType media,
+ SdpDirectionAttribute::Direction dir,
+ uint16_t port,
+ SdpMediaSection::Protocol proto,
+ sdp::AddrType addrType,
+ const std::string& addr) override;
+
+ virtual void Serialize(std::ostream&) const override;
+
+ private:
+ using InternalResults = SdpParser::InternalResults;
+
+ SipccSdp() : mOrigin("", 0, 0, sdp::kIPv4, ""), mAttributeList(nullptr) {}
+
+ bool Load(sdp_t* sdp, InternalResults& results);
+ bool LoadOrigin(sdp_t* sdp, InternalResults& results);
+
+ SdpOrigin mOrigin;
+ SipccSdpBandwidths mBandwidths;
+ SipccSdpAttributeList mAttributeList;
+ std::vector<UniquePtr<SipccSdpMediaSection>> mMediaSections;
+};
+
+} // namespace mozilla
+
+#endif // _sdp_h_
diff --git a/dom/media/webrtc/sdp/SipccSdpAttributeList.cpp b/dom/media/webrtc/sdp/SipccSdpAttributeList.cpp
new file mode 100644
index 0000000000..15572b6dd3
--- /dev/null
+++ b/dom/media/webrtc/sdp/SipccSdpAttributeList.cpp
@@ -0,0 +1,1386 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "sdp/SipccSdpAttributeList.h"
+
+#include <ostream>
+#include "mozilla/Assertions.h"
+
+extern "C" {
+#include "sdp_private.h"
+}
+
+namespace mozilla {
+
+using InternalResults = SdpParser::InternalResults;
+
+/* static */
+const std::string SipccSdpAttributeList::kEmptyString = "";
+
+SipccSdpAttributeList::SipccSdpAttributeList(
+ const SipccSdpAttributeList* sessionLevel)
+ : mSessionLevel(sessionLevel) {
+ memset(&mAttributes, 0, sizeof(mAttributes));
+}
+
+SipccSdpAttributeList::SipccSdpAttributeList(
+ const SipccSdpAttributeList& aOrig,
+ const SipccSdpAttributeList* sessionLevel)
+ : SipccSdpAttributeList(sessionLevel) {
+ for (size_t i = 0; i < kNumAttributeTypes; ++i) {
+ if (aOrig.mAttributes[i]) {
+ mAttributes[i] = aOrig.mAttributes[i]->Clone();
+ }
+ }
+}
+
+SipccSdpAttributeList::~SipccSdpAttributeList() {
+ for (size_t i = 0; i < kNumAttributeTypes; ++i) {
+ delete mAttributes[i];
+ }
+}
+
+bool SipccSdpAttributeList::HasAttribute(AttributeType type,
+ bool sessionFallback) const {
+ return !!GetAttribute(type, sessionFallback);
+}
+
+const SdpAttribute* SipccSdpAttributeList::GetAttribute(
+ AttributeType type, bool sessionFallback) const {
+ const SdpAttribute* value = mAttributes[static_cast<size_t>(type)];
+ // Only do fallback when the attribute can appear at both the media and
+ // session level
+ if (!value && !AtSessionLevel() && sessionFallback &&
+ SdpAttribute::IsAllowedAtSessionLevel(type) &&
+ SdpAttribute::IsAllowedAtMediaLevel(type)) {
+ return mSessionLevel->GetAttribute(type, false);
+ }
+ return value;
+}
+
+void SipccSdpAttributeList::RemoveAttribute(AttributeType type) {
+ delete mAttributes[static_cast<size_t>(type)];
+ mAttributes[static_cast<size_t>(type)] = nullptr;
+}
+
+void SipccSdpAttributeList::Clear() {
+ for (size_t i = 0; i < kNumAttributeTypes; ++i) {
+ RemoveAttribute(static_cast<AttributeType>(i));
+ }
+}
+
+uint32_t SipccSdpAttributeList::Count() const {
+ uint32_t count = 0;
+ for (size_t i = 0; i < kNumAttributeTypes; ++i) {
+ if (mAttributes[i]) {
+ count++;
+ }
+ }
+ return count;
+}
+
+void SipccSdpAttributeList::SetAttribute(SdpAttribute* attr) {
+ if (!IsAllowedHere(attr->GetType())) {
+ MOZ_ASSERT(false, "This type of attribute is not allowed here");
+ return;
+ }
+ RemoveAttribute(attr->GetType());
+ mAttributes[attr->GetType()] = attr;
+}
+
+void SipccSdpAttributeList::LoadSimpleString(sdp_t* sdp, uint16_t level,
+ sdp_attr_e attr,
+ AttributeType targetType,
+ InternalResults& results) {
+ const char* value = sdp_attr_get_simple_string(sdp, attr, level, 0, 1);
+ if (value) {
+ if (!IsAllowedHere(targetType)) {
+ uint32_t lineNumber = sdp_attr_line_number(sdp, attr, level, 0, 1);
+ WarnAboutMisplacedAttribute(targetType, lineNumber, results);
+ } else {
+ SetAttribute(new SdpStringAttribute(targetType, std::string(value)));
+ }
+ }
+}
+
+void SipccSdpAttributeList::LoadSimpleStrings(sdp_t* sdp, uint16_t level,
+ InternalResults& results) {
+ LoadSimpleString(sdp, level, SDP_ATTR_MID, SdpAttribute::kMidAttribute,
+ results);
+ LoadSimpleString(sdp, level, SDP_ATTR_LABEL, SdpAttribute::kLabelAttribute,
+ results);
+}
+
+void SipccSdpAttributeList::LoadSimpleNumber(sdp_t* sdp, uint16_t level,
+ sdp_attr_e attr,
+ AttributeType targetType,
+ InternalResults& results) {
+ if (sdp_attr_valid(sdp, attr, level, 0, 1)) {
+ if (!IsAllowedHere(targetType)) {
+ uint32_t lineNumber = sdp_attr_line_number(sdp, attr, level, 0, 1);
+ WarnAboutMisplacedAttribute(targetType, lineNumber, results);
+ } else {
+ uint32_t value = sdp_attr_get_simple_u32(sdp, attr, level, 0, 1);
+ SetAttribute(new SdpNumberAttribute(targetType, value));
+ }
+ }
+}
+
+void SipccSdpAttributeList::LoadSimpleNumbers(sdp_t* sdp, uint16_t level,
+ InternalResults& results) {
+ LoadSimpleNumber(sdp, level, SDP_ATTR_PTIME, SdpAttribute::kPtimeAttribute,
+ results);
+ LoadSimpleNumber(sdp, level, SDP_ATTR_MAXPTIME,
+ SdpAttribute::kMaxptimeAttribute, results);
+ LoadSimpleNumber(sdp, level, SDP_ATTR_SCTPPORT,
+ SdpAttribute::kSctpPortAttribute, results);
+ LoadSimpleNumber(sdp, level, SDP_ATTR_MAXMESSAGESIZE,
+ SdpAttribute::kMaxMessageSizeAttribute, results);
+}
+
+void SipccSdpAttributeList::LoadFlags(sdp_t* sdp, uint16_t level) {
+ if (AtSessionLevel()) {
+ if (sdp_attr_valid(sdp, SDP_ATTR_ICE_LITE, level, 0, 1)) {
+ SetAttribute(new SdpFlagAttribute(SdpAttribute::kIceLiteAttribute));
+ }
+ } else { // media-level
+ if (sdp_attr_valid(sdp, SDP_ATTR_RTCP_MUX, level, 0, 1)) {
+ SetAttribute(new SdpFlagAttribute(SdpAttribute::kRtcpMuxAttribute));
+ }
+ if (sdp_attr_valid(sdp, SDP_ATTR_END_OF_CANDIDATES, level, 0, 1)) {
+ SetAttribute(
+ new SdpFlagAttribute(SdpAttribute::kEndOfCandidatesAttribute));
+ }
+ if (sdp_attr_valid(sdp, SDP_ATTR_BUNDLE_ONLY, level, 0, 1)) {
+ SetAttribute(new SdpFlagAttribute(SdpAttribute::kBundleOnlyAttribute));
+ }
+ if (sdp_attr_valid(sdp, SDP_ATTR_RTCP_RSIZE, level, 0, 1))
+ SetAttribute(new SdpFlagAttribute(SdpAttribute::kRtcpRsizeAttribute));
+ }
+}
+
+static void ConvertDirection(sdp_direction_e sipcc_direction,
+ SdpDirectionAttribute::Direction* dir_outparam) {
+ switch (sipcc_direction) {
+ case SDP_DIRECTION_SENDRECV:
+ *dir_outparam = SdpDirectionAttribute::kSendrecv;
+ return;
+ case SDP_DIRECTION_SENDONLY:
+ *dir_outparam = SdpDirectionAttribute::kSendonly;
+ return;
+ case SDP_DIRECTION_RECVONLY:
+ *dir_outparam = SdpDirectionAttribute::kRecvonly;
+ return;
+ case SDP_DIRECTION_INACTIVE:
+ *dir_outparam = SdpDirectionAttribute::kInactive;
+ return;
+ case SDP_MAX_QOS_DIRECTIONS:
+ // Nothing actually sets this value.
+ // Fall through to MOZ_CRASH below.
+ {}
+ }
+
+ MOZ_CRASH("Invalid direction from sipcc; this is probably corruption");
+}
+
+void SipccSdpAttributeList::LoadDirection(sdp_t* sdp, uint16_t level,
+ InternalResults& results) {
+ SdpDirectionAttribute::Direction dir;
+ ConvertDirection(sdp_get_media_direction(sdp, level, 0), &dir);
+ SetAttribute(new SdpDirectionAttribute(dir));
+}
+
+void SipccSdpAttributeList::LoadIceAttributes(sdp_t* sdp, uint16_t level) {
+ char* value;
+ sdp_result_e sdpres =
+ sdp_attr_get_ice_attribute(sdp, level, 0, SDP_ATTR_ICE_UFRAG, 1, &value);
+ if (sdpres == SDP_SUCCESS) {
+ SetAttribute(new SdpStringAttribute(SdpAttribute::kIceUfragAttribute,
+ std::string(value)));
+ }
+ sdpres =
+ sdp_attr_get_ice_attribute(sdp, level, 0, SDP_ATTR_ICE_PWD, 1, &value);
+ if (sdpres == SDP_SUCCESS) {
+ SetAttribute(new SdpStringAttribute(SdpAttribute::kIcePwdAttribute,
+ std::string(value)));
+ }
+
+ const char* iceOptVal =
+ sdp_attr_get_simple_string(sdp, SDP_ATTR_ICE_OPTIONS, level, 0, 1);
+ if (iceOptVal) {
+ auto* iceOptions =
+ new SdpOptionsAttribute(SdpAttribute::kIceOptionsAttribute);
+ iceOptions->Load(iceOptVal);
+ SetAttribute(iceOptions);
+ }
+}
+
+bool SipccSdpAttributeList::LoadFingerprint(sdp_t* sdp, uint16_t level,
+ InternalResults& results) {
+ char* value;
+ UniquePtr<SdpFingerprintAttributeList> fingerprintAttrs;
+
+ for (uint16_t i = 1; i < UINT16_MAX; ++i) {
+ sdp_result_e result = sdp_attr_get_dtls_fingerprint_attribute(
+ sdp, level, 0, SDP_ATTR_DTLS_FINGERPRINT, i, &value);
+
+ if (result != SDP_SUCCESS) {
+ break;
+ }
+
+ std::string fingerprintAttr(value);
+ uint32_t lineNumber =
+ sdp_attr_line_number(sdp, SDP_ATTR_DTLS_FINGERPRINT, level, 0, i);
+
+ // sipcc does not expose parse code for this
+ size_t start = fingerprintAttr.find_first_not_of(" \t");
+ if (start == std::string::npos) {
+ results.AddParseError(lineNumber, "Empty fingerprint attribute");
+ return false;
+ }
+
+ size_t end = fingerprintAttr.find_first_of(" \t", start);
+ if (end == std::string::npos) {
+ // One token, no trailing ws
+ results.AddParseError(lineNumber,
+ "Only one token in fingerprint attribute");
+ return false;
+ }
+
+ std::string algorithmToken(fingerprintAttr.substr(start, end - start));
+
+ start = fingerprintAttr.find_first_not_of(" \t", end);
+ if (start == std::string::npos) {
+ // One token, trailing ws
+ results.AddParseError(lineNumber,
+ "Only one token in fingerprint attribute");
+ return false;
+ }
+
+ std::string fingerprintToken(fingerprintAttr.substr(start));
+
+ std::vector<uint8_t> fingerprint =
+ SdpFingerprintAttributeList::ParseFingerprint(fingerprintToken);
+ if (fingerprint.empty()) {
+ results.AddParseError(lineNumber, "Malformed fingerprint token");
+ return false;
+ }
+
+ if (!fingerprintAttrs) {
+ fingerprintAttrs.reset(new SdpFingerprintAttributeList);
+ }
+
+ // Don't assert on unknown algorithm, just skip
+ fingerprintAttrs->PushEntry(algorithmToken, fingerprint, false);
+ }
+
+ if (fingerprintAttrs) {
+ SetAttribute(fingerprintAttrs.release());
+ }
+
+ return true;
+}
+
+void SipccSdpAttributeList::LoadCandidate(sdp_t* sdp, uint16_t level) {
+ char* value;
+ auto candidates =
+ MakeUnique<SdpMultiStringAttribute>(SdpAttribute::kCandidateAttribute);
+
+ for (uint16_t i = 1; i < UINT16_MAX; ++i) {
+ sdp_result_e result = sdp_attr_get_ice_attribute(
+ sdp, level, 0, SDP_ATTR_ICE_CANDIDATE, i, &value);
+
+ if (result != SDP_SUCCESS) {
+ break;
+ }
+
+ candidates->mValues.push_back(value);
+ }
+
+ if (!candidates->mValues.empty()) {
+ SetAttribute(candidates.release());
+ }
+}
+
+bool SipccSdpAttributeList::LoadSctpmap(sdp_t* sdp, uint16_t level,
+ InternalResults& results) {
+ auto sctpmap = MakeUnique<SdpSctpmapAttributeList>();
+ for (uint16_t i = 0; i < UINT16_MAX; ++i) {
+ sdp_attr_t* attr = sdp_find_attr(sdp, level, 0, SDP_ATTR_SCTPMAP, i + 1);
+
+ if (!attr) {
+ break;
+ }
+
+ // Yeah, this is a little weird, but for now we'll just store this as a
+ // payload type.
+ uint16_t payloadType = attr->attr.sctpmap.port;
+ uint16_t streams = attr->attr.sctpmap.streams;
+ const char* name = attr->attr.sctpmap.protocol;
+
+ std::ostringstream osPayloadType;
+ osPayloadType << payloadType;
+ sctpmap->PushEntry(osPayloadType.str(), name, streams);
+ }
+
+ if (!sctpmap->mSctpmaps.empty()) {
+ SetAttribute(sctpmap.release());
+ }
+
+ return true;
+}
+
+SdpRtpmapAttributeList::CodecType SipccSdpAttributeList::GetCodecType(
+ rtp_ptype type) {
+ switch (type) {
+ case RTP_PCMU:
+ return SdpRtpmapAttributeList::kPCMU;
+ case RTP_PCMA:
+ return SdpRtpmapAttributeList::kPCMA;
+ case RTP_G722:
+ return SdpRtpmapAttributeList::kG722;
+ case RTP_H264_P0:
+ case RTP_H264_P1:
+ return SdpRtpmapAttributeList::kH264;
+ case RTP_OPUS:
+ return SdpRtpmapAttributeList::kOpus;
+ case RTP_VP8:
+ return SdpRtpmapAttributeList::kVP8;
+ case RTP_VP9:
+ return SdpRtpmapAttributeList::kVP9;
+ case RTP_RED:
+ return SdpRtpmapAttributeList::kRed;
+ case RTP_ULPFEC:
+ return SdpRtpmapAttributeList::kUlpfec;
+ case RTP_RTX:
+ return SdpRtpmapAttributeList::kRtx;
+ case RTP_TELEPHONE_EVENT:
+ return SdpRtpmapAttributeList::kTelephoneEvent;
+ case RTP_NONE:
+ // Happens when sipcc doesn't know how to translate to the enum
+ case RTP_CELP:
+ case RTP_G726:
+ case RTP_GSM:
+ case RTP_G723:
+ case RTP_DVI4:
+ case RTP_DVI4_II:
+ case RTP_LPC:
+ case RTP_G728:
+ case RTP_G729:
+ case RTP_JPEG:
+ case RTP_NV:
+ case RTP_H261:
+ case RTP_L16:
+ case RTP_H263:
+ case RTP_ILBC:
+ case RTP_I420:
+ return SdpRtpmapAttributeList::kOtherCodec;
+ }
+ MOZ_CRASH("Invalid codec type from sipcc. Probably corruption.");
+}
+
+bool SipccSdpAttributeList::LoadRtpmap(sdp_t* sdp, uint16_t level,
+ InternalResults& results) {
+ auto rtpmap = MakeUnique<SdpRtpmapAttributeList>();
+ uint16_t count;
+ sdp_result_e result =
+ sdp_attr_num_instances(sdp, level, 0, SDP_ATTR_RTPMAP, &count);
+ if (result != SDP_SUCCESS) {
+ MOZ_ASSERT(false, "Unable to get rtpmap size");
+ results.AddParseError(sdp_get_media_line_number(sdp, level),
+ "Unable to get rtpmap size");
+ return false;
+ }
+ for (uint16_t i = 0; i < count; ++i) {
+ uint16_t pt = sdp_attr_get_rtpmap_payload_type(sdp, level, 0, i + 1);
+ const char* ccName = sdp_attr_get_rtpmap_encname(sdp, level, 0, i + 1);
+
+ if (!ccName) {
+ // Probably no rtpmap attribute for a pt in an m-line
+ results.AddParseError(sdp_get_media_line_number(sdp, level),
+ "No rtpmap attribute for payload type");
+ continue;
+ }
+
+ std::string name(ccName);
+
+ SdpRtpmapAttributeList::CodecType codec =
+ GetCodecType(sdp_get_known_payload_type(sdp, level, pt));
+
+ uint32_t clock = sdp_attr_get_rtpmap_clockrate(sdp, level, 0, i + 1);
+ uint16_t channels = 0;
+
+ // sipcc gives us a channels value of "1" for video
+ if (sdp_get_media_type(sdp, level) == SDP_MEDIA_AUDIO) {
+ channels = sdp_attr_get_rtpmap_num_chan(sdp, level, 0, i + 1);
+ }
+
+ std::ostringstream osPayloadType;
+ osPayloadType << pt;
+ rtpmap->PushEntry(osPayloadType.str(), codec, name, clock, channels);
+ }
+
+ if (!rtpmap->mRtpmaps.empty()) {
+ SetAttribute(rtpmap.release());
+ }
+
+ return true;
+}
+
+void SipccSdpAttributeList::LoadSetup(sdp_t* sdp, uint16_t level) {
+ sdp_setup_type_e setupType;
+ auto sdpres = sdp_attr_get_setup_attribute(sdp, level, 0, 1, &setupType);
+
+ if (sdpres != SDP_SUCCESS) {
+ return;
+ }
+
+ switch (setupType) {
+ case SDP_SETUP_ACTIVE:
+ SetAttribute(new SdpSetupAttribute(SdpSetupAttribute::kActive));
+ return;
+ case SDP_SETUP_PASSIVE:
+ SetAttribute(new SdpSetupAttribute(SdpSetupAttribute::kPassive));
+ return;
+ case SDP_SETUP_ACTPASS:
+ SetAttribute(new SdpSetupAttribute(SdpSetupAttribute::kActpass));
+ return;
+ case SDP_SETUP_HOLDCONN:
+ SetAttribute(new SdpSetupAttribute(SdpSetupAttribute::kHoldconn));
+ return;
+ case SDP_SETUP_UNKNOWN:
+ return;
+ case SDP_SETUP_NOT_FOUND:
+ case SDP_MAX_SETUP:
+ // There is no code that will set these.
+ // Fall through to MOZ_CRASH() below.
+ {}
+ }
+
+ MOZ_CRASH("Invalid setup type from sipcc. This is probably corruption.");
+}
+
+void SipccSdpAttributeList::LoadSsrc(sdp_t* sdp, uint16_t level) {
+ auto ssrcs = MakeUnique<SdpSsrcAttributeList>();
+
+ for (uint16_t i = 1; i < UINT16_MAX; ++i) {
+ sdp_attr_t* attr = sdp_find_attr(sdp, level, 0, SDP_ATTR_SSRC, i);
+
+ if (!attr) {
+ break;
+ }
+
+ sdp_ssrc_t* ssrc = &(attr->attr.ssrc);
+ ssrcs->PushEntry(ssrc->ssrc, ssrc->attribute);
+ }
+
+ if (!ssrcs->mSsrcs.empty()) {
+ SetAttribute(ssrcs.release());
+ }
+}
+
+void SipccSdpAttributeList::LoadSsrcGroup(sdp_t* sdp, uint16_t level) {
+ auto ssrcGroups = MakeUnique<SdpSsrcGroupAttributeList>();
+
+ for (uint16_t i = 1; i < UINT16_MAX; ++i) {
+ sdp_attr_t* attr = sdp_find_attr(sdp, level, 0, SDP_ATTR_SSRC_GROUP, i);
+
+ if (!attr) {
+ break;
+ }
+
+ sdp_ssrc_group_t* ssrc_group = &(attr->attr.ssrc_group);
+
+ SdpSsrcGroupAttributeList::Semantics semantic;
+ switch (ssrc_group->semantic) {
+ case SDP_SSRC_GROUP_ATTR_FEC:
+ semantic = SdpSsrcGroupAttributeList::kFec;
+ break;
+ case SDP_SSRC_GROUP_ATTR_FID:
+ semantic = SdpSsrcGroupAttributeList::kFid;
+ break;
+ case SDP_SSRC_GROUP_ATTR_FECFR:
+ semantic = SdpSsrcGroupAttributeList::kFecFr;
+ break;
+ case SDP_SSRC_GROUP_ATTR_DUP:
+ semantic = SdpSsrcGroupAttributeList::kDup;
+ break;
+ case SDP_SSRC_GROUP_ATTR_SIM:
+ semantic = SdpSsrcGroupAttributeList::kSim;
+ break;
+ case SDP_MAX_SSRC_GROUP_ATTR_VAL:
+ continue;
+ case SDP_SSRC_GROUP_ATTR_UNSUPPORTED:
+ continue;
+ }
+
+ std::vector<uint32_t> ssrcs;
+ ssrcs.reserve(ssrc_group->num_ssrcs);
+ for (int i = 0; i < ssrc_group->num_ssrcs; ++i) {
+ ssrcs.push_back(ssrc_group->ssrcs[i]);
+ }
+
+ ssrcGroups->PushEntry(semantic, ssrcs);
+ }
+
+ if (!ssrcGroups->mSsrcGroups.empty()) {
+ SetAttribute(ssrcGroups.release());
+ }
+}
+
+bool SipccSdpAttributeList::LoadImageattr(sdp_t* sdp, uint16_t level,
+ InternalResults& results) {
+ UniquePtr<SdpImageattrAttributeList> imageattrs(
+ new SdpImageattrAttributeList);
+
+ for (uint16_t i = 1; i < UINT16_MAX; ++i) {
+ const char* imageattrRaw =
+ sdp_attr_get_simple_string(sdp, SDP_ATTR_IMAGEATTR, level, 0, i);
+ if (!imageattrRaw) {
+ break;
+ }
+
+ std::string error;
+ size_t errorPos;
+ if (!imageattrs->PushEntry(imageattrRaw, &error, &errorPos)) {
+ std::ostringstream fullError;
+ fullError << error << " at column " << errorPos;
+ results.AddParseError(
+ sdp_attr_line_number(sdp, SDP_ATTR_IMAGEATTR, level, 0, i),
+ fullError.str());
+ return false;
+ }
+ }
+
+ if (!imageattrs->mImageattrs.empty()) {
+ SetAttribute(imageattrs.release());
+ }
+ return true;
+}
+
+bool SipccSdpAttributeList::LoadSimulcast(sdp_t* sdp, uint16_t level,
+ InternalResults& results) {
+ const char* simulcastRaw =
+ sdp_attr_get_simple_string(sdp, SDP_ATTR_SIMULCAST, level, 0, 1);
+ if (!simulcastRaw) {
+ return true;
+ }
+
+ UniquePtr<SdpSimulcastAttribute> simulcast(new SdpSimulcastAttribute);
+
+ std::istringstream is(simulcastRaw);
+ std::string error;
+ if (!simulcast->Parse(is, &error)) {
+ std::ostringstream fullError;
+ fullError << error << " at column " << is.tellg();
+ results.AddParseError(
+ sdp_attr_line_number(sdp, SDP_ATTR_SIMULCAST, level, 0, 1),
+ fullError.str());
+ return false;
+ }
+
+ SetAttribute(simulcast.release());
+ return true;
+}
+
+bool SipccSdpAttributeList::LoadGroups(sdp_t* sdp, uint16_t level,
+ InternalResults& results) {
+ uint16_t attrCount = 0;
+ if (sdp_attr_num_instances(sdp, level, 0, SDP_ATTR_GROUP, &attrCount) !=
+ SDP_SUCCESS) {
+ MOZ_ASSERT(false, "Could not get count of group attributes");
+ results.AddParseError(0, "Could not get count of group attributes");
+ return false;
+ }
+
+ UniquePtr<SdpGroupAttributeList> groups = MakeUnique<SdpGroupAttributeList>();
+ for (uint16_t attr = 1; attr <= attrCount; ++attr) {
+ SdpGroupAttributeList::Semantics semantics;
+ std::vector<std::string> tags;
+
+ switch (sdp_get_group_attr(sdp, level, 0, attr)) {
+ case SDP_GROUP_ATTR_FID:
+ semantics = SdpGroupAttributeList::kFid;
+ break;
+ case SDP_GROUP_ATTR_LS:
+ semantics = SdpGroupAttributeList::kLs;
+ break;
+ case SDP_GROUP_ATTR_ANAT:
+ semantics = SdpGroupAttributeList::kAnat;
+ break;
+ case SDP_GROUP_ATTR_BUNDLE:
+ semantics = SdpGroupAttributeList::kBundle;
+ break;
+ default:
+ continue;
+ }
+
+ uint16_t idCount = sdp_get_group_num_id(sdp, level, 0, attr);
+ for (uint16_t id = 1; id <= idCount; ++id) {
+ const char* idStr = sdp_get_group_id(sdp, level, 0, attr, id);
+ if (!idStr) {
+ std::ostringstream os;
+ os << "bad a=group identifier at " << (attr - 1) << ", " << (id - 1);
+ results.AddParseError(0, os.str());
+ return false;
+ }
+ tags.push_back(std::string(idStr));
+ }
+ groups->PushEntry(semantics, tags);
+ }
+
+ if (!groups->mGroups.empty()) {
+ SetAttribute(groups.release());
+ }
+
+ return true;
+}
+
+bool SipccSdpAttributeList::LoadMsidSemantics(sdp_t* sdp, uint16_t level,
+ InternalResults& results) {
+ auto msidSemantics = MakeUnique<SdpMsidSemanticAttributeList>();
+
+ for (uint16_t i = 1; i < UINT16_MAX; ++i) {
+ sdp_attr_t* attr = sdp_find_attr(sdp, level, 0, SDP_ATTR_MSID_SEMANTIC, i);
+
+ if (!attr) {
+ break;
+ }
+
+ sdp_msid_semantic_t* msid_semantic = &(attr->attr.msid_semantic);
+ std::vector<std::string> msids;
+ for (size_t i = 0; i < SDP_MAX_MEDIA_STREAMS; ++i) {
+ if (!msid_semantic->msids[i]) {
+ break;
+ }
+
+ msids.push_back(msid_semantic->msids[i]);
+ }
+
+ msidSemantics->PushEntry(msid_semantic->semantic, msids);
+ }
+
+ if (!msidSemantics->mMsidSemantics.empty()) {
+ SetAttribute(msidSemantics.release());
+ }
+ return true;
+}
+
+void SipccSdpAttributeList::LoadIdentity(sdp_t* sdp, uint16_t level) {
+ const char* val =
+ sdp_attr_get_long_string(sdp, SDP_ATTR_IDENTITY, level, 0, 1);
+ if (val) {
+ SetAttribute(new SdpStringAttribute(SdpAttribute::kIdentityAttribute,
+ std::string(val)));
+ }
+}
+
+void SipccSdpAttributeList::LoadDtlsMessage(sdp_t* sdp, uint16_t level) {
+ const char* val =
+ sdp_attr_get_long_string(sdp, SDP_ATTR_DTLS_MESSAGE, level, 0, 1);
+ if (val) {
+ // sipcc does not expose parse code for this, so we use a SDParta-provided
+ // parser
+ std::string strval(val);
+ SetAttribute(new SdpDtlsMessageAttribute(strval));
+ }
+}
+
+void SipccSdpAttributeList::LoadFmtp(sdp_t* sdp, uint16_t level) {
+ auto fmtps = MakeUnique<SdpFmtpAttributeList>();
+
+ for (uint16_t i = 1; i < UINT16_MAX; ++i) {
+ sdp_attr_t* attr = sdp_find_attr(sdp, level, 0, SDP_ATTR_FMTP, i);
+
+ if (!attr) {
+ break;
+ }
+
+ sdp_fmtp_t* fmtp = &(attr->attr.fmtp);
+
+ // Get the payload type
+ std::stringstream osPayloadType;
+ // payload_num is the number in the fmtp attribute, verbatim
+ osPayloadType << fmtp->payload_num;
+
+ // Get parsed form of parameters, if supported
+ UniquePtr<SdpFmtpAttributeList::Parameters> parameters;
+
+ rtp_ptype codec = sdp_get_known_payload_type(sdp, level, fmtp->payload_num);
+
+ switch (codec) {
+ case RTP_H264_P0:
+ case RTP_H264_P1: {
+ SdpFmtpAttributeList::H264Parameters* h264Parameters(
+ new SdpFmtpAttributeList::H264Parameters);
+
+ sstrncpy(h264Parameters->sprop_parameter_sets, fmtp->parameter_sets,
+ sizeof(h264Parameters->sprop_parameter_sets));
+
+ h264Parameters->level_asymmetry_allowed =
+ !!(fmtp->level_asymmetry_allowed);
+
+ h264Parameters->packetization_mode = fmtp->packetization_mode;
+ sscanf(fmtp->profile_level_id, "%x", &h264Parameters->profile_level_id);
+ h264Parameters->max_mbps = fmtp->max_mbps;
+ h264Parameters->max_fs = fmtp->max_fs;
+ h264Parameters->max_cpb = fmtp->max_cpb;
+ h264Parameters->max_dpb = fmtp->max_dpb;
+ h264Parameters->max_br = fmtp->max_br;
+
+ parameters.reset(h264Parameters);
+ } break;
+ case RTP_VP9: {
+ SdpFmtpAttributeList::VP8Parameters* vp9Parameters(
+ new SdpFmtpAttributeList::VP8Parameters(
+ SdpRtpmapAttributeList::kVP9));
+
+ vp9Parameters->max_fs = fmtp->max_fs;
+ vp9Parameters->max_fr = fmtp->max_fr;
+
+ parameters.reset(vp9Parameters);
+ } break;
+ case RTP_VP8: {
+ SdpFmtpAttributeList::VP8Parameters* vp8Parameters(
+ new SdpFmtpAttributeList::VP8Parameters(
+ SdpRtpmapAttributeList::kVP8));
+
+ vp8Parameters->max_fs = fmtp->max_fs;
+ vp8Parameters->max_fr = fmtp->max_fr;
+
+ parameters.reset(vp8Parameters);
+ } break;
+ case RTP_RED: {
+ SdpFmtpAttributeList::RedParameters* redParameters(
+ new SdpFmtpAttributeList::RedParameters);
+ for (int i = 0; i < SDP_FMTP_MAX_REDUNDANT_ENCODINGS &&
+ fmtp->redundant_encodings[i];
+ ++i) {
+ redParameters->encodings.push_back(fmtp->redundant_encodings[i]);
+ }
+
+ parameters.reset(redParameters);
+ } break;
+ case RTP_OPUS: {
+ SdpFmtpAttributeList::OpusParameters* opusParameters(
+ new SdpFmtpAttributeList::OpusParameters);
+ opusParameters->maxplaybackrate = fmtp->maxplaybackrate;
+ opusParameters->stereo = fmtp->stereo;
+ opusParameters->useInBandFec = fmtp->useinbandfec;
+ opusParameters->maxAverageBitrate = fmtp->maxaveragebitrate;
+ opusParameters->useDTX = fmtp->usedtx;
+ parameters.reset(opusParameters);
+ } break;
+ case RTP_TELEPHONE_EVENT: {
+ SdpFmtpAttributeList::TelephoneEventParameters* teParameters(
+ new SdpFmtpAttributeList::TelephoneEventParameters);
+ if (strlen(fmtp->dtmf_tones) > 0) {
+ teParameters->dtmfTones = fmtp->dtmf_tones;
+ }
+ parameters.reset(teParameters);
+ } break;
+ case RTP_RTX: {
+ SdpFmtpAttributeList::RtxParameters* rtxParameters(
+ new SdpFmtpAttributeList::RtxParameters);
+ rtxParameters->apt = fmtp->apt;
+ if (fmtp->has_rtx_time == TRUE) {
+ rtxParameters->rtx_time = Some(fmtp->rtx_time);
+ }
+ parameters.reset(rtxParameters);
+ } break;
+ default: {
+ }
+ }
+
+ if (parameters) {
+ fmtps->PushEntry(osPayloadType.str(), *parameters);
+ }
+ }
+
+ if (!fmtps->mFmtps.empty()) {
+ SetAttribute(fmtps.release());
+ }
+}
+
+void SipccSdpAttributeList::LoadMsids(sdp_t* sdp, uint16_t level,
+ InternalResults& results) {
+ uint16_t attrCount = 0;
+ if (sdp_attr_num_instances(sdp, level, 0, SDP_ATTR_MSID, &attrCount) !=
+ SDP_SUCCESS) {
+ MOZ_ASSERT(false, "Unable to get count of msid attributes");
+ results.AddParseError(0, "Unable to get count of msid attributes");
+ return;
+ }
+ auto msids = MakeUnique<SdpMsidAttributeList>();
+ for (uint16_t i = 1; i <= attrCount; ++i) {
+ uint32_t lineNumber = sdp_attr_line_number(sdp, SDP_ATTR_MSID, level, 0, i);
+
+ const char* identifier = sdp_attr_get_msid_identifier(sdp, level, 0, i);
+ if (!identifier) {
+ results.AddParseError(lineNumber, "msid attribute with bad identity");
+ continue;
+ }
+
+ const char* appdata = sdp_attr_get_msid_appdata(sdp, level, 0, i);
+ if (!appdata) {
+ results.AddParseError(lineNumber, "msid attribute with bad appdata");
+ continue;
+ }
+
+ msids->PushEntry(identifier, appdata);
+ }
+
+ if (!msids->mMsids.empty()) {
+ SetAttribute(msids.release());
+ }
+}
+
+bool SipccSdpAttributeList::LoadRid(sdp_t* sdp, uint16_t level,
+ InternalResults& results) {
+ UniquePtr<SdpRidAttributeList> rids(new SdpRidAttributeList);
+
+ for (uint16_t i = 1; i < UINT16_MAX; ++i) {
+ const char* ridRaw =
+ sdp_attr_get_simple_string(sdp, SDP_ATTR_RID, level, 0, i);
+ if (!ridRaw) {
+ break;
+ }
+
+ std::string error;
+ size_t errorPos;
+ if (!rids->PushEntry(ridRaw, &error, &errorPos)) {
+ std::ostringstream fullError;
+ fullError << error << " at column " << errorPos;
+ results.AddParseError(
+ sdp_attr_line_number(sdp, SDP_ATTR_RID, level, 0, i),
+ fullError.str());
+ return false;
+ }
+ }
+
+ if (!rids->mRids.empty()) {
+ SetAttribute(rids.release());
+ }
+ return true;
+}
+
+void SipccSdpAttributeList::LoadExtmap(sdp_t* sdp, uint16_t level,
+ InternalResults& results) {
+ auto extmaps = MakeUnique<SdpExtmapAttributeList>();
+
+ for (uint16_t i = 1; i < UINT16_MAX; ++i) {
+ sdp_attr_t* attr = sdp_find_attr(sdp, level, 0, SDP_ATTR_EXTMAP, i);
+
+ if (!attr) {
+ break;
+ }
+
+ sdp_extmap_t* extmap = &(attr->attr.extmap);
+
+ SdpDirectionAttribute::Direction dir = SdpDirectionAttribute::kSendrecv;
+
+ if (extmap->media_direction_specified) {
+ ConvertDirection(extmap->media_direction, &dir);
+ }
+
+ extmaps->PushEntry(extmap->id, dir, extmap->media_direction_specified,
+ extmap->uri, extmap->extension_attributes);
+ }
+
+ if (!extmaps->mExtmaps.empty()) {
+ if (!AtSessionLevel() &&
+ mSessionLevel->HasAttribute(SdpAttribute::kExtmapAttribute)) {
+ uint32_t lineNumber =
+ sdp_attr_line_number(sdp, SDP_ATTR_EXTMAP, level, 0, 1);
+ results.AddParseError(
+ lineNumber, "extmap attributes in both session and media level");
+ }
+ SetAttribute(extmaps.release());
+ }
+}
+
+void SipccSdpAttributeList::LoadRtcpFb(sdp_t* sdp, uint16_t level,
+ InternalResults& results) {
+ auto rtcpfbs = MakeUnique<SdpRtcpFbAttributeList>();
+
+ for (uint16_t i = 1; i < UINT16_MAX; ++i) {
+ sdp_attr_t* attr = sdp_find_attr(sdp, level, 0, SDP_ATTR_RTCP_FB, i);
+
+ if (!attr) {
+ break;
+ }
+
+ sdp_fmtp_fb_t* rtcpfb = &attr->attr.rtcp_fb;
+
+ SdpRtcpFbAttributeList::Type type;
+ std::string parameter;
+
+ // Set type and parameter
+ switch (rtcpfb->feedback_type) {
+ case SDP_RTCP_FB_ACK:
+ type = SdpRtcpFbAttributeList::kAck;
+ switch (rtcpfb->param.ack) {
+ // TODO: sipcc doesn't seem to support ack with no following token.
+ // Issue 189.
+ case SDP_RTCP_FB_ACK_RPSI:
+ parameter = SdpRtcpFbAttributeList::rpsi;
+ break;
+ case SDP_RTCP_FB_ACK_APP:
+ parameter = SdpRtcpFbAttributeList::app;
+ break;
+ default:
+ // Type we don't care about, ignore.
+ continue;
+ }
+ break;
+ case SDP_RTCP_FB_CCM:
+ type = SdpRtcpFbAttributeList::kCcm;
+ switch (rtcpfb->param.ccm) {
+ case SDP_RTCP_FB_CCM_FIR:
+ parameter = SdpRtcpFbAttributeList::fir;
+ break;
+ case SDP_RTCP_FB_CCM_TMMBR:
+ parameter = SdpRtcpFbAttributeList::tmmbr;
+ break;
+ case SDP_RTCP_FB_CCM_TSTR:
+ parameter = SdpRtcpFbAttributeList::tstr;
+ break;
+ case SDP_RTCP_FB_CCM_VBCM:
+ parameter = SdpRtcpFbAttributeList::vbcm;
+ break;
+ default:
+ // Type we don't care about, ignore.
+ continue;
+ }
+ break;
+ case SDP_RTCP_FB_NACK:
+ type = SdpRtcpFbAttributeList::kNack;
+ switch (rtcpfb->param.nack) {
+ case SDP_RTCP_FB_NACK_BASIC:
+ break;
+ case SDP_RTCP_FB_NACK_SLI:
+ parameter = SdpRtcpFbAttributeList::sli;
+ break;
+ case SDP_RTCP_FB_NACK_PLI:
+ parameter = SdpRtcpFbAttributeList::pli;
+ break;
+ case SDP_RTCP_FB_NACK_RPSI:
+ parameter = SdpRtcpFbAttributeList::rpsi;
+ break;
+ case SDP_RTCP_FB_NACK_APP:
+ parameter = SdpRtcpFbAttributeList::app;
+ break;
+ default:
+ // Type we don't care about, ignore.
+ continue;
+ }
+ break;
+ case SDP_RTCP_FB_TRR_INT: {
+ type = SdpRtcpFbAttributeList::kTrrInt;
+ std::ostringstream os;
+ os << rtcpfb->param.trr_int;
+ parameter = os.str();
+ } break;
+ case SDP_RTCP_FB_REMB: {
+ type = SdpRtcpFbAttributeList::kRemb;
+ } break;
+ case SDP_RTCP_FB_TRANSPORT_CC: {
+ type = SdpRtcpFbAttributeList::kTransportCC;
+ } break;
+ default:
+ // Type we don't care about, ignore.
+ continue;
+ }
+
+ std::stringstream osPayloadType;
+ if (rtcpfb->payload_num == UINT16_MAX) {
+ osPayloadType << "*";
+ } else {
+ osPayloadType << rtcpfb->payload_num;
+ }
+
+ std::string pt(osPayloadType.str());
+ std::string extra(rtcpfb->extra);
+
+ rtcpfbs->PushEntry(pt, type, parameter, extra);
+ }
+
+ if (!rtcpfbs->mFeedbacks.empty()) {
+ SetAttribute(rtcpfbs.release());
+ }
+}
+
+void SipccSdpAttributeList::LoadRtcp(sdp_t* sdp, uint16_t level,
+ InternalResults& results) {
+ sdp_attr_t* attr = sdp_find_attr(sdp, level, 0, SDP_ATTR_RTCP, 1);
+
+ if (!attr) {
+ return;
+ }
+
+ sdp_rtcp_t* rtcp = &attr->attr.rtcp;
+
+ if (rtcp->nettype != SDP_NT_INTERNET) {
+ return;
+ }
+
+ if (rtcp->addrtype != SDP_AT_IP4 && rtcp->addrtype != SDP_AT_IP6) {
+ return;
+ }
+
+ if (!strlen(rtcp->addr)) {
+ SetAttribute(new SdpRtcpAttribute(rtcp->port));
+ } else {
+ SetAttribute(new SdpRtcpAttribute(
+ rtcp->port, sdp::kInternet,
+ rtcp->addrtype == SDP_AT_IP4 ? sdp::kIPv4 : sdp::kIPv6, rtcp->addr));
+ }
+}
+
+bool SipccSdpAttributeList::Load(sdp_t* sdp, uint16_t level,
+ InternalResults& results) {
+ LoadSimpleStrings(sdp, level, results);
+ LoadSimpleNumbers(sdp, level, results);
+ LoadFlags(sdp, level);
+ LoadDirection(sdp, level, results);
+
+ if (AtSessionLevel()) {
+ if (!LoadGroups(sdp, level, results)) {
+ return false;
+ }
+
+ if (!LoadMsidSemantics(sdp, level, results)) {
+ return false;
+ }
+
+ LoadIdentity(sdp, level);
+ LoadDtlsMessage(sdp, level);
+ } else {
+ sdp_media_e mtype = sdp_get_media_type(sdp, level);
+ if (mtype == SDP_MEDIA_APPLICATION) {
+ LoadSctpmap(sdp, level, results);
+ } else {
+ if (!LoadRtpmap(sdp, level, results)) {
+ return false;
+ }
+ }
+ LoadCandidate(sdp, level);
+ LoadFmtp(sdp, level);
+ LoadMsids(sdp, level, results);
+ LoadRtcpFb(sdp, level, results);
+ LoadRtcp(sdp, level, results);
+ LoadSsrc(sdp, level);
+ LoadSsrcGroup(sdp, level);
+ if (!LoadImageattr(sdp, level, results)) {
+ return false;
+ }
+ if (!LoadSimulcast(sdp, level, results)) {
+ return false;
+ }
+ if (!LoadRid(sdp, level, results)) {
+ return false;
+ }
+ }
+
+ LoadIceAttributes(sdp, level);
+ if (!LoadFingerprint(sdp, level, results)) {
+ return false;
+ }
+ LoadSetup(sdp, level);
+ LoadExtmap(sdp, level, results);
+
+ return true;
+}
+
+bool SipccSdpAttributeList::IsAllowedHere(
+ SdpAttribute::AttributeType type) const {
+ if (AtSessionLevel() && !SdpAttribute::IsAllowedAtSessionLevel(type)) {
+ return false;
+ }
+
+ if (!AtSessionLevel() && !SdpAttribute::IsAllowedAtMediaLevel(type)) {
+ return false;
+ }
+
+ return true;
+}
+
+void SipccSdpAttributeList::WarnAboutMisplacedAttribute(
+ SdpAttribute::AttributeType type, uint32_t lineNumber,
+ InternalResults& results) {
+ std::string warning = SdpAttribute::GetAttributeTypeString(type) +
+ (AtSessionLevel() ? " at session level. Ignoring."
+ : " at media level. Ignoring.");
+ results.AddParseError(lineNumber, warning);
+}
+
+const std::vector<std::string>& SipccSdpAttributeList::GetCandidate() const {
+ if (!HasAttribute(SdpAttribute::kCandidateAttribute)) {
+ MOZ_CRASH();
+ }
+
+ return static_cast<const SdpMultiStringAttribute*>(
+ GetAttribute(SdpAttribute::kCandidateAttribute))
+ ->mValues;
+}
+
+const SdpConnectionAttribute& SipccSdpAttributeList::GetConnection() const {
+ if (!HasAttribute(SdpAttribute::kConnectionAttribute)) {
+ MOZ_CRASH();
+ }
+
+ return *static_cast<const SdpConnectionAttribute*>(
+ GetAttribute(SdpAttribute::kConnectionAttribute));
+}
+
+SdpDirectionAttribute::Direction SipccSdpAttributeList::GetDirection() const {
+ if (!HasAttribute(SdpAttribute::kDirectionAttribute)) {
+ MOZ_CRASH();
+ }
+
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kDirectionAttribute);
+ return static_cast<const SdpDirectionAttribute*>(attr)->mValue;
+}
+
+const SdpDtlsMessageAttribute& SipccSdpAttributeList::GetDtlsMessage() const {
+ if (!HasAttribute(SdpAttribute::kDtlsMessageAttribute)) {
+ MOZ_CRASH();
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kDtlsMessageAttribute);
+ return *static_cast<const SdpDtlsMessageAttribute*>(attr);
+}
+
+const SdpExtmapAttributeList& SipccSdpAttributeList::GetExtmap() const {
+ if (!HasAttribute(SdpAttribute::kExtmapAttribute)) {
+ MOZ_CRASH();
+ }
+
+ return *static_cast<const SdpExtmapAttributeList*>(
+ GetAttribute(SdpAttribute::kExtmapAttribute));
+}
+
+const SdpFingerprintAttributeList& SipccSdpAttributeList::GetFingerprint()
+ const {
+ if (!HasAttribute(SdpAttribute::kFingerprintAttribute)) {
+ MOZ_CRASH();
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kFingerprintAttribute);
+ return *static_cast<const SdpFingerprintAttributeList*>(attr);
+}
+
+const SdpFmtpAttributeList& SipccSdpAttributeList::GetFmtp() const {
+ if (!HasAttribute(SdpAttribute::kFmtpAttribute)) {
+ MOZ_CRASH();
+ }
+
+ return *static_cast<const SdpFmtpAttributeList*>(
+ GetAttribute(SdpAttribute::kFmtpAttribute));
+}
+
+const SdpGroupAttributeList& SipccSdpAttributeList::GetGroup() const {
+ if (!HasAttribute(SdpAttribute::kGroupAttribute)) {
+ MOZ_CRASH();
+ }
+
+ return *static_cast<const SdpGroupAttributeList*>(
+ GetAttribute(SdpAttribute::kGroupAttribute));
+}
+
+const SdpOptionsAttribute& SipccSdpAttributeList::GetIceOptions() const {
+ if (!HasAttribute(SdpAttribute::kIceOptionsAttribute)) {
+ MOZ_CRASH();
+ }
+
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kIceOptionsAttribute);
+ return *static_cast<const SdpOptionsAttribute*>(attr);
+}
+
+const std::string& SipccSdpAttributeList::GetIcePwd() const {
+ if (!HasAttribute(SdpAttribute::kIcePwdAttribute)) {
+ return kEmptyString;
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kIcePwdAttribute);
+ return static_cast<const SdpStringAttribute*>(attr)->mValue;
+}
+
+const std::string& SipccSdpAttributeList::GetIceUfrag() const {
+ if (!HasAttribute(SdpAttribute::kIceUfragAttribute)) {
+ return kEmptyString;
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kIceUfragAttribute);
+ return static_cast<const SdpStringAttribute*>(attr)->mValue;
+}
+
+const std::string& SipccSdpAttributeList::GetIdentity() const {
+ if (!HasAttribute(SdpAttribute::kIdentityAttribute)) {
+ return kEmptyString;
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kIdentityAttribute);
+ return static_cast<const SdpStringAttribute*>(attr)->mValue;
+}
+
+const SdpImageattrAttributeList& SipccSdpAttributeList::GetImageattr() const {
+ if (!HasAttribute(SdpAttribute::kImageattrAttribute)) {
+ MOZ_CRASH();
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kImageattrAttribute);
+ return *static_cast<const SdpImageattrAttributeList*>(attr);
+}
+
+const SdpSimulcastAttribute& SipccSdpAttributeList::GetSimulcast() const {
+ if (!HasAttribute(SdpAttribute::kSimulcastAttribute)) {
+ MOZ_CRASH();
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kSimulcastAttribute);
+ return *static_cast<const SdpSimulcastAttribute*>(attr);
+}
+
+const std::string& SipccSdpAttributeList::GetLabel() const {
+ if (!HasAttribute(SdpAttribute::kLabelAttribute)) {
+ return kEmptyString;
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kLabelAttribute);
+ return static_cast<const SdpStringAttribute*>(attr)->mValue;
+}
+
+uint32_t SipccSdpAttributeList::GetMaxptime() const {
+ if (!HasAttribute(SdpAttribute::kMaxptimeAttribute)) {
+ MOZ_CRASH();
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kMaxptimeAttribute);
+ return static_cast<const SdpNumberAttribute*>(attr)->mValue;
+}
+
+const std::string& SipccSdpAttributeList::GetMid() const {
+ if (!HasAttribute(SdpAttribute::kMidAttribute)) {
+ return kEmptyString;
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kMidAttribute);
+ return static_cast<const SdpStringAttribute*>(attr)->mValue;
+}
+
+const SdpMsidAttributeList& SipccSdpAttributeList::GetMsid() const {
+ if (!HasAttribute(SdpAttribute::kMsidAttribute)) {
+ MOZ_CRASH();
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kMsidAttribute);
+ return *static_cast<const SdpMsidAttributeList*>(attr);
+}
+
+const SdpMsidSemanticAttributeList& SipccSdpAttributeList::GetMsidSemantic()
+ const {
+ if (!HasAttribute(SdpAttribute::kMsidSemanticAttribute)) {
+ MOZ_CRASH();
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kMsidSemanticAttribute);
+ return *static_cast<const SdpMsidSemanticAttributeList*>(attr);
+}
+
+const SdpRidAttributeList& SipccSdpAttributeList::GetRid() const {
+ if (!HasAttribute(SdpAttribute::kRidAttribute)) {
+ MOZ_CRASH();
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kRidAttribute);
+ return *static_cast<const SdpRidAttributeList*>(attr);
+}
+
+uint32_t SipccSdpAttributeList::GetPtime() const {
+ if (!HasAttribute(SdpAttribute::kPtimeAttribute)) {
+ MOZ_CRASH();
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kPtimeAttribute);
+ return static_cast<const SdpNumberAttribute*>(attr)->mValue;
+}
+
+const SdpRtcpAttribute& SipccSdpAttributeList::GetRtcp() const {
+ if (!HasAttribute(SdpAttribute::kRtcpAttribute)) {
+ MOZ_CRASH();
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kRtcpAttribute);
+ return *static_cast<const SdpRtcpAttribute*>(attr);
+}
+
+const SdpRtcpFbAttributeList& SipccSdpAttributeList::GetRtcpFb() const {
+ if (!HasAttribute(SdpAttribute::kRtcpFbAttribute)) {
+ MOZ_CRASH();
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kRtcpFbAttribute);
+ return *static_cast<const SdpRtcpFbAttributeList*>(attr);
+}
+
+const SdpRemoteCandidatesAttribute& SipccSdpAttributeList::GetRemoteCandidates()
+ const {
+ MOZ_CRASH("Not yet implemented");
+}
+
+const SdpRtpmapAttributeList& SipccSdpAttributeList::GetRtpmap() const {
+ if (!HasAttribute(SdpAttribute::kRtpmapAttribute)) {
+ MOZ_CRASH();
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kRtpmapAttribute);
+ return *static_cast<const SdpRtpmapAttributeList*>(attr);
+}
+
+const SdpSctpmapAttributeList& SipccSdpAttributeList::GetSctpmap() const {
+ if (!HasAttribute(SdpAttribute::kSctpmapAttribute)) {
+ MOZ_CRASH();
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kSctpmapAttribute);
+ return *static_cast<const SdpSctpmapAttributeList*>(attr);
+}
+
+uint32_t SipccSdpAttributeList::GetSctpPort() const {
+ if (!HasAttribute(SdpAttribute::kSctpPortAttribute)) {
+ MOZ_CRASH();
+ }
+
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kSctpPortAttribute);
+ return static_cast<const SdpNumberAttribute*>(attr)->mValue;
+}
+
+uint32_t SipccSdpAttributeList::GetMaxMessageSize() const {
+ if (!HasAttribute(SdpAttribute::kMaxMessageSizeAttribute)) {
+ MOZ_CRASH();
+ }
+
+ const SdpAttribute* attr =
+ GetAttribute(SdpAttribute::kMaxMessageSizeAttribute);
+ return static_cast<const SdpNumberAttribute*>(attr)->mValue;
+}
+
+const SdpSetupAttribute& SipccSdpAttributeList::GetSetup() const {
+ if (!HasAttribute(SdpAttribute::kSetupAttribute)) {
+ MOZ_CRASH();
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kSetupAttribute);
+ return *static_cast<const SdpSetupAttribute*>(attr);
+}
+
+const SdpSsrcAttributeList& SipccSdpAttributeList::GetSsrc() const {
+ if (!HasAttribute(SdpAttribute::kSsrcAttribute)) {
+ MOZ_CRASH();
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kSsrcAttribute);
+ return *static_cast<const SdpSsrcAttributeList*>(attr);
+}
+
+const SdpSsrcGroupAttributeList& SipccSdpAttributeList::GetSsrcGroup() const {
+ if (!HasAttribute(SdpAttribute::kSsrcGroupAttribute)) {
+ MOZ_CRASH();
+ }
+ const SdpAttribute* attr = GetAttribute(SdpAttribute::kSsrcGroupAttribute);
+ return *static_cast<const SdpSsrcGroupAttributeList*>(attr);
+}
+
+void SipccSdpAttributeList::Serialize(std::ostream& os) const {
+ for (size_t i = 0; i < kNumAttributeTypes; ++i) {
+ if (mAttributes[i]) {
+ os << *mAttributes[i];
+ }
+ }
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/sdp/SipccSdpAttributeList.h b/dom/media/webrtc/sdp/SipccSdpAttributeList.h
new file mode 100644
index 0000000000..4499e84bb9
--- /dev/null
+++ b/dom/media/webrtc/sdp/SipccSdpAttributeList.h
@@ -0,0 +1,145 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _SIPCCSDPATTRIBUTELIST_H_
+#define _SIPCCSDPATTRIBUTELIST_H_
+
+#include "sdp/SdpParser.h"
+#include "sdp/SdpAttributeList.h"
+
+extern "C" {
+#include "sipcc_sdp.h"
+}
+
+namespace mozilla {
+
+class SipccSdp;
+class SipccSdpMediaSection;
+
+class SipccSdpAttributeList : public SdpAttributeList {
+ friend class SipccSdpMediaSection;
+ friend class SipccSdp;
+
+ public:
+ // Make sure we don't hide the default arg thunks
+ using SdpAttributeList::GetAttribute;
+ using SdpAttributeList::HasAttribute;
+
+ virtual bool HasAttribute(AttributeType type,
+ bool sessionFallback) const override;
+ virtual const SdpAttribute* GetAttribute(AttributeType type,
+ bool sessionFallback) const override;
+ virtual void SetAttribute(SdpAttribute* attr) override;
+ virtual void RemoveAttribute(AttributeType type) override;
+ virtual void Clear() override;
+ virtual uint32_t Count() const override;
+
+ virtual const SdpConnectionAttribute& GetConnection() const override;
+ virtual const SdpFingerprintAttributeList& GetFingerprint() const override;
+ virtual const SdpGroupAttributeList& GetGroup() const override;
+ virtual const SdpOptionsAttribute& GetIceOptions() const override;
+ virtual const SdpRtcpAttribute& GetRtcp() const override;
+ virtual const SdpRemoteCandidatesAttribute& GetRemoteCandidates()
+ const override;
+ virtual const SdpSetupAttribute& GetSetup() const override;
+ virtual const SdpSsrcAttributeList& GetSsrc() const override;
+ virtual const SdpSsrcGroupAttributeList& GetSsrcGroup() const override;
+ virtual const SdpDtlsMessageAttribute& GetDtlsMessage() const override;
+
+ // These attributes can appear multiple times, so the returned
+ // classes actually represent a collection of values.
+ virtual const std::vector<std::string>& GetCandidate() const override;
+ virtual const SdpExtmapAttributeList& GetExtmap() const override;
+ virtual const SdpFmtpAttributeList& GetFmtp() const override;
+ virtual const SdpImageattrAttributeList& GetImageattr() const override;
+ const SdpSimulcastAttribute& GetSimulcast() const override;
+ virtual const SdpMsidAttributeList& GetMsid() const override;
+ virtual const SdpMsidSemanticAttributeList& GetMsidSemantic() const override;
+ const SdpRidAttributeList& GetRid() const override;
+ virtual const SdpRtcpFbAttributeList& GetRtcpFb() const override;
+ virtual const SdpRtpmapAttributeList& GetRtpmap() const override;
+ virtual const SdpSctpmapAttributeList& GetSctpmap() const override;
+ virtual uint32_t GetSctpPort() const override;
+ virtual uint32_t GetMaxMessageSize() const override;
+
+ // These attributes are effectively simple types, so we'll make life
+ // easy by just returning their value.
+ virtual const std::string& GetIcePwd() const override;
+ virtual const std::string& GetIceUfrag() const override;
+ virtual const std::string& GetIdentity() const override;
+ virtual const std::string& GetLabel() const override;
+ virtual unsigned int GetMaxptime() const override;
+ virtual const std::string& GetMid() const override;
+ virtual unsigned int GetPtime() const override;
+
+ virtual SdpDirectionAttribute::Direction GetDirection() const override;
+
+ virtual void Serialize(std::ostream&) const override;
+
+ virtual ~SipccSdpAttributeList();
+
+ private:
+ static const std::string kEmptyString;
+ static const size_t kNumAttributeTypes = SdpAttribute::kLastAttribute + 1;
+
+ // Pass a session-level attribute list if constructing a media-level one,
+ // otherwise pass nullptr
+ explicit SipccSdpAttributeList(const SipccSdpAttributeList* sessionLevel);
+
+ // Copy c'tor, sort of
+ SipccSdpAttributeList(const SipccSdpAttributeList& aOrig,
+ const SipccSdpAttributeList* sessionLevel);
+
+ using InternalResults = SdpParser::InternalResults;
+
+ bool Load(sdp_t* sdp, uint16_t level, InternalResults& results);
+ void LoadSimpleStrings(sdp_t* sdp, uint16_t level, InternalResults& results);
+ void LoadSimpleString(sdp_t* sdp, uint16_t level, sdp_attr_e attr,
+ AttributeType targetType, InternalResults& results);
+ void LoadSimpleNumbers(sdp_t* sdp, uint16_t level, InternalResults& results);
+ void LoadSimpleNumber(sdp_t* sdp, uint16_t level, sdp_attr_e attr,
+ AttributeType targetType, InternalResults& results);
+ void LoadFlags(sdp_t* sdp, uint16_t level);
+ void LoadDirection(sdp_t* sdp, uint16_t level, InternalResults& results);
+ bool LoadRtpmap(sdp_t* sdp, uint16_t level, InternalResults& results);
+ bool LoadSctpmap(sdp_t* sdp, uint16_t level, InternalResults& results);
+ void LoadIceAttributes(sdp_t* sdp, uint16_t level);
+ bool LoadFingerprint(sdp_t* sdp, uint16_t level, InternalResults& results);
+ void LoadCandidate(sdp_t* sdp, uint16_t level);
+ void LoadSetup(sdp_t* sdp, uint16_t level);
+ void LoadSsrc(sdp_t* sdp, uint16_t level);
+ void LoadSsrcGroup(sdp_t* sdp, uint16_t level);
+ bool LoadImageattr(sdp_t* sdp, uint16_t level, InternalResults& results);
+ bool LoadSimulcast(sdp_t* sdp, uint16_t level, InternalResults& results);
+ bool LoadGroups(sdp_t* sdp, uint16_t level, InternalResults& results);
+ bool LoadMsidSemantics(sdp_t* sdp, uint16_t level, InternalResults& results);
+ void LoadIdentity(sdp_t* sdp, uint16_t level);
+ void LoadDtlsMessage(sdp_t* sdp, uint16_t level);
+ void LoadFmtp(sdp_t* sdp, uint16_t level);
+ void LoadMsids(sdp_t* sdp, uint16_t level, InternalResults& results);
+ bool LoadRid(sdp_t* sdp, uint16_t level, InternalResults& results);
+ void LoadExtmap(sdp_t* sdp, uint16_t level, InternalResults& results);
+ void LoadRtcpFb(sdp_t* sdp, uint16_t level, InternalResults& results);
+ void LoadRtcp(sdp_t* sdp, uint16_t level, InternalResults& results);
+ static SdpRtpmapAttributeList::CodecType GetCodecType(rtp_ptype type);
+
+ bool AtSessionLevel() const { return !mSessionLevel; }
+ bool IsAllowedHere(SdpAttribute::AttributeType type) const;
+ void WarnAboutMisplacedAttribute(SdpAttribute::AttributeType type,
+ uint32_t lineNumber,
+ InternalResults& results);
+
+ const SipccSdpAttributeList* mSessionLevel;
+
+ SdpAttribute* mAttributes[kNumAttributeTypes];
+
+ SipccSdpAttributeList(const SipccSdpAttributeList& orig) = delete;
+ SipccSdpAttributeList& operator=(const SipccSdpAttributeList& rhs) = delete;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/sdp/SipccSdpMediaSection.cpp b/dom/media/webrtc/sdp/SipccSdpMediaSection.cpp
new file mode 100644
index 0000000000..4304ca541e
--- /dev/null
+++ b/dom/media/webrtc/sdp/SipccSdpMediaSection.cpp
@@ -0,0 +1,401 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "sdp/SipccSdpMediaSection.h"
+
+#include <ostream>
+#include "sdp/SdpParser.h"
+
+extern "C" {
+#include "sipcc_sdp.h"
+}
+
+#ifdef CRLF
+# undef CRLF
+#endif
+#define CRLF "\r\n"
+
+namespace mozilla {
+
+SipccSdpMediaSection::SipccSdpMediaSection(
+ const SipccSdpMediaSection& aOrig,
+ const SipccSdpAttributeList* sessionLevel)
+ : SdpMediaSection(aOrig),
+ mMediaType(aOrig.mMediaType),
+ mPort(aOrig.mPort),
+ mPortCount(aOrig.mPortCount),
+ mProtocol(aOrig.mProtocol),
+ mFormats(aOrig.mFormats),
+ mConnection(new SdpConnection(*aOrig.mConnection)),
+ mBandwidths(aOrig.mBandwidths),
+ mAttributeList(aOrig.mAttributeList, sessionLevel) {}
+
+unsigned int SipccSdpMediaSection::GetPort() const { return mPort; }
+
+void SipccSdpMediaSection::SetPort(unsigned int port) { mPort = port; }
+
+unsigned int SipccSdpMediaSection::GetPortCount() const { return mPortCount; }
+
+SdpMediaSection::Protocol SipccSdpMediaSection::GetProtocol() const {
+ return mProtocol;
+}
+
+const SdpConnection& SipccSdpMediaSection::GetConnection() const {
+ return *mConnection;
+}
+
+SdpConnection& SipccSdpMediaSection::GetConnection() { return *mConnection; }
+
+uint32_t SipccSdpMediaSection::GetBandwidth(const std::string& type) const {
+ auto found = mBandwidths.find(type);
+ if (found == mBandwidths.end()) {
+ return 0;
+ }
+ return found->second;
+}
+
+const std::vector<std::string>& SipccSdpMediaSection::GetFormats() const {
+ return mFormats;
+}
+
+const SdpAttributeList& SipccSdpMediaSection::GetAttributeList() const {
+ return mAttributeList;
+}
+
+SdpAttributeList& SipccSdpMediaSection::GetAttributeList() {
+ return mAttributeList;
+}
+
+SdpDirectionAttribute SipccSdpMediaSection::GetDirectionAttribute() const {
+ return SdpDirectionAttribute(mAttributeList.GetDirection());
+}
+
+bool SipccSdpMediaSection::Load(sdp_t* sdp, uint16_t level,
+ InternalResults& results) {
+ switch (sdp_get_media_type(sdp, level)) {
+ case SDP_MEDIA_AUDIO:
+ mMediaType = kAudio;
+ break;
+ case SDP_MEDIA_VIDEO:
+ mMediaType = kVideo;
+ break;
+ case SDP_MEDIA_APPLICATION:
+ mMediaType = kApplication;
+ break;
+ case SDP_MEDIA_TEXT:
+ mMediaType = kText;
+ break;
+
+ default:
+ results.AddParseError(sdp_get_media_line_number(sdp, level),
+ "Unsupported media section type");
+ return false;
+ }
+
+ mPort = sdp_get_media_portnum(sdp, level);
+ int32_t pc = sdp_get_media_portcount(sdp, level);
+ if (pc == SDP_INVALID_VALUE) {
+ // SDP_INVALID_VALUE (ie; -2) is used when there is no port count. :(
+ mPortCount = 0;
+ } else if (pc > static_cast<int32_t>(UINT16_MAX) || pc < 0) {
+ results.AddParseError(sdp_get_media_line_number(sdp, level),
+ "Invalid port count");
+ return false;
+ } else {
+ mPortCount = pc;
+ }
+
+ if (!LoadProtocol(sdp, level, results)) {
+ return false;
+ }
+
+ if (!LoadFormats(sdp, level, results)) {
+ return false;
+ }
+
+ if (!mAttributeList.Load(sdp, level, results)) {
+ return false;
+ }
+
+ if (!ValidateSimulcast(sdp, level, results)) {
+ return false;
+ }
+
+ if (!mBandwidths.Load(sdp, level, results)) {
+ return false;
+ }
+
+ return LoadConnection(sdp, level, results);
+}
+
+bool SipccSdpMediaSection::LoadProtocol(sdp_t* sdp, uint16_t level,
+ InternalResults& results) {
+ switch (sdp_get_media_transport(sdp, level)) {
+ case SDP_TRANSPORT_RTPAVP:
+ mProtocol = kRtpAvp;
+ break;
+ case SDP_TRANSPORT_RTPSAVP:
+ mProtocol = kRtpSavp;
+ break;
+ case SDP_TRANSPORT_RTPAVPF:
+ mProtocol = kRtpAvpf;
+ break;
+ case SDP_TRANSPORT_RTPSAVPF:
+ mProtocol = kRtpSavpf;
+ break;
+ case SDP_TRANSPORT_UDPTLSRTPSAVP:
+ mProtocol = kUdpTlsRtpSavp;
+ break;
+ case SDP_TRANSPORT_UDPTLSRTPSAVPF:
+ mProtocol = kUdpTlsRtpSavpf;
+ break;
+ case SDP_TRANSPORT_TCPDTLSRTPSAVP:
+ mProtocol = kTcpDtlsRtpSavp;
+ break;
+ case SDP_TRANSPORT_TCPDTLSRTPSAVPF:
+ mProtocol = kTcpDtlsRtpSavpf;
+ break;
+ case SDP_TRANSPORT_DTLSSCTP:
+ mProtocol = kDtlsSctp;
+ break;
+ case SDP_TRANSPORT_UDPDTLSSCTP:
+ mProtocol = kUdpDtlsSctp;
+ break;
+ case SDP_TRANSPORT_TCPDTLSSCTP:
+ mProtocol = kTcpDtlsSctp;
+ break;
+
+ default:
+ results.AddParseError(sdp_get_media_line_number(sdp, level),
+ "Unsupported media transport type");
+ return false;
+ }
+ return true;
+}
+
+bool SipccSdpMediaSection::LoadFormats(sdp_t* sdp, uint16_t level,
+ InternalResults& results) {
+ sdp_media_e mtype = sdp_get_media_type(sdp, level);
+
+ if (mtype == SDP_MEDIA_APPLICATION) {
+ sdp_transport_e ttype = sdp_get_media_transport(sdp, level);
+ if ((ttype == SDP_TRANSPORT_UDPDTLSSCTP) ||
+ (ttype == SDP_TRANSPORT_TCPDTLSSCTP)) {
+ if (sdp_get_media_sctp_fmt(sdp, level) ==
+ SDP_SCTP_MEDIA_FMT_WEBRTC_DATACHANNEL) {
+ mFormats.push_back("webrtc-datachannel");
+ }
+ } else {
+ uint32_t ptype = sdp_get_media_sctp_port(sdp, level);
+ std::ostringstream osPayloadType;
+ osPayloadType << ptype;
+ mFormats.push_back(osPayloadType.str());
+ }
+ } else if (mtype == SDP_MEDIA_AUDIO || mtype == SDP_MEDIA_VIDEO) {
+ uint16_t count = sdp_get_media_num_payload_types(sdp, level);
+ for (uint16_t i = 0; i < count; ++i) {
+ sdp_payload_ind_e indicator; // we ignore this, which is fine
+ uint32_t ptype =
+ sdp_get_media_payload_type(sdp, level, i + 1, &indicator);
+
+ if (GET_DYN_PAYLOAD_TYPE_VALUE(ptype) > UINT8_MAX) {
+ results.AddParseError(sdp_get_media_line_number(sdp, level),
+ "Format is too large");
+ return false;
+ }
+
+ std::ostringstream osPayloadType;
+ // sipcc stores payload types in a funny way. When sipcc and the SDP it
+ // parsed differ on what payload type number should be used for a given
+ // codec, sipcc's value goes in the lower byte, and the SDP's value in
+ // the upper byte. When they do not differ, only the lower byte is used.
+ // We want what was in the SDP, verbatim.
+ osPayloadType << GET_DYN_PAYLOAD_TYPE_VALUE(ptype);
+ mFormats.push_back(osPayloadType.str());
+ }
+ }
+
+ return true;
+}
+
+bool SipccSdpMediaSection::ValidateSimulcast(sdp_t* sdp, uint16_t level,
+ InternalResults& results) const {
+ if (!GetAttributeList().HasAttribute(SdpAttribute::kSimulcastAttribute)) {
+ return true;
+ }
+
+ const SdpSimulcastAttribute& simulcast(GetAttributeList().GetSimulcast());
+ if (!ValidateSimulcastVersions(sdp, level, simulcast.sendVersions, sdp::kSend,
+ results)) {
+ return false;
+ }
+ if (!ValidateSimulcastVersions(sdp, level, simulcast.recvVersions, sdp::kRecv,
+ results)) {
+ return false;
+ }
+ return true;
+}
+
+bool SipccSdpMediaSection::ValidateSimulcastVersions(
+ sdp_t* sdp, uint16_t level, const SdpSimulcastAttribute::Versions& versions,
+ sdp::Direction direction, InternalResults& results) const {
+ for (const SdpSimulcastAttribute::Version& version : versions) {
+ for (const SdpSimulcastAttribute::Encoding& encoding : version.choices) {
+ const SdpRidAttributeList::Rid* ridAttr = FindRid(encoding.rid);
+ if (!ridAttr || (ridAttr->direction != direction)) {
+ std::ostringstream os;
+ os << "No rid attribute for \'" << encoding.rid << "\'";
+ results.AddParseError(sdp_get_media_line_number(sdp, level), os.str());
+ results.AddParseError(sdp_get_media_line_number(sdp, level), os.str());
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+bool SipccSdpMediaSection::LoadConnection(sdp_t* sdp, uint16_t level,
+ InternalResults& results) {
+ if (!sdp_connection_valid(sdp, level)) {
+ level = SDP_SESSION_LEVEL;
+ if (!sdp_connection_valid(sdp, level)) {
+ results.AddParseError(sdp_get_media_line_number(sdp, level),
+ "Missing c= line");
+ return false;
+ }
+ }
+
+ sdp_nettype_e type = sdp_get_conn_nettype(sdp, level);
+ if (type != SDP_NT_INTERNET) {
+ results.AddParseError(sdp_get_media_line_number(sdp, level),
+ "Unsupported network type");
+ return false;
+ }
+
+ sdp::AddrType addrType;
+ switch (sdp_get_conn_addrtype(sdp, level)) {
+ case SDP_AT_IP4:
+ addrType = sdp::kIPv4;
+ break;
+ case SDP_AT_IP6:
+ addrType = sdp::kIPv6;
+ break;
+ default:
+ results.AddParseError(sdp_get_media_line_number(sdp, level),
+ "Unsupported address type");
+ return false;
+ }
+
+ std::string address = sdp_get_conn_address(sdp, level);
+ int16_t ttl = static_cast<uint16_t>(sdp_get_mcast_ttl(sdp, level));
+ if (ttl < 0) {
+ ttl = 0;
+ }
+ int32_t numAddr =
+ static_cast<uint32_t>(sdp_get_mcast_num_of_addresses(sdp, level));
+ if (numAddr < 0) {
+ numAddr = 0;
+ }
+ mConnection = MakeUnique<SdpConnection>(addrType, address, ttl, numAddr);
+ return true;
+}
+
+void SipccSdpMediaSection::AddCodec(const std::string& pt,
+ const std::string& name, uint32_t clockrate,
+ uint16_t channels) {
+ mFormats.push_back(pt);
+
+ SdpRtpmapAttributeList* rtpmap = new SdpRtpmapAttributeList();
+ if (mAttributeList.HasAttribute(SdpAttribute::kRtpmapAttribute)) {
+ const SdpRtpmapAttributeList& old = mAttributeList.GetRtpmap();
+ for (auto it = old.mRtpmaps.begin(); it != old.mRtpmaps.end(); ++it) {
+ rtpmap->mRtpmaps.push_back(*it);
+ }
+ }
+ SdpRtpmapAttributeList::CodecType codec = SdpRtpmapAttributeList::kOtherCodec;
+ if (name == "opus") {
+ codec = SdpRtpmapAttributeList::kOpus;
+ } else if (name == "G722") {
+ codec = SdpRtpmapAttributeList::kG722;
+ } else if (name == "PCMU") {
+ codec = SdpRtpmapAttributeList::kPCMU;
+ } else if (name == "PCMA") {
+ codec = SdpRtpmapAttributeList::kPCMA;
+ } else if (name == "VP8") {
+ codec = SdpRtpmapAttributeList::kVP8;
+ } else if (name == "VP9") {
+ codec = SdpRtpmapAttributeList::kVP9;
+ } else if (name == "H264") {
+ codec = SdpRtpmapAttributeList::kH264;
+ }
+
+ rtpmap->PushEntry(pt, codec, name, clockrate, channels);
+ mAttributeList.SetAttribute(rtpmap);
+}
+
+void SipccSdpMediaSection::ClearCodecs() {
+ mFormats.clear();
+ mAttributeList.RemoveAttribute(SdpAttribute::kRtpmapAttribute);
+ mAttributeList.RemoveAttribute(SdpAttribute::kFmtpAttribute);
+ mAttributeList.RemoveAttribute(SdpAttribute::kSctpmapAttribute);
+ mAttributeList.RemoveAttribute(SdpAttribute::kRtcpFbAttribute);
+}
+
+void SipccSdpMediaSection::AddDataChannel(const std::string& name,
+ uint16_t port, uint16_t streams,
+ uint32_t message_size) {
+ // Only one allowed, for now. This may change as the specs (and deployments)
+ // evolve.
+ mFormats.clear();
+ if ((mProtocol == kUdpDtlsSctp) || (mProtocol == kTcpDtlsSctp)) {
+ // new data channel format according to draft 21
+ mFormats.push_back(name);
+ mAttributeList.SetAttribute(
+ new SdpNumberAttribute(SdpAttribute::kSctpPortAttribute, port));
+ if (message_size) {
+ mAttributeList.SetAttribute(new SdpNumberAttribute(
+ SdpAttribute::kMaxMessageSizeAttribute, message_size));
+ }
+ } else {
+ // old data channels format according to draft 05
+ std::string port_str = std::to_string(port);
+ mFormats.push_back(port_str);
+ SdpSctpmapAttributeList* sctpmap = new SdpSctpmapAttributeList();
+ sctpmap->PushEntry(port_str, name, streams);
+ mAttributeList.SetAttribute(sctpmap);
+ if (message_size) {
+ // This is a workaround to allow detecting Firefox's w/o EOR support
+ mAttributeList.SetAttribute(new SdpNumberAttribute(
+ SdpAttribute::kMaxMessageSizeAttribute, message_size));
+ }
+ }
+}
+
+void SipccSdpMediaSection::Serialize(std::ostream& os) const {
+ os << "m=" << mMediaType << " " << mPort;
+ if (mPortCount) {
+ os << "/" << mPortCount;
+ }
+ os << " " << mProtocol;
+ for (auto i = mFormats.begin(); i != mFormats.end(); ++i) {
+ os << " " << (*i);
+ }
+ os << CRLF;
+
+ // We dont do i=
+
+ if (mConnection) {
+ os << *mConnection;
+ }
+
+ mBandwidths.Serialize(os);
+
+ // We dont do k= because they're evil
+
+ os << mAttributeList;
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/sdp/SipccSdpMediaSection.h b/dom/media/webrtc/sdp/SipccSdpMediaSection.h
new file mode 100644
index 0000000000..c9abb4d33c
--- /dev/null
+++ b/dom/media/webrtc/sdp/SipccSdpMediaSection.h
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _SIPCCSDPMEDIASECTION_H_
+#define _SIPCCSDPMEDIASECTION_H_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+#include "sdp/SdpMediaSection.h"
+#include "sdp/SipccSdpAttributeList.h"
+
+#include <map>
+
+extern "C" {
+#include "sipcc_sdp.h"
+}
+
+namespace mozilla {
+
+class SipccSdp;
+class SdpParser;
+
+using InternalResults = SdpParser::InternalResults;
+
+class SipccSdpBandwidths final : public std::map<std::string, uint32_t> {
+ public:
+ bool Load(sdp_t* sdp, uint16_t level, InternalResults& results);
+ void Serialize(std::ostream& os) const;
+};
+
+class SipccSdpMediaSection final : public SdpMediaSection {
+ friend class SipccSdp;
+
+ public:
+ ~SipccSdpMediaSection() {}
+
+ virtual MediaType GetMediaType() const override { return mMediaType; }
+
+ virtual unsigned int GetPort() const override;
+ virtual void SetPort(unsigned int port) override;
+ virtual unsigned int GetPortCount() const override;
+ virtual Protocol GetProtocol() const override;
+ virtual const SdpConnection& GetConnection() const override;
+ virtual SdpConnection& GetConnection() override;
+ virtual uint32_t GetBandwidth(const std::string& type) const override;
+ virtual const std::vector<std::string>& GetFormats() const override;
+
+ virtual const SdpAttributeList& GetAttributeList() const override;
+ virtual SdpAttributeList& GetAttributeList() override;
+ virtual SdpDirectionAttribute GetDirectionAttribute() const override;
+
+ virtual void AddCodec(const std::string& pt, const std::string& name,
+ uint32_t clockrate, uint16_t channels) override;
+ virtual void ClearCodecs() override;
+
+ virtual void AddDataChannel(const std::string& name, uint16_t port,
+ uint16_t streams, uint32_t message_size) override;
+
+ virtual void Serialize(std::ostream&) const override;
+
+ private:
+ SipccSdpMediaSection(size_t level, const SipccSdpAttributeList* sessionLevel)
+ : SdpMediaSection(level),
+ mMediaType(static_cast<MediaType>(0)),
+ mPort(0),
+ mPortCount(0),
+ mProtocol(static_cast<Protocol>(0)),
+ mAttributeList(sessionLevel) {}
+
+ SipccSdpMediaSection(const SipccSdpMediaSection& aOrig,
+ const SipccSdpAttributeList* sessionLevel);
+
+ bool Load(sdp_t* sdp, uint16_t level, InternalResults& results);
+ bool LoadConnection(sdp_t* sdp, uint16_t level, InternalResults& results);
+ bool LoadProtocol(sdp_t* sdp, uint16_t level, InternalResults& results);
+ bool LoadFormats(sdp_t* sdp, uint16_t level, InternalResults& results);
+ bool ValidateSimulcast(sdp_t* sdp, uint16_t level,
+ InternalResults& results) const;
+ bool ValidateSimulcastVersions(
+ sdp_t* sdp, uint16_t level,
+ const SdpSimulcastAttribute::Versions& versions, sdp::Direction direction,
+ InternalResults& results) const;
+
+ // the following values are cached on first get
+ MediaType mMediaType;
+ uint16_t mPort;
+ uint16_t mPortCount;
+ Protocol mProtocol;
+ std::vector<std::string> mFormats;
+
+ UniquePtr<SdpConnection> mConnection;
+ SipccSdpBandwidths mBandwidths;
+
+ SipccSdpAttributeList mAttributeList;
+};
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/sdp/SipccSdpParser.cpp b/dom/media/webrtc/sdp/SipccSdpParser.cpp
new file mode 100644
index 0000000000..901efd041f
--- /dev/null
+++ b/dom/media/webrtc/sdp/SipccSdpParser.cpp
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "sdp/SipccSdpParser.h"
+#include "sdp/SipccSdp.h"
+
+#include <utility>
+extern "C" {
+#include "sipcc_sdp.h"
+}
+
+namespace mozilla {
+
+extern "C" {
+
+void sipcc_sdp_parser_results_handler(void* context, uint32_t line,
+ const char* message) {
+ auto* results = static_cast<UniquePtr<InternalResults>*>(context);
+ std::string err(message);
+ (*results)->AddParseError(line, err);
+}
+
+} // extern "C"
+
+const std::string& SipccSdpParser::ParserName() {
+ static const std::string SIPCC_NAME = "SIPCC";
+ return SIPCC_NAME;
+}
+
+UniquePtr<SdpParser::Results> SipccSdpParser::Parse(const std::string& aText) {
+ UniquePtr<InternalResults> results(new InternalResults(Name()));
+ sdp_conf_options_t* sipcc_config = sdp_init_config();
+ if (!sipcc_config) {
+ return UniquePtr<SdpParser::Results>();
+ }
+
+ sdp_nettype_supported(sipcc_config, SDP_NT_INTERNET, true);
+ sdp_addrtype_supported(sipcc_config, SDP_AT_IP4, true);
+ sdp_addrtype_supported(sipcc_config, SDP_AT_IP6, true);
+ sdp_transport_supported(sipcc_config, SDP_TRANSPORT_RTPAVP, true);
+ sdp_transport_supported(sipcc_config, SDP_TRANSPORT_RTPAVPF, true);
+ sdp_transport_supported(sipcc_config, SDP_TRANSPORT_RTPSAVP, true);
+ sdp_transport_supported(sipcc_config, SDP_TRANSPORT_RTPSAVPF, true);
+ sdp_transport_supported(sipcc_config, SDP_TRANSPORT_UDPTLSRTPSAVP, true);
+ sdp_transport_supported(sipcc_config, SDP_TRANSPORT_UDPTLSRTPSAVPF, true);
+ sdp_transport_supported(sipcc_config, SDP_TRANSPORT_TCPDTLSRTPSAVP, true);
+ sdp_transport_supported(sipcc_config, SDP_TRANSPORT_TCPDTLSRTPSAVPF, true);
+ sdp_transport_supported(sipcc_config, SDP_TRANSPORT_DTLSSCTP, true);
+ sdp_transport_supported(sipcc_config, SDP_TRANSPORT_UDPDTLSSCTP, true);
+ sdp_transport_supported(sipcc_config, SDP_TRANSPORT_TCPDTLSSCTP, true);
+ sdp_require_session_name(sipcc_config, false);
+
+ sdp_config_set_error_handler(sipcc_config, &sipcc_sdp_parser_results_handler,
+ &results);
+
+ // Takes ownership of |sipcc_config| iff it succeeds
+ sdp_t* sdp = sdp_init_description(sipcc_config);
+ if (!sdp) {
+ sdp_free_config(sipcc_config);
+ return results;
+ }
+
+ const char* rawString = aText.c_str();
+ sdp_result_e sdpres = sdp_parse(sdp, rawString, aText.length());
+ if (sdpres != SDP_SUCCESS) {
+ sdp_free_description(sdp);
+ return results;
+ }
+
+ UniquePtr<SipccSdp> sipccSdp(new SipccSdp);
+
+ bool success = sipccSdp->Load(sdp, *results);
+ sdp_free_description(sdp);
+ if (success) {
+ results->SetSdp(UniquePtr<mozilla::Sdp>(std::move(sipccSdp)));
+ }
+
+ return results;
+}
+
+bool SipccSdpParser::IsNamed(const std::string& aName) {
+ return aName == ParserName();
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/sdp/SipccSdpParser.h b/dom/media/webrtc/sdp/SipccSdpParser.h
new file mode 100644
index 0000000000..dc9c1ea02d
--- /dev/null
+++ b/dom/media/webrtc/sdp/SipccSdpParser.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _SIPCCSDPPARSER_H_
+#define _SIPCCSDPPARSER_H_
+
+#include <string>
+
+#include "mozilla/UniquePtr.h"
+
+#include "sdp/Sdp.h"
+#include "sdp/SdpParser.h"
+
+namespace mozilla {
+
+class SipccSdpParser final : public SdpParser {
+ static const std::string& ParserName();
+
+ public:
+ SipccSdpParser() = default;
+ virtual ~SipccSdpParser() = default;
+
+ const std::string& Name() const override { return ParserName(); }
+
+ UniquePtr<SdpParser::Results> Parse(const std::string& aText) override;
+
+ static bool IsNamed(const std::string& aName);
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/sdp/moz.build b/dom/media/webrtc/sdp/moz.build
new file mode 100644
index 0000000000..675009acd0
--- /dev/null
+++ b/dom/media/webrtc/sdp/moz.build
@@ -0,0 +1,48 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+if CONFIG["OS_TARGET"] == "WINNT":
+ DEFINES["SIP_OS_WINDOWS"] = True
+elif CONFIG["OS_TARGET"] == "Darwin":
+ DEFINES["SIP_OS_OSX"] = True
+else:
+ DEFINES["SIP_OS_LINUX"] = True
+
+# Add libFuzzer configuration directives
+include("/tools/fuzzing/libfuzzer-config.mozbuild")
+
+LOCAL_INCLUDES += [
+ "/dom/media/webrtc",
+ "/media/webrtc",
+ "/third_party/sipcc",
+]
+
+UNIFIED_SOURCES += [
+ "HybridSdpParser.cpp",
+ "ParsingResultComparer.cpp",
+ "SdpAttribute.cpp",
+ "SdpHelper.cpp",
+ "SdpLog.cpp",
+ "SdpMediaSection.cpp",
+ "SdpPref.cpp",
+ "SdpTelemetry.cpp",
+ "SipccSdp.cpp",
+ "SipccSdpAttributeList.cpp",
+ "SipccSdpMediaSection.cpp",
+ "SipccSdpParser.cpp",
+]
+
+SOURCES += [
+ # Building these as part of the unified build leads to multiply defined
+ # symbols on windows.
+ "RsdparsaSdp.cpp",
+ "RsdparsaSdpAttributeList.cpp",
+ "RsdparsaSdpGlue.cpp",
+ "RsdparsaSdpMediaSection.cpp",
+ "RsdparsaSdpParser.cpp",
+]
+
+FINAL_LIBRARY = "xul"
diff --git a/dom/media/webrtc/sdp/rsdparsa_capi/Cargo.toml b/dom/media/webrtc/sdp/rsdparsa_capi/Cargo.toml
new file mode 100644
index 0000000000..a0aeff086f
--- /dev/null
+++ b/dom/media/webrtc/sdp/rsdparsa_capi/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "rsdparsa_capi"
+version = "0.1.0"
+authors = ["Paul Ellenbogen <pe5@cs.princeton.edu>",
+ "Nils Ohlmeier <github@ohlmeier.org>"]
+license = "MPL-2.0"
+
+[dependencies]
+libc = "^0.2.0"
+log = "0.4"
+rsdparsa = {package = "webrtc-sdp", version = "0.3.10"}
+nserror = { path = "../../../../../xpcom/rust/nserror" }
diff --git a/dom/media/webrtc/sdp/rsdparsa_capi/src/attribute.rs b/dom/media/webrtc/sdp/rsdparsa_capi/src/attribute.rs
new file mode 100644
index 0000000000..82483f151f
--- /dev/null
+++ b/dom/media/webrtc/sdp/rsdparsa_capi/src/attribute.rs
@@ -0,0 +1,1472 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use libc::{c_float, size_t};
+use std::ptr;
+use std::slice;
+
+use nserror::{nsresult, NS_ERROR_INVALID_ARG, NS_OK};
+use rsdparsa::attribute_type::*;
+use rsdparsa::SdpSession;
+
+use network::{RustAddress, RustExplicitlyTypedAddress};
+use types::StringView;
+
+#[no_mangle]
+pub unsafe extern "C" fn num_attributes(session: *const SdpSession) -> u32 {
+ (*session).attribute.len() as u32
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn get_attribute_ptr(
+ session: *const SdpSession,
+ index: u32,
+ ret: *mut *const SdpAttribute,
+) -> nsresult {
+ match (*session).attribute.get(index as usize) {
+ Some(attribute) => {
+ *ret = attribute as *const SdpAttribute;
+ NS_OK
+ }
+ None => NS_ERROR_INVALID_ARG,
+ }
+}
+
+fn count_attribute(attributes: &[SdpAttribute], search: SdpAttributeType) -> usize {
+ let mut count = 0;
+ for attribute in (*attributes).iter() {
+ if SdpAttributeType::from(attribute) == search {
+ count += 1;
+ }
+ }
+ count
+}
+
+fn argsearch(attributes: &[SdpAttribute], attribute_type: SdpAttributeType) -> Option<usize> {
+ for (i, attribute) in (*attributes).iter().enumerate() {
+ if SdpAttributeType::from(attribute) == attribute_type {
+ return Some(i);
+ }
+ }
+ None
+}
+
+pub unsafe fn has_attribute(
+ attributes: *const Vec<SdpAttribute>,
+ attribute_type: SdpAttributeType,
+) -> bool {
+ argsearch((*attributes).as_slice(), attribute_type).is_some()
+}
+
+fn get_attribute(
+ attributes: &[SdpAttribute],
+ attribute_type: SdpAttributeType,
+) -> Option<&SdpAttribute> {
+ argsearch(attributes, attribute_type).map(|i| &attributes[i])
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub enum RustSdpAttributeDtlsMessageType {
+ Client,
+ Server,
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct RustSdpAttributeDtlsMessage {
+ pub role: u8,
+ pub value: StringView,
+}
+
+impl<'a> From<&'a SdpAttributeDtlsMessage> for RustSdpAttributeDtlsMessage {
+ fn from(other: &SdpAttributeDtlsMessage) -> Self {
+ match other {
+ &SdpAttributeDtlsMessage::Client(ref x) => RustSdpAttributeDtlsMessage {
+ role: RustSdpAttributeDtlsMessageType::Client as u8,
+ value: StringView::from(x.as_str()),
+ },
+ &SdpAttributeDtlsMessage::Server(ref x) => RustSdpAttributeDtlsMessage {
+ role: RustSdpAttributeDtlsMessageType::Server as u8,
+ value: StringView::from(x.as_str()),
+ },
+ }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_dtls_message(
+ attributes: *const Vec<SdpAttribute>,
+ ret: *mut RustSdpAttributeDtlsMessage,
+) -> nsresult {
+ let attr = get_attribute((*attributes).as_slice(), SdpAttributeType::DtlsMessage);
+ if let Some(&SdpAttribute::DtlsMessage(ref dtls_message)) = attr {
+ *ret = RustSdpAttributeDtlsMessage::from(dtls_message);
+ return NS_OK;
+ }
+ NS_ERROR_INVALID_ARG
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_iceufrag(
+ attributes: *const Vec<SdpAttribute>,
+ ret: *mut StringView,
+) -> nsresult {
+ let attr = get_attribute((*attributes).as_slice(), SdpAttributeType::IceUfrag);
+ if let Some(&SdpAttribute::IceUfrag(ref string)) = attr {
+ *ret = StringView::from(string.as_str());
+ return NS_OK;
+ }
+ NS_ERROR_INVALID_ARG
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_icepwd(
+ attributes: *const Vec<SdpAttribute>,
+ ret: *mut StringView,
+) -> nsresult {
+ let attr = get_attribute((*attributes).as_slice(), SdpAttributeType::IcePwd);
+ if let Some(&SdpAttribute::IcePwd(ref string)) = attr {
+ *ret = StringView::from(string.as_str());
+ return NS_OK;
+ }
+ NS_ERROR_INVALID_ARG
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_identity(
+ attributes: *const Vec<SdpAttribute>,
+ ret: *mut StringView,
+) -> nsresult {
+ let attr = get_attribute((*attributes).as_slice(), SdpAttributeType::Identity);
+ if let Some(&SdpAttribute::Identity(ref string)) = attr {
+ *ret = StringView::from(string.as_str());
+ return NS_OK;
+ }
+ NS_ERROR_INVALID_ARG
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_iceoptions(
+ attributes: *const Vec<SdpAttribute>,
+ ret: *mut *const Vec<String>,
+) -> nsresult {
+ let attr = get_attribute((*attributes).as_slice(), SdpAttributeType::IceOptions);
+ if let Some(&SdpAttribute::IceOptions(ref options)) = attr {
+ *ret = options;
+ return NS_OK;
+ }
+ NS_ERROR_INVALID_ARG
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_maxptime(
+ attributes: *const Vec<SdpAttribute>,
+ ret: *mut u64,
+) -> nsresult {
+ let attr = get_attribute((*attributes).as_slice(), SdpAttributeType::MaxPtime);
+ if let Some(&SdpAttribute::MaxPtime(ref max_ptime)) = attr {
+ *ret = *max_ptime;
+ return NS_OK;
+ }
+ NS_ERROR_INVALID_ARG
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct RustSdpAttributeFingerprint {
+ hash_algorithm: u16,
+ fingerprint: *const Vec<u8>,
+}
+
+impl<'a> From<&'a SdpAttributeFingerprint> for RustSdpAttributeFingerprint {
+ fn from(other: &SdpAttributeFingerprint) -> Self {
+ RustSdpAttributeFingerprint {
+ hash_algorithm: other.hash_algorithm as u16,
+ fingerprint: &other.fingerprint,
+ }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_fingerprint_count(attributes: *const Vec<SdpAttribute>) -> size_t {
+ count_attribute((*attributes).as_slice(), SdpAttributeType::Fingerprint)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_fingerprints(
+ attributes: *const Vec<SdpAttribute>,
+ ret_size: size_t,
+ ret_fingerprints: *mut RustSdpAttributeFingerprint,
+) {
+ let attrs: Vec<_> = (*attributes)
+ .iter()
+ .filter_map(|x| {
+ if let SdpAttribute::Fingerprint(ref data) = *x {
+ Some(RustSdpAttributeFingerprint::from(data))
+ } else {
+ None
+ }
+ })
+ .collect();
+ let fingerprints = slice::from_raw_parts_mut(ret_fingerprints, ret_size);
+ fingerprints.copy_from_slice(attrs.as_slice());
+}
+
+#[repr(C)]
+#[derive(Clone)]
+pub enum RustSdpAttributeSetup {
+ Active,
+ Actpass,
+ Holdconn,
+ Passive,
+}
+
+impl<'a> From<&'a SdpAttributeSetup> for RustSdpAttributeSetup {
+ fn from(other: &SdpAttributeSetup) -> Self {
+ match *other {
+ SdpAttributeSetup::Active => RustSdpAttributeSetup::Active,
+ SdpAttributeSetup::Actpass => RustSdpAttributeSetup::Actpass,
+ SdpAttributeSetup::Holdconn => RustSdpAttributeSetup::Holdconn,
+ SdpAttributeSetup::Passive => RustSdpAttributeSetup::Passive,
+ }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_setup(
+ attributes: *const Vec<SdpAttribute>,
+ ret: *mut RustSdpAttributeSetup,
+) -> nsresult {
+ let attr = get_attribute((*attributes).as_slice(), SdpAttributeType::Setup);
+ if let Some(&SdpAttribute::Setup(ref setup)) = attr {
+ *ret = RustSdpAttributeSetup::from(setup);
+ return NS_OK;
+ }
+ NS_ERROR_INVALID_ARG
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct RustSdpAttributeSsrc {
+ pub id: u32,
+ pub attribute: StringView,
+ pub value: StringView,
+}
+
+impl<'a> From<&'a SdpAttributeSsrc> for RustSdpAttributeSsrc {
+ fn from(other: &SdpAttributeSsrc) -> Self {
+ RustSdpAttributeSsrc {
+ id: other.id,
+ attribute: StringView::from(&other.attribute),
+ value: StringView::from(&other.value),
+ }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_ssrc_count(attributes: *const Vec<SdpAttribute>) -> size_t {
+ count_attribute((*attributes).as_slice(), SdpAttributeType::Ssrc)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_ssrcs(
+ attributes: *const Vec<SdpAttribute>,
+ ret_size: size_t,
+ ret_ssrcs: *mut RustSdpAttributeSsrc,
+) {
+ let attrs: Vec<_> = (*attributes)
+ .iter()
+ .filter_map(|x| {
+ if let SdpAttribute::Ssrc(ref data) = *x {
+ Some(RustSdpAttributeSsrc::from(data))
+ } else {
+ None
+ }
+ })
+ .collect();
+ let ssrcs = slice::from_raw_parts_mut(ret_ssrcs, ret_size);
+ ssrcs.copy_from_slice(attrs.as_slice());
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub enum RustSdpSsrcGroupSemantic {
+ Duplication,
+ FlowIdentification,
+ ForwardErrorCorrection,
+ ForwardErrorCorrectionFr,
+ SIM,
+}
+
+impl<'a> From<&'a SdpSsrcGroupSemantic> for RustSdpSsrcGroupSemantic {
+ fn from(other: &SdpSsrcGroupSemantic) -> Self {
+ match *other {
+ SdpSsrcGroupSemantic::Duplication => RustSdpSsrcGroupSemantic::Duplication,
+ SdpSsrcGroupSemantic::FlowIdentification => {
+ RustSdpSsrcGroupSemantic::FlowIdentification
+ }
+ SdpSsrcGroupSemantic::ForwardErrorCorrection => {
+ RustSdpSsrcGroupSemantic::ForwardErrorCorrection
+ }
+ SdpSsrcGroupSemantic::ForwardErrorCorrectionFr => {
+ RustSdpSsrcGroupSemantic::ForwardErrorCorrectionFr
+ }
+ SdpSsrcGroupSemantic::Sim => RustSdpSsrcGroupSemantic::SIM,
+ }
+ }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct RustSdpSsrcGroup {
+ pub semantic: RustSdpSsrcGroupSemantic,
+ pub ssrcs: *const Vec<SdpAttributeSsrc>,
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_ssrc_group_count(attributes: *const Vec<SdpAttribute>) -> size_t {
+ count_attribute((*attributes).as_slice(), SdpAttributeType::SsrcGroup)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_ssrc_groups(
+ attributes: *const Vec<SdpAttribute>,
+ ret_size: size_t,
+ ret_ssrc_groups: *mut RustSdpSsrcGroup,
+) {
+ let attrs: Vec<_> = (*attributes)
+ .iter()
+ .filter_map(|x| {
+ if let SdpAttribute::SsrcGroup(ref semantic, ref ssrcs) = *x {
+ Some(RustSdpSsrcGroup {
+ semantic: RustSdpSsrcGroupSemantic::from(semantic),
+ ssrcs: ssrcs,
+ })
+ } else {
+ None
+ }
+ })
+ .collect();
+ let ssrc_groups = slice::from_raw_parts_mut(ret_ssrc_groups, ret_size);
+ ssrc_groups.copy_from_slice(attrs.as_slice());
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct RustSdpAttributeRtpmap {
+ pub payload_type: u8,
+ pub codec_name: StringView,
+ pub frequency: u32,
+ pub channels: u32,
+}
+
+impl<'a> From<&'a SdpAttributeRtpmap> for RustSdpAttributeRtpmap {
+ fn from(other: &SdpAttributeRtpmap) -> Self {
+ RustSdpAttributeRtpmap {
+ payload_type: other.payload_type as u8,
+ codec_name: StringView::from(other.codec_name.as_str()),
+ frequency: other.frequency as u32,
+ channels: other.channels.unwrap_or(0),
+ }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_rtpmap_count(attributes: *const Vec<SdpAttribute>) -> size_t {
+ count_attribute((*attributes).as_slice(), SdpAttributeType::Rtpmap)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_rtpmaps(
+ attributes: *const Vec<SdpAttribute>,
+ ret_size: size_t,
+ ret_rtpmaps: *mut RustSdpAttributeRtpmap,
+) {
+ let attrs: Vec<_> = (*attributes)
+ .iter()
+ .filter_map(|x| {
+ if let SdpAttribute::Rtpmap(ref data) = *x {
+ Some(RustSdpAttributeRtpmap::from(data))
+ } else {
+ None
+ }
+ })
+ .collect();
+ let rtpmaps = slice::from_raw_parts_mut(ret_rtpmaps, ret_size);
+ rtpmaps.copy_from_slice(attrs.as_slice());
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct RustRtxFmtpParameters {
+ pub apt: u8,
+ pub has_rtx_time: bool,
+ pub rtx_time: u32,
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct RustSdpAttributeFmtpParameters {
+ // H264
+ pub packetization_mode: u32,
+ pub level_asymmetry_allowed: bool,
+ pub profile_level_id: u32,
+ pub max_fs: u32,
+ pub max_cpb: u32,
+ pub max_dpb: u32,
+ pub max_br: u32,
+ pub max_mbps: u32,
+
+ // VP8 and VP9
+ // max_fs, already defined in H264
+ pub max_fr: u32,
+
+ // Opus
+ pub maxplaybackrate: u32,
+ pub maxaveragebitrate: u32,
+ pub usedtx: bool,
+ pub stereo: bool,
+ pub useinbandfec: bool,
+ pub cbr: bool,
+ pub ptime: u32,
+ pub minptime: u32,
+ pub maxptime: u32,
+
+ // telephone-event
+ pub dtmf_tones: StringView,
+
+ // RTX
+ pub rtx: RustRtxFmtpParameters,
+
+ // Red
+ pub encodings: *const Vec<u8>,
+
+ // Unknown
+ pub unknown_tokens: *const Vec<String>,
+}
+
+impl<'a> From<&'a SdpAttributeFmtpParameters> for RustSdpAttributeFmtpParameters {
+ fn from(other: &SdpAttributeFmtpParameters) -> Self {
+ let rtx = if let Some(rtx) = other.rtx {
+ RustRtxFmtpParameters {
+ apt: rtx.apt,
+ has_rtx_time: rtx.rtx_time.is_some(),
+ rtx_time: rtx.rtx_time.unwrap_or(0),
+ }
+ } else {
+ RustRtxFmtpParameters {
+ apt: 0,
+ has_rtx_time: false,
+ rtx_time: 0,
+ }
+ };
+
+ RustSdpAttributeFmtpParameters {
+ packetization_mode: other.packetization_mode,
+ level_asymmetry_allowed: other.level_asymmetry_allowed,
+ profile_level_id: other.profile_level_id,
+ max_fs: other.max_fs,
+ max_cpb: other.max_cpb,
+ max_dpb: other.max_dpb,
+ max_br: other.max_br,
+ max_mbps: other.max_mbps,
+ usedtx: other.usedtx,
+ stereo: other.stereo,
+ useinbandfec: other.useinbandfec,
+ cbr: other.cbr,
+ max_fr: other.max_fr,
+ maxplaybackrate: other.maxplaybackrate,
+ maxaveragebitrate: other.maxaveragebitrate,
+ ptime: other.ptime,
+ minptime: other.minptime,
+ maxptime: other.maxptime,
+ dtmf_tones: StringView::from(other.dtmf_tones.as_str()),
+ rtx,
+ encodings: &other.encodings,
+ unknown_tokens: &other.unknown_tokens,
+ }
+ }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct RustSdpAttributeFmtp {
+ pub payload_type: u8,
+ pub codec_name: StringView,
+ pub parameters: RustSdpAttributeFmtpParameters,
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_fmtp_count(attributes: *const Vec<SdpAttribute>) -> size_t {
+ count_attribute((*attributes).as_slice(), SdpAttributeType::Fmtp)
+}
+
+fn find_payload_type(attributes: &[SdpAttribute], payload_type: u8) -> Option<&SdpAttributeRtpmap> {
+ attributes
+ .iter()
+ .filter_map(|x| {
+ if let SdpAttribute::Rtpmap(ref data) = *x {
+ if data.payload_type == payload_type {
+ Some(data)
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ })
+ .next()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_fmtp(
+ attributes: *const Vec<SdpAttribute>,
+ ret_size: size_t,
+ ret_fmtp: *mut RustSdpAttributeFmtp,
+) -> size_t {
+ let fmtps = (*attributes).iter().filter_map(|x| {
+ if let SdpAttribute::Fmtp(ref data) = *x {
+ Some(data)
+ } else {
+ None
+ }
+ });
+ let mut rust_fmtps = Vec::new();
+ for fmtp in fmtps {
+ if let Some(rtpmap) = find_payload_type((*attributes).as_slice(), fmtp.payload_type) {
+ rust_fmtps.push(RustSdpAttributeFmtp {
+ payload_type: fmtp.payload_type as u8,
+ codec_name: StringView::from(rtpmap.codec_name.as_str()),
+ parameters: RustSdpAttributeFmtpParameters::from(&fmtp.parameters),
+ });
+ }
+ }
+ let fmtps = if ret_size <= rust_fmtps.len() {
+ slice::from_raw_parts_mut(ret_fmtp, ret_size)
+ } else {
+ slice::from_raw_parts_mut(ret_fmtp, rust_fmtps.len())
+ };
+ fmtps.copy_from_slice(rust_fmtps.as_slice());
+ fmtps.len()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_ptime(attributes: *const Vec<SdpAttribute>) -> i64 {
+ for attribute in (*attributes).iter() {
+ if let SdpAttribute::Ptime(time) = *attribute {
+ return time as i64;
+ }
+ }
+ -1
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_max_msg_size(attributes: *const Vec<SdpAttribute>) -> i64 {
+ for attribute in (*attributes).iter() {
+ if let SdpAttribute::MaxMessageSize(max_msg_size) = *attribute {
+ return max_msg_size as i64;
+ }
+ }
+ -1
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_sctp_port(attributes: *const Vec<SdpAttribute>) -> i64 {
+ for attribute in (*attributes).iter() {
+ if let SdpAttribute::SctpPort(port) = *attribute {
+ return port as i64;
+ }
+ }
+ -1
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct RustSdpAttributeFlags {
+ pub ice_lite: bool,
+ pub rtcp_mux: bool,
+ pub rtcp_rsize: bool,
+ pub bundle_only: bool,
+ pub end_of_candidates: bool,
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_attribute_flags(
+ attributes: *const Vec<SdpAttribute>,
+) -> RustSdpAttributeFlags {
+ let mut ret = RustSdpAttributeFlags {
+ ice_lite: false,
+ rtcp_mux: false,
+ rtcp_rsize: false,
+ bundle_only: false,
+ end_of_candidates: false,
+ };
+ for attribute in (*attributes).iter() {
+ if let SdpAttribute::IceLite = *attribute {
+ ret.ice_lite = true;
+ } else if let SdpAttribute::RtcpMux = *attribute {
+ ret.rtcp_mux = true;
+ } else if let SdpAttribute::RtcpRsize = *attribute {
+ ret.rtcp_rsize = true;
+ } else if let SdpAttribute::BundleOnly = *attribute {
+ ret.bundle_only = true;
+ } else if let SdpAttribute::EndOfCandidates = *attribute {
+ ret.end_of_candidates = true;
+ }
+ }
+ ret
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_mid(
+ attributes: *const Vec<SdpAttribute>,
+ ret: *mut StringView,
+) -> nsresult {
+ for attribute in (*attributes).iter() {
+ if let SdpAttribute::Mid(ref data) = *attribute {
+ *ret = StringView::from(data.as_str());
+ return NS_OK;
+ }
+ }
+ NS_ERROR_INVALID_ARG
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct RustSdpAttributeMsid {
+ id: StringView,
+ appdata: StringView,
+}
+
+impl<'a> From<&'a SdpAttributeMsid> for RustSdpAttributeMsid {
+ fn from(other: &SdpAttributeMsid) -> Self {
+ RustSdpAttributeMsid {
+ id: StringView::from(other.id.as_str()),
+ appdata: StringView::from(&other.appdata),
+ }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_msid_count(attributes: *const Vec<SdpAttribute>) -> size_t {
+ count_attribute((*attributes).as_slice(), SdpAttributeType::Msid)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_msids(
+ attributes: *const Vec<SdpAttribute>,
+ ret_size: size_t,
+ ret_msids: *mut RustSdpAttributeMsid,
+) {
+ let attrs: Vec<_> = (*attributes)
+ .iter()
+ .filter_map(|x| {
+ if let SdpAttribute::Msid(ref data) = *x {
+ Some(RustSdpAttributeMsid::from(data))
+ } else {
+ None
+ }
+ })
+ .collect();
+ let msids = slice::from_raw_parts_mut(ret_msids, ret_size);
+ msids.copy_from_slice(attrs.as_slice());
+}
+
+// TODO: Finish msid attributes once parsing is changed upstream.
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct RustSdpAttributeMsidSemantic {
+ pub semantic: StringView,
+ pub msids: *const Vec<String>,
+}
+
+impl<'a> From<&'a SdpAttributeMsidSemantic> for RustSdpAttributeMsidSemantic {
+ fn from(other: &SdpAttributeMsidSemantic) -> Self {
+ RustSdpAttributeMsidSemantic {
+ semantic: StringView::from(other.semantic.as_str()),
+ msids: &other.msids,
+ }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_msid_semantic_count(
+ attributes: *const Vec<SdpAttribute>,
+) -> size_t {
+ count_attribute((*attributes).as_slice(), SdpAttributeType::MsidSemantic)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_msid_semantics(
+ attributes: *const Vec<SdpAttribute>,
+ ret_size: size_t,
+ ret_msid_semantics: *mut RustSdpAttributeMsidSemantic,
+) {
+ let attrs: Vec<_> = (*attributes)
+ .iter()
+ .filter_map(|x| {
+ if let SdpAttribute::MsidSemantic(ref data) = *x {
+ Some(RustSdpAttributeMsidSemantic::from(data))
+ } else {
+ None
+ }
+ })
+ .collect();
+ let msid_semantics = slice::from_raw_parts_mut(ret_msid_semantics, ret_size);
+ msid_semantics.copy_from_slice(attrs.as_slice());
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub enum RustSdpAttributeGroupSemantic {
+ LipSynchronization,
+ FlowIdentification,
+ SingleReservationFlow,
+ AlternateNetworkAddressType,
+ ForwardErrorCorrection,
+ DecodingDependency,
+ Bundle,
+}
+
+impl<'a> From<&'a SdpAttributeGroupSemantic> for RustSdpAttributeGroupSemantic {
+ fn from(other: &SdpAttributeGroupSemantic) -> Self {
+ match *other {
+ SdpAttributeGroupSemantic::LipSynchronization => {
+ RustSdpAttributeGroupSemantic::LipSynchronization
+ }
+ SdpAttributeGroupSemantic::FlowIdentification => {
+ RustSdpAttributeGroupSemantic::FlowIdentification
+ }
+ SdpAttributeGroupSemantic::SingleReservationFlow => {
+ RustSdpAttributeGroupSemantic::SingleReservationFlow
+ }
+ SdpAttributeGroupSemantic::AlternateNetworkAddressType => {
+ RustSdpAttributeGroupSemantic::AlternateNetworkAddressType
+ }
+ SdpAttributeGroupSemantic::ForwardErrorCorrection => {
+ RustSdpAttributeGroupSemantic::ForwardErrorCorrection
+ }
+ SdpAttributeGroupSemantic::DecodingDependency => {
+ RustSdpAttributeGroupSemantic::DecodingDependency
+ }
+ SdpAttributeGroupSemantic::Bundle => RustSdpAttributeGroupSemantic::Bundle,
+ }
+ }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct RustSdpAttributeGroup {
+ pub semantic: RustSdpAttributeGroupSemantic,
+ pub tags: *const Vec<String>,
+}
+
+impl<'a> From<&'a SdpAttributeGroup> for RustSdpAttributeGroup {
+ fn from(other: &SdpAttributeGroup) -> Self {
+ RustSdpAttributeGroup {
+ semantic: RustSdpAttributeGroupSemantic::from(&other.semantics),
+ tags: &other.tags,
+ }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_group_count(attributes: *const Vec<SdpAttribute>) -> size_t {
+ count_attribute((*attributes).as_slice(), SdpAttributeType::Group)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_groups(
+ attributes: *const Vec<SdpAttribute>,
+ ret_size: size_t,
+ ret_groups: *mut RustSdpAttributeGroup,
+) {
+ let attrs: Vec<_> = (*attributes)
+ .iter()
+ .filter_map(|x| {
+ if let SdpAttribute::Group(ref data) = *x {
+ Some(RustSdpAttributeGroup::from(data))
+ } else {
+ None
+ }
+ })
+ .collect();
+ let groups = slice::from_raw_parts_mut(ret_groups, ret_size);
+ groups.copy_from_slice(attrs.as_slice());
+}
+
+#[repr(C)]
+pub struct RustSdpAttributeRtcp {
+ pub port: u32,
+ pub unicast_addr: RustExplicitlyTypedAddress,
+ pub has_address: bool,
+}
+
+impl<'a> From<&'a SdpAttributeRtcp> for RustSdpAttributeRtcp {
+ fn from(other: &SdpAttributeRtcp) -> Self {
+ match other.unicast_addr {
+ Some(ref address) => RustSdpAttributeRtcp {
+ port: other.port as u32,
+ unicast_addr: address.into(),
+ has_address: true,
+ },
+ None => RustSdpAttributeRtcp {
+ port: other.port as u32,
+ unicast_addr: RustExplicitlyTypedAddress::default(),
+ has_address: false,
+ },
+ }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_rtcp(
+ attributes: *const Vec<SdpAttribute>,
+ ret: *mut RustSdpAttributeRtcp,
+) -> nsresult {
+ let attr = get_attribute((*attributes).as_slice(), SdpAttributeType::Rtcp);
+ if let Some(&SdpAttribute::Rtcp(ref data)) = attr {
+ *ret = RustSdpAttributeRtcp::from(data);
+ return NS_OK;
+ }
+ NS_ERROR_INVALID_ARG
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct RustSdpAttributeRtcpFb {
+ pub payload_type: u32,
+ pub feedback_type: u32,
+ pub parameter: StringView,
+ pub extra: StringView,
+}
+
+impl<'a> From<&'a SdpAttributeRtcpFb> for RustSdpAttributeRtcpFb {
+ fn from(other: &SdpAttributeRtcpFb) -> Self {
+ RustSdpAttributeRtcpFb {
+ payload_type: match other.payload_type {
+ SdpAttributePayloadType::Wildcard => u32::max_value(),
+ SdpAttributePayloadType::PayloadType(x) => x as u32,
+ },
+ feedback_type: other.feedback_type.clone() as u32,
+ parameter: StringView::from(other.parameter.as_str()),
+ extra: StringView::from(other.extra.as_str()),
+ }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_rtcpfb_count(attributes: *const Vec<SdpAttribute>) -> size_t {
+ count_attribute((*attributes).as_slice(), SdpAttributeType::Rtcpfb)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_rtcpfbs(
+ attributes: *const Vec<SdpAttribute>,
+ ret_size: size_t,
+ ret_rtcpfbs: *mut RustSdpAttributeRtcpFb,
+) {
+ let attrs: Vec<_> = (*attributes)
+ .iter()
+ .filter_map(|x| {
+ if let SdpAttribute::Rtcpfb(ref data) = *x {
+ Some(RustSdpAttributeRtcpFb::from(data))
+ } else {
+ None
+ }
+ })
+ .collect();
+ let rtcpfbs = slice::from_raw_parts_mut(ret_rtcpfbs, ret_size);
+ rtcpfbs.clone_from_slice(attrs.as_slice());
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct RustSdpAttributeImageAttrXyRange {
+ // range
+ pub min: u32,
+ pub max: u32,
+ pub step: u32,
+
+ // discrete values
+ pub discrete_values: *const Vec<u32>,
+}
+
+impl<'a> From<&'a SdpAttributeImageAttrXyRange> for RustSdpAttributeImageAttrXyRange {
+ fn from(other: &SdpAttributeImageAttrXyRange) -> Self {
+ match other {
+ &SdpAttributeImageAttrXyRange::Range(min, max, step) => {
+ RustSdpAttributeImageAttrXyRange {
+ min,
+ max,
+ step: step.unwrap_or(1),
+ discrete_values: ptr::null(),
+ }
+ }
+ &SdpAttributeImageAttrXyRange::DiscreteValues(ref discrete_values) => {
+ RustSdpAttributeImageAttrXyRange {
+ min: 0,
+ max: 1,
+ step: 1,
+ discrete_values,
+ }
+ }
+ }
+ }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct RustSdpAttributeImageAttrSRange {
+ // range
+ pub min: c_float,
+ pub max: c_float,
+
+ // discrete values
+ pub discrete_values: *const Vec<c_float>,
+}
+
+impl<'a> From<&'a SdpAttributeImageAttrSRange> for RustSdpAttributeImageAttrSRange {
+ fn from(other: &SdpAttributeImageAttrSRange) -> Self {
+ match other {
+ &SdpAttributeImageAttrSRange::Range(min, max) => RustSdpAttributeImageAttrSRange {
+ min,
+ max,
+ discrete_values: ptr::null(),
+ },
+ &SdpAttributeImageAttrSRange::DiscreteValues(ref discrete_values) => {
+ RustSdpAttributeImageAttrSRange {
+ min: 0.0,
+ max: 1.0,
+ discrete_values,
+ }
+ }
+ }
+ }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct RustSdpAttributeImageAttrPRange {
+ pub min: c_float,
+ pub max: c_float,
+}
+
+impl<'a> From<&'a SdpAttributeImageAttrPRange> for RustSdpAttributeImageAttrPRange {
+ fn from(other: &SdpAttributeImageAttrPRange) -> Self {
+ RustSdpAttributeImageAttrPRange {
+ min: other.min,
+ max: other.max,
+ }
+ }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct RustSdpAttributeImageAttrSet {
+ pub x: RustSdpAttributeImageAttrXyRange,
+ pub y: RustSdpAttributeImageAttrXyRange,
+
+ pub has_sar: bool,
+ pub sar: RustSdpAttributeImageAttrSRange,
+
+ pub has_par: bool,
+ pub par: RustSdpAttributeImageAttrPRange,
+
+ pub q: c_float,
+}
+
+impl<'a> From<&'a SdpAttributeImageAttrSet> for RustSdpAttributeImageAttrSet {
+ fn from(other: &SdpAttributeImageAttrSet) -> Self {
+ RustSdpAttributeImageAttrSet {
+ x: RustSdpAttributeImageAttrXyRange::from(&other.x),
+ y: RustSdpAttributeImageAttrXyRange::from(&other.y),
+
+ has_sar: other.sar.is_some(),
+ sar: match other.sar {
+ Some(ref x) => RustSdpAttributeImageAttrSRange::from(x),
+ // This is just any valid value accepted by rust,
+ // it might as well by uninitilized
+ None => RustSdpAttributeImageAttrSRange::from(
+ &SdpAttributeImageAttrSRange::DiscreteValues(vec![]),
+ ),
+ },
+
+ has_par: other.par.is_some(),
+ par: match other.par {
+ Some(ref x) => RustSdpAttributeImageAttrPRange::from(x),
+ // This is just any valid value accepted by rust,
+ // it might as well by uninitilized
+ None => RustSdpAttributeImageAttrPRange { min: 0.0, max: 1.0 },
+ },
+
+ q: other.q.unwrap_or(0.5),
+ }
+ }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct RustSdpAttributeImageAttrSetList {
+ pub sets: *const Vec<SdpAttributeImageAttrSet>,
+}
+
+impl<'a> From<&'a SdpAttributeImageAttrSetList> for RustSdpAttributeImageAttrSetList {
+ fn from(other: &SdpAttributeImageAttrSetList) -> Self {
+ match other {
+ &SdpAttributeImageAttrSetList::Wildcard => {
+ RustSdpAttributeImageAttrSetList { sets: ptr::null() }
+ }
+ &SdpAttributeImageAttrSetList::Sets(ref sets) => {
+ RustSdpAttributeImageAttrSetList { sets: sets }
+ }
+ }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_imageattr_get_set_count(
+ sets: *const Vec<SdpAttributeImageAttrSet>,
+) -> size_t {
+ (*sets).len()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_imageattr_get_sets(
+ sets: *const Vec<SdpAttributeImageAttrSet>,
+ ret_size: size_t,
+ ret: *mut RustSdpAttributeImageAttrSet,
+) {
+ let rust_sets: Vec<_> = (*sets)
+ .iter()
+ .map(RustSdpAttributeImageAttrSet::from)
+ .collect();
+ let sets = slice::from_raw_parts_mut(ret, ret_size);
+ sets.clone_from_slice(rust_sets.as_slice());
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct RustSdpAttributeImageAttr {
+ pub pt: u32,
+ pub send: RustSdpAttributeImageAttrSetList,
+ pub recv: RustSdpAttributeImageAttrSetList,
+}
+
+impl<'a> From<&'a SdpAttributeImageAttr> for RustSdpAttributeImageAttr {
+ fn from(other: &SdpAttributeImageAttr) -> Self {
+ RustSdpAttributeImageAttr {
+ pt: match other.pt {
+ SdpAttributePayloadType::Wildcard => u32::max_value(),
+ SdpAttributePayloadType::PayloadType(x) => x as u32,
+ },
+ send: RustSdpAttributeImageAttrSetList::from(&other.send),
+ recv: RustSdpAttributeImageAttrSetList::from(&other.recv),
+ }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_imageattr_count(attributes: *const Vec<SdpAttribute>) -> size_t {
+ count_attribute((*attributes).as_slice(), SdpAttributeType::ImageAttr)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_imageattrs(
+ attributes: *const Vec<SdpAttribute>,
+ ret_size: size_t,
+ ret_attrs: *mut RustSdpAttributeImageAttr,
+) {
+ let attrs: Vec<_> = (*attributes)
+ .iter()
+ .filter_map(|x| {
+ if let SdpAttribute::ImageAttr(ref data) = *x {
+ Some(RustSdpAttributeImageAttr::from(data))
+ } else {
+ None
+ }
+ })
+ .collect();
+ let imageattrs = slice::from_raw_parts_mut(ret_attrs, ret_size);
+ imageattrs.copy_from_slice(attrs.as_slice());
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct RustSdpAttributeSctpmap {
+ pub port: u32,
+ pub channels: u32,
+}
+
+impl<'a> From<&'a SdpAttributeSctpmap> for RustSdpAttributeSctpmap {
+ fn from(other: &SdpAttributeSctpmap) -> Self {
+ RustSdpAttributeSctpmap {
+ port: other.port as u32,
+ channels: other.channels,
+ }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_sctpmap_count(attributes: *const Vec<SdpAttribute>) -> size_t {
+ count_attribute((*attributes).as_slice(), SdpAttributeType::Sctpmap)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_sctpmaps(
+ attributes: *const Vec<SdpAttribute>,
+ ret_size: size_t,
+ ret_sctpmaps: *mut RustSdpAttributeSctpmap,
+) {
+ let attrs: Vec<_> = (*attributes)
+ .iter()
+ .filter_map(|x| {
+ if let SdpAttribute::Sctpmap(ref data) = *x {
+ Some(RustSdpAttributeSctpmap::from(data))
+ } else {
+ None
+ }
+ })
+ .collect();
+ let sctpmaps = slice::from_raw_parts_mut(ret_sctpmaps, ret_size);
+ sctpmaps.copy_from_slice(attrs.as_slice());
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct RustSdpAttributeSimulcastId {
+ pub id: StringView,
+ pub paused: bool,
+}
+
+impl<'a> From<&'a SdpAttributeSimulcastId> for RustSdpAttributeSimulcastId {
+ fn from(other: &SdpAttributeSimulcastId) -> Self {
+ RustSdpAttributeSimulcastId {
+ id: StringView::from(other.id.as_str()),
+ paused: other.paused,
+ }
+ }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct RustSdpAttributeSimulcastVersion {
+ pub ids: *const Vec<SdpAttributeSimulcastId>,
+}
+
+impl<'a> From<&'a SdpAttributeSimulcastVersion> for RustSdpAttributeSimulcastVersion {
+ fn from(other: &SdpAttributeSimulcastVersion) -> Self {
+ RustSdpAttributeSimulcastVersion { ids: &other.ids }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_simulcast_get_ids_count(
+ ids: *const Vec<SdpAttributeSimulcastId>,
+) -> size_t {
+ (*ids).len()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_simulcast_get_ids(
+ ids: *const Vec<SdpAttributeSimulcastId>,
+ ret_size: size_t,
+ ret: *mut RustSdpAttributeSimulcastId,
+) {
+ let rust_ids: Vec<_> = (*ids)
+ .iter()
+ .map(RustSdpAttributeSimulcastId::from)
+ .collect();
+ let ids = slice::from_raw_parts_mut(ret, ret_size);
+ ids.clone_from_slice(rust_ids.as_slice());
+}
+
+#[repr(C)]
+pub struct RustSdpAttributeSimulcast {
+ pub send: *const Vec<SdpAttributeSimulcastVersion>,
+ pub receive: *const Vec<SdpAttributeSimulcastVersion>,
+}
+
+impl<'a> From<&'a SdpAttributeSimulcast> for RustSdpAttributeSimulcast {
+ fn from(other: &SdpAttributeSimulcast) -> Self {
+ RustSdpAttributeSimulcast {
+ send: &other.send,
+ receive: &other.receive,
+ }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_simulcast_get_version_count(
+ version_list: *const Vec<SdpAttributeSimulcastVersion>,
+) -> size_t {
+ (*version_list).len()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_simulcast_get_versions(
+ version_list: *const Vec<SdpAttributeSimulcastVersion>,
+ ret_size: size_t,
+ ret: *mut RustSdpAttributeSimulcastVersion,
+) {
+ let rust_versions_list: Vec<_> = (*version_list)
+ .iter()
+ .map(RustSdpAttributeSimulcastVersion::from)
+ .collect();
+ let versions = slice::from_raw_parts_mut(ret, ret_size);
+ versions.clone_from_slice(rust_versions_list.as_slice())
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_simulcast(
+ attributes: *const Vec<SdpAttribute>,
+ ret: *mut RustSdpAttributeSimulcast,
+) -> nsresult {
+ let attr = get_attribute((*attributes).as_slice(), SdpAttributeType::Simulcast);
+ if let Some(&SdpAttribute::Simulcast(ref data)) = attr {
+ *ret = RustSdpAttributeSimulcast::from(data);
+ return NS_OK;
+ }
+ NS_ERROR_INVALID_ARG
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub enum RustDirection {
+ Recvonly,
+ Sendonly,
+ Sendrecv,
+ Inactive,
+}
+
+impl<'a> From<&'a Option<SdpAttributeDirection>> for RustDirection {
+ fn from(other: &Option<SdpAttributeDirection>) -> Self {
+ match *other {
+ Some(ref direction) => match *direction {
+ SdpAttributeDirection::Recvonly => RustDirection::Recvonly,
+ SdpAttributeDirection::Sendonly => RustDirection::Sendonly,
+ SdpAttributeDirection::Sendrecv => RustDirection::Sendrecv,
+ },
+ None => RustDirection::Inactive,
+ }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_direction(attributes: *const Vec<SdpAttribute>) -> RustDirection {
+ for attribute in (*attributes).iter() {
+ match *attribute {
+ SdpAttribute::Recvonly => {
+ return RustDirection::Recvonly;
+ }
+ SdpAttribute::Sendonly => {
+ return RustDirection::Sendonly;
+ }
+ SdpAttribute::Sendrecv => {
+ return RustDirection::Sendrecv;
+ }
+ SdpAttribute::Inactive => {
+ return RustDirection::Inactive;
+ }
+ _ => (),
+ }
+ }
+ RustDirection::Sendrecv
+}
+
+#[repr(C)]
+pub struct RustSdpAttributeRemoteCandidate {
+ pub component: u32,
+ pub address: RustAddress,
+ pub port: u32,
+}
+
+impl<'a> From<&'a SdpAttributeRemoteCandidate> for RustSdpAttributeRemoteCandidate {
+ fn from(other: &SdpAttributeRemoteCandidate) -> Self {
+ RustSdpAttributeRemoteCandidate {
+ component: other.component,
+ address: RustAddress::from(&other.address),
+ port: other.port,
+ }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_remote_candidate_count(
+ attributes: *const Vec<SdpAttribute>,
+) -> size_t {
+ count_attribute((*attributes).as_slice(), SdpAttributeType::RemoteCandidate)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_remote_candidates(
+ attributes: *const Vec<SdpAttribute>,
+ ret_size: size_t,
+ ret_candidates: *mut RustSdpAttributeRemoteCandidate,
+) {
+ let attrs = (*attributes).iter().filter_map(|x| {
+ if let SdpAttribute::RemoteCandidate(ref data) = *x {
+ Some(RustSdpAttributeRemoteCandidate::from(data))
+ } else {
+ None
+ }
+ });
+ let candidates = slice::from_raw_parts_mut(ret_candidates, ret_size);
+ for (source, destination) in attrs.zip(candidates) {
+ *destination = source
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_candidate_count(attributes: *const Vec<SdpAttribute>) -> size_t {
+ count_attribute((*attributes).as_slice(), SdpAttributeType::Candidate)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_candidates(
+ attributes: *const Vec<SdpAttribute>,
+ _ret_size: size_t,
+ ret: *mut *const Vec<String>,
+) {
+ let attr_strings: Vec<String> = (*attributes)
+ .iter()
+ .filter_map(|x| {
+ if let SdpAttribute::Candidate(ref attr) = *x {
+ // The serialized attribute starts with "candidate:...", this needs to be removed
+ Some(attr.to_string())
+ } else {
+ None
+ }
+ })
+ .collect();
+
+ *ret = Box::into_raw(Box::from(attr_strings));
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct RustSdpAttributeRidParameters {
+ pub max_width: u32,
+ pub max_height: u32,
+ pub max_fps: u32,
+ pub max_fs: u32,
+ pub max_br: u32,
+ pub max_pps: u32,
+ pub unknown: *const Vec<String>,
+}
+
+impl<'a> From<&'a SdpAttributeRidParameters> for RustSdpAttributeRidParameters {
+ fn from(other: &SdpAttributeRidParameters) -> Self {
+ RustSdpAttributeRidParameters {
+ max_width: other.max_width,
+ max_height: other.max_height,
+ max_fps: other.max_fps,
+ max_fs: other.max_fs,
+ max_br: other.max_br,
+ max_pps: other.max_pps,
+
+ unknown: &other.unknown,
+ }
+ }
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct RustSdpAttributeRid {
+ pub id: StringView,
+ pub direction: u32,
+ pub formats: *const Vec<u16>,
+ pub params: RustSdpAttributeRidParameters,
+ pub depends: *const Vec<String>,
+}
+
+impl<'a> From<&'a SdpAttributeRid> for RustSdpAttributeRid {
+ fn from(other: &SdpAttributeRid) -> Self {
+ RustSdpAttributeRid {
+ id: StringView::from(other.id.as_str()),
+ direction: other.direction.clone() as u32,
+ formats: &other.formats,
+ params: RustSdpAttributeRidParameters::from(&other.params),
+ depends: &other.depends,
+ }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_rid_count(attributes: *const Vec<SdpAttribute>) -> size_t {
+ count_attribute((*attributes).as_slice(), SdpAttributeType::Rid)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_rids(
+ attributes: *const Vec<SdpAttribute>,
+ ret_size: size_t,
+ ret_rids: *mut RustSdpAttributeRid,
+) {
+ let attrs: Vec<_> = (*attributes)
+ .iter()
+ .filter_map(|x| {
+ if let SdpAttribute::Rid(ref data) = *x {
+ Some(RustSdpAttributeRid::from(data))
+ } else {
+ None
+ }
+ })
+ .collect();
+ let rids = slice::from_raw_parts_mut(ret_rids, ret_size);
+ rids.clone_from_slice(attrs.as_slice());
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct RustSdpAttributeExtmap {
+ pub id: u16,
+ pub direction_specified: bool,
+ pub direction: RustDirection,
+ pub url: StringView,
+ pub extension_attributes: StringView,
+}
+
+impl<'a> From<&'a SdpAttributeExtmap> for RustSdpAttributeExtmap {
+ fn from(other: &SdpAttributeExtmap) -> Self {
+ let dir = if other.direction.is_some() {
+ RustDirection::from(&other.direction)
+ } else {
+ RustDirection::from(&Some(SdpAttributeDirection::Sendrecv))
+ };
+ RustSdpAttributeExtmap {
+ id: other.id as u16,
+ direction_specified: other.direction.is_some(),
+ direction: dir,
+ url: StringView::from(other.url.as_str()),
+ extension_attributes: StringView::from(&other.extension_attributes),
+ }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_extmap_count(attributes: *const Vec<SdpAttribute>) -> size_t {
+ count_attribute((*attributes).as_slice(), SdpAttributeType::Extmap)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_extmaps(
+ attributes: *const Vec<SdpAttribute>,
+ ret_size: size_t,
+ ret_rids: *mut RustSdpAttributeExtmap,
+) {
+ let attrs: Vec<_> = (*attributes)
+ .iter()
+ .filter_map(|x| {
+ if let SdpAttribute::Extmap(ref data) = *x {
+ Some(RustSdpAttributeExtmap::from(data))
+ } else {
+ None
+ }
+ })
+ .collect();
+ let extmaps = slice::from_raw_parts_mut(ret_rids, ret_size);
+ extmaps.copy_from_slice(attrs.as_slice());
+}
diff --git a/dom/media/webrtc/sdp/rsdparsa_capi/src/lib.rs b/dom/media/webrtc/sdp/rsdparsa_capi/src/lib.rs
new file mode 100644
index 0000000000..20a13900a2
--- /dev/null
+++ b/dom/media/webrtc/sdp/rsdparsa_capi/src/lib.rs
@@ -0,0 +1,298 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+extern crate libc;
+extern crate nserror;
+extern crate rsdparsa;
+
+use std::ffi::CString;
+use std::os::raw::c_char;
+use std::ptr;
+
+use libc::size_t;
+
+use std::rc::Rc;
+
+use std::convert::{TryFrom, TryInto};
+
+use nserror::{nsresult, NS_ERROR_INVALID_ARG, NS_OK};
+use rsdparsa::address::ExplicitlyTypedAddress;
+use rsdparsa::anonymizer::{AnonymizingClone, StatefulSdpAnonymizer};
+use rsdparsa::attribute_type::SdpAttribute;
+use rsdparsa::error::SdpParserError;
+use rsdparsa::media_type::{SdpMediaValue, SdpProtocolValue};
+use rsdparsa::{SdpBandwidth, SdpSession, SdpTiming};
+
+#[macro_use]
+extern crate log;
+
+pub mod attribute;
+pub mod media_section;
+pub mod network;
+pub mod types;
+
+use network::{
+ get_bandwidth, origin_view_helper, RustAddressType, RustSdpConnection, RustSdpOrigin,
+};
+pub use types::{StringView, NULL_STRING};
+
+#[no_mangle]
+pub unsafe extern "C" fn parse_sdp(
+ sdp: StringView,
+ fail_on_warning: bool,
+ session: *mut *const SdpSession,
+ parser_error: *mut *const SdpParserError,
+) -> nsresult {
+ let sdp_str: String = match sdp.try_into() {
+ Ok(string) => string,
+ Err(boxed_error) => {
+ *session = ptr::null();
+ *parser_error = Box::into_raw(Box::new(SdpParserError::Sequence {
+ message: format!("{}", boxed_error),
+ line_number: 0,
+ }));
+ return NS_ERROR_INVALID_ARG;
+ }
+ };
+
+ let parser_result = rsdparsa::parse_sdp(&sdp_str, fail_on_warning);
+ match parser_result {
+ Ok(mut parsed) => {
+ *parser_error = match parsed.warnings.len() {
+ 0 => ptr::null(),
+ _ => Box::into_raw(Box::new(parsed.warnings.remove(0))),
+ };
+ *session = Rc::into_raw(Rc::new(parsed));
+ NS_OK
+ }
+ Err(e) => {
+ *session = ptr::null();
+ error!("Error parsing SDP in rust: {}", e);
+ *parser_error = Box::into_raw(Box::new(e));
+ NS_ERROR_INVALID_ARG
+ }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn create_anonymized_sdp_clone(
+ session: *const SdpSession,
+) -> *const SdpSession {
+ Rc::into_raw(Rc::new(
+ (*session).masked_clone(&mut StatefulSdpAnonymizer::new()),
+ ))
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn create_sdp_clone(session: *const SdpSession) -> *const SdpSession {
+ Rc::into_raw(Rc::new((*session).clone()))
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_free_session(sdp_ptr: *mut SdpSession) {
+ let sdp = Rc::from_raw(sdp_ptr);
+ drop(sdp);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_new_reference(session: *mut SdpSession) -> *const SdpSession {
+ let original = Rc::from_raw(session);
+ let ret = Rc::into_raw(Rc::clone(&original));
+ Rc::into_raw(original); // So the original reference doesn't get dropped
+ ret
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_error_line_num(parser_error: *mut SdpParserError) -> size_t {
+ match *parser_error {
+ SdpParserError::Line { line_number, .. }
+ | SdpParserError::Unsupported { line_number, .. }
+ | SdpParserError::Sequence { line_number, .. } => line_number,
+ }
+}
+
+#[no_mangle]
+// Callee must check that a nullptr is not returned
+pub unsafe extern "C" fn sdp_get_error_message(parser_error: *mut SdpParserError) -> *mut c_char {
+ let message = format!("{}", *parser_error);
+ return match CString::new(message.as_str()) {
+ Ok(c_char_ptr) => c_char_ptr.into_raw(),
+ Err(_) => 0 as *mut c_char,
+ };
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_free_error_message(message: *mut c_char) {
+ if message != 0 as *mut c_char {
+ let _tmp = CString::from_raw(message);
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_free_error(parser_error: *mut SdpParserError) {
+ let e = Box::from_raw(parser_error);
+ drop(e);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn get_version(session: *const SdpSession) -> u64 {
+ (*session).get_version()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_origin(session: *const SdpSession) -> RustSdpOrigin {
+ origin_view_helper((*session).get_origin())
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn session_view(session: *const SdpSession) -> StringView {
+ StringView::from((*session).get_session())
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_session_has_connection(session: *const SdpSession) -> bool {
+ (*session).connection.is_some()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_session_connection(
+ session: *const SdpSession,
+ connection: *mut RustSdpConnection,
+) -> nsresult {
+ match (*session).connection {
+ Some(ref c) => {
+ *connection = RustSdpConnection::from(c);
+ NS_OK
+ }
+ None => NS_ERROR_INVALID_ARG,
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_add_media_section(
+ session: *mut SdpSession,
+ media_type: u32,
+ direction: u32,
+ port: u16,
+ protocol: u32,
+ addr_type: u32,
+ address: StringView,
+) -> nsresult {
+ let addr_type = match RustAddressType::try_from(addr_type) {
+ Ok(a) => a.into(),
+ Err(e) => {
+ return e;
+ }
+ };
+ let address_string: String = match address.try_into() {
+ Ok(x) => x,
+ Err(boxed_error) => {
+ error!("Error while parsing string, description: {}", boxed_error);
+ return NS_ERROR_INVALID_ARG;
+ }
+ };
+ let address = match ExplicitlyTypedAddress::try_from((addr_type, address_string.as_str())) {
+ Ok(a) => a,
+ Err(_) => {
+ return NS_ERROR_INVALID_ARG;
+ }
+ };
+
+ let media_type = match media_type {
+ 0 => SdpMediaValue::Audio, // MediaType::kAudio
+ 1 => SdpMediaValue::Video, // MediaType::kVideo
+ 3 => SdpMediaValue::Application, // MediaType::kApplication
+ _ => {
+ return NS_ERROR_INVALID_ARG;
+ }
+ };
+ let protocol = match protocol {
+ 20 => SdpProtocolValue::RtpSavpf, // Protocol::kRtpSavpf
+ 21 => SdpProtocolValue::UdpTlsRtpSavp, // Protocol::kUdpTlsRtpSavp
+ 22 => SdpProtocolValue::TcpDtlsRtpSavp, // Protocol::kTcpDtlsRtpSavp
+ 24 => SdpProtocolValue::UdpTlsRtpSavpf, // Protocol::kUdpTlsRtpSavpf
+ 25 => SdpProtocolValue::TcpDtlsRtpSavpf, // Protocol::kTcpTlsRtpSavpf
+ 37 => SdpProtocolValue::DtlsSctp, // Protocol::kDtlsSctp
+ 38 => SdpProtocolValue::UdpDtlsSctp, // Protocol::kUdpDtlsSctp
+ 39 => SdpProtocolValue::TcpDtlsSctp, // Protocol::kTcpDtlsSctp
+ _ => {
+ return NS_ERROR_INVALID_ARG;
+ }
+ };
+ let direction = match direction {
+ 1 => SdpAttribute::Sendonly,
+ 2 => SdpAttribute::Recvonly,
+ 3 => SdpAttribute::Sendrecv,
+ _ => {
+ return NS_ERROR_INVALID_ARG;
+ }
+ };
+
+ match (*session).add_media(media_type, direction, port as u32, protocol, address) {
+ Ok(_) => NS_OK,
+ Err(_) => NS_ERROR_INVALID_ARG,
+ }
+}
+
+#[repr(C)]
+#[derive(Clone)]
+pub struct RustSdpTiming {
+ pub start: u64,
+ pub stop: u64,
+}
+
+impl<'a> From<&'a SdpTiming> for RustSdpTiming {
+ fn from(timing: &SdpTiming) -> Self {
+ RustSdpTiming {
+ start: timing.start,
+ stop: timing.stop,
+ }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_session_has_timing(session: *const SdpSession) -> bool {
+ (*session).timing.is_some()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_session_timing(
+ session: *const SdpSession,
+ timing: *mut RustSdpTiming,
+) -> nsresult {
+ match (*session).timing {
+ Some(ref t) => {
+ *timing = RustSdpTiming::from(t);
+ NS_OK
+ }
+ None => NS_ERROR_INVALID_ARG,
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_media_section_count(session: *const SdpSession) -> size_t {
+ (*session).media.len()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn get_sdp_bandwidth(
+ session: *const SdpSession,
+ bandwidth_type: *const c_char,
+) -> u32 {
+ get_bandwidth(&(*session).bandwidth, bandwidth_type)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_session_bandwidth_vec(
+ session: *const SdpSession,
+) -> *const Vec<SdpBandwidth> {
+ &(*session).bandwidth
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn get_sdp_session_attributes(
+ session: *const SdpSession,
+) -> *const Vec<SdpAttribute> {
+ &(*session).attribute
+}
diff --git a/dom/media/webrtc/sdp/rsdparsa_capi/src/media_section.rs b/dom/media/webrtc/sdp/rsdparsa_capi/src/media_section.rs
new file mode 100644
index 0000000000..8429ab2815
--- /dev/null
+++ b/dom/media/webrtc/sdp/rsdparsa_capi/src/media_section.rs
@@ -0,0 +1,233 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use std::convert::TryInto;
+use std::os::raw::c_char;
+use std::ptr;
+
+use libc::size_t;
+
+use nserror::{nsresult, NS_ERROR_INVALID_ARG, NS_OK};
+use rsdparsa::attribute_type::{SdpAttribute, SdpAttributeRtpmap};
+use rsdparsa::media_type::{SdpFormatList, SdpMedia, SdpMediaValue, SdpProtocolValue};
+use rsdparsa::{SdpBandwidth, SdpSession};
+
+use network::{get_bandwidth, RustSdpConnection};
+use types::StringView;
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_media_section(
+ session: *const SdpSession,
+ index: size_t,
+) -> *const SdpMedia {
+ return match (*session).media.get(index) {
+ Some(m) => m,
+ None => ptr::null(),
+ };
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub enum RustSdpMediaValue {
+ Audio,
+ Video,
+ Application,
+}
+
+impl<'a> From<&'a SdpMediaValue> for RustSdpMediaValue {
+ fn from(val: &SdpMediaValue) -> Self {
+ match *val {
+ SdpMediaValue::Audio => RustSdpMediaValue::Audio,
+ SdpMediaValue::Video => RustSdpMediaValue::Video,
+ SdpMediaValue::Application => RustSdpMediaValue::Application,
+ }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_rust_get_media_type(sdp_media: *const SdpMedia) -> RustSdpMediaValue {
+ RustSdpMediaValue::from((*sdp_media).get_type())
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub enum RustSdpProtocolValue {
+ RtpSavpf,
+ UdpTlsRtpSavp,
+ TcpDtlsRtpSavp,
+ UdpTlsRtpSavpf,
+ TcpDtlsRtpSavpf,
+ DtlsSctp,
+ UdpDtlsSctp,
+ TcpDtlsSctp,
+ RtpAvp,
+ RtpAvpf,
+ RtpSavp,
+}
+
+impl<'a> From<&'a SdpProtocolValue> for RustSdpProtocolValue {
+ fn from(val: &SdpProtocolValue) -> Self {
+ match *val {
+ SdpProtocolValue::RtpSavpf => RustSdpProtocolValue::RtpSavpf,
+ SdpProtocolValue::UdpTlsRtpSavp => RustSdpProtocolValue::UdpTlsRtpSavp,
+ SdpProtocolValue::TcpDtlsRtpSavp => RustSdpProtocolValue::TcpDtlsRtpSavp,
+ SdpProtocolValue::UdpTlsRtpSavpf => RustSdpProtocolValue::UdpTlsRtpSavpf,
+ SdpProtocolValue::TcpDtlsRtpSavpf => RustSdpProtocolValue::TcpDtlsRtpSavpf,
+ SdpProtocolValue::DtlsSctp => RustSdpProtocolValue::DtlsSctp,
+ SdpProtocolValue::UdpDtlsSctp => RustSdpProtocolValue::UdpDtlsSctp,
+ SdpProtocolValue::TcpDtlsSctp => RustSdpProtocolValue::TcpDtlsSctp,
+ SdpProtocolValue::RtpAvp => RustSdpProtocolValue::RtpAvp,
+ SdpProtocolValue::RtpAvpf => RustSdpProtocolValue::RtpAvpf,
+ SdpProtocolValue::RtpSavp => RustSdpProtocolValue::RtpSavp,
+ }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_media_protocol(
+ sdp_media: *const SdpMedia,
+) -> RustSdpProtocolValue {
+ RustSdpProtocolValue::from((*sdp_media).get_proto())
+}
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub enum RustSdpFormatType {
+ Integers,
+ Strings,
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_format_type(sdp_media: *const SdpMedia) -> RustSdpFormatType {
+ match *(*sdp_media).get_formats() {
+ SdpFormatList::Integers(_) => RustSdpFormatType::Integers,
+ SdpFormatList::Strings(_) => RustSdpFormatType::Strings,
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_format_string_vec(
+ sdp_media: *const SdpMedia,
+) -> *const Vec<String> {
+ if let SdpFormatList::Strings(ref formats) = *(*sdp_media).get_formats() {
+ formats
+ } else {
+ ptr::null()
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_format_u32_vec(sdp_media: *const SdpMedia) -> *const Vec<u32> {
+ if let SdpFormatList::Integers(ref formats) = *(*sdp_media).get_formats() {
+ formats
+ } else {
+ ptr::null()
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_set_media_port(sdp_media: *mut SdpMedia, port: u32) {
+ (*sdp_media).set_port(port);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_media_port(sdp_media: *const SdpMedia) -> u32 {
+ (*sdp_media).get_port()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_media_port_count(sdp_media: *const SdpMedia) -> u32 {
+ (*sdp_media).get_port_count()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_media_bandwidth(
+ sdp_media: *const SdpMedia,
+ bandwidth_type: *const c_char,
+) -> u32 {
+ get_bandwidth((*sdp_media).get_bandwidth(), bandwidth_type)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_media_bandwidth_vec(
+ sdp_media: *const SdpMedia,
+) -> *const Vec<SdpBandwidth> {
+ (*sdp_media).get_bandwidth()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_media_has_connection(sdp_media: *const SdpMedia) -> bool {
+ (*sdp_media).get_connection().is_some()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_media_connection(
+ sdp_media: *const SdpMedia,
+ ret: *mut RustSdpConnection,
+) -> nsresult {
+ if let &Some(ref connection) = (*sdp_media).get_connection() {
+ *ret = RustSdpConnection::from(connection);
+ return NS_OK;
+ }
+ NS_ERROR_INVALID_ARG
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_get_media_attribute_list(
+ sdp_media: *const SdpMedia,
+) -> *const Vec<SdpAttribute> {
+ (*sdp_media).get_attributes()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_media_clear_codecs(sdp_media: *mut SdpMedia) {
+ (*sdp_media).remove_codecs()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_media_add_codec(
+ sdp_media: *mut SdpMedia,
+ pt: u8,
+ codec_name: StringView,
+ clockrate: u32,
+ channels: u16,
+) -> nsresult {
+ let rtpmap = SdpAttributeRtpmap {
+ payload_type: pt,
+ codec_name: match codec_name.try_into() {
+ Ok(x) => x,
+ Err(boxed_error) => {
+ error!("Error while parsing string, description: {}", boxed_error);
+ return NS_ERROR_INVALID_ARG;
+ }
+ },
+ frequency: clockrate,
+ channels: Some(channels as u32),
+ };
+
+ match (*sdp_media).add_codec(rtpmap) {
+ Ok(_) => NS_OK,
+ Err(_) => NS_ERROR_INVALID_ARG,
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_media_add_datachannel(
+ sdp_media: *mut SdpMedia,
+ name: StringView,
+ port: u16,
+ streams: u16,
+ message_size: u32,
+) -> nsresult {
+ let name_str = match name.try_into() {
+ Ok(x) => x,
+ Err(_) => {
+ return NS_ERROR_INVALID_ARG;
+ }
+ };
+ match (*sdp_media).add_datachannel(name_str, port, streams, message_size) {
+ Ok(_) => NS_OK,
+ Err(_) => NS_ERROR_INVALID_ARG,
+ }
+}
diff --git a/dom/media/webrtc/sdp/rsdparsa_capi/src/network.rs b/dom/media/webrtc/sdp/rsdparsa_capi/src/network.rs
new file mode 100644
index 0000000000..970f7c8dec
--- /dev/null
+++ b/dom/media/webrtc/sdp/rsdparsa_capi/src/network.rs
@@ -0,0 +1,266 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+extern crate nserror;
+
+use std::ffi::{CStr, CString};
+use std::net::IpAddr;
+use std::os::raw::c_char;
+
+use rsdparsa::address::{Address, AddressType, AddressTyped, ExplicitlyTypedAddress};
+use rsdparsa::{SdpBandwidth, SdpConnection, SdpOrigin};
+use std::convert::TryFrom;
+use types::{StringView, NULL_STRING};
+
+#[repr(C)]
+#[derive(Clone, Copy, PartialEq)]
+pub enum RustAddressType {
+ IP4,
+ IP6,
+}
+
+impl TryFrom<u32> for RustAddressType {
+ type Error = nserror::nsresult;
+ fn try_from(address_type: u32) -> Result<Self, Self::Error> {
+ match address_type {
+ 1 => Ok(RustAddressType::IP4),
+ 2 => Ok(RustAddressType::IP6),
+ _ => Err(nserror::NS_ERROR_INVALID_ARG),
+ }
+ }
+}
+
+impl From<AddressType> for RustAddressType {
+ fn from(address_type: AddressType) -> Self {
+ match address_type {
+ AddressType::IpV4 => RustAddressType::IP4,
+ AddressType::IpV6 => RustAddressType::IP6,
+ }
+ }
+}
+
+impl Into<AddressType> for RustAddressType {
+ fn into(self) -> AddressType {
+ match self {
+ RustAddressType::IP4 => AddressType::IpV4,
+ RustAddressType::IP6 => AddressType::IpV6,
+ }
+ }
+}
+
+impl<'a> From<&'a IpAddr> for RustAddressType {
+ fn from(addr: &IpAddr) -> RustAddressType {
+ addr.address_type().into()
+ }
+}
+
+pub fn get_octets(addr: &IpAddr) -> [u8; 16] {
+ let mut octets = [0; 16];
+ match *addr {
+ IpAddr::V4(v4_addr) => {
+ let v4_octets = v4_addr.octets();
+ (&mut octets[0..4]).copy_from_slice(&v4_octets);
+ }
+ IpAddr::V6(v6_addr) => {
+ let v6_octets = v6_addr.octets();
+ octets.copy_from_slice(&v6_octets);
+ }
+ }
+ octets
+}
+
+#[repr(C)]
+pub struct RustAddress {
+ ip_address: [u8; 50],
+ fqdn: StringView,
+ is_fqdn: bool,
+}
+
+impl<'a> From<&'a Address> for RustAddress {
+ fn from(address: &Address) -> Self {
+ match address {
+ Address::Ip(ip) => Self::from(ip),
+ Address::Fqdn(fqdn) => Self {
+ ip_address: [0; 50],
+ fqdn: fqdn.as_str().into(),
+ is_fqdn: true,
+ },
+ }
+ }
+}
+
+impl<'a> From<&'a IpAddr> for RustAddress {
+ fn from(addr: &IpAddr) -> Self {
+ let mut c_addr = [0; 50];
+ let str_addr = format!("{}", addr);
+ let str_bytes = str_addr.as_bytes();
+ if str_bytes.len() < 50 {
+ c_addr[..str_bytes.len()].copy_from_slice(&str_bytes);
+ }
+ Self {
+ ip_address: c_addr,
+ fqdn: NULL_STRING,
+ is_fqdn: false,
+ }
+ }
+}
+
+#[repr(C)]
+pub struct RustExplicitlyTypedAddress {
+ address_type: RustAddressType,
+ address: RustAddress,
+}
+
+impl Default for RustExplicitlyTypedAddress {
+ fn default() -> Self {
+ Self {
+ address_type: RustAddressType::IP4,
+ address: RustAddress {
+ ip_address: [0; 50],
+ fqdn: NULL_STRING,
+ is_fqdn: false,
+ },
+ }
+ }
+}
+
+impl<'a> From<&'a ExplicitlyTypedAddress> for RustExplicitlyTypedAddress {
+ fn from(address: &ExplicitlyTypedAddress) -> Self {
+ match address {
+ ExplicitlyTypedAddress::Fqdn { domain, .. } => Self {
+ address_type: address.address_type().into(),
+ address: RustAddress {
+ ip_address: [0; 50],
+ fqdn: StringView::from(domain.as_str()),
+ is_fqdn: true,
+ },
+ },
+ ExplicitlyTypedAddress::Ip(ip_address) => Self {
+ address_type: ip_address.address_type().into(),
+ address: ip_address.into(),
+ },
+ }
+ }
+}
+
+// TODO @@NG remove
+impl<'a> From<&'a Option<IpAddr>> for RustExplicitlyTypedAddress {
+ fn from(addr: &Option<IpAddr>) -> Self {
+ match *addr {
+ Some(ref x) => Self {
+ address_type: RustAddressType::from(x.address_type()),
+ address: RustAddress::from(x),
+ },
+ None => Self::default(),
+ }
+ }
+}
+
+#[repr(C)]
+pub struct RustSdpConnection {
+ pub addr: RustExplicitlyTypedAddress,
+ pub ttl: u8,
+ pub amount: u64,
+}
+
+impl<'a> From<&'a SdpConnection> for RustSdpConnection {
+ fn from(sdp_connection: &SdpConnection) -> Self {
+ let ttl = match sdp_connection.ttl {
+ Some(x) => x as u8,
+ None => 0,
+ };
+ let amount = match sdp_connection.amount {
+ Some(x) => x as u64,
+ None => 0,
+ };
+ RustSdpConnection {
+ addr: RustExplicitlyTypedAddress::from(&sdp_connection.address),
+ ttl: ttl,
+ amount: amount,
+ }
+ }
+}
+
+#[repr(C)]
+pub struct RustSdpOrigin {
+ username: StringView,
+ session_id: u64,
+ session_version: u64,
+ addr: RustExplicitlyTypedAddress,
+}
+
+fn bandwidth_match(str_bw: &str, enum_bw: &SdpBandwidth) -> bool {
+ match *enum_bw {
+ SdpBandwidth::As(_) => str_bw == "AS",
+ SdpBandwidth::Ct(_) => str_bw == "CT",
+ SdpBandwidth::Tias(_) => str_bw == "TIAS",
+ SdpBandwidth::Unknown(ref type_name, _) => str_bw == type_name,
+ }
+}
+
+fn bandwidth_value(bandwidth: &SdpBandwidth) -> u32 {
+ match *bandwidth {
+ SdpBandwidth::As(x) | SdpBandwidth::Ct(x) | SdpBandwidth::Tias(x) => x,
+ SdpBandwidth::Unknown(_, _) => 0,
+ }
+}
+
+pub unsafe fn get_bandwidth(bandwidths: &Vec<SdpBandwidth>, bandwidth_type: *const c_char) -> u32 {
+ let bw_type = match CStr::from_ptr(bandwidth_type).to_str() {
+ Ok(string) => string,
+ Err(_) => return 0,
+ };
+ for bandwidth in bandwidths.iter() {
+ if bandwidth_match(bw_type, bandwidth) {
+ return bandwidth_value(bandwidth);
+ }
+ }
+ 0
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_serialize_bandwidth(bw: *const Vec<SdpBandwidth>) -> *mut c_char {
+ let mut builder = String::new();
+ for bandwidth in (*bw).iter() {
+ match *bandwidth {
+ SdpBandwidth::As(val) => {
+ builder.push_str("b=AS:");
+ builder.push_str(&val.to_string());
+ builder.push_str("\r\n");
+ }
+ SdpBandwidth::Ct(val) => {
+ builder.push_str("b=CT:");
+ builder.push_str(&val.to_string());
+ builder.push_str("\r\n");
+ }
+ SdpBandwidth::Tias(val) => {
+ builder.push_str("b=TIAS:");
+ builder.push_str(&val.to_string());
+ builder.push_str("\r\n");
+ }
+ SdpBandwidth::Unknown(ref name, val) => {
+ builder.push_str("b=");
+ builder.push_str(name.as_str());
+ builder.push(':');
+ builder.push_str(&val.to_string());
+ builder.push_str("\r\n");
+ }
+ }
+ }
+ CString::from_vec_unchecked(builder.into_bytes()).into_raw()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn sdp_free_string(s: *mut c_char) {
+ drop(CString::from_raw(s));
+}
+
+pub unsafe fn origin_view_helper(origin: &SdpOrigin) -> RustSdpOrigin {
+ RustSdpOrigin {
+ username: StringView::from(origin.username.as_str()),
+ session_id: origin.session_id,
+ session_version: origin.session_version,
+ addr: RustExplicitlyTypedAddress::from(&origin.unicast_addr),
+ }
+}
diff --git a/dom/media/webrtc/sdp/rsdparsa_capi/src/types.rs b/dom/media/webrtc/sdp/rsdparsa_capi/src/types.rs
new file mode 100644
index 0000000000..2522c8333d
--- /dev/null
+++ b/dom/media/webrtc/sdp/rsdparsa_capi/src/types.rs
@@ -0,0 +1,199 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use libc::size_t;
+use std::boxed::Box;
+use std::convert::TryInto;
+use std::error::Error;
+use std::ffi::CStr;
+use std::{slice, str};
+
+use nserror::{nsresult, NS_ERROR_INVALID_ARG, NS_OK};
+
+use rsdparsa::attribute_type::SdpAttributeSsrc;
+
+#[repr(C)]
+#[derive(Clone, Copy)]
+pub struct StringView {
+ buffer: *const u8,
+ len: size_t,
+}
+
+pub const NULL_STRING: StringView = StringView {
+ buffer: 0 as *const u8,
+ len: 0,
+};
+
+impl<'a> From<&'a str> for StringView {
+ fn from(input: &str) -> StringView {
+ StringView {
+ buffer: input.as_ptr(),
+ len: input.len(),
+ }
+ }
+}
+
+impl TryInto<String> for StringView {
+ type Error = Box<dyn Error>;
+ fn try_into(self) -> Result<String, Box<dyn Error>> {
+ // This block must be unsafe as it converts a StringView, most likly provided from the
+ // C++ code, into a rust String and thus needs to operate with raw pointers.
+ let string_slice: &[u8];
+ unsafe {
+ // Add one to the length as the length passed in the StringView is the length of
+ // the string and is missing the null terminator
+ string_slice = slice::from_raw_parts(self.buffer, self.len + 1 as usize);
+ }
+
+ let c_str = match CStr::from_bytes_with_nul(string_slice) {
+ Ok(string) => string,
+ Err(x) => {
+ return Err(Box::new(x));
+ }
+ };
+
+ let str_slice: &str = match str::from_utf8(c_str.to_bytes()) {
+ Ok(string) => string,
+ Err(x) => {
+ return Err(Box::new(x));
+ }
+ };
+
+ Ok(str_slice.to_string())
+ }
+}
+
+impl<'a, T: AsRef<str>> From<&'a Option<T>> for StringView {
+ fn from(input: &Option<T>) -> StringView {
+ match *input {
+ Some(ref x) => StringView {
+ buffer: x.as_ref().as_ptr(),
+ len: x.as_ref().len(),
+ },
+ None => NULL_STRING,
+ }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn string_vec_len(vec: *const Vec<String>) -> size_t {
+ (*vec).len() as size_t
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn string_vec_get_view(
+ vec: *const Vec<String>,
+ index: size_t,
+ ret: *mut StringView,
+) -> nsresult {
+ match (*vec).get(index) {
+ Some(ref string) => {
+ *ret = StringView::from(string.as_str());
+ NS_OK
+ }
+ None => NS_ERROR_INVALID_ARG,
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn free_boxed_string_vec(ptr: *mut Vec<String>) -> nsresult {
+ drop(Box::from_raw(ptr));
+ NS_OK
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn f32_vec_len(vec: *const Vec<f32>) -> size_t {
+ (*vec).len()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn f32_vec_get(
+ vec: *const Vec<f32>,
+ index: size_t,
+ ret: *mut f32,
+) -> nsresult {
+ match (*vec).get(index) {
+ Some(val) => {
+ *ret = *val;
+ NS_OK
+ }
+ None => NS_ERROR_INVALID_ARG,
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn u32_vec_len(vec: *const Vec<u32>) -> size_t {
+ (*vec).len()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn u32_vec_get(
+ vec: *const Vec<u32>,
+ index: size_t,
+ ret: *mut u32,
+) -> nsresult {
+ match (*vec).get(index) {
+ Some(val) => {
+ *ret = *val;
+ NS_OK
+ }
+ None => NS_ERROR_INVALID_ARG,
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn u16_vec_len(vec: *const Vec<u16>) -> size_t {
+ (*vec).len()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn u16_vec_get(
+ vec: *const Vec<u16>,
+ index: size_t,
+ ret: *mut u16,
+) -> nsresult {
+ match (*vec).get(index) {
+ Some(val) => {
+ *ret = *val;
+ NS_OK
+ }
+ None => NS_ERROR_INVALID_ARG,
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn u8_vec_len(vec: *const Vec<u8>) -> size_t {
+ (*vec).len()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn u8_vec_get(vec: *const Vec<u8>, index: size_t, ret: *mut u8) -> nsresult {
+ match (*vec).get(index) {
+ Some(val) => {
+ *ret = *val;
+ NS_OK
+ }
+ None => NS_ERROR_INVALID_ARG,
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn ssrc_vec_len(vec: *const Vec<SdpAttributeSsrc>) -> size_t {
+ (*vec).len()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn ssrc_vec_get_id(
+ vec: *const Vec<SdpAttributeSsrc>,
+ index: size_t,
+ ret: *mut u32,
+) -> nsresult {
+ match (*vec).get(index) {
+ Some(val) => {
+ *ret = val.id;
+ NS_OK
+ }
+ None => NS_ERROR_INVALID_ARG,
+ }
+}
diff --git a/dom/media/webrtc/tests/crashtests/1770075.html b/dom/media/webrtc/tests/crashtests/1770075.html
new file mode 100644
index 0000000000..4d451216bd
--- /dev/null
+++ b/dom/media/webrtc/tests/crashtests/1770075.html
@@ -0,0 +1,8 @@
+<script>
+window.addEventListener('load', () => {
+ let a = new RTCPeerConnection({}, {})
+ a.createOffer({'offerToReceiveVideo': true})
+ let b = new WeakRef(a.getTransceivers()[0])
+ setTimeout("self.close()", 200)
+})
+</script>
diff --git a/dom/media/webrtc/tests/crashtests/1789908.html b/dom/media/webrtc/tests/crashtests/1789908.html
new file mode 100644
index 0000000000..3d58d3dc6b
--- /dev/null
+++ b/dom/media/webrtc/tests/crashtests/1789908.html
@@ -0,0 +1,25 @@
+<script>
+window.addEventListener('load', () => {
+ const sdp = `v=0
+o=mozilla...THIS_IS_SDPARTA-99.0 4978061689314146455 0 IN IP4 0.0.0.0
+s=-
+t=0 0
+a=fingerprint:sha-256 1D:E5:0C:97:18:43:38:3D:FF:7D:6A:BF:E3:AC:CA:70:AB:53:5A:35:95:92:4F:98:86:61:CA:5D:D5:9D:5E:41
+a=group:BUNDLE 0
+a=ice-options:trickle
+a=msid-semantic:WMS *
+m=video 9 UDP/TLS/RTP/SAVPF 120
+c=IN IP4 0.0.0.0
+a=fmtp:120 max-fs=12288;max-fr=60
+a=ice-pwd:c3a5e05023a8c38f671aef91ed1802d6
+a=ice-ufrag:91e4526d
+a=setup:actpass
+
+a=rtpmap:120 VP8/90000
+`;
+
+ let a = new RTCPeerConnection()
+ a.setRemoteDescription({sdp, type: "offer"});
+ setTimeout("self.close()", 200)
+})
+</script>
diff --git a/dom/media/webrtc/tests/crashtests/1799168.html b/dom/media/webrtc/tests/crashtests/1799168.html
new file mode 100644
index 0000000000..6c5c9db237
--- /dev/null
+++ b/dom/media/webrtc/tests/crashtests/1799168.html
@@ -0,0 +1,16 @@
+<script>
+window.addEventListener('load', async () => {
+ const offerer = new RTCPeerConnection();
+ const answerer = new RTCPeerConnection();
+ offerer.addTransceiver('audio');
+ await offerer.setLocalDescription();
+ await answerer.setRemoteDescription(offerer.localDescription);
+ const answer = await answerer.createAnswer();
+ await offerer.setRemoteDescription(answer);
+ // relay candidate with TCP!
+ const candidate = 'candidate:3 1 tcp 18087935 20.253.151.225 3478 typ relay raddr 10.0.48.153 rport 3478 tcptype passive';
+ await offerer.addIceCandidate({candidate, sdpMLineIndex: 0});
+ await new Promise(r => setTimeout(r, 2000));
+ self.close();
+})
+</script>
diff --git a/dom/media/webrtc/tests/crashtests/1816708.html b/dom/media/webrtc/tests/crashtests/1816708.html
new file mode 100644
index 0000000000..c7ba824041
--- /dev/null
+++ b/dom/media/webrtc/tests/crashtests/1816708.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<script>
+document.addEventListener('DOMContentLoaded', async () => {
+ const peer = new RTCPeerConnection()
+ const stream = await navigator.mediaDevices.getUserMedia({
+ video: true,
+ audio: true,
+ fake: true,
+ peerIdentity: 'name',
+ })
+ stream.getTracks().forEach((track) => peer.addTrack(track, stream))
+ const offer = await peer.createOffer({})
+ await peer.setLocalDescription(offer)
+ await peer.setRemoteDescription(offer)
+ document.documentElement.removeAttribute("class");
+})
+</script>
+</head>
+</html>
diff --git a/dom/media/webrtc/tests/crashtests/1821477.html b/dom/media/webrtc/tests/crashtests/1821477.html
new file mode 100644
index 0000000000..c37bd6bd02
--- /dev/null
+++ b/dom/media/webrtc/tests/crashtests/1821477.html
@@ -0,0 +1,16 @@
+<html class="reftest-wait">
+<script>
+document.addEventListener("DOMContentLoaded", async () => {
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ try {
+ (await navigator.mediaDevices.getDisplayMedia({
+ "video": {
+ "frameRate": 2147483647,
+ },
+ })).stop();
+ } finally {
+ document.documentElement.removeAttribute("class");
+ }
+});
+</script>
+</html>
diff --git a/dom/media/webrtc/tests/crashtests/crashtests.list b/dom/media/webrtc/tests/crashtests/crashtests.list
new file mode 100644
index 0000000000..64434b28e2
--- /dev/null
+++ b/dom/media/webrtc/tests/crashtests/crashtests.list
@@ -0,0 +1,7 @@
+defaults pref(media.navigator.permission.disabled,true) pref(media.devices.insecure.enabled,true) pref(media.getusermedia.insecure.enabled,true)
+
+load 1770075.html
+load 1789908.html
+load 1799168.html
+load 1816708.html
+skip-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) load 1821477.html
diff --git a/dom/media/webrtc/tests/fuzztests/moz.build b/dom/media/webrtc/tests/fuzztests/moz.build
new file mode 100644
index 0000000000..fef388e6c9
--- /dev/null
+++ b/dom/media/webrtc/tests/fuzztests/moz.build
@@ -0,0 +1,22 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Library("FuzzingSdp")
+
+LOCAL_INCLUDES += [
+ "/dom/media/webrtc",
+ "/ipc/chromium/src",
+ "/media/webrtc",
+]
+
+# Add libFuzzer configuration directives
+include("/tools/fuzzing/libfuzzer-config.mozbuild")
+
+SOURCES += [
+ "sdp_parser_libfuzz.cpp",
+]
+
+FINAL_LIBRARY = "xul-gtest"
diff --git a/dom/media/webrtc/tests/fuzztests/sdp_parser_libfuzz.cpp b/dom/media/webrtc/tests/fuzztests/sdp_parser_libfuzz.cpp
new file mode 100644
index 0000000000..3451d6fd21
--- /dev/null
+++ b/dom/media/webrtc/tests/fuzztests/sdp_parser_libfuzz.cpp
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "gtest/gtest.h"
+
+#include "FuzzingInterface.h"
+
+#include "sdp/SipccSdpParser.h"
+
+using namespace mozilla;
+
+static mozilla::UniquePtr<SdpParser::Results> sdpPtr;
+static SipccSdpParser mParser;
+
+int FuzzingInitSdpParser(int* argc, char*** argv) { return 0; }
+
+static int RunSdpParserFuzzing(const uint8_t* data, size_t size) {
+ std::string message(reinterpret_cast<const char*>(data), size);
+
+ sdpPtr = mParser.Parse(message);
+
+ return 0;
+}
+
+MOZ_FUZZING_INTERFACE_RAW(FuzzingInitSdpParser, RunSdpParserFuzzing, SdpParser);
diff --git a/dom/media/webrtc/tests/mochitests/NetworkPreparationChromeScript.js b/dom/media/webrtc/tests/mochitests/NetworkPreparationChromeScript.js
new file mode 100644
index 0000000000..d3872f1519
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/NetworkPreparationChromeScript.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var browser = Services.wm.getMostRecentWindow("navigator:browser");
+var connection = browser.navigator.mozMobileConnections[0];
+
+// provide a fake APN and enable data connection.
+// enable 3G radio
+function enableRadio() {
+ if (connection.radioState !== "enabled") {
+ connection.setRadioEnabled(true);
+ }
+}
+
+// disable 3G radio
+function disableRadio() {
+ if (connection.radioState === "enabled") {
+ connection.setRadioEnabled(false);
+ }
+}
+
+addMessageListener("prepare-network", function (message) {
+ connection.addEventListener("datachange", function onDataChange() {
+ if (connection.data.connected) {
+ connection.removeEventListener("datachange", onDataChange);
+ Services.prefs.setIntPref("network.proxy.type", 2);
+ sendAsyncMessage("network-ready", true);
+ }
+ });
+
+ enableRadio();
+});
+
+addMessageListener("network-cleanup", function (message) {
+ connection.addEventListener("datachange", function onDataChange() {
+ if (!connection.data.connected) {
+ connection.removeEventListener("datachange", onDataChange);
+ Services.prefs.setIntPref("network.proxy.type", 2);
+ sendAsyncMessage("network-disabled", true);
+ }
+ });
+ disableRadio();
+});
diff --git a/dom/media/webrtc/tests/mochitests/addTurnsSelfsignedCert.js b/dom/media/webrtc/tests/mochitests/addTurnsSelfsignedCert.js
new file mode 100644
index 0000000000..1e8be3a397
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/addTurnsSelfsignedCert.js
@@ -0,0 +1,32 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* eslint-env mozilla/chrome-script */
+
+"use strict";
+
+// This is only usable from the parent process, even for doing simple stuff like
+// serializing a cert.
+var gCertMaker = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+);
+
+var gCertOverrides = Cc["@mozilla.org/security/certoverride;1"].getService(
+ Ci.nsICertOverrideService
+);
+
+addMessageListener("add-turns-certs", certs => {
+ var port = 5349;
+ certs.forEach(certDescription => {
+ var cert = gCertMaker.constructX509FromBase64(certDescription.cert);
+ gCertOverrides.rememberValidityOverride(
+ certDescription.hostname,
+ port,
+ {},
+ cert,
+ false
+ );
+ });
+ sendAsyncMessage("certs-added");
+});
diff --git a/dom/media/webrtc/tests/mochitests/blacksilence.js b/dom/media/webrtc/tests/mochitests/blacksilence.js
new file mode 100644
index 0000000000..5ea35f8a7f
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/blacksilence.js
@@ -0,0 +1,134 @@
+(function (global) {
+ "use strict";
+
+ // an invertible check on the condition.
+ // if the constraint is applied, then the check is direct
+ // if not applied, then the result should be reversed
+ function check(constraintApplied, condition, message) {
+ var good = constraintApplied ? condition : !condition;
+ message =
+ (constraintApplied ? "with" : "without") +
+ " constraint: should " +
+ (constraintApplied ? "" : "not ") +
+ message +
+ " = " +
+ (good ? "OK" : "waiting...");
+ info(message);
+ return good;
+ }
+
+ function mkElement(type) {
+ // This makes an unattached element.
+ // It's not rendered to save the cycles that costs on b2g emulator
+ // and it gets dropped (and GC'd) when the test is done.
+ var e = document.createElement(type);
+ e.width = 32;
+ e.height = 24;
+ document.getElementById("display").appendChild(e);
+ return e;
+ }
+
+ // Runs checkFunc until it reports success.
+ // This is kludgy, but you have to wait for media to start flowing, and it
+ // can't be any old media, it has to include real data, for which we have no
+ // reliable signals to use as a trigger.
+ function periodicCheck(checkFunc) {
+ var resolve;
+ var done = false;
+ // This returns a function so that we create 10 closures in the loop, not
+ // one; and so that the timers don't all start straight away
+ var waitAndCheck = counter => () => {
+ if (done) {
+ return Promise.resolve();
+ }
+ return new Promise(r => setTimeout(r, 200 << counter)).then(() => {
+ if (checkFunc()) {
+ done = true;
+ resolve();
+ }
+ });
+ };
+
+ var chain = Promise.resolve();
+ for (var i = 0; i < 10; ++i) {
+ chain = chain.then(waitAndCheck(i));
+ }
+ return new Promise(r => (resolve = r));
+ }
+
+ function isSilence(audioData) {
+ var silence = true;
+ for (var i = 0; i < audioData.length; ++i) {
+ if (audioData[i] !== 128) {
+ silence = false;
+ }
+ }
+ return silence;
+ }
+
+ function checkAudio(constraintApplied, stream) {
+ var audio = mkElement("audio");
+ audio.srcObject = stream;
+ audio.play();
+
+ var context = new AudioContext();
+ var source = context.createMediaStreamSource(stream);
+ var analyser = context.createAnalyser();
+ source.connect(analyser);
+ analyser.connect(context.destination);
+
+ return periodicCheck(() => {
+ var sampleCount = analyser.frequencyBinCount;
+ info("got some audio samples: " + sampleCount);
+ var buffer = new Uint8Array(sampleCount);
+ analyser.getByteTimeDomainData(buffer);
+
+ var silent = check(
+ constraintApplied,
+ isSilence(buffer),
+ "be silence for audio"
+ );
+ return sampleCount > 0 && silent;
+ }).then(() => {
+ source.disconnect();
+ analyser.disconnect();
+ audio.pause();
+ ok(true, "audio is " + (constraintApplied ? "" : "not ") + "silent");
+ });
+ }
+
+ function checkVideo(constraintApplied, stream) {
+ var video = mkElement("video");
+ video.srcObject = stream;
+ video.play();
+
+ return periodicCheck(() => {
+ try {
+ var canvas = mkElement("canvas");
+ var ctx = canvas.getContext("2d");
+ // Have to guard drawImage with the try as well, due to bug 879717. If
+ // we get an error, this round fails, but that failure is usually just
+ // transitory.
+ ctx.drawImage(video, 0, 0);
+ ctx.getImageData(0, 0, 1, 1);
+ return check(
+ constraintApplied,
+ false,
+ "throw on getImageData for video"
+ );
+ } catch (e) {
+ return check(
+ constraintApplied,
+ e.name === "SecurityError",
+ "get a security error: " + e.name
+ );
+ }
+ }).then(() => {
+ video.pause();
+ ok(true, "video is " + (constraintApplied ? "" : "not ") + "protected");
+ });
+ }
+
+ global.audioIsSilence = checkAudio;
+ global.videoIsBlack = checkVideo;
+})(this);
diff --git a/dom/media/webrtc/tests/mochitests/dataChannel.js b/dom/media/webrtc/tests/mochitests/dataChannel.js
new file mode 100644
index 0000000000..eac52f96ab
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/dataChannel.js
@@ -0,0 +1,352 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Returns the contents of a blob as text
+ *
+ * @param {Blob} blob
+ The blob to retrieve the contents from
+ */
+function getBlobContent(blob) {
+ return new Promise(resolve => {
+ var reader = new FileReader();
+ // Listen for 'onloadend' which will always be called after a success or failure
+ reader.onloadend = event => resolve(event.target.result);
+ reader.readAsText(blob);
+ });
+}
+
+var commandsCreateDataChannel = [
+ function PC_REMOTE_EXPECT_DATA_CHANNEL(test) {
+ test.pcRemote.expectDataChannel();
+ },
+
+ function PC_LOCAL_CREATE_DATA_CHANNEL(test) {
+ var channel = test.pcLocal.createDataChannel({});
+ is(channel.binaryType, "blob", channel + " is of binary type 'blob'");
+
+ is(
+ test.pcLocal.signalingState,
+ STABLE,
+ "Create datachannel does not change signaling state"
+ );
+ return test.pcLocal.observedNegotiationNeeded;
+ },
+];
+
+var commandsWaitForDataChannel = [
+ function PC_LOCAL_VERIFY_DATA_CHANNEL_STATE(test) {
+ return test.pcLocal.dataChannels[0].opened;
+ },
+
+ function PC_REMOTE_VERIFY_DATA_CHANNEL_STATE(test) {
+ return test.pcRemote.nextDataChannel.then(channel => channel.opened);
+ },
+];
+
+var commandsCheckDataChannel = [
+ function SEND_MESSAGE(test) {
+ var message = "Lorem ipsum dolor sit amet";
+
+ info("Sending message:" + message);
+ return test.send(message).then(result => {
+ is(
+ result.data,
+ message,
+ "Message correctly transmitted from pcLocal to pcRemote."
+ );
+ });
+ },
+
+ function SEND_BLOB(test) {
+ var contents = "At vero eos et accusam et justo duo dolores et ea rebum.";
+ var blob = new Blob([contents], { type: "text/plain" });
+
+ info("Sending blob");
+ return test
+ .send(blob)
+ .then(result => {
+ ok(result.data instanceof Blob, "Received data is of instance Blob");
+ is(result.data.size, blob.size, "Received data has the correct size.");
+
+ return getBlobContent(result.data);
+ })
+ .then(recv_contents =>
+ is(recv_contents, contents, "Received data has the correct content.")
+ );
+ },
+
+ function CREATE_SECOND_DATA_CHANNEL(test) {
+ return test.createDataChannel({}).then(result => {
+ is(
+ result.remote.binaryType,
+ "blob",
+ "remote data channel is of binary type 'blob'"
+ );
+ });
+ },
+
+ function SEND_MESSAGE_THROUGH_LAST_OPENED_CHANNEL(test) {
+ var channels = test.pcRemote.dataChannels;
+ var message = "I am the Omega";
+
+ info("Sending message:" + message);
+ return test.send(message).then(result => {
+ is(
+ channels.indexOf(result.channel),
+ channels.length - 1,
+ "Last channel used"
+ );
+ is(result.data, message, "Received message has the correct content.");
+ });
+ },
+
+ function SEND_MESSAGE_THROUGH_FIRST_CHANNEL(test) {
+ var message = "Message through 1st channel";
+ var options = {
+ sourceChannel: test.pcLocal.dataChannels[0],
+ targetChannel: test.pcRemote.dataChannels[0],
+ };
+
+ info("Sending message:" + message);
+ return test.send(message, options).then(result => {
+ is(
+ test.pcRemote.dataChannels.indexOf(result.channel),
+ 0,
+ "1st channel used"
+ );
+ is(result.data, message, "Received message has the correct content.");
+ });
+ },
+
+ function SEND_MESSAGE_BACK_THROUGH_FIRST_CHANNEL(test) {
+ var message = "Return a message also through 1st channel";
+ var options = {
+ sourceChannel: test.pcRemote.dataChannels[0],
+ targetChannel: test.pcLocal.dataChannels[0],
+ };
+
+ info("Sending message:" + message);
+ return test.send(message, options).then(result => {
+ is(
+ test.pcLocal.dataChannels.indexOf(result.channel),
+ 0,
+ "1st channel used"
+ );
+ is(result.data, message, "Return message has the correct content.");
+ });
+ },
+
+ function CREATE_NEGOTIATED_DATA_CHANNEL_MAX_RETRANSMITS(test) {
+ var options = {
+ negotiated: true,
+ id: 5,
+ protocol: "foo/bar",
+ ordered: false,
+ maxRetransmits: 500,
+ };
+ return test.createDataChannel(options).then(result => {
+ is(
+ result.local.binaryType,
+ "blob",
+ result.remote + " is of binary type 'blob'"
+ );
+ is(
+ result.local.id,
+ options.id,
+ result.local + " id is:" + result.local.id
+ );
+ is(
+ result.local.protocol,
+ options.protocol,
+ result.local + " protocol is:" + result.local.protocol
+ );
+ is(
+ result.local.reliable,
+ false,
+ result.local + " reliable is:" + result.local.reliable
+ );
+ is(
+ result.local.ordered,
+ options.ordered,
+ result.local + " ordered is:" + result.local.ordered
+ );
+ is(
+ result.local.maxRetransmits,
+ options.maxRetransmits,
+ result.local + " maxRetransmits is:" + result.local.maxRetransmits
+ );
+ is(
+ result.local.maxPacketLifeTime,
+ null,
+ result.local + " maxPacketLifeTime is:" + result.local.maxPacketLifeTime
+ );
+
+ is(
+ result.remote.binaryType,
+ "blob",
+ result.remote + " is of binary type 'blob'"
+ );
+ is(
+ result.remote.id,
+ options.id,
+ result.remote + " id is:" + result.remote.id
+ );
+ is(
+ result.remote.protocol,
+ options.protocol,
+ result.remote + " protocol is:" + result.remote.protocol
+ );
+ is(
+ result.remote.reliable,
+ false,
+ result.remote + " reliable is:" + result.remote.reliable
+ );
+ is(
+ result.remote.ordered,
+ options.ordered,
+ result.remote + " ordered is:" + result.remote.ordered
+ );
+ is(
+ result.remote.maxRetransmits,
+ options.maxRetransmits,
+ result.remote + " maxRetransmits is:" + result.remote.maxRetransmits
+ );
+ is(
+ result.remote.maxPacketLifeTime,
+ null,
+ result.remote +
+ " maxPacketLifeTime is:" +
+ result.remote.maxPacketLifeTime
+ );
+ });
+ },
+
+ function SEND_MESSAGE_THROUGH_LAST_OPENED_CHANNEL2(test) {
+ var channels = test.pcRemote.dataChannels;
+ var message = "I am the walrus; Goo goo g'joob";
+
+ info("Sending message:" + message);
+ return test.send(message).then(result => {
+ is(
+ channels.indexOf(result.channel),
+ channels.length - 1,
+ "Last channel used"
+ );
+ is(result.data, message, "Received message has the correct content.");
+ });
+ },
+
+ function CREATE_NEGOTIATED_DATA_CHANNEL_MAX_PACKET_LIFE_TIME(test) {
+ var options = {
+ ordered: false,
+ maxPacketLifeTime: 10,
+ };
+ return test.createDataChannel(options).then(result => {
+ is(
+ result.local.binaryType,
+ "blob",
+ result.local + " is of binary type 'blob'"
+ );
+ is(
+ result.local.protocol,
+ "",
+ result.local + " protocol is:" + result.local.protocol
+ );
+ is(
+ result.local.reliable,
+ false,
+ result.local + " reliable is:" + result.local.reliable
+ );
+ is(
+ result.local.ordered,
+ options.ordered,
+ result.local + " ordered is:" + result.local.ordered
+ );
+ is(
+ result.local.maxRetransmits,
+ null,
+ result.local + " maxRetransmits is:" + result.local.maxRetransmits
+ );
+ is(
+ result.local.maxPacketLifeTime,
+ options.maxPacketLifeTime,
+ result.local + " maxPacketLifeTime is:" + result.local.maxPacketLifeTime
+ );
+
+ is(
+ result.remote.binaryType,
+ "blob",
+ result.remote + " is of binary type 'blob'"
+ );
+ is(
+ result.remote.protocol,
+ "",
+ result.remote + " protocol is:" + result.remote.protocol
+ );
+ is(
+ result.remote.reliable,
+ false,
+ result.remote + " reliable is:" + result.remote.reliable
+ );
+ is(
+ result.remote.ordered,
+ options.ordered,
+ result.remote + " ordered is:" + result.remote.ordered
+ );
+ is(
+ result.remote.maxRetransmits,
+ null,
+ result.remote + " maxRetransmits is:" + result.remote.maxRetransmits
+ );
+ is(
+ result.remote.maxPacketLifeTime,
+ options.maxPacketLifeTime,
+ result.remote +
+ " maxPacketLifeTime is:" +
+ result.remote.maxPacketLifeTime
+ );
+ });
+ },
+
+ function SEND_MESSAGE_THROUGH_LAST_OPENED_CHANNEL3(test) {
+ var channels = test.pcRemote.dataChannels;
+ var message = "Nice to see you working maxPacketLifeTime";
+
+ info("Sending message:" + message);
+ return test.send(message).then(result => {
+ is(
+ channels.indexOf(result.channel),
+ channels.length - 1,
+ "Last channel used"
+ );
+ is(result.data, message, "Received message has the correct content.");
+ });
+ },
+];
+
+var commandsCheckLargeXfer = [
+ function SEND_BIG_BUFFER(test) {
+ var size = 2 * 1024 * 1024; // SCTP internal buffer is now 1MB, so use 2MB to ensure the buffer gets full
+ var buffer = new ArrayBuffer(size);
+ // note: type received is always blob for binary data
+ var options = {};
+ options.bufferedAmountLowThreshold = 64 * 1024;
+ info("Sending arraybuffer");
+ return test.send(buffer, options).then(result => {
+ ok(result.data instanceof Blob, "Received data is of instance Blob");
+ is(result.data.size, size, "Received data has the correct size.");
+ });
+ },
+];
+
+function addInitialDataChannel(chain) {
+ chain.insertBefore("PC_LOCAL_CREATE_OFFER", commandsCreateDataChannel);
+ chain.insertBefore(
+ "PC_LOCAL_WAIT_FOR_MEDIA_FLOW",
+ commandsWaitForDataChannel
+ );
+ chain.removeAfter("PC_REMOTE_CHECK_ICE_CONNECTIONS");
+ chain.append(commandsCheckDataChannel);
+}
diff --git a/dom/media/webrtc/tests/mochitests/head.js b/dom/media/webrtc/tests/mochitests/head.js
new file mode 100644
index 0000000000..7c9f6d52de
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/head.js
@@ -0,0 +1,1445 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var Cc = SpecialPowers.Cc;
+var Ci = SpecialPowers.Ci;
+
+// Specifies if we want fake audio streams for this run
+let WANT_FAKE_AUDIO = true;
+// Specifies if we want fake video streams for this run
+let WANT_FAKE_VIDEO = true;
+let TEST_AUDIO_FREQ = 1000;
+
+/**
+ * Reads the current values of preferences affecting fake and loopback devices
+ * and sets the WANT_FAKE_AUDIO and WANT_FAKE_VIDEO gloabals appropriately.
+ */
+function updateConfigFromFakeAndLoopbackPrefs() {
+ let audioDevice = SpecialPowers.getCharPref("media.audio_loopback_dev", "");
+ if (audioDevice) {
+ WANT_FAKE_AUDIO = false;
+ dump("TEST DEVICES: Got loopback audio: " + audioDevice + "\n");
+ } else {
+ WANT_FAKE_AUDIO = true;
+ dump(
+ "TEST DEVICES: No test device found in media.audio_loopback_dev, using fake audio streams.\n"
+ );
+ }
+ let videoDevice = SpecialPowers.getCharPref("media.video_loopback_dev", "");
+ if (videoDevice) {
+ WANT_FAKE_VIDEO = false;
+ dump("TEST DEVICES: Got loopback video: " + videoDevice + "\n");
+ } else {
+ WANT_FAKE_VIDEO = true;
+ dump(
+ "TEST DEVICES: No test device found in media.video_loopback_dev, using fake video streams.\n"
+ );
+ }
+}
+
+updateConfigFromFakeAndLoopbackPrefs();
+
+/**
+ * Global flag to skip LoopbackTone
+ */
+let DISABLE_LOOPBACK_TONE = false;
+/**
+ * Helper class to setup a sine tone of a given frequency.
+ */
+class LoopbackTone {
+ constructor(audioContext, frequency) {
+ if (!audioContext) {
+ throw new Error("You must provide a valid AudioContext");
+ }
+ this.oscNode = audioContext.createOscillator();
+ var gainNode = audioContext.createGain();
+ gainNode.gain.value = 0.5;
+ this.oscNode.connect(gainNode);
+ gainNode.connect(audioContext.destination);
+ this.changeFrequency(frequency);
+ }
+
+ // Method should be used when WANT_FAKE_AUDIO is false.
+ start() {
+ if (!this.oscNode) {
+ throw new Error("Attempt to start a stopped LoopbackTone");
+ }
+ info(`Start loopback tone at ${this.oscNode.frequency.value}`);
+ this.oscNode.start();
+ }
+
+ // Change the frequency of the tone. It can be used after start.
+ // Frequency will change on the fly. No need to stop and create a new instance.
+ changeFrequency(frequency) {
+ if (!this.oscNode) {
+ throw new Error("Attempt to change frequency on a stopped LoopbackTone");
+ }
+ this.oscNode.frequency.value = frequency;
+ }
+
+ stop() {
+ if (!this.oscNode) {
+ throw new Error("Attempt to stop a stopped LoopbackTone");
+ }
+ this.oscNode.stop();
+ this.oscNode = null;
+ }
+}
+// Object that holds the default loopback tone.
+var DefaultLoopbackTone = null;
+
+/**
+ * This class provides helpers around analysing the audio content in a stream
+ * using WebAudio AnalyserNodes.
+ *
+ * @constructor
+ * @param {object} stream
+ * A MediaStream object whose audio track we shall analyse.
+ */
+function AudioStreamAnalyser(ac, stream) {
+ this.audioContext = ac;
+ this.stream = stream;
+ this.sourceNodes = [];
+ this.analyser = this.audioContext.createAnalyser();
+ // Setting values lower than default for speedier testing on emulators
+ this.analyser.smoothingTimeConstant = 0.2;
+ this.analyser.fftSize = 1024;
+ this.connectTrack = t => {
+ let source = this.audioContext.createMediaStreamSource(
+ new MediaStream([t])
+ );
+ this.sourceNodes.push(source);
+ source.connect(this.analyser);
+ };
+ this.stream.getAudioTracks().forEach(t => this.connectTrack(t));
+ this.onaddtrack = ev => this.connectTrack(ev.track);
+ this.stream.addEventListener("addtrack", this.onaddtrack);
+ this.data = new Uint8Array(this.analyser.frequencyBinCount);
+}
+
+AudioStreamAnalyser.prototype = {
+ /**
+ * Get an array of frequency domain data for our stream's audio track.
+ *
+ * @returns {array} A Uint8Array containing the frequency domain data.
+ */
+ getByteFrequencyData() {
+ this.analyser.getByteFrequencyData(this.data);
+ return this.data;
+ },
+
+ /**
+ * Append a canvas to the DOM where the frequency data are drawn.
+ * Useful to debug tests.
+ */
+ enableDebugCanvas() {
+ var cvs = (this.debugCanvas = document.createElement("canvas"));
+ const content = document.getElementById("content");
+ content.insertBefore(cvs, content.children[0]);
+
+ // Easy: 1px per bin
+ cvs.width = this.analyser.frequencyBinCount;
+ cvs.height = 128;
+ cvs.style.border = "1px solid red";
+
+ var c = cvs.getContext("2d");
+ c.fillStyle = "black";
+
+ var self = this;
+ function render() {
+ c.clearRect(0, 0, cvs.width, cvs.height);
+ var array = self.getByteFrequencyData();
+ for (var i = 0; i < array.length; i++) {
+ c.fillRect(i, cvs.height - array[i] / 2, 1, cvs.height);
+ }
+ if (!cvs.stopDrawing) {
+ requestAnimationFrame(render);
+ }
+ }
+ requestAnimationFrame(render);
+ },
+
+ /**
+ * Stop drawing of and remove the debug canvas from the DOM if it was
+ * previously added.
+ */
+ disableDebugCanvas() {
+ if (!this.debugCanvas || !this.debugCanvas.parentElement) {
+ return;
+ }
+
+ this.debugCanvas.stopDrawing = true;
+ this.debugCanvas.parentElement.removeChild(this.debugCanvas);
+ },
+
+ /**
+ * Disconnects the input stream from our internal analyser node.
+ * Call this to reduce main thread processing, mostly necessary on slow
+ * devices.
+ */
+ disconnect() {
+ this.disableDebugCanvas();
+ this.sourceNodes.forEach(n => n.disconnect());
+ this.sourceNodes = [];
+ this.stream.removeEventListener("addtrack", this.onaddtrack);
+ },
+
+ /**
+ * Return a Promise, that will be resolved when the function passed as
+ * argument, when called, returns true (meaning the analysis was a
+ * success). The promise is rejected if the cancel promise resolves first.
+ *
+ * @param {function} analysisFunction
+ * A function that performs an analysis, and resolves with true if the
+ * analysis was a success (i.e. it found what it was looking for)
+ * @param {promise} cancel
+ * A promise that on resolving will reject the promise we returned.
+ */
+ async waitForAnalysisSuccess(
+ analysisFunction,
+ cancel = wait(60000, new Error("Audio analysis timed out"))
+ ) {
+ let aborted = false;
+ cancel.then(() => (aborted = true));
+
+ // We need to give the Analyser some time to start gathering data.
+ await wait(200);
+
+ do {
+ await new Promise(resolve => requestAnimationFrame(resolve));
+ if (aborted) {
+ throw await cancel;
+ }
+ } while (!analysisFunction(this.getByteFrequencyData()));
+ },
+
+ /**
+ * Return the FFT bin index for a given frequency.
+ *
+ * @param {double} frequency
+ * The frequency for whicht to return the bin number.
+ * @returns {integer} the index of the bin in the FFT array.
+ */
+ binIndexForFrequency(frequency) {
+ return (
+ 1 +
+ Math.round(
+ (frequency * this.analyser.fftSize) / this.audioContext.sampleRate
+ )
+ );
+ },
+
+ /**
+ * Reverse operation, get the frequency for a bin index.
+ *
+ * @param {integer} index an index in an FFT array
+ * @returns {double} the frequency for this bin
+ */
+ frequencyForBinIndex(index) {
+ return ((index - 1) * this.audioContext.sampleRate) / this.analyser.fftSize;
+ },
+};
+
+/**
+ * Creates a MediaStream with an audio track containing a sine tone at the
+ * given frequency.
+ *
+ * @param {AudioContext} ac
+ * AudioContext in which to create the OscillatorNode backing the stream
+ * @param {double} frequency
+ * The frequency in Hz of the generated sine tone
+ * @returns {MediaStream} the MediaStream containing sine tone audio track
+ */
+function createOscillatorStream(ac, frequency) {
+ var osc = ac.createOscillator();
+ osc.frequency.value = frequency;
+
+ var oscDest = ac.createMediaStreamDestination();
+ osc.connect(oscDest);
+ osc.start();
+ return oscDest.stream;
+}
+
+/**
+ * Create the necessary HTML elements for head and body as used by Mochitests
+ *
+ * @param {object} meta
+ * Meta information of the test
+ * @param {string} meta.title
+ * Description of the test
+ * @param {string} [meta.bug]
+ * Bug the test was created for
+ * @param {boolean} [meta.visible=false]
+ * Visibility of the media elements
+ */
+function realCreateHTML(meta) {
+ var test = document.getElementById("test");
+
+ // Create the head content
+ var elem = document.createElement("meta");
+ elem.setAttribute("charset", "utf-8");
+ document.head.appendChild(elem);
+
+ var title = document.createElement("title");
+ title.textContent = meta.title;
+ document.head.appendChild(title);
+
+ // Create the body content
+ var anchor = document.createElement("a");
+ anchor.textContent = meta.title;
+ if (meta.bug) {
+ anchor.setAttribute(
+ "href",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=" + meta.bug
+ );
+ } else {
+ anchor.setAttribute("target", "_blank");
+ }
+
+ document.body.insertBefore(anchor, test);
+
+ var display = document.createElement("p");
+ display.setAttribute("id", "display");
+ document.body.insertBefore(display, test);
+
+ var content = document.createElement("div");
+ content.setAttribute("id", "content");
+ content.style.display = meta.visible ? "block" : "none";
+ document.body.appendChild(content);
+}
+
+/**
+ * Creates an element of the given type, assigns the given id, sets the controls
+ * and autoplay attributes and adds it to the content node.
+ *
+ * @param {string} type
+ * Defining if we should create an "audio" or "video" element
+ * @param {string} id
+ * A string to use as the element id.
+ */
+function createMediaElement(type, id) {
+ const element = document.createElement(type);
+ element.setAttribute("id", id);
+ element.setAttribute("height", 100);
+ element.setAttribute("width", 150);
+ element.setAttribute("controls", "controls");
+ element.setAttribute("autoplay", "autoplay");
+ element.setAttribute("muted", "muted");
+ element.muted = true;
+ document.getElementById("content").appendChild(element);
+
+ return element;
+}
+
+/**
+ * Returns an existing element for the given track with the given idPrefix,
+ * as it was added by createMediaElementForTrack().
+ *
+ * @param {MediaStreamTrack} track
+ * Track used as the element's source.
+ * @param {string} idPrefix
+ * A string to use as the element id. The track id will also be appended.
+ */
+function getMediaElementForTrack(track, idPrefix) {
+ return document.getElementById(idPrefix + "_" + track.id);
+}
+
+/**
+ * Create a media element with a track as source and attach it to the content
+ * node.
+ *
+ * @param {MediaStreamTrack} track
+ * Track for use as source.
+ * @param {string} idPrefix
+ * A string to use as the element id. The track id will also be appended.
+ * @return {HTMLMediaElement} The created HTML media element
+ */
+function createMediaElementForTrack(track, idPrefix) {
+ const id = idPrefix + "_" + track.id;
+ const element = createMediaElement(track.kind, id);
+ element.srcObject = new MediaStream([track]);
+
+ return element;
+}
+
+/**
+ * Wrapper function for mediaDevices.getUserMedia used by some tests. Whether
+ * to use fake devices or not is now determined in pref further below instead.
+ *
+ * @param {Dictionary} constraints
+ * The constraints for this mozGetUserMedia callback
+ */
+function getUserMedia(constraints) {
+ // Tests may have changed the values of prefs, so recheck
+ updateConfigFromFakeAndLoopbackPrefs();
+ if (
+ !WANT_FAKE_AUDIO &&
+ !constraints.fake &&
+ constraints.audio &&
+ !DISABLE_LOOPBACK_TONE
+ ) {
+ // Loopback device is configured, start the default loopback tone
+ if (!DefaultLoopbackTone) {
+ TEST_AUDIO_FREQ = 440;
+ DefaultLoopbackTone = new LoopbackTone(
+ new AudioContext(),
+ TEST_AUDIO_FREQ
+ );
+ DefaultLoopbackTone.start();
+ }
+ // Disable input processing mode when it's not explicity enabled.
+ // This is to avoid distortion of the loopback tone
+ constraints.audio = Object.assign(
+ {},
+ { autoGainControl: false },
+ { echoCancellation: false },
+ { noiseSuppression: false },
+ constraints.audio
+ );
+ } else {
+ // Fake device configured, ensure our test freq is correct.
+ TEST_AUDIO_FREQ = 1000;
+ }
+ info("Call getUserMedia for " + JSON.stringify(constraints));
+ return navigator.mediaDevices
+ .getUserMedia(constraints)
+ .then(stream => (checkMediaStreamTracks(constraints, stream), stream));
+}
+
+// These are the promises we use to track that the prerequisites for the test
+// are in place before running it.
+var setTestOptions;
+var testConfigured = new Promise(r => (setTestOptions = r));
+
+function pushPrefs(...p) {
+ return SpecialPowers.pushPrefEnv({ set: p });
+}
+
+async function withPrefs(prefs, func) {
+ await SpecialPowers.pushPrefEnv({ set: prefs });
+ try {
+ return await func();
+ } finally {
+ await SpecialPowers.popPrefEnv();
+ }
+}
+
+function setupEnvironment() {
+ var defaultMochitestPrefs = {
+ set: [
+ ["media.peerconnection.enabled", true],
+ ["media.peerconnection.identity.enabled", true],
+ ["media.peerconnection.identity.timeout", 120000],
+ ["media.peerconnection.ice.stun_client_maximum_transmits", 14],
+ ["media.peerconnection.ice.trickle_grace_period", 30000],
+ ["media.navigator.permission.disabled", true],
+ // If either fake audio or video is desired we enable fake streams.
+ // If loopback devices are set they will be chosen instead of fakes in gecko.
+ ["media.navigator.streams.fake", WANT_FAKE_AUDIO || WANT_FAKE_VIDEO],
+ ["media.getusermedia.audiocapture.enabled", true],
+ ["media.getusermedia.screensharing.enabled", true],
+ ["media.getusermedia.window.focus_source.enabled", false],
+ ["media.recorder.audio_node.enabled", true],
+ ["media.peerconnection.ice.obfuscate_host_addresses", false],
+ ["media.peerconnection.nat_simulator.filtering_type", ""],
+ ["media.peerconnection.nat_simulator.mapping_type", ""],
+ ["media.peerconnection.nat_simulator.block_tcp", false],
+ ["media.peerconnection.nat_simulator.block_udp", false],
+ ["media.peerconnection.nat_simulator.redirect_address", ""],
+ ["media.peerconnection.nat_simulator.redirect_targets", ""],
+ ],
+ };
+
+ if (navigator.userAgent.includes("Android")) {
+ defaultMochitestPrefs.set.push(
+ ["media.navigator.video.default_width", 320],
+ ["media.navigator.video.default_height", 240],
+ ["media.navigator.video.max_fr", 10],
+ ["media.autoplay.default", Ci.nsIAutoplay.ALLOWED]
+ );
+ }
+
+ // Platform codec prefs should be matched because fake H.264 GMP codec doesn't
+ // produce/consume real bitstreams. [TODO] remove after bug 1509012 is fixed.
+ const platformEncoderEnabled = SpecialPowers.getBoolPref(
+ "media.webrtc.platformencoder"
+ );
+ defaultMochitestPrefs.set.push([
+ "media.navigator.mediadatadecoder_h264_enabled",
+ platformEncoderEnabled,
+ ]);
+
+ // Running as a Mochitest.
+ SimpleTest.requestFlakyTimeout("WebRTC inherently depends on timeouts");
+ window.finish = () => SimpleTest.finish();
+ SpecialPowers.pushPrefEnv(defaultMochitestPrefs, setTestOptions);
+
+ // We don't care about waiting for this to complete, we just want to ensure
+ // that we don't build up a huge backlog of GC work.
+ SpecialPowers.exactGC();
+}
+
+// [TODO] remove after bug 1509012 is fixed.
+async function matchPlatformH264CodecPrefs() {
+ const hasHW264 =
+ SpecialPowers.getBoolPref("media.webrtc.platformencoder") &&
+ !SpecialPowers.getBoolPref("media.webrtc.platformencoder.sw_only") &&
+ (navigator.userAgent.includes("Android") ||
+ navigator.userAgent.includes("Mac OS X"));
+
+ await pushPrefs(
+ ["media.webrtc.platformencoder", hasHW264],
+ ["media.navigator.mediadatadecoder_h264_enabled", hasHW264]
+ );
+}
+
+async function runTestWhenReady(testFunc) {
+ setupEnvironment();
+ const options = await testConfigured;
+ try {
+ await testFunc(options);
+ } catch (e) {
+ ok(
+ false,
+ `Error executing test: ${e}
+${e.stack ? e.stack : ""}`
+ );
+ } finally {
+ SimpleTest.finish();
+ }
+}
+
+/**
+ * Checks that the media stream tracks have the expected amount of tracks
+ * with the correct attributes based on the type and constraints given.
+ *
+ * @param {Object} constraints specifies whether the stream should have
+ * audio, video, or both
+ * @param {String} type the type of media stream tracks being checked
+ * @param {sequence<MediaStreamTrack>} mediaStreamTracks the media stream
+ * tracks being checked
+ */
+function checkMediaStreamTracksByType(constraints, type, mediaStreamTracks) {
+ if (constraints[type]) {
+ is(mediaStreamTracks.length, 1, "One " + type + " track shall be present");
+
+ if (mediaStreamTracks.length) {
+ is(mediaStreamTracks[0].kind, type, "Track kind should be " + type);
+ ok(mediaStreamTracks[0].id, "Track id should be defined");
+ ok(!mediaStreamTracks[0].muted, "Track should not be muted");
+ }
+ } else {
+ is(mediaStreamTracks.length, 0, "No " + type + " tracks shall be present");
+ }
+}
+
+/**
+ * Check that the given media stream contains the expected media stream
+ * tracks given the associated audio & video constraints provided.
+ *
+ * @param {Object} constraints specifies whether the stream should have
+ * audio, video, or both
+ * @param {MediaStream} mediaStream the media stream being checked
+ */
+function checkMediaStreamTracks(constraints, mediaStream) {
+ checkMediaStreamTracksByType(
+ constraints,
+ "audio",
+ mediaStream.getAudioTracks()
+ );
+ checkMediaStreamTracksByType(
+ constraints,
+ "video",
+ mediaStream.getVideoTracks()
+ );
+}
+
+/**
+ * Check that a media stream contains exactly a set of media stream tracks.
+ *
+ * @param {MediaStream} mediaStream the media stream being checked
+ * @param {Array} tracks the tracks that should exist in mediaStream
+ * @param {String} [message] an optional message to pass to asserts
+ */
+function checkMediaStreamContains(mediaStream, tracks, message) {
+ message = message ? message + ": " : "";
+ tracks.forEach(t =>
+ ok(
+ mediaStream.getTrackById(t.id),
+ message + "MediaStream " + mediaStream.id + " contains track " + t.id
+ )
+ );
+ is(
+ mediaStream.getTracks().length,
+ tracks.length,
+ message + "MediaStream " + mediaStream.id + " contains no extra tracks"
+ );
+}
+
+function checkMediaStreamCloneAgainstOriginal(clone, original) {
+ isnot(clone.id.length, 0, "Stream clone should have an id string");
+ isnot(clone, original, "Stream clone should be different from the original");
+ isnot(
+ clone.id,
+ original.id,
+ "Stream clone's id should be different from the original's"
+ );
+ is(
+ clone.getAudioTracks().length,
+ original.getAudioTracks().length,
+ "All audio tracks should get cloned"
+ );
+ is(
+ clone.getVideoTracks().length,
+ original.getVideoTracks().length,
+ "All video tracks should get cloned"
+ );
+ is(clone.active, original.active, "Active state should be preserved");
+ original
+ .getTracks()
+ .forEach(t =>
+ ok(!clone.getTrackById(t.id), "The clone's tracks should be originals")
+ );
+}
+
+function checkMediaStreamTrackCloneAgainstOriginal(clone, original) {
+ isnot(clone.id.length, 0, "Track clone should have an id string");
+ isnot(clone, original, "Track clone should be different from the original");
+ isnot(
+ clone.id,
+ original.id,
+ "Track clone's id should be different from the original's"
+ );
+ is(
+ clone.kind,
+ original.kind,
+ "Track clone's kind should be same as the original's"
+ );
+ is(
+ clone.enabled,
+ original.enabled,
+ "Track clone's kind should be same as the original's"
+ );
+ is(
+ clone.readyState,
+ original.readyState,
+ "Track clone's readyState should be same as the original's"
+ );
+ is(
+ clone.muted,
+ original.muted,
+ "Track clone's muted state should be same as the original's"
+ );
+}
+
+/*** Utility methods */
+
+/** The dreadful setTimeout, use sparingly */
+function wait(time, message) {
+ return new Promise(r => setTimeout(() => r(message), time));
+}
+
+/** The even more dreadful setInterval, use even more sparingly */
+function waitUntil(func, time) {
+ return new Promise(resolve => {
+ var interval = setInterval(() => {
+ if (func()) {
+ clearInterval(interval);
+ resolve();
+ }
+ }, time || 200);
+ });
+}
+
+/** Time out while waiting for a promise to get resolved or rejected. */
+var timeout = (promise, time, msg) =>
+ Promise.race([
+ promise,
+ wait(time).then(() => Promise.reject(new Error(msg))),
+ ]);
+
+/** Adds a |finally| function to a promise whose argument is invoked whether the
+ * promise is resolved or rejected, and that does not interfere with chaining.*/
+var addFinallyToPromise = promise => {
+ promise.finally = func => {
+ return promise.then(
+ result => {
+ func();
+ return Promise.resolve(result);
+ },
+ error => {
+ func();
+ return Promise.reject(error);
+ }
+ );
+ };
+ return promise;
+};
+
+/** Use event listener to call passed-in function on fire until it returns true */
+var listenUntil = (target, eventName, onFire) => {
+ return new Promise(resolve =>
+ target.addEventListener(eventName, function callback(event) {
+ var result = onFire(event);
+ if (result) {
+ target.removeEventListener(eventName, callback);
+ resolve(result);
+ }
+ })
+ );
+};
+
+/* Test that a function throws the right error */
+function mustThrowWith(msg, reason, f) {
+ try {
+ f();
+ ok(false, msg + " must throw");
+ } catch (e) {
+ is(e.name, reason, msg + " must throw: " + e.message);
+ }
+}
+
+/* Get a dummy audio track */
+function getSilentTrack() {
+ let ctx = new AudioContext(),
+ oscillator = ctx.createOscillator();
+ let dst = oscillator.connect(ctx.createMediaStreamDestination());
+ oscillator.start();
+ return Object.assign(dst.stream.getAudioTracks()[0], { enabled: false });
+}
+
+function getBlackTrack({ width = 640, height = 480 } = {}) {
+ let canvas = Object.assign(document.createElement("canvas"), {
+ width,
+ height,
+ });
+ canvas.getContext("2d").fillRect(0, 0, width, height);
+ let stream = canvas.captureStream();
+ return Object.assign(stream.getVideoTracks()[0], { enabled: false });
+}
+
+/*** Test control flow methods */
+
+/**
+ * Generates a callback function fired only under unexpected circumstances
+ * while running the tests. The generated function kills off the test as well
+ * gracefully.
+ *
+ * @param {String} [message]
+ * An optional message to show if no object gets passed into the
+ * generated callback method.
+ */
+function generateErrorCallback(message) {
+ var stack = new Error().stack.split("\n");
+ stack.shift(); // Don't include this instantiation frame
+
+ /**
+ * @param {object} aObj
+ * The object fired back from the callback
+ */
+ return aObj => {
+ if (aObj) {
+ if (aObj.name && aObj.message) {
+ ok(
+ false,
+ "Unexpected callback for '" +
+ aObj.name +
+ "' with message = '" +
+ aObj.message +
+ "' at " +
+ JSON.stringify(stack)
+ );
+ } else {
+ ok(
+ false,
+ "Unexpected callback with = '" +
+ aObj +
+ "' at: " +
+ JSON.stringify(stack)
+ );
+ }
+ } else {
+ ok(
+ false,
+ "Unexpected callback with message = '" +
+ message +
+ "' at: " +
+ JSON.stringify(stack)
+ );
+ }
+ throw new Error("Unexpected callback");
+ };
+}
+
+var unexpectedEventArrived;
+var rejectOnUnexpectedEvent = new Promise((x, reject) => {
+ unexpectedEventArrived = reject;
+});
+
+/**
+ * Generates a callback function fired only for unexpected events happening.
+ *
+ * @param {String} description
+ Description of the object for which the event has been fired
+ * @param {String} eventName
+ Name of the unexpected event
+ */
+function unexpectedEvent(message, eventName) {
+ var stack = new Error().stack.split("\n");
+ stack.shift(); // Don't include this instantiation frame
+
+ return e => {
+ var details =
+ "Unexpected event '" +
+ eventName +
+ "' fired with message = '" +
+ message +
+ "' at: " +
+ JSON.stringify(stack);
+ ok(false, details);
+ unexpectedEventArrived(new Error(details));
+ };
+}
+
+/**
+ * Implements the one-shot event pattern used throughout. Each of the 'onxxx'
+ * attributes on the wrappers can be set with a custom handler. Prior to the
+ * handler being set, if the event fires, it causes the test execution to halt.
+ * That handler is used exactly once, after which the original, error-generating
+ * handler is re-installed. Thus, each event handler is used at most once.
+ *
+ * @param {object} wrapper
+ * The wrapper on which the psuedo-handler is installed
+ * @param {object} obj
+ * The real source of events
+ * @param {string} event
+ * The name of the event
+ */
+function createOneShotEventWrapper(wrapper, obj, event) {
+ var onx = "on" + event;
+ var unexpected = unexpectedEvent(wrapper, event);
+ wrapper[onx] = unexpected;
+ obj[onx] = e => {
+ info(wrapper + ': "on' + event + '" event fired');
+ e.wrapper = wrapper;
+ wrapper[onx](e);
+ wrapper[onx] = unexpected;
+ };
+}
+
+/**
+ * Returns a promise that resolves when `target` has raised an event with the
+ * given name the given number of times. Cancel the returned promise by passing
+ * in a `cancel` promise and resolving it.
+ *
+ * @param {object} target
+ * The target on which the event should occur.
+ * @param {string} name
+ * The name of the event that should occur.
+ * @param {integer} count
+ * Optional number of times the event should be raised before resolving.
+ * @param {promise} cancel
+ * Optional promise that on resolving rejects the returned promise,
+ * so we can avoid logging results after a test has finished.
+ * @returns {promise} A promise that resolves to the last of the seen events.
+ */
+function haveEvents(target, name, count, cancel) {
+ var listener;
+ var counter = count || 1;
+ return Promise.race([
+ (cancel || new Promise(() => {})).then(e => Promise.reject(e)),
+ new Promise(resolve =>
+ target.addEventListener(
+ name,
+ (listener = e => --counter < 1 && resolve(e))
+ )
+ ),
+ ]).then(e => (target.removeEventListener(name, listener), e));
+}
+
+/**
+ * Returns a promise that resolves when `target` has raised an event with the
+ * given name. Cancel the returned promise by passing in a `cancel` promise and
+ * resolving it.
+ *
+ * @param {object} target
+ * The target on which the event should occur.
+ * @param {string} name
+ * The name of the event that should occur.
+ * @param {promise} cancel
+ * Optional promise that on resolving rejects the returned promise,
+ * so we can avoid logging results after a test has finished.
+ * @returns {promise} A promise that resolves to the seen event.
+ */
+function haveEvent(target, name, cancel) {
+ return haveEvents(target, name, 1, cancel);
+}
+
+/**
+ * Returns a promise that resolves if the target has not seen the given event
+ * after one crank (or until the given timeoutPromise resolves) of the event
+ * loop.
+ *
+ * @param {object} target
+ * The target on which the event should not occur.
+ * @param {string} name
+ * The name of the event that should not occur.
+ * @param {promise} timeoutPromise
+ * Optional promise defining how long we should wait before resolving.
+ * @returns {promise} A promise that is rejected if we see the given event, or
+ * resolves after a timeout otherwise.
+ */
+function haveNoEvent(target, name, timeoutPromise) {
+ return haveEvent(target, name, timeoutPromise || wait(0)).then(
+ () => Promise.reject(new Error("Too many " + name + " events")),
+ () => {}
+ );
+}
+
+/**
+ * Returns a promise that resolves after the target has seen the given number
+ * of events but no such event in a following crank of the event loop.
+ *
+ * @param {object} target
+ * The target on which the events should occur.
+ * @param {string} name
+ * The name of the event that should occur.
+ * @param {integer} count
+ * Optional number of times the event should be raised before resolving.
+ * @param {promise} cancel
+ * Optional promise that on resolving rejects the returned promise,
+ * so we can avoid logging results after a test has finished.
+ * @returns {promise} A promise that resolves to the last of the seen events.
+ */
+function haveEventsButNoMore(target, name, count, cancel) {
+ return haveEvents(target, name, count, cancel).then(e =>
+ haveNoEvent(target, name).then(() => e)
+ );
+}
+
+/*
+ * Resolves the returned promise with an object with usage and reportCount
+ * properties. `usage` is in the same units as reported by the reporter for
+ * `path`.
+ */
+const collectMemoryUsage = async path => {
+ const MemoryReporterManager = Cc[
+ "@mozilla.org/memory-reporter-manager;1"
+ ].getService(Ci.nsIMemoryReporterManager);
+
+ let usage = 0;
+ let reportCount = 0;
+ await new Promise(resolve =>
+ MemoryReporterManager.getReports(
+ (aProcess, aPath, aKind, aUnits, aAmount, aDesc) => {
+ if (aPath != path) {
+ return;
+ }
+ ++reportCount;
+ usage += aAmount;
+ },
+ null,
+ resolve,
+ null,
+ /* anonymized = */ false
+ )
+ );
+ return { usage, reportCount };
+};
+
+// Some DNS helper functions
+const dnsLookup = async hostname => {
+ // Convenience API for various networking related stuff. _Almost_ convenient
+ // enough.
+ const neckoDashboard = SpecialPowers.Cc[
+ "@mozilla.org/network/dashboard;1"
+ ].getService(Ci.nsIDashboard);
+
+ const results = await new Promise(r => {
+ neckoDashboard.requestDNSLookup(hostname, results => {
+ r(SpecialPowers.wrap(results));
+ });
+ });
+
+ // |address| is an array-like dictionary (ie; keys are all integers).
+ // We convert to an array to make it less unwieldy.
+ const addresses = [...results.address];
+ info(`DNS results for ${hostname}: ${JSON.stringify(addresses)}`);
+ return addresses;
+};
+
+const dnsLookupV4 = async hostname => {
+ const addresses = await dnsLookup(hostname);
+ return addresses.filter(address => !address.includes(":"));
+};
+
+const dnsLookupV6 = async hostname => {
+ const addresses = await dnsLookup(hostname);
+ return addresses.filter(address => address.includes(":"));
+};
+
+const getTurnHostname = turnUrl => {
+ const urlNoParams = turnUrl.split("?")[0];
+ // Strip off scheme
+ const hostAndMaybePort = urlNoParams.split(":", 2)[1];
+ if (hostAndMaybePort[0] == "[") {
+ // IPV6 literal, strip out '[', and split at closing ']'
+ return hostAndMaybePort.substring(1).split("]")[0];
+ }
+ return hostAndMaybePort.split(":")[0];
+};
+
+// Yo dawg I heard you like yo dawg I heard you like Proxies
+// Example: let value = await GleanTest.category.metric.testGetValue();
+// For labeled metrics:
+// let value = await GleanTest.category.metric["label"].testGetValue();
+// Please don't try to use the string "testGetValue" as a label.
+const GleanTest = new Proxy(
+ {},
+ {
+ get(target, categoryName, receiver) {
+ return new Proxy(
+ {},
+ {
+ get(target, metricName, receiver) {
+ return new Proxy(
+ {
+ async testGetValue() {
+ return SpecialPowers.spawnChrome(
+ [categoryName, metricName],
+ async (categoryName, metricName) => {
+ await Services.fog.testFlushAllChildren();
+ const window = this.browsingContext.topChromeWindow;
+ return window.Glean[categoryName][
+ metricName
+ ].testGetValue();
+ }
+ );
+ },
+ },
+ {
+ get(target, prop, receiver) {
+ // The only prop that will be there is testGetValue, but we
+ // might add more later.
+ if (prop in target) {
+ return target[prop];
+ }
+
+ // |prop| must be a label?
+ const label = prop;
+ return {
+ async testGetValue() {
+ return SpecialPowers.spawnChrome(
+ [categoryName, metricName, label],
+ async (categoryName, metricName, label) => {
+ await Services.fog.testFlushAllChildren();
+ const window = this.browsingContext.topChromeWindow;
+ return window.Glean[categoryName][metricName][
+ label
+ ].testGetValue();
+ }
+ );
+ },
+ };
+ },
+ }
+ );
+ },
+ }
+ );
+ },
+ }
+);
+
+/**
+ * This class executes a series of functions in a continuous sequence.
+ * Promise-bearing functions are executed after the previous promise completes.
+ *
+ * @constructor
+ * @param {object} framework
+ * A back reference to the framework which makes use of the class. It is
+ * passed to each command callback.
+ * @param {function[]} commandList
+ * Commands to set during initialization
+ */
+function CommandChain(framework, commandList) {
+ this._framework = framework;
+ this.commands = commandList || [];
+}
+
+CommandChain.prototype = {
+ /**
+ * Start the command chain. This returns a promise that always resolves
+ * cleanly (this catches errors and fails the test case).
+ */
+ execute() {
+ return this.commands
+ .reduce((prev, next, i) => {
+ if (typeof next !== "function" || !next.name) {
+ throw new Error("registered non-function" + next);
+ }
+
+ return prev.then(() => {
+ info("Run step " + (i + 1) + ": " + next.name);
+ return Promise.race([next(this._framework), rejectOnUnexpectedEvent]);
+ });
+ }, Promise.resolve())
+ .catch(e =>
+ ok(
+ false,
+ "Error in test execution: " +
+ e +
+ (typeof e.stack === "string"
+ ? " " + e.stack.split("\n").join(" ... ")
+ : "")
+ )
+ );
+ },
+
+ /**
+ * Add new commands to the end of the chain
+ */
+ append(commands) {
+ this.commands = this.commands.concat(commands);
+ },
+
+ /**
+ * Returns the index of the specified command in the chain.
+ * @param {occurrence} Optional param specifying which occurrence to match,
+ * with 0 representing the first occurrence.
+ */
+ indexOf(functionOrName, occurrence) {
+ occurrence = occurrence || 0;
+ return this.commands.findIndex(func => {
+ if (typeof functionOrName === "string") {
+ if (func.name !== functionOrName) {
+ return false;
+ }
+ } else if (func !== functionOrName) {
+ return false;
+ }
+ if (occurrence) {
+ --occurrence;
+ return false;
+ }
+ return true;
+ });
+ },
+
+ mustHaveIndexOf(functionOrName, occurrence) {
+ var index = this.indexOf(functionOrName, occurrence);
+ if (index == -1) {
+ throw new Error("Unknown test: " + functionOrName);
+ }
+ return index;
+ },
+
+ /**
+ * Inserts the new commands after the specified command.
+ */
+ insertAfter(functionOrName, commands, all, occurrence) {
+ this._insertHelper(functionOrName, commands, 1, all, occurrence);
+ },
+
+ /**
+ * Inserts the new commands after every occurrence of the specified command
+ */
+ insertAfterEach(functionOrName, commands) {
+ this._insertHelper(functionOrName, commands, 1, true);
+ },
+
+ /**
+ * Inserts the new commands before the specified command.
+ */
+ insertBefore(functionOrName, commands, all, occurrence) {
+ this._insertHelper(functionOrName, commands, 0, all, occurrence);
+ },
+
+ _insertHelper(functionOrName, commands, delta, all, occurrence) {
+ occurrence = occurrence || 0;
+ for (
+ var index = this.mustHaveIndexOf(functionOrName, occurrence);
+ index !== -1;
+ index = this.indexOf(functionOrName, ++occurrence)
+ ) {
+ this.commands = [].concat(
+ this.commands.slice(0, index + delta),
+ commands,
+ this.commands.slice(index + delta)
+ );
+ if (!all) {
+ break;
+ }
+ }
+ },
+
+ /**
+ * Removes the specified command, returns what was removed.
+ */
+ remove(functionOrName, occurrence) {
+ return this.commands.splice(
+ this.mustHaveIndexOf(functionOrName, occurrence),
+ 1
+ );
+ },
+
+ /**
+ * Removes all commands after the specified one, returns what was removed.
+ */
+ removeAfter(functionOrName, occurrence) {
+ return this.commands.splice(
+ this.mustHaveIndexOf(functionOrName, occurrence) + 1
+ );
+ },
+
+ /**
+ * Removes all commands before the specified one, returns what was removed.
+ */
+ removeBefore(functionOrName, occurrence) {
+ return this.commands.splice(
+ 0,
+ this.mustHaveIndexOf(functionOrName, occurrence)
+ );
+ },
+
+ /**
+ * Replaces a single command, returns what was removed.
+ */
+ replace(functionOrName, commands) {
+ this.insertBefore(functionOrName, commands);
+ return this.remove(functionOrName);
+ },
+
+ /**
+ * Replaces all commands after the specified one, returns what was removed.
+ */
+ replaceAfter(functionOrName, commands, occurrence) {
+ var oldCommands = this.removeAfter(functionOrName, occurrence);
+ this.append(commands);
+ return oldCommands;
+ },
+
+ /**
+ * Replaces all commands before the specified one, returns what was removed.
+ */
+ replaceBefore(functionOrName, commands) {
+ var oldCommands = this.removeBefore(functionOrName);
+ this.insertBefore(functionOrName, commands);
+ return oldCommands;
+ },
+
+ /**
+ * Remove all commands whose name match the specified regex.
+ */
+ filterOut(id_match) {
+ this.commands = this.commands.filter(c => !id_match.test(c.name));
+ },
+};
+
+function AudioStreamHelper() {
+ this._context = new AudioContext();
+}
+
+AudioStreamHelper.prototype = {
+ checkAudio(stream, analyser, fun) {
+ /*
+ analyser.enableDebugCanvas();
+ return analyser.waitForAnalysisSuccess(fun)
+ .then(() => analyser.disableDebugCanvas());
+ */
+ return analyser.waitForAnalysisSuccess(fun);
+ },
+
+ checkAudioFlowing(stream) {
+ var analyser = new AudioStreamAnalyser(this._context, stream);
+ var freq = analyser.binIndexForFrequency(TEST_AUDIO_FREQ);
+ return this.checkAudio(stream, analyser, array => array[freq] > 200);
+ },
+
+ checkAudioNotFlowing(stream) {
+ var analyser = new AudioStreamAnalyser(this._context, stream);
+ var freq = analyser.binIndexForFrequency(TEST_AUDIO_FREQ);
+ return this.checkAudio(stream, analyser, array => array[freq] < 50);
+ },
+};
+
+class VideoFrameEmitter {
+ constructor(color1, color2, width, height) {
+ if (!width) {
+ width = 50;
+ }
+ if (!height) {
+ height = width;
+ }
+ this._helper = new CaptureStreamTestHelper2D(width, height);
+ this._canvas = this._helper.createAndAppendElement(
+ "canvas",
+ "source_canvas"
+ );
+ this._canvas.width = width;
+ this._canvas.height = height;
+ this._color1 = color1 ? color1 : this._helper.green;
+ this._color2 = color2 ? color2 : this._helper.red;
+ // Make sure this is initted
+ this._helper.drawColor(this._canvas, this._color1);
+ this._stream = this._canvas.captureStream();
+ this._started = false;
+ }
+
+ stream() {
+ return this._stream;
+ }
+
+ helper() {
+ return this._helper;
+ }
+
+ colors(color1, color2) {
+ this._color1 = color1 ? color1 : this._helper.green;
+ this._color2 = color2 ? color2 : this._helper.red;
+ try {
+ this._helper.drawColor(this._canvas, this._color1);
+ } catch (e) {
+ // ignore; stream might have shut down
+ }
+ }
+
+ size(width, height) {
+ this._canvas.width = width;
+ this._canvas.height = height;
+ }
+
+ start() {
+ if (this._started) {
+ info("*** emitter already started");
+ return;
+ }
+
+ let i = 0;
+ this._started = true;
+ this._intervalId = setInterval(() => {
+ try {
+ this._helper.drawColor(this._canvas, i ? this._color1 : this._color2);
+ i = 1 - i;
+ } catch (e) {
+ // ignore; stream might have shut down, and we don't bother clearing
+ // the setInterval.
+ }
+ }, 500);
+ }
+
+ stop() {
+ if (this._started) {
+ clearInterval(this._intervalId);
+ this._started = false;
+ }
+ }
+}
+
+class VideoStreamHelper {
+ constructor() {
+ this._helper = new CaptureStreamTestHelper2D(50, 50);
+ }
+
+ async checkHasFrame(video, { offsetX, offsetY, threshold } = {}) {
+ const h = this._helper;
+ await h.waitForPixel(
+ video,
+ px => {
+ let result = h.isOpaquePixelNot(px, h.black, threshold);
+ info(
+ "Checking that we have a frame, got [" +
+ Array.from(px) +
+ "]. Ref=[" +
+ Array.from(h.black.data) +
+ "]. Threshold=" +
+ threshold +
+ ". Pass=" +
+ result
+ );
+ return result;
+ },
+ { offsetX, offsetY }
+ );
+ }
+
+ async checkVideoPlaying(
+ video,
+ { offsetX = 10, offsetY = 10, threshold = 16 } = {}
+ ) {
+ const h = this._helper;
+ await this.checkHasFrame(video, { offsetX, offsetY, threshold });
+ let startPixel = {
+ data: h.getPixel(video, offsetX, offsetY),
+ name: "startcolor",
+ };
+ await h.waitForPixel(
+ video,
+ px => {
+ let result = h.isPixelNot(px, startPixel, threshold);
+ info(
+ "Checking playing, [" +
+ Array.from(px) +
+ "] vs [" +
+ Array.from(startPixel.data) +
+ "]. Threshold=" +
+ threshold +
+ " Pass=" +
+ result
+ );
+ return result;
+ },
+ { offsetX, offsetY }
+ );
+ }
+
+ async checkVideoPaused(
+ video,
+ { offsetX = 10, offsetY = 10, threshold = 16, time = 5000 } = {}
+ ) {
+ const h = this._helper;
+ await this.checkHasFrame(video, { offsetX, offsetY, threshold });
+ let startPixel = {
+ data: h.getPixel(video, offsetX, offsetY),
+ name: "startcolor",
+ };
+ try {
+ await h.waitForPixel(
+ video,
+ px => {
+ let result = h.isOpaquePixelNot(px, startPixel, threshold);
+ info(
+ "Checking paused, [" +
+ Array.from(px) +
+ "] vs [" +
+ Array.from(startPixel.data) +
+ "]. Threshold=" +
+ threshold +
+ " Pass=" +
+ result
+ );
+ return result;
+ },
+ { offsetX, offsetY, cancel: wait(time, "timeout") }
+ );
+ ok(false, "Frame changed within " + time / 1000 + " seconds");
+ } catch (e) {
+ is(
+ e,
+ "timeout",
+ "Frame shouldn't change for " + time / 1000 + " seconds"
+ );
+ }
+ }
+}
+
+(function () {
+ var el = document.createElement("link");
+ el.rel = "stylesheet";
+ el.type = "text/css";
+ el.href = "/tests/SimpleTest/test.css";
+ document.head.appendChild(el);
+})();
diff --git a/dom/media/webrtc/tests/mochitests/helpers_from_wpt/sdp.js b/dom/media/webrtc/tests/mochitests/helpers_from_wpt/sdp.js
new file mode 100644
index 0000000000..6460f64a44
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/helpers_from_wpt/sdp.js
@@ -0,0 +1,889 @@
+/* eslint-env node */
+"use strict";
+
+// SDP helpers.
+var SDPUtils = {};
+
+// Generate an alphanumeric identifier for cname or mids.
+// TODO: use UUIDs instead? https://gist.github.com/jed/982883
+SDPUtils.generateIdentifier = function() {
+ return Math.random()
+ .toString(36)
+ .substr(2, 10);
+};
+
+// The RTCP CNAME used by all peerconnections from the same JS.
+SDPUtils.localCName = SDPUtils.generateIdentifier();
+
+// Splits SDP into lines, dealing with both CRLF and LF.
+SDPUtils.splitLines = function(blob) {
+ return blob
+ .trim()
+ .split("\n")
+ .map(function(line) {
+ return line.trim();
+ });
+};
+// Splits SDP into sessionpart and mediasections. Ensures CRLF.
+SDPUtils.splitSections = function(blob) {
+ var parts = blob.split("\nm=");
+ return parts.map(function(part, index) {
+ return (index > 0 ? "m=" + part : part).trim() + "\r\n";
+ });
+};
+
+// returns the session description.
+SDPUtils.getDescription = function(blob) {
+ var sections = SDPUtils.splitSections(blob);
+ return sections && sections[0];
+};
+
+// returns the individual media sections.
+SDPUtils.getMediaSections = function(blob) {
+ var sections = SDPUtils.splitSections(blob);
+ sections.shift();
+ return sections;
+};
+
+// Returns lines that start with a certain prefix.
+SDPUtils.matchPrefix = function(blob, prefix) {
+ return SDPUtils.splitLines(blob).filter(function(line) {
+ return line.indexOf(prefix) === 0;
+ });
+};
+
+// Parses an ICE candidate line. Sample input:
+// candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8
+// rport 55996"
+SDPUtils.parseCandidate = function(line) {
+ var parts;
+ // Parse both variants.
+ if (line.indexOf("a=candidate:") === 0) {
+ parts = line.substring(12).split(" ");
+ } else {
+ parts = line.substring(10).split(" ");
+ }
+
+ var candidate = {
+ foundation: parts[0],
+ component: parseInt(parts[1], 10),
+ protocol: parts[2].toLowerCase(),
+ priority: parseInt(parts[3], 10),
+ ip: parts[4],
+ address: parts[4], // address is an alias for ip.
+ port: parseInt(parts[5], 10),
+ // skip parts[6] == 'typ'
+ type: parts[7],
+ };
+
+ for (var i = 8; i < parts.length; i += 2) {
+ switch (parts[i]) {
+ case "raddr":
+ candidate.relatedAddress = parts[i + 1];
+ break;
+ case "rport":
+ candidate.relatedPort = parseInt(parts[i + 1], 10);
+ break;
+ case "tcptype":
+ candidate.tcpType = parts[i + 1];
+ break;
+ case "ufrag":
+ candidate.ufrag = parts[i + 1]; // for backward compability.
+ candidate.usernameFragment = parts[i + 1];
+ break;
+ default:
+ // extension handling, in particular ufrag
+ candidate[parts[i]] = parts[i + 1];
+ break;
+ }
+ }
+ return candidate;
+};
+
+// Translates a candidate object into SDP candidate attribute.
+SDPUtils.writeCandidate = function(candidate) {
+ var sdp = [];
+ sdp.push(candidate.foundation);
+ sdp.push(candidate.component);
+ sdp.push(candidate.protocol.toUpperCase());
+ sdp.push(candidate.priority);
+ sdp.push(candidate.address || candidate.ip);
+ sdp.push(candidate.port);
+
+ var type = candidate.type;
+ sdp.push("typ");
+ sdp.push(type);
+ if (type !== "host" && candidate.relatedAddress && candidate.relatedPort) {
+ sdp.push("raddr");
+ sdp.push(candidate.relatedAddress);
+ sdp.push("rport");
+ sdp.push(candidate.relatedPort);
+ }
+ if (candidate.tcpType && candidate.protocol.toLowerCase() === "tcp") {
+ sdp.push("tcptype");
+ sdp.push(candidate.tcpType);
+ }
+ if (candidate.usernameFragment || candidate.ufrag) {
+ sdp.push("ufrag");
+ sdp.push(candidate.usernameFragment || candidate.ufrag);
+ }
+ return "candidate:" + sdp.join(" ");
+};
+
+// Parses an ice-options line, returns an array of option tags.
+// a=ice-options:foo bar
+SDPUtils.parseIceOptions = function(line) {
+ return line.substr(14).split(" ");
+};
+
+// Parses an rtpmap line, returns RTCRtpCoddecParameters. Sample input:
+// a=rtpmap:111 opus/48000/2
+SDPUtils.parseRtpMap = function(line) {
+ var parts = line.substr(9).split(" ");
+ var parsed = {
+ payloadType: parseInt(parts.shift(), 10), // was: id
+ };
+
+ parts = parts[0].split("/");
+
+ parsed.name = parts[0];
+ parsed.clockRate = parseInt(parts[1], 10); // was: clockrate
+ parsed.channels = parts.length === 3 ? parseInt(parts[2], 10) : 1;
+ // legacy alias, got renamed back to channels in ORTC.
+ parsed.numChannels = parsed.channels;
+ return parsed;
+};
+
+// Generate an a=rtpmap line from RTCRtpCodecCapability or
+// RTCRtpCodecParameters.
+SDPUtils.writeRtpMap = function(codec) {
+ var pt = codec.payloadType;
+ if (codec.preferredPayloadType !== undefined) {
+ pt = codec.preferredPayloadType;
+ }
+ var channels = codec.channels || codec.numChannels || 1;
+ return (
+ "a=rtpmap:" +
+ pt +
+ " " +
+ codec.name +
+ "/" +
+ codec.clockRate +
+ (channels !== 1 ? "/" + channels : "") +
+ "\r\n"
+ );
+};
+
+// Parses an a=extmap line (headerextension from RFC 5285). Sample input:
+// a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
+// a=extmap:2/sendonly urn:ietf:params:rtp-hdrext:toffset
+SDPUtils.parseExtmap = function(line) {
+ var parts = line.substr(9).split(" ");
+ return {
+ id: parseInt(parts[0], 10),
+ direction: parts[0].indexOf("/") > 0 ? parts[0].split("/")[1] : "sendrecv",
+ uri: parts[1],
+ };
+};
+
+// Generates a=extmap line from RTCRtpHeaderExtensionParameters or
+// RTCRtpHeaderExtension.
+SDPUtils.writeExtmap = function(headerExtension) {
+ return (
+ "a=extmap:" +
+ (headerExtension.id || headerExtension.preferredId) +
+ (headerExtension.direction && headerExtension.direction !== "sendrecv"
+ ? "/" + headerExtension.direction
+ : "") +
+ " " +
+ headerExtension.uri +
+ "\r\n"
+ );
+};
+
+// Parses an ftmp line, returns dictionary. Sample input:
+// a=fmtp:96 vbr=on;cng=on
+// Also deals with vbr=on; cng=on
+SDPUtils.parseFmtp = function(line) {
+ var parsed = {};
+ var kv;
+ var parts = line.substr(line.indexOf(" ") + 1).split(";");
+ for (var j = 0; j < parts.length; j++) {
+ kv = parts[j].trim().split("=");
+ parsed[kv[0].trim()] = kv[1];
+ }
+ return parsed;
+};
+
+// Generates an a=ftmp line from RTCRtpCodecCapability or RTCRtpCodecParameters.
+SDPUtils.writeFmtp = function(codec) {
+ var line = "";
+ var pt = codec.payloadType;
+ if (codec.preferredPayloadType !== undefined) {
+ pt = codec.preferredPayloadType;
+ }
+ if (codec.parameters && Object.keys(codec.parameters).length) {
+ var params = [];
+ Object.keys(codec.parameters).forEach(function(param) {
+ if (codec.parameters[param]) {
+ params.push(param + "=" + codec.parameters[param]);
+ } else {
+ params.push(param);
+ }
+ });
+ line += "a=fmtp:" + pt + " " + params.join(";") + "\r\n";
+ }
+ return line;
+};
+
+// Parses an rtcp-fb line, returns RTCPRtcpFeedback object. Sample input:
+// a=rtcp-fb:98 nack rpsi
+SDPUtils.parseRtcpFb = function(line) {
+ var parts = line.substr(line.indexOf(" ") + 1).split(" ");
+ return {
+ type: parts.shift(),
+ parameter: parts.join(" "),
+ };
+};
+// Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters.
+SDPUtils.writeRtcpFb = function(codec) {
+ var lines = "";
+ var pt = codec.payloadType;
+ if (codec.preferredPayloadType !== undefined) {
+ pt = codec.preferredPayloadType;
+ }
+ if (codec.rtcpFeedback && codec.rtcpFeedback.length) {
+ // FIXME: special handling for trr-int?
+ codec.rtcpFeedback.forEach(function(fb) {
+ lines +=
+ "a=rtcp-fb:" +
+ pt +
+ " " +
+ fb.type +
+ (fb.parameter && fb.parameter.length ? " " + fb.parameter : "") +
+ "\r\n";
+ });
+ }
+ return lines;
+};
+
+// Parses an RFC 5576 ssrc media attribute. Sample input:
+// a=ssrc:3735928559 cname:something
+SDPUtils.parseSsrcMedia = function(line) {
+ var sp = line.indexOf(" ");
+ var parts = {
+ ssrc: parseInt(line.substr(7, sp - 7), 10),
+ };
+ var colon = line.indexOf(":", sp);
+ if (colon > -1) {
+ parts.attribute = line.substr(sp + 1, colon - sp - 1);
+ parts.value = line.substr(colon + 1);
+ } else {
+ parts.attribute = line.substr(sp + 1);
+ }
+ return parts;
+};
+
+SDPUtils.parseSsrcGroup = function(line) {
+ var parts = line.substr(13).split(" ");
+ return {
+ semantics: parts.shift(),
+ ssrcs: parts.map(function(ssrc) {
+ return parseInt(ssrc, 10);
+ }),
+ };
+};
+
+// Extracts the MID (RFC 5888) from a media section.
+// returns the MID or undefined if no mid line was found.
+SDPUtils.getMid = function(mediaSection) {
+ var mid = SDPUtils.matchPrefix(mediaSection, "a=mid:")[0];
+ if (mid) {
+ return mid.substr(6);
+ }
+};
+
+SDPUtils.parseFingerprint = function(line) {
+ var parts = line.substr(14).split(" ");
+ return {
+ algorithm: parts[0].toLowerCase(), // algorithm is case-sensitive in Edge.
+ value: parts[1],
+ };
+};
+
+// Extracts DTLS parameters from SDP media section or sessionpart.
+// FIXME: for consistency with other functions this should only
+// get the fingerprint line as input. See also getIceParameters.
+SDPUtils.getDtlsParameters = function(mediaSection, sessionpart) {
+ var lines = SDPUtils.matchPrefix(
+ mediaSection + sessionpart,
+ "a=fingerprint:"
+ );
+ // Note: a=setup line is ignored since we use the 'auto' role.
+ // Note2: 'algorithm' is not case sensitive except in Edge.
+ return {
+ role: "auto",
+ fingerprints: lines.map(SDPUtils.parseFingerprint),
+ };
+};
+
+// Serializes DTLS parameters to SDP.
+SDPUtils.writeDtlsParameters = function(params, setupType) {
+ var sdp = "a=setup:" + setupType + "\r\n";
+ params.fingerprints.forEach(function(fp) {
+ sdp += "a=fingerprint:" + fp.algorithm + " " + fp.value + "\r\n";
+ });
+ return sdp;
+};
+
+// Parses a=crypto lines into
+// https://rawgit.com/aboba/edgertc/master/msortc-rs4.html#dictionary-rtcsrtpsdesparameters-members
+SDPUtils.parseCryptoLine = function(line) {
+ var parts = line.substr(9).split(" ");
+ return {
+ tag: parseInt(parts[0], 10),
+ cryptoSuite: parts[1],
+ keyParams: parts[2],
+ sessionParams: parts.slice(3),
+ };
+};
+
+SDPUtils.writeCryptoLine = function(parameters) {
+ return (
+ "a=crypto:" +
+ parameters.tag +
+ " " +
+ parameters.cryptoSuite +
+ " " +
+ (typeof parameters.keyParams === "object"
+ ? SDPUtils.writeCryptoKeyParams(parameters.keyParams)
+ : parameters.keyParams) +
+ (parameters.sessionParams ? " " + parameters.sessionParams.join(" ") : "") +
+ "\r\n"
+ );
+};
+
+// Parses the crypto key parameters into
+// https://rawgit.com/aboba/edgertc/master/msortc-rs4.html#rtcsrtpkeyparam*
+SDPUtils.parseCryptoKeyParams = function(keyParams) {
+ if (keyParams.indexOf("inline:") !== 0) {
+ return null;
+ }
+ var parts = keyParams.substr(7).split("|");
+ return {
+ keyMethod: "inline",
+ keySalt: parts[0],
+ lifeTime: parts[1],
+ mkiValue: parts[2] ? parts[2].split(":")[0] : undefined,
+ mkiLength: parts[2] ? parts[2].split(":")[1] : undefined,
+ };
+};
+
+SDPUtils.writeCryptoKeyParams = function(keyParams) {
+ return (
+ keyParams.keyMethod +
+ ":" +
+ keyParams.keySalt +
+ (keyParams.lifeTime ? "|" + keyParams.lifeTime : "") +
+ (keyParams.mkiValue && keyParams.mkiLength
+ ? "|" + keyParams.mkiValue + ":" + keyParams.mkiLength
+ : "")
+ );
+};
+
+// Extracts all SDES paramters.
+SDPUtils.getCryptoParameters = function(mediaSection, sessionpart) {
+ var lines = SDPUtils.matchPrefix(mediaSection + sessionpart, "a=crypto:");
+ return lines.map(SDPUtils.parseCryptoLine);
+};
+
+// Parses ICE information from SDP media section or sessionpart.
+// FIXME: for consistency with other functions this should only
+// get the ice-ufrag and ice-pwd lines as input.
+SDPUtils.getIceParameters = function(mediaSection, sessionpart) {
+ var ufrag = SDPUtils.matchPrefix(
+ mediaSection + sessionpart,
+ "a=ice-ufrag:"
+ )[0];
+ var pwd = SDPUtils.matchPrefix(mediaSection + sessionpart, "a=ice-pwd:")[0];
+ if (!(ufrag && pwd)) {
+ return null;
+ }
+ return {
+ usernameFragment: ufrag.substr(12),
+ password: pwd.substr(10),
+ };
+};
+
+// Serializes ICE parameters to SDP.
+SDPUtils.writeIceParameters = function(params) {
+ return (
+ "a=ice-ufrag:" +
+ params.usernameFragment +
+ "\r\n" +
+ "a=ice-pwd:" +
+ params.password +
+ "\r\n"
+ );
+};
+
+// Parses the SDP media section and returns RTCRtpParameters.
+SDPUtils.parseRtpParameters = function(mediaSection) {
+ var description = {
+ codecs: [],
+ headerExtensions: [],
+ fecMechanisms: [],
+ rtcp: [],
+ };
+ var lines = SDPUtils.splitLines(mediaSection);
+ var mline = lines[0].split(" ");
+ for (var i = 3; i < mline.length; i++) {
+ // find all codecs from mline[3..]
+ var pt = mline[i];
+ var rtpmapline = SDPUtils.matchPrefix(
+ mediaSection,
+ "a=rtpmap:" + pt + " "
+ )[0];
+ if (rtpmapline) {
+ var codec = SDPUtils.parseRtpMap(rtpmapline);
+ var fmtps = SDPUtils.matchPrefix(mediaSection, "a=fmtp:" + pt + " ");
+ // Only the first a=fmtp:<pt> is considered.
+ codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {};
+ codec.rtcpFeedback = SDPUtils.matchPrefix(
+ mediaSection,
+ "a=rtcp-fb:" + pt + " "
+ ).map(SDPUtils.parseRtcpFb);
+ description.codecs.push(codec);
+ // parse FEC mechanisms from rtpmap lines.
+ switch (codec.name.toUpperCase()) {
+ case "RED":
+ case "ULPFEC":
+ description.fecMechanisms.push(codec.name.toUpperCase());
+ break;
+ default:
+ // only RED and ULPFEC are recognized as FEC mechanisms.
+ break;
+ }
+ }
+ }
+ SDPUtils.matchPrefix(mediaSection, "a=extmap:").forEach(function(line) {
+ description.headerExtensions.push(SDPUtils.parseExtmap(line));
+ });
+ // FIXME: parse rtcp.
+ return description;
+};
+
+// Generates parts of the SDP media section describing the capabilities /
+// parameters.
+SDPUtils.writeRtpDescription = function(kind, caps) {
+ var sdp = "";
+
+ // Build the mline.
+ sdp += "m=" + kind + " ";
+ sdp += caps.codecs.length > 0 ? "9" : "0"; // reject if no codecs.
+ sdp += " UDP/TLS/RTP/SAVPF ";
+ sdp +=
+ caps.codecs
+ .map(function(codec) {
+ if (codec.preferredPayloadType !== undefined) {
+ return codec.preferredPayloadType;
+ }
+ return codec.payloadType;
+ })
+ .join(" ") + "\r\n";
+
+ sdp += "c=IN IP4 0.0.0.0\r\n";
+ sdp += "a=rtcp:9 IN IP4 0.0.0.0\r\n";
+
+ // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb.
+ caps.codecs.forEach(function(codec) {
+ sdp += SDPUtils.writeRtpMap(codec);
+ sdp += SDPUtils.writeFmtp(codec);
+ sdp += SDPUtils.writeRtcpFb(codec);
+ });
+ var maxptime = 0;
+ caps.codecs.forEach(function(codec) {
+ if (codec.maxptime > maxptime) {
+ maxptime = codec.maxptime;
+ }
+ });
+ if (maxptime > 0) {
+ sdp += "a=maxptime:" + maxptime + "\r\n";
+ }
+ sdp += "a=rtcp-mux\r\n";
+
+ if (caps.headerExtensions) {
+ caps.headerExtensions.forEach(function(extension) {
+ sdp += SDPUtils.writeExtmap(extension);
+ });
+ }
+ // FIXME: write fecMechanisms.
+ return sdp;
+};
+
+// Parses the SDP media section and returns an array of
+// RTCRtpEncodingParameters.
+SDPUtils.parseRtpEncodingParameters = function(mediaSection) {
+ var encodingParameters = [];
+ var description = SDPUtils.parseRtpParameters(mediaSection);
+ var hasRed = description.fecMechanisms.indexOf("RED") !== -1;
+ var hasUlpfec = description.fecMechanisms.indexOf("ULPFEC") !== -1;
+
+ // filter a=ssrc:... cname:, ignore PlanB-msid
+ var ssrcs = SDPUtils.matchPrefix(mediaSection, "a=ssrc:")
+ .map(function(line) {
+ return SDPUtils.parseSsrcMedia(line);
+ })
+ .filter(function(parts) {
+ return parts.attribute === "cname";
+ });
+ var primarySsrc = ssrcs.length > 0 && ssrcs[0].ssrc;
+ var secondarySsrc;
+
+ var flows = SDPUtils.matchPrefix(mediaSection, "a=ssrc-group:FID").map(
+ function(line) {
+ var parts = line.substr(17).split(" ");
+ return parts.map(function(part) {
+ return parseInt(part, 10);
+ });
+ }
+ );
+ if (flows.length > 0 && flows[0].length > 1 && flows[0][0] === primarySsrc) {
+ secondarySsrc = flows[0][1];
+ }
+
+ description.codecs.forEach(function(codec) {
+ if (codec.name.toUpperCase() === "RTX" && codec.parameters.apt) {
+ var encParam = {
+ ssrc: primarySsrc,
+ codecPayloadType: parseInt(codec.parameters.apt, 10),
+ };
+ if (primarySsrc && secondarySsrc) {
+ encParam.rtx = { ssrc: secondarySsrc };
+ }
+ encodingParameters.push(encParam);
+ if (hasRed) {
+ encParam = JSON.parse(JSON.stringify(encParam));
+ encParam.fec = {
+ ssrc: primarySsrc,
+ mechanism: hasUlpfec ? "red+ulpfec" : "red",
+ };
+ encodingParameters.push(encParam);
+ }
+ }
+ });
+ if (encodingParameters.length === 0 && primarySsrc) {
+ encodingParameters.push({
+ ssrc: primarySsrc,
+ });
+ }
+
+ // we support both b=AS and b=TIAS but interpret AS as TIAS.
+ var bandwidth = SDPUtils.matchPrefix(mediaSection, "b=");
+ if (bandwidth.length) {
+ if (bandwidth[0].indexOf("b=TIAS:") === 0) {
+ bandwidth = parseInt(bandwidth[0].substr(7), 10);
+ } else if (bandwidth[0].indexOf("b=AS:") === 0) {
+ // use formula from JSEP to convert b=AS to TIAS value.
+ bandwidth =
+ parseInt(bandwidth[0].substr(5), 10) * 1000 * 0.95 - 50 * 40 * 8;
+ } else {
+ bandwidth = undefined;
+ }
+ encodingParameters.forEach(function(params) {
+ params.maxBitrate = bandwidth;
+ });
+ }
+ return encodingParameters;
+};
+
+// parses http://draft.ortc.org/#rtcrtcpparameters*
+SDPUtils.parseRtcpParameters = function(mediaSection) {
+ var rtcpParameters = {};
+
+ // Gets the first SSRC. Note tha with RTX there might be multiple
+ // SSRCs.
+ var remoteSsrc = SDPUtils.matchPrefix(mediaSection, "a=ssrc:")
+ .map(function(line) {
+ return SDPUtils.parseSsrcMedia(line);
+ })
+ .filter(function(obj) {
+ return obj.attribute === "cname";
+ })[0];
+ if (remoteSsrc) {
+ rtcpParameters.cname = remoteSsrc.value;
+ rtcpParameters.ssrc = remoteSsrc.ssrc;
+ }
+
+ // Edge uses the compound attribute instead of reducedSize
+ // compound is !reducedSize
+ var rsize = SDPUtils.matchPrefix(mediaSection, "a=rtcp-rsize");
+ rtcpParameters.reducedSize = rsize.length > 0;
+ rtcpParameters.compound = rsize.length === 0;
+
+ // parses the rtcp-mux attrіbute.
+ // Note that Edge does not support unmuxed RTCP.
+ var mux = SDPUtils.matchPrefix(mediaSection, "a=rtcp-mux");
+ rtcpParameters.mux = mux.length > 0;
+
+ return rtcpParameters;
+};
+
+// parses either a=msid: or a=ssrc:... msid lines and returns
+// the id of the MediaStream and MediaStreamTrack.
+SDPUtils.parseMsid = function(mediaSection) {
+ var parts;
+ var spec = SDPUtils.matchPrefix(mediaSection, "a=msid:");
+ if (spec.length === 1) {
+ parts = spec[0].substr(7).split(" ");
+ return { stream: parts[0], track: parts[1] };
+ }
+ var planB = SDPUtils.matchPrefix(mediaSection, "a=ssrc:")
+ .map(function(line) {
+ return SDPUtils.parseSsrcMedia(line);
+ })
+ .filter(function(msidParts) {
+ return msidParts.attribute === "msid";
+ });
+ if (planB.length > 0) {
+ parts = planB[0].value.split(" ");
+ return { stream: parts[0], track: parts[1] };
+ }
+};
+
+// SCTP
+// parses draft-ietf-mmusic-sctp-sdp-26 first and falls back
+// to draft-ietf-mmusic-sctp-sdp-05
+SDPUtils.parseSctpDescription = function(mediaSection) {
+ var mline = SDPUtils.parseMLine(mediaSection);
+ var maxSizeLine = SDPUtils.matchPrefix(mediaSection, "a=max-message-size:");
+ var maxMessageSize;
+ if (maxSizeLine.length > 0) {
+ maxMessageSize = parseInt(maxSizeLine[0].substr(19), 10);
+ }
+ if (isNaN(maxMessageSize)) {
+ maxMessageSize = 65536;
+ }
+ var sctpPort = SDPUtils.matchPrefix(mediaSection, "a=sctp-port:");
+ if (sctpPort.length > 0) {
+ return {
+ port: parseInt(sctpPort[0].substr(12), 10),
+ protocol: mline.fmt,
+ maxMessageSize,
+ };
+ }
+ var sctpMapLines = SDPUtils.matchPrefix(mediaSection, "a=sctpmap:");
+ if (sctpMapLines.length > 0) {
+ var parts = SDPUtils.matchPrefix(mediaSection, "a=sctpmap:")[0]
+ .substr(10)
+ .split(" ");
+ return {
+ port: parseInt(parts[0], 10),
+ protocol: parts[1],
+ maxMessageSize,
+ };
+ }
+};
+
+// SCTP
+// outputs the draft-ietf-mmusic-sctp-sdp-26 version that all browsers
+// support by now receiving in this format, unless we originally parsed
+// as the draft-ietf-mmusic-sctp-sdp-05 format (indicated by the m-line
+// protocol of DTLS/SCTP -- without UDP/ or TCP/)
+SDPUtils.writeSctpDescription = function(media, sctp) {
+ var output = [];
+ if (media.protocol !== "DTLS/SCTP") {
+ output = [
+ "m=" + media.kind + " 9 " + media.protocol + " " + sctp.protocol + "\r\n",
+ "c=IN IP4 0.0.0.0\r\n",
+ "a=sctp-port:" + sctp.port + "\r\n",
+ ];
+ } else {
+ output = [
+ "m=" + media.kind + " 9 " + media.protocol + " " + sctp.port + "\r\n",
+ "c=IN IP4 0.0.0.0\r\n",
+ "a=sctpmap:" + sctp.port + " " + sctp.protocol + " 65535\r\n",
+ ];
+ }
+ if (sctp.maxMessageSize !== undefined) {
+ output.push("a=max-message-size:" + sctp.maxMessageSize + "\r\n");
+ }
+ return output.join("");
+};
+
+// Generate a session ID for SDP.
+// https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-20#section-5.2.1
+// recommends using a cryptographically random +ve 64-bit value
+// but right now this should be acceptable and within the right range
+SDPUtils.generateSessionId = function() {
+ return Math.floor((Math.random() * 4294967296) + 1);
+};
+
+// Write boilder plate for start of SDP
+// sessId argument is optional - if not supplied it will
+// be generated randomly
+// sessVersion is optional and defaults to 2
+// sessUser is optional and defaults to 'thisisadapterortc'
+SDPUtils.writeSessionBoilerplate = function(sessId, sessVer, sessUser) {
+ var sessionId;
+ var version = sessVer !== undefined ? sessVer : 2;
+ if (sessId) {
+ sessionId = sessId;
+ } else {
+ sessionId = SDPUtils.generateSessionId();
+ }
+ var user = sessUser || "thisisadapterortc";
+ // FIXME: sess-id should be an NTP timestamp.
+ return (
+ "v=0\r\n" +
+ "o=" +
+ user +
+ " " +
+ sessionId +
+ " " +
+ version +
+ " IN IP4 127.0.0.1\r\n" +
+ "s=-\r\n" +
+ "t=0 0\r\n"
+ );
+};
+
+SDPUtils.writeMediaSection = function(transceiver, caps, type, stream) {
+ var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps);
+
+ // Map ICE parameters (ufrag, pwd) to SDP.
+ sdp += SDPUtils.writeIceParameters(
+ transceiver.iceGatherer.getLocalParameters()
+ );
+
+ // Map DTLS parameters to SDP.
+ sdp += SDPUtils.writeDtlsParameters(
+ transceiver.dtlsTransport.getLocalParameters(),
+ type === "offer" ? "actpass" : "active"
+ );
+
+ sdp += "a=mid:" + transceiver.mid + "\r\n";
+
+ if (transceiver.direction) {
+ sdp += "a=" + transceiver.direction + "\r\n";
+ } else if (transceiver.rtpSender && transceiver.rtpReceiver) {
+ sdp += "a=sendrecv\r\n";
+ } else if (transceiver.rtpSender) {
+ sdp += "a=sendonly\r\n";
+ } else if (transceiver.rtpReceiver) {
+ sdp += "a=recvonly\r\n";
+ } else {
+ sdp += "a=inactive\r\n";
+ }
+
+ if (transceiver.rtpSender) {
+ // spec.
+ var msid =
+ "msid:" + stream.id + " " + transceiver.rtpSender.track.id + "\r\n";
+ sdp += "a=" + msid;
+
+ // for Chrome.
+ sdp += "a=ssrc:" + transceiver.sendEncodingParameters[0].ssrc + " " + msid;
+ if (transceiver.sendEncodingParameters[0].rtx) {
+ sdp +=
+ "a=ssrc:" + transceiver.sendEncodingParameters[0].rtx.ssrc + " " + msid;
+ sdp +=
+ "a=ssrc-group:FID " +
+ transceiver.sendEncodingParameters[0].ssrc +
+ " " +
+ transceiver.sendEncodingParameters[0].rtx.ssrc +
+ "\r\n";
+ }
+ }
+ // FIXME: this should be written by writeRtpDescription.
+ sdp +=
+ "a=ssrc:" +
+ transceiver.sendEncodingParameters[0].ssrc +
+ " cname:" +
+ SDPUtils.localCName +
+ "\r\n";
+ if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) {
+ sdp +=
+ "a=ssrc:" +
+ transceiver.sendEncodingParameters[0].rtx.ssrc +
+ " cname:" +
+ SDPUtils.localCName +
+ "\r\n";
+ }
+ return sdp;
+};
+
+// Gets the direction from the mediaSection or the sessionpart.
+SDPUtils.getDirection = function(mediaSection, sessionpart) {
+ // Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv.
+ var lines = SDPUtils.splitLines(mediaSection);
+ for (var i = 0; i < lines.length; i++) {
+ switch (lines[i]) {
+ case "a=sendrecv":
+ case "a=sendonly":
+ case "a=recvonly":
+ case "a=inactive":
+ return lines[i].substr(2);
+ default:
+ // FIXME: What should happen here?
+ }
+ }
+ if (sessionpart) {
+ return SDPUtils.getDirection(sessionpart);
+ }
+ return "sendrecv";
+};
+
+SDPUtils.getKind = function(mediaSection) {
+ var lines = SDPUtils.splitLines(mediaSection);
+ var mline = lines[0].split(" ");
+ return mline[0].substr(2);
+};
+
+SDPUtils.isRejected = function(mediaSection) {
+ return mediaSection.split(" ", 2)[1] === "0";
+};
+
+SDPUtils.parseMLine = function(mediaSection) {
+ var lines = SDPUtils.splitLines(mediaSection);
+ var parts = lines[0].substr(2).split(" ");
+ return {
+ kind: parts[0],
+ port: parseInt(parts[1], 10),
+ protocol: parts[2],
+ fmt: parts.slice(3).join(" "),
+ };
+};
+
+SDPUtils.parseOLine = function(mediaSection) {
+ var line = SDPUtils.matchPrefix(mediaSection, "o=")[0];
+ var parts = line.substr(2).split(" ");
+ return {
+ username: parts[0],
+ sessionId: parts[1],
+ sessionVersion: parseInt(parts[2], 10),
+ netType: parts[3],
+ addressType: parts[4],
+ address: parts[5],
+ };
+};
+
+// a very naive interpretation of a valid SDP.
+SDPUtils.isValidSDP = function(blob) {
+ if (typeof blob !== "string" || blob.length === 0) {
+ return false;
+ }
+ var lines = SDPUtils.splitLines(blob);
+ for (var i = 0; i < lines.length; i++) {
+ if (lines[i].length < 2 || lines[i].charAt(1) !== "=") {
+ return false;
+ }
+ // TODO: check the modifier a bit more.
+ }
+ return true;
+};
+
+// Expose public methods.
+if (typeof module === "object") {
+ module.exports = SDPUtils;
+}
diff --git a/dom/media/webrtc/tests/mochitests/iceTestUtils.js b/dom/media/webrtc/tests/mochitests/iceTestUtils.js
new file mode 100644
index 0000000000..d4d1f5c4b4
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/iceTestUtils.js
@@ -0,0 +1,302 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// This is mostly so test_peerConnection_gatherWithStun300.html and
+// test_peerConnection_gatherWithStun300IPv6 can share this code. I would have
+// put the ipv6 test code in the same file, but our ipv6 tester support is
+// inconsistent enough that we need to be able to track the ipv6 test
+// separately.
+
+async function findStatsRelayCandidates(pc, protocol) {
+ const stats = await pc.getStats();
+ return [...stats.values()].filter(
+ v =>
+ v.type == "local-candidate" &&
+ v.candidateType == "relay" &&
+ v.relayProtocol == protocol
+ );
+}
+
+// Trickles candidates if pcDst is set, and resolves the candidate list
+async function trickleIce(pc, pcDst) {
+ const candidates = [],
+ addCandidatePromises = [];
+ while (true) {
+ const { candidate } = await new Promise(r =>
+ pc.addEventListener("icecandidate", r, { once: true })
+ );
+ if (!candidate) {
+ break;
+ }
+ candidates.push(candidate);
+ if (pcDst) {
+ addCandidatePromises.push(pcDst.addIceCandidate(candidate));
+ }
+ }
+ await Promise.all(addCandidatePromises);
+ return candidates;
+}
+
+async function gather(pc) {
+ if (pc.signalingState == "stable") {
+ await pc.setLocalDescription(
+ await pc.createOffer({ offerToReceiveAudio: true })
+ );
+ } else if (pc.signalingState == "have-remote-offer") {
+ await pc.setLocalDescription();
+ }
+
+ return trickleIce(pc);
+}
+
+async function gatherWithTimeout(pc, timeout, context) {
+ const throwOnTimeout = async () => {
+ await wait(timeout);
+ throw new Error(
+ `Gathering did not complete within ${timeout} ms with ${context}`
+ );
+ };
+
+ return Promise.race([gather(pc), throwOnTimeout()]);
+}
+
+async function iceConnected(pc) {
+ return new Promise((resolve, reject) => {
+ pc.addEventListener("iceconnectionstatechange", () => {
+ if (["connected", "completed"].includes(pc.iceConnectionState)) {
+ resolve();
+ } else if (pc.iceConnectionState == "failed") {
+ reject(new Error(`ICE failed`));
+ }
+ });
+ });
+}
+
+// Set up trickle, but does not wait for it to complete. Can be used by itself
+// in cases where we do not expect any new candidates, but want to still set up
+// the signal handling in case new candidates _do_ show up.
+async function connectNoTrickleWait(offerer, answerer, timeout, context) {
+ return connect(offerer, answerer, timeout, context, true);
+}
+
+async function connect(
+ offerer,
+ answerer,
+ timeout,
+ context,
+ noTrickleWait = false
+) {
+ const trickle1 = trickleIce(offerer, answerer);
+ const trickle2 = trickleIce(answerer, offerer);
+ try {
+ const offer = await offerer.createOffer({ offerToReceiveAudio: true });
+ await offerer.setLocalDescription(offer);
+ await answerer.setRemoteDescription(offer);
+ const answer = await answerer.createAnswer();
+ await Promise.all([
+ offerer.setRemoteDescription(answer),
+ answerer.setLocalDescription(answer),
+ ]);
+
+ const throwOnTimeout = async () => {
+ if (timeout) {
+ await wait(timeout);
+ throw new Error(
+ `ICE did not complete within ${timeout} ms with ${context}`
+ );
+ }
+ };
+
+ await Promise.race([
+ Promise.all([iceConnected(offerer), iceConnected(answerer)]),
+ throwOnTimeout(timeout, context),
+ ]);
+ } finally {
+ if (!noTrickleWait) {
+ // TODO(bug 1751509): For now, we need to let gathering finish before we
+ // proceed, because there are races in ICE restart wrt gathering state.
+ await Promise.all([trickle1, trickle2]);
+ }
+ }
+}
+
+function isV6HostCandidate(candidate) {
+ const fields = candidate.candidate.split(" ");
+ const type = fields[7];
+ const ipAddress = fields[4];
+ return type == "host" && ipAddress.includes(":");
+}
+
+async function ipv6Supported() {
+ const pc = new RTCPeerConnection();
+ const candidates = await gatherWithTimeout(pc, 8000);
+ info(`baseline candidates: ${JSON.stringify(candidates)}`);
+ pc.close();
+ return candidates.some(isV6HostCandidate);
+}
+
+function makeContextString(iceServers) {
+ const currentRedirectAddress = SpecialPowers.getCharPref(
+ "media.peerconnection.nat_simulator.redirect_address",
+ ""
+ );
+ const currentRedirectTargets = SpecialPowers.getCharPref(
+ "media.peerconnection.nat_simulator.redirect_targets",
+ ""
+ );
+ return `redirect rule: ${currentRedirectAddress}=>${currentRedirectTargets} iceServers: ${JSON.stringify(
+ iceServers
+ )}`;
+}
+
+async function checkSrflx(iceServers) {
+ const context = makeContextString(iceServers);
+ info(`checkSrflx ${context}`);
+ const pc = new RTCPeerConnection({
+ iceServers,
+ bundlePolicy: "max-bundle", // Avoids extra candidates
+ });
+ const candidates = await gatherWithTimeout(pc, 8000, context);
+ const srflxCandidates = candidates.filter(c => c.candidate.includes("srflx"));
+ info(`candidates: ${JSON.stringify(srflxCandidates)}`);
+ // TODO(bug 1339203): Once we support rtcpMuxPolicy, set it to "require" to
+ // result in a single srflx candidate
+ is(
+ srflxCandidates.length,
+ 2,
+ `Should have two srflx candidates with ${context}`
+ );
+ pc.close();
+}
+
+async function checkNoSrflx(iceServers) {
+ const context = makeContextString(iceServers);
+ info(`checkNoSrflx ${context}`);
+ const pc = new RTCPeerConnection({
+ iceServers,
+ bundlePolicy: "max-bundle", // Avoids extra candidates
+ });
+ const candidates = await gatherWithTimeout(pc, 8000, context);
+ const srflxCandidates = candidates.filter(c => c.candidate.includes("srflx"));
+ info(`candidates: ${JSON.stringify(srflxCandidates)}`);
+ is(
+ srflxCandidates.length,
+ 0,
+ `Should have no srflx candidates with ${context}`
+ );
+ pc.close();
+}
+
+async function checkRelayUdp(iceServers) {
+ const context = makeContextString(iceServers);
+ info(`checkRelayUdp ${context}`);
+ const pc = new RTCPeerConnection({
+ iceServers,
+ bundlePolicy: "max-bundle", // Avoids extra candidates
+ });
+ const candidates = await gatherWithTimeout(pc, 8000, context);
+ const relayCandidates = candidates.filter(c => c.candidate.includes("relay"));
+ info(`candidates: ${JSON.stringify(relayCandidates)}`);
+ // TODO(bug 1339203): Once we support rtcpMuxPolicy, set it to "require" to
+ // result in a single relay candidate
+ is(
+ relayCandidates.length,
+ 2,
+ `Should have two relay candidates with ${context}`
+ );
+ // It would be nice if RTCIceCandidate had a field telling us what the
+ // "related protocol" is (similar to relatedAddress and relatedPort).
+ // Because there is no such thing, we need to go through the stats API,
+ // which _does_ have that information.
+ is(
+ (await findStatsRelayCandidates(pc, "tcp")).length,
+ 0,
+ `No TCP relay candidates should be present with ${context}`
+ );
+ pc.close();
+}
+
+async function checkRelayTcp(iceServers) {
+ const context = makeContextString(iceServers);
+ info(`checkRelayTcp ${context}`);
+ const pc = new RTCPeerConnection({
+ iceServers,
+ bundlePolicy: "max-bundle", // Avoids extra candidates
+ });
+ const candidates = await gatherWithTimeout(pc, 8000, context);
+ const relayCandidates = candidates.filter(c => c.candidate.includes("relay"));
+ info(`candidates: ${JSON.stringify(relayCandidates)}`);
+ // TODO(bug 1339203): Once we support rtcpMuxPolicy, set it to "require" to
+ // result in a single relay candidate
+ is(
+ relayCandidates.length,
+ 2,
+ `Should have two relay candidates with ${context}`
+ );
+ // It would be nice if RTCIceCandidate had a field telling us what the
+ // "related protocol" is (similar to relatedAddress and relatedPort).
+ // Because there is no such thing, we need to go through the stats API,
+ // which _does_ have that information.
+ is(
+ (await findStatsRelayCandidates(pc, "udp")).length,
+ 0,
+ `No UDP relay candidates should be present with ${context}`
+ );
+ pc.close();
+}
+
+async function checkRelayUdpTcp(iceServers) {
+ const context = makeContextString(iceServers);
+ info(`checkRelayUdpTcp ${context}`);
+ const pc = new RTCPeerConnection({
+ iceServers,
+ bundlePolicy: "max-bundle", // Avoids extra candidates
+ });
+ const candidates = await gatherWithTimeout(pc, 8000, context);
+ const relayCandidates = candidates.filter(c => c.candidate.includes("relay"));
+ info(`candidates: ${JSON.stringify(relayCandidates)}`);
+ // TODO(bug 1339203): Once we support rtcpMuxPolicy, set it to "require" to
+ // result in a single relay candidate each for UDP and TCP
+ is(
+ relayCandidates.length,
+ 4,
+ `Should have two relay candidates for each protocol with ${context}`
+ );
+ // It would be nice if RTCIceCandidate had a field telling us what the
+ // "related protocol" is (similar to relatedAddress and relatedPort).
+ // Because there is no such thing, we need to go through the stats API,
+ // which _does_ have that information.
+ is(
+ (await findStatsRelayCandidates(pc, "udp")).length,
+ 2,
+ `Two UDP relay candidates should be present with ${context}`
+ );
+ // TODO(bug 1705563): This is 1 because of bug 1705563
+ is(
+ (await findStatsRelayCandidates(pc, "tcp")).length,
+ 1,
+ `One TCP relay candidates should be present with ${context}`
+ );
+ pc.close();
+}
+
+async function checkNoRelay(iceServers) {
+ const context = makeContextString(iceServers);
+ info(`checkNoRelay ${context}`);
+ const pc = new RTCPeerConnection({
+ iceServers,
+ bundlePolicy: "max-bundle", // Avoids extra candidates
+ });
+ const candidates = await gatherWithTimeout(pc, 8000, context);
+ const relayCandidates = candidates.filter(c => c.candidate.includes("relay"));
+ info(`candidates: ${JSON.stringify(relayCandidates)}`);
+ is(
+ relayCandidates.length,
+ 0,
+ `Should have no relay candidates with ${context}`
+ );
+ pc.close();
+}
diff --git a/dom/media/webrtc/tests/mochitests/identity/identityPcTest.js b/dom/media/webrtc/tests/mochitests/identity/identityPcTest.js
new file mode 100644
index 0000000000..1381873f9d
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/identity/identityPcTest.js
@@ -0,0 +1,79 @@
+function identityPcTest(remoteOptions) {
+ var user = "someone";
+ var domain1 = "test1.example.com";
+ var domain2 = "test2.example.com";
+ var id1 = user + "@" + domain1;
+ var id2 = user + "@" + domain2;
+
+ test = new PeerConnectionTest({
+ config_local: {
+ peerIdentity: id2,
+ },
+ config_remote: {
+ peerIdentity: id1,
+ },
+ });
+ test.setMediaConstraints(
+ [
+ {
+ audio: true,
+ video: true,
+ peerIdentity: id2,
+ },
+ ],
+ [
+ remoteOptions || {
+ audio: true,
+ video: true,
+ peerIdentity: id1,
+ },
+ ]
+ );
+ test.pcLocal.setIdentityProvider("test1.example.com", { protocol: "idp.js" });
+ test.pcRemote.setIdentityProvider("test2.example.com", {
+ protocol: "idp.js",
+ });
+ test.chain.append([
+ function PEER_IDENTITY_IS_SET_CORRECTLY(test) {
+ // no need to wait to check identity in this case,
+ // setRemoteDescription should wait for the IdP to complete
+ function checkIdentity(pc, pfx, idp, name) {
+ return pc.peerIdentity.then(peerInfo => {
+ is(peerInfo.idp, idp, pfx + "IdP check");
+ is(peerInfo.name, name + "@" + idp, pfx + "identity check");
+ });
+ }
+
+ return Promise.all([
+ checkIdentity(
+ test.pcLocal._pc,
+ "local: ",
+ "test2.example.com",
+ "someone"
+ ),
+ checkIdentity(
+ test.pcRemote._pc,
+ "remote: ",
+ "test1.example.com",
+ "someone"
+ ),
+ ]);
+ },
+
+ function REMOTE_STREAMS_ARE_RESTRICTED(test) {
+ var remoteStream = test.pcLocal._pc.getRemoteStreams()[0];
+ for (const track of remoteStream.getTracks()) {
+ mustThrowWith(
+ `Freshly received ${track.kind} track with peerIdentity`,
+ "SecurityError",
+ () => new MediaRecorder(new MediaStream([track])).start()
+ );
+ }
+ return Promise.all([
+ audioIsSilence(true, remoteStream),
+ videoIsBlack(true, remoteStream),
+ ]);
+ },
+ ]);
+ return test.run();
+}
diff --git a/dom/media/webrtc/tests/mochitests/identity/idp-bad.js b/dom/media/webrtc/tests/mochitests/identity/idp-bad.js
new file mode 100644
index 0000000000..86e1cb7a34
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/identity/idp-bad.js
@@ -0,0 +1 @@
+<This isn't valid JS>
diff --git a/dom/media/webrtc/tests/mochitests/identity/idp-min.js b/dom/media/webrtc/tests/mochitests/identity/idp-min.js
new file mode 100644
index 0000000000..a4b2c55cee
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/identity/idp-min.js
@@ -0,0 +1,24 @@
+(function (global) {
+ "use strict";
+ // A minimal implementation of the interface.
+ // Though this isn't particularly functional.
+ // This is needed so that we can have a "working" IdP served
+ // from two different locations in the tree.
+ global.rtcIdentityProvider.register({
+ generateAssertion(payload, origin, usernameHint) {
+ dump("idp: generateAssertion(" + payload + ")\n");
+ return Promise.resolve({
+ idp: { domain: "example.com", protocol: "idp.js" },
+ assertion: "bogus",
+ });
+ },
+
+ validateAssertion(assertion, origin) {
+ dump("idp: validateAssertion(" + assertion + ")\n");
+ return Promise.resolve({
+ identity: "user@example.com",
+ contents: "bogus",
+ });
+ },
+ });
+})(this);
diff --git a/dom/media/webrtc/tests/mochitests/identity/idp-redirect-http-trick.js b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-http-trick.js
new file mode 100644
index 0000000000..75390cbf4f
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-http-trick.js
@@ -0,0 +1,3 @@
+(function () {
+ dump("ERROR\n");
+})();
diff --git a/dom/media/webrtc/tests/mochitests/identity/idp-redirect-http-trick.js^headers^ b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-http-trick.js^headers^
new file mode 100644
index 0000000000..b3a2afd90a
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-http-trick.js^headers^
@@ -0,0 +1,2 @@
+HTTP 301 Moved Permanently
+Location: http://example.com/.well-known/idp-proxy/idp-redirect-https.js
diff --git a/dom/media/webrtc/tests/mochitests/identity/idp-redirect-http.js b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-http.js
new file mode 100644
index 0000000000..75390cbf4f
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-http.js
@@ -0,0 +1,3 @@
+(function () {
+ dump("ERROR\n");
+})();
diff --git a/dom/media/webrtc/tests/mochitests/identity/idp-redirect-http.js^headers^ b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-http.js^headers^
new file mode 100644
index 0000000000..d2380984e7
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-http.js^headers^
@@ -0,0 +1,2 @@
+HTTP 301 Moved Permanently
+Location: http://example.com/.well-known/idp-proxy/idp.js
diff --git a/dom/media/webrtc/tests/mochitests/identity/idp-redirect-https-double.js b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-https-double.js
new file mode 100644
index 0000000000..75390cbf4f
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-https-double.js
@@ -0,0 +1,3 @@
+(function () {
+ dump("ERROR\n");
+})();
diff --git a/dom/media/webrtc/tests/mochitests/identity/idp-redirect-https-double.js^headers^ b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-https-double.js^headers^
new file mode 100644
index 0000000000..3fb8a35ae7
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-https-double.js^headers^
@@ -0,0 +1,2 @@
+HTTP 301 Moved Permanently
+Location: https://example.com/.well-known/idp-proxy/idp-redirect-https.js
diff --git a/dom/media/webrtc/tests/mochitests/identity/idp-redirect-https-odd-path.js b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-https-odd-path.js
new file mode 100644
index 0000000000..75390cbf4f
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-https-odd-path.js
@@ -0,0 +1,3 @@
+(function () {
+ dump("ERROR\n");
+})();
diff --git a/dom/media/webrtc/tests/mochitests/identity/idp-redirect-https-odd-path.js^headers^ b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-https-odd-path.js^headers^
new file mode 100644
index 0000000000..6e2931eda9
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-https-odd-path.js^headers^
@@ -0,0 +1,2 @@
+HTTP 301 Moved Permanently
+Location: https://example.com/.well-known/idp-min.js
diff --git a/dom/media/webrtc/tests/mochitests/identity/idp-redirect-https.js b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-https.js
new file mode 100644
index 0000000000..75390cbf4f
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-https.js
@@ -0,0 +1,3 @@
+(function () {
+ dump("ERROR\n");
+})();
diff --git a/dom/media/webrtc/tests/mochitests/identity/idp-redirect-https.js^headers^ b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-https.js^headers^
new file mode 100644
index 0000000000..77d56ac442
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/identity/idp-redirect-https.js^headers^
@@ -0,0 +1,2 @@
+HTTP 301 Moved Permanently
+Location: https://example.com/.well-known/idp-proxy/idp.js
diff --git a/dom/media/webrtc/tests/mochitests/identity/idp.js b/dom/media/webrtc/tests/mochitests/identity/idp.js
new file mode 100644
index 0000000000..557740657f
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/identity/idp.js
@@ -0,0 +1,119 @@
+(function (global) {
+ "use strict";
+
+ // rather than create a million different IdP configurations and litter the
+ // world with files all containing near-identical code, let's use the hash/URL
+ // fragment as a way of generating instructions for the IdP
+ var instructions = global.location.hash.replace("#", "").split(":");
+ function is(target) {
+ return function (instruction) {
+ return instruction === target;
+ };
+ }
+
+ function IDPJS() {
+ this.domain = global.location.host;
+ var path = global.location.pathname;
+ this.protocol =
+ path.substring(path.lastIndexOf("/") + 1) + global.location.hash;
+ this.id = crypto.getRandomValues(new Uint8Array(10)).join(".");
+ }
+
+ IDPJS.prototype = {
+ getLogin() {
+ return fetch(
+ "https://example.com/.well-known/idp-proxy/idp.sjs?" + this.id
+ ).then(response => response.status === 200);
+ },
+ checkLogin(result) {
+ return this.getLogin().then(loggedIn => {
+ if (loggedIn) {
+ return result;
+ }
+ return Promise.reject({
+ name: "IdpLoginError",
+ loginUrl:
+ "https://example.com/.well-known/idp-proxy/login.html#" + this.id,
+ });
+ });
+ },
+
+ borkResult(result) {
+ if (instructions.some(is("throw"))) {
+ throw new Error("Throwing!");
+ }
+ if (instructions.some(is("fail"))) {
+ return Promise.reject(new Error("Failing!"));
+ }
+ if (instructions.some(is("login"))) {
+ return this.checkLogin(result);
+ }
+ if (instructions.some(is("hang"))) {
+ return new Promise(r => {});
+ }
+ dump("idp: result=" + JSON.stringify(result) + "\n");
+ return Promise.resolve(result);
+ },
+
+ _selectUsername(usernameHint) {
+ dump("_selectUsername: usernameHint(" + usernameHint + ")\n");
+ var username = "someone@" + this.domain;
+ if (usernameHint) {
+ var at = usernameHint.indexOf("@");
+ if (at < 0) {
+ username = usernameHint + "@" + this.domain;
+ } else if (usernameHint.substring(at + 1) === this.domain) {
+ username = usernameHint;
+ }
+ }
+ return username;
+ },
+
+ generateAssertion(payload, origin, options) {
+ dump(
+ "idp: generateAssertion(" +
+ payload +
+ ", " +
+ origin +
+ ", " +
+ JSON.stringify(options) +
+ ")\n"
+ );
+ var idpDetails = {
+ domain: this.domain,
+ protocol: this.protocol,
+ };
+ if (instructions.some(is("bad-assert"))) {
+ idpDetails = {};
+ }
+ return this.borkResult({
+ idp: idpDetails,
+ assertion: JSON.stringify({
+ username: this._selectUsername(options.usernameHint),
+ contents: payload,
+ }),
+ });
+ },
+
+ validateAssertion(assertion, origin) {
+ dump("idp: validateAssertion(" + assertion + ")\n");
+ var assertion = JSON.parse(assertion);
+ if (instructions.some(is("bad-validate"))) {
+ assertion.contents = {};
+ }
+ return this.borkResult({
+ identity: assertion.username,
+ contents: assertion.contents,
+ });
+ },
+ };
+
+ if (!instructions.some(is("not_ready"))) {
+ dump("registering idp.js" + global.location.hash + "\n");
+ var idp = new IDPJS();
+ global.rtcIdentityProvider.register({
+ generateAssertion: idp.generateAssertion.bind(idp),
+ validateAssertion: idp.validateAssertion.bind(idp),
+ });
+ }
+})(this);
diff --git a/dom/media/webrtc/tests/mochitests/identity/idp.sjs b/dom/media/webrtc/tests/mochitests/identity/idp.sjs
new file mode 100644
index 0000000000..e1a245be78
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/identity/idp.sjs
@@ -0,0 +1,18 @@
+function handleRequest(request, response) {
+ var key = "/.well-known/idp-proxy/" + request.queryString;
+ dump(getState(key) + "\n");
+ if (request.method === "GET") {
+ if (getState(key)) {
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ } else {
+ response.setStatusLine(request.httpVersion, 404, "Not Found");
+ }
+ } else if (request.method === "PUT") {
+ setState(key, "OK");
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ } else {
+ response.setStatusLine(request.httpVersion, 406, "Method Not Allowed");
+ }
+ response.setHeader("Content-Type", "text/plain;charset=UTF-8");
+ response.write("OK");
+}
diff --git a/dom/media/webrtc/tests/mochitests/identity/login.html b/dom/media/webrtc/tests/mochitests/identity/login.html
new file mode 100644
index 0000000000..eafba22f2d
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/identity/login.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Identity Provider Login</title>
+ <script type="application/javascript">
+ window.onload = () => {
+ var xhr = new XMLHttpRequest();
+ xhr.open("PUT", "https://example.com/.well-known/idp-proxy/idp.sjs?" +
+ window.location.hash.replace('#', ''));
+ xhr.onload = () => {
+ var isFramed = (window !== window.top);
+ var parent = isFramed ? window.parent : window.opener;
+ // Using '*' is cheating, but that's OK.
+ parent.postMessage('LOGINDONE', '*');
+ var done = document.createElement('div');
+
+ done.textContent = 'Done';
+ document.body.appendChild(done);
+
+ if (!isFramed) {
+ window.close();
+ }
+ };
+ xhr.send();
+ };
+ </script>
+</head>
+<body>
+ <div>Logging in...</div>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/identity/mochitest.ini b/dom/media/webrtc/tests/mochitests/identity/mochitest.ini
new file mode 100644
index 0000000000..7a60cc6c6e
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/identity/mochitest.ini
@@ -0,0 +1,47 @@
+[DEFAULT]
+subsuite = media
+skip-if = (os == 'linux' && !debug)
+support-files =
+ /.well-known/idp-proxy/idp.js
+ identityPcTest.js
+ !/dom/media/webrtc/tests/mochitests/blacksilence.js
+ !/dom/media/webrtc/tests/mochitests/dataChannel.js
+ !/dom/media/webrtc/tests/mochitests/head.js
+ !/dom/media/webrtc/tests/mochitests/network.js
+ !/dom/media/webrtc/tests/mochitests/pc.js
+ !/dom/media/webrtc/tests/mochitests/sdpUtils.js
+ !/dom/media/webrtc/tests/mochitests/templates.js
+ !/dom/media/webrtc/tests/mochitests/turnConfig.js
+tags = mtg
+
+[test_fingerprints.html]
+scheme=https
+[test_getIdentityAssertion.html]
+[test_idpproxy.html]
+support-files =
+ /.well-known/idp-proxy/idp-redirect-http.js
+ /.well-known/idp-proxy/idp-redirect-http.js^headers^
+ /.well-known/idp-proxy/idp-redirect-http-trick.js
+ /.well-known/idp-proxy/idp-redirect-http-trick.js^headers^
+ /.well-known/idp-proxy/idp-redirect-https.js
+ /.well-known/idp-proxy/idp-redirect-https.js^headers^
+ /.well-known/idp-proxy/idp-redirect-https-double.js
+ /.well-known/idp-proxy/idp-redirect-https-double.js^headers^
+ /.well-known/idp-proxy/idp-redirect-https-odd-path.js
+ /.well-known/idp-proxy/idp-redirect-https-odd-path.js^headers^
+ /.well-known/idp-min.js
+ /.well-known/idp-proxy/idp-bad.js
+[test_loginNeeded.html]
+support-files =
+ /.well-known/idp-proxy/login.html
+ /.well-known/idp-proxy/idp.sjs
+[test_peerConnection_asymmetricIsolation.html]
+scheme=https
+skip-if = os == 'android'
+[test_peerConnection_peerIdentity.html]
+scheme=https
+skip-if = os == 'android'
+[test_setIdentityProvider.html]
+scheme=https
+[test_setIdentityProviderWithErrors.html]
+scheme=https
diff --git a/dom/media/webrtc/tests/mochitests/identity/test_fingerprints.html b/dom/media/webrtc/tests/mochitests/identity/test_fingerprints.html
new file mode 100644
index 0000000000..0a7f0a2033
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/identity/test_fingerprints.html
@@ -0,0 +1,91 @@
+<html>
+<head>
+<meta charset="utf-8" />
+<script type="application/javascript">var scriptRelativePath = "../";</script>
+<script type="application/javascript" src="../pc.js"></script>
+</head>
+<body>
+<script class="testbody" type="application/javascript">
+createHTML({ title: "Test multiple identity fingerprints", bug: "1005152" });
+
+// here we call the identity provider directly
+async function getIdentityAssertion(fingerprint) {
+ const { IdpSandbox } = SpecialPowers.ChromeUtils.import(
+ 'resource://gre/modules/media/IdpSandbox.jsm'
+ );
+ const sandbox = new IdpSandbox('example.com', 'idp.js', window);
+ const idp = SpecialPowers.wrap(await sandbox.start());
+ const assertion = SpecialPowers.wrap(await
+ idp.generateAssertion(JSON.stringify({ fingerprint }),
+ 'https://example.com',
+ {}));
+ const assertionString = btoa(JSON.stringify(assertion));
+ sandbox.stop();
+ return assertionString;
+}
+
+// This takes a real fingerprint and makes some extra bad ones.
+function makeFingerprints(algorithm, digest) {
+ const fingerprints = [];
+ fingerprints.push({ algorithm, digest });
+ for (var i = 0; i < 3; ++i) {
+ fingerprints.push({
+ algorithm,
+ digest: digest.replace(/:./g, ':' + i.toString(16))
+ });
+ }
+ return fingerprints;
+}
+
+const fingerprintRegex = /^a=fingerprint:(\S+) (\S+)/m;
+const identityRegex = /^a=identity:(\S+)/m;
+
+function fingerprintSdp(fingerprints) {
+ return fingerprints.map(fp => 'a=fInGeRpRiNt:' + fp.algorithm +
+ ' ' + fp.digest + '\n').join('');
+}
+
+// Firefox only uses a single fingerprint.
+// That doesn't mean we have it create SDP that describes two.
+// This function synthesizes that SDP and tries to set it.
+
+runNetworkTest(async () => {
+ // this one fails setRemoteDescription if the identity is not good
+ const pcStrict = new RTCPeerConnection({ peerIdentity: 'someone@example.com'});
+ // this one will be manually tweaked to have two fingerprints
+ const pcDouble = new RTCPeerConnection({});
+
+ const stream = await getUserMedia({ video: true });
+ ok(stream, 'Got test stream');
+ const [track] = stream.getTracks();
+ pcDouble.addTrack(track, stream);
+ try {
+ const offer = await pcDouble.createOffer();
+ ok(offer, 'Got offer');
+ const match = offer.sdp.match(fingerprintRegex);
+ if (!match) {
+ throw new Error('No fingerprint in offer SDP');
+ }
+ const fingerprints = makeFingerprints(match[1], match[2]);
+ const assertion = await getIdentityAssertion(fingerprints);
+ ok(assertion, 'Should have assertion');
+
+ const sdp = offer.sdp.slice(0, match.index) +
+ 'a=identity:' + assertion + '\n' +
+ fingerprintSdp(fingerprints.slice(1)) +
+ offer.sdp.slice(match.index);
+
+ await pcStrict.setRemoteDescription({ type: 'offer', sdp });
+ ok(true, 'Modified fingerprints were accepted');
+ } catch (error) {
+ const e = SpecialPowers.wrap(error);
+ ok(false, 'error in test: ' +
+ (e.message ? (e.message + '\n' + e.stack) : e));
+ }
+ pcStrict.close();
+ pcDouble.close();
+ track.stop();
+});
+</script>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/identity/test_getIdentityAssertion.html b/dom/media/webrtc/tests/mochitests/identity/test_getIdentityAssertion.html
new file mode 100644
index 0000000000..47e1cb1df6
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/identity/test_getIdentityAssertion.html
@@ -0,0 +1,101 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript">var scriptRelativePath = "../";</script>
+ <script type="application/javascript" src="../pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ title: "getIdentityAssertion Tests",
+ bug: "942367"
+ });
+
+function checkIdentity(assertion, identity) {
+ // here we dig into the payload, which means we need to know something
+ // about how the IdP actually works (not good in general, but OK here)
+ var assertion = JSON.parse(atob(assertion)).assertion;
+ var user = JSON.parse(assertion).username;
+ is(user, identity, 'id should be "' + identity + '" is "' + user + '"');
+}
+
+function getAssertion(t, instructions, userHint) {
+ dump('instructions: ' + instructions + '\n');
+ dump('userHint: ' + userHint + '\n');
+ t.pcLocal.setIdentityProvider('example.com',
+ { protocol: 'idp.js' + instructions,
+ usernameHint: userHint });
+ return t.pcLocal._pc.getIdentityAssertion();
+}
+
+var test;
+function theTest() {
+ test = new PeerConnectionTest();
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ test.chain.removeAfter('PC_REMOTE_CHECK_INITIAL_SIGNALINGSTATE');
+ test.chain.append([
+ function PC_LOCAL_IDENTITY_ASSERTION_FAILS_WITHOUT_PROVIDER(t) {
+ return t.pcLocal._pc.getIdentityAssertion()
+ .then(a => ok(false, 'should fail without provider'),
+ e => ok(e, 'should fail without provider'));
+ },
+
+ function PC_LOCAL_IDENTITY_ASSERTION_FAILS_WITH_BAD_PROVIDER(t) {
+ t.pcLocal._pc.setIdentityProvider('example.com',
+ { protocol: 'idp-bad.js',
+ usernameHint: '' });
+ return t.pcLocal._pc.getIdentityAssertion()
+ .then(a => ok(false, 'should fail with bad provider'),
+ e => {
+ is(e.name, 'IdpError', 'should fail with bad provider');
+ ok(e.message, 'should include a nice message');
+ });
+ },
+
+ function PC_LOCAL_GET_TWO_ASSERTIONS(t) {
+ return Promise.all([
+ getAssertion(t, ''),
+ getAssertion(t, '')
+ ]).then(assertions => {
+ is(assertions.length, 2, "Two assertions generated");
+ assertions.forEach(a => checkIdentity(a, 'someone@example.com'));
+ });
+ },
+
+ function PC_LOCAL_IDP_FAILS(t) {
+ return getAssertion(t, '#fail')
+ .then(a => ok(false, '#fail should not get an identity result'),
+ e => is(e.name, 'IdpError', '#fail should cause rejection'));
+ },
+
+ function PC_LOCAL_IDP_LOGIN_ERROR(t) {
+ return getAssertion(t, '#login')
+ .then(a => ok(false, '#login should not work'),
+ e => {
+ is(e.name, 'IdpLoginError', 'name is IdpLoginError');
+ is(t.pcLocal._pc.idpLoginUrl.split('#')[0],
+ 'https://example.com/.well-known/idp-proxy/login.html',
+ 'got the right login URL from the IdP');
+ });
+ },
+
+ function PC_LOCAL_IDP_NOT_READY(t) {
+ return getAssertion(t, '#not_ready')
+ .then(a => ok(false, '#not_ready should not get an identity result'),
+ e => is(e.name, 'IdpError', '#not_ready should cause rejection'));
+ },
+
+ function PC_LOCAL_ASSERTION_WITH_SPECIFIC_NAME(t) {
+ return getAssertion(t, '', 'user@example.com')
+ .then(a => checkIdentity(a, 'user@example.com'));
+ }
+ ]);
+ return test.run();
+}
+runNetworkTest(theTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/identity/test_idpproxy.html b/dom/media/webrtc/tests/mochitests/identity/test_idpproxy.html
new file mode 100644
index 0000000000..065501b8a4
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/identity/test_idpproxy.html
@@ -0,0 +1,178 @@
+<html>
+<head>
+<meta charset="utf-8" />
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+ <script class="testbody" type="application/javascript">
+"use strict";
+var { IdpSandbox } = SpecialPowers.ChromeUtils.import(
+ "resource://gre/modules/media/IdpSandbox.jsm"
+);
+var dummyPayload = JSON.stringify({
+ this: 'is',
+ a: ['stu', 6],
+ obj: null
+});
+
+function test_domain_sandbox() {
+ var diabolical = {
+ toString() {
+ return 'example.com/path';
+ }
+ };
+ var domains = [ 'ex/foo', 'user@ex', 'user:pass@ex', 'ex#foo', 'ex?foo',
+ '', 12, null, diabolical, true ];
+ domains.forEach(function(domain) {
+ try {
+ var idp = new IdpSandbox(domain, undefined, window);
+ ok(false, 'IdpSandbox allowed a bad domain: ' + domain);
+ } catch (e) {
+ var str = (typeof domain === 'string') ? domain : typeof domain;
+ ok(true, 'Evil domain "' + str + '" raises exception');
+ }
+ });
+}
+
+function test_protocol_sandbox() {
+ var protos = [ '../evil/proto', '..%2Fevil%2Fproto',
+ '\\evil', '%5cevil', 12, true, {} ];
+ protos.forEach(function(proto) {
+ try {
+ var idp = new IdpSandbox('example.com', proto, window);
+ ok(false, 'IdpSandbox allowed a bad protocol: ' + proto);
+ } catch (e) {
+ var str = (typeof proto === 'string') ? proto : typeof proto;
+ ok(true, 'Evil protocol "' + proto + '" raises exception');
+ }
+ });
+}
+
+function idpName(hash) {
+ return 'idp.js' + (hash ? ('#' + hash) : '');
+}
+
+function makeSandbox(js) {
+ var name = js || idpName();
+ info('Creating a sandbox for the protocol: ' + name);
+ var sandbox = new IdpSandbox('example.com', name, window);
+ return sandbox.start().then(idp => SpecialPowers.wrap(idp));
+}
+
+function test_generate_assertion() {
+ return makeSandbox()
+ .then(idp => idp.generateAssertion(dummyPayload,
+ 'https://example.net',
+ {}))
+ .then(response => {
+ response = SpecialPowers.wrap(response);
+ is(response.idp.domain, 'example.com', 'domain is correct');
+ is(response.idp.protocol, 'idp.js', 'protocol is correct');
+ ok(typeof response.assertion === 'string', 'assertion is present');
+ });
+}
+
+// test that the test IdP can eat its own dogfood; which is the only way to test
+// validateAssertion, since that consumes the output of generateAssertion (in
+// theory, generateAssertion could identify a different IdP domain).
+
+function test_validate_assertion() {
+ return makeSandbox()
+ .then(idp => idp.generateAssertion(dummyPayload,
+ 'https://example.net',
+ { usernameHint: 'user' }))
+ .then(assertion => {
+ var wrapped = SpecialPowers.wrap(assertion);
+ return makeSandbox()
+ .then(idp => idp.validateAssertion(wrapped.assertion,
+ 'https://example.net'));
+ }).then(response => {
+ response = SpecialPowers.wrap(response);
+ is(response.identity, 'user@example.com');
+ is(response.contents, dummyPayload);
+ });
+}
+
+// We don't want to test the #bad or the #hang instructions,
+// errors of the sort those generate aren't handled by the sandbox code.
+function test_assertion_failure(reason) {
+ return () => {
+ return makeSandbox(idpName(reason))
+ .then(idp => idp.generateAssertion('hello', 'example.net', {}))
+ .then(r => ok(false, 'should not succeed on ' + reason),
+ e => ok(true, 'failed correctly on ' + reason));
+ };
+}
+
+function test_load_failure() {
+ return makeSandbox('non-existent-file')
+ .then(() => ok(false, 'Should fail to load non-existent file'),
+ e => ok(e, 'Should fail to load non-existent file'));
+}
+
+function test_redirect_ok(from) {
+ return () => {
+ return makeSandbox(from)
+ .then(idp => idp.generateAssertion('hello', 'example.net'))
+ .then(r => ok(SpecialPowers.wrap(r).assertion,
+ 'Redirect to https should be OK'));
+ };
+}
+
+function test_redirect_fail(from) {
+ return () => {
+ return makeSandbox(from)
+ .then(() => ok(false, 'Redirect to https should fail'),
+ e => ok(e, 'Redirect to https should fail'));
+ };
+}
+
+function test_bad_js() {
+ return makeSandbox('idp-bad.js')
+ .then(() => ok(false, 'Bad JS should not load'),
+ e => ok(e, 'Bad JS should not load'));
+}
+
+function run_all_tests() {
+ [
+ test_domain_sandbox,
+ test_protocol_sandbox,
+ test_generate_assertion,
+ test_validate_assertion,
+
+ // fail of the IdP fails
+ test_assertion_failure('fail'),
+ // fail if the IdP throws
+ test_assertion_failure('throw'),
+ // fail if the IdP is not ready
+ test_assertion_failure('not_ready'),
+
+ test_load_failure(),
+ // Test a redirect to an HTTPS origin, which should be OK
+ test_redirect_ok('idp-redirect-https.js'),
+ // Two redirects is fine too
+ test_redirect_ok('idp-redirect-https-double.js'),
+ // A secure redirect to a path other than /.well-known/idp-proxy/* should
+ // also work fine.
+ test_redirect_ok('idp-redirect-https-odd-path.js'),
+ // A redirect to HTTP is not-cool
+ test_redirect_fail('idp-redirect-http.js'),
+ // Also catch tricks like https->http->https
+ test_redirect_fail('idp-redirect-http-trick.js'),
+
+ test_bad_js
+ ].reduce((p, test) => {
+ return p.then(test)
+ .catch(e => ok(false, test.name + ' failed: ' +
+ SpecialPowers.wrap(e).message + '\n' +
+ SpecialPowers.wrap(e).stack));
+ }, Promise.resolve())
+ .then(() => SimpleTest.finish());
+}
+
+SimpleTest.waitForExplicitFinish();
+run_all_tests();
+</script>
+ </body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/identity/test_loginNeeded.html b/dom/media/webrtc/tests/mochitests/identity/test_loginNeeded.html
new file mode 100644
index 0000000000..550dc20d92
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/identity/test_loginNeeded.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript">var scriptRelativePath = "../";</script>
+ <script type="application/javascript" src="../pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ title: 'RTCPeerConnection identity with login',
+ bug: '1153314'
+ });
+
+function waitForLoginDone() {
+ return new Promise(resolve => {
+ window.addEventListener('message', function listener(e) {
+ is(e.origin, 'https://example.com', 'got the right message origin');
+ is(e.data, 'LOGINDONE', 'got the right message');
+ window.removeEventListener('message', listener);
+ resolve();
+ });
+ });
+}
+
+function checkLogin(t, name, onLoginNeeded) {
+ t.pcLocal.setIdentityProvider('example.com',
+ { protocol: 'idp.js#login:' + name });
+ return t.pcLocal._pc.getIdentityAssertion()
+ .then(a => ok(false, 'should request login'),
+ e => {
+ is(e.name, 'IdpLoginError', 'name is IdpLoginError');
+ is(t.pcLocal._pc.idpLoginUrl.split('#')[0],
+ 'https://example.com/.well-known/idp-proxy/login.html',
+ 'got the right login URL from the IdP');
+ return t.pcLocal._pc.idpLoginUrl;
+ })
+ .then(onLoginNeeded)
+ .then(waitForLoginDone)
+ .then(() => t.pcLocal._pc.getIdentityAssertion())
+ .then(a => ok(a, 'got assertion'));
+}
+
+function theTest() {
+ var test = new PeerConnectionTest();
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ test.chain.removeAfter('PC_REMOTE_CHECK_INITIAL_SIGNALINGSTATE');
+ test.chain.append([
+ function PC_LOCAL_IDENTITY_ASSERTION_WITH_IFRAME_LOGIN(t) {
+ return checkLogin(t, 'iframe', loginUrl => {
+ var iframe = document.createElement('iframe');
+ iframe.setAttribute('src', loginUrl);
+ iframe.frameBorder = 0;
+ iframe.width = 400;
+ iframe.height = 60;
+ document.getElementById('display').appendChild(iframe);
+ });
+ },
+ function PC_LOCAL_IDENTITY_ASSERTION_WITH_WINDOW_LOGIN(t) {
+ return checkLogin(t, 'openwin', loginUrl => {
+ window.open(loginUrl, 'login', 'width=400,height=60');
+ });
+ }
+ ]);
+ return test.run();
+}
+runNetworkTest(theTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/identity/test_peerConnection_asymmetricIsolation.html b/dom/media/webrtc/tests/mochitests/identity/test_peerConnection_asymmetricIsolation.html
new file mode 100644
index 0000000000..65a2fc5392
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/identity/test_peerConnection_asymmetricIsolation.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript">var scriptRelativePath = "../";</script>
+ <script type="application/javascript" src="../pc.js"></script>
+ <script type="application/javascript" src="../blacksilence.js"></script>
+ <script type="application/javascript" src="identityPcTest.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ title: "Non-isolated media entering an isolated session becomes isolated",
+ bug: "996238"
+});
+
+function theTest() {
+ // Override the remote media capture options to remove isolation for the
+ // remote party; the test verifies that the media it receives on the local
+ // side is isolated anyway.
+ return identityPcTest({
+ audio: true,
+ video: true
+ });
+}
+runNetworkTest(theTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/identity/test_peerConnection_peerIdentity.html b/dom/media/webrtc/tests/mochitests/identity/test_peerConnection_peerIdentity.html
new file mode 100644
index 0000000000..a8116cc451
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/identity/test_peerConnection_peerIdentity.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript">var scriptRelativePath = "../";</script>
+ <script type="application/javascript" src="../pc.js"></script>
+ <script type="application/javascript" src="../blacksilence.js"></script>
+ <script type="application/javascript" src="identityPcTest.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ title: "setIdentityProvider leads to peerIdentity and assertions in SDP",
+ bug: "942367"
+});
+
+runNetworkTest(identityPcTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/identity/test_setIdentityProvider.html b/dom/media/webrtc/tests/mochitests/identity/test_setIdentityProvider.html
new file mode 100644
index 0000000000..ac7cba6a5e
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/identity/test_setIdentityProvider.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript">var scriptRelativePath = "../";</script>
+ <script type="application/javascript" src="../pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ title: "setIdentityProvider leads to peerIdentity and assertions in SDP",
+ bug: "942367"
+ });
+
+function checkIdentity(peer, prefix, idp, name) {
+ prefix = prefix + ": ";
+ return peer._pc.peerIdentity.then(peerIdentity => {
+ ok(peerIdentity, prefix + "peerIdentity is set");
+ is(peerIdentity.idp, idp, prefix + "IdP is correct");
+ is(peerIdentity.name, name + "@" + idp, prefix + "identity is correct");
+ });
+}
+
+function theTest() {
+ var test = new PeerConnectionTest();
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ test.pcLocal.setIdentityProvider("test1.example.com",
+ { protocol: "idp.js",
+ usernameHint: "someone" });
+ test.pcRemote.setIdentityProvider("test2.example.com",
+ { protocol: "idp.js",
+ usernameHinte: "someone"});
+
+ test.chain.append([
+ function PC_LOCAL_PEER_IDENTITY_IS_SET_CORRECTLY(test) {
+ return checkIdentity(test.pcLocal, "local", "test2.example.com", "someone");
+ },
+ function PC_REMOTE_PEER_IDENTITY_IS_SET_CORRECTLY(test) {
+ return checkIdentity(test.pcRemote, "remote", "test1.example.com", "someone");
+ },
+
+ function OFFER_AND_ANSWER_INCLUDES_IDENTITY(test) {
+ ok(test.originalOffer.sdp.includes("a=identity"), "a=identity is in the offer SDP");
+ ok(test.originalAnswer.sdp.includes("a=identity"), "a=identity is in the answer SDP");
+ },
+
+ function PC_LOCAL_DESCRIPTIONS_CONTAIN_IDENTITY(test) {
+ ok(test.pcLocal.localDescription.sdp.includes("a=identity"),
+ "a=identity is in the local copy of the offer");
+ ok(test.pcLocal.remoteDescription.sdp.includes("a=identity"),
+ "a=identity is in the local copy of the answer");
+ },
+ function PC_REMOTE_DESCRIPTIONS_CONTAIN_IDENTITY(test) {
+ ok(test.pcRemote.localDescription.sdp.includes("a=identity"),
+ "a=identity is in the remote copy of the offer");
+ ok(test.pcRemote.remoteDescription.sdp.includes("a=identity"),
+ "a=identity is in the remote copy of the answer");
+ }
+ ]);
+ return test.run();
+}
+runNetworkTest(theTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/identity/test_setIdentityProviderWithErrors.html b/dom/media/webrtc/tests/mochitests/identity/test_setIdentityProviderWithErrors.html
new file mode 100644
index 0000000000..ce6832d1e6
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/identity/test_setIdentityProviderWithErrors.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript">var scriptRelativePath = "../";</script>
+ <script type="application/javascript" src="../pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+'use strict';
+ createHTML({
+ title: "Identity Provider returning errors is handled correctly",
+ bug: "942367"
+ });
+
+runNetworkTest(function () {
+ var test = new PeerConnectionTest();
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ // No IdP for local.
+ // Remote generates a bad assertion, but that only fails to validate
+ test.pcRemote.setIdentityProvider('example.com',
+ { protocol: 'idp.js#bad-validate',
+ usernameHint: 'nobody' });
+
+ // Save the peerIdentity promises now, since when they reject they are
+ // replaced and we expect them to be rejected this time
+ var peerIdentityLocal = test.pcLocal._pc.peerIdentity;
+ var peerIdentityRemote = test.pcRemote._pc.peerIdentity;
+
+ test.chain.append([
+ function ONLY_REMOTE_SDP_INCLUDES_IDENTITY_ASSERTION(t) {
+ ok(!t.originalOffer.sdp.includes('a=identity'),
+ 'a=identity not contained in the offer SDP');
+ ok(t.originalAnswer.sdp.includes('a=identity'),
+ 'a=identity is contained in the answer SDP');
+ },
+ function PEER_IDENTITY_IS_EMPTY(t) {
+ // we are only waiting for the local side to complete
+ // an error on the remote side is immediately fatal though
+ return Promise.race([
+ peerIdentityLocal.then(
+ () => ok(false, t.pcLocal + ' incorrectly received valid peer identity'),
+ e => ok(e, t.pcLocal + ' correctly failed to validate peer identity')),
+ peerIdentityRemote.then(
+ () => ok(false, t.pcRemote + ' incorrecly received a valid peer identity'),
+ e => ok(false, t.pcRemote + ' incorrectly rejected peer identity'))
+ ]);
+ }
+ ]);
+
+ return test.run();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/mediaStreamPlayback.js b/dom/media/webrtc/tests/mochitests/mediaStreamPlayback.js
new file mode 100644
index 0000000000..44c1c78ea0
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/mediaStreamPlayback.js
@@ -0,0 +1,241 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const ENDED_TIMEOUT_LENGTH = 30000;
+
+/* The time we wait depends primarily on the canplaythrough event firing
+ * Note: this needs to be at least 30s because the
+ * B2G emulator in VMs is really slow. */
+const VERIFYPLAYING_TIMEOUT_LENGTH = 60000;
+
+/**
+ * This class manages playback of a HTMLMediaElement with a MediaStream.
+ * When constructed by a caller, an object instance is created with
+ * a media element and a media stream object.
+ *
+ * @param {HTMLMediaElement} mediaElement the media element for playback
+ * @param {MediaStream} mediaStream the media stream used in
+ * the mediaElement for playback
+ */
+function MediaStreamPlayback(mediaElement, mediaStream) {
+ this.mediaElement = mediaElement;
+ this.mediaStream = mediaStream;
+}
+
+MediaStreamPlayback.prototype = {
+ /**
+ * Starts media element with a media stream, runs it until a canplaythrough
+ * and timeupdate event fires, and calls stop() on all its tracks.
+ *
+ * @param {Boolean} isResume specifies if this media element is being resumed
+ * from a previous run
+ */
+ playMedia(isResume) {
+ this.startMedia(isResume);
+ return this.verifyPlaying()
+ .then(() => this.stopTracksForStreamInMediaPlayback())
+ .then(() => this.detachFromMediaElement());
+ },
+
+ /**
+ * Stops the local media stream's tracks while it's currently in playback in
+ * a media element.
+ *
+ * Precondition: The media stream and element should both be actively
+ * being played. All the stream's tracks must be local.
+ */
+ stopTracksForStreamInMediaPlayback() {
+ var elem = this.mediaElement;
+ return Promise.all([
+ haveEvent(
+ elem,
+ "ended",
+ wait(ENDED_TIMEOUT_LENGTH, new Error("Timeout"))
+ ),
+ ...this.mediaStream
+ .getTracks()
+ .map(t => (t.stop(), haveNoEvent(t, "ended"))),
+ ]);
+ },
+
+ /**
+ * Starts media with a media stream, runs it until a canplaythrough and
+ * timeupdate event fires, and detaches from the element without stopping media.
+ *
+ * @param {Boolean} isResume specifies if this media element is being resumed
+ * from a previous run
+ */
+ playMediaWithoutStoppingTracks(isResume) {
+ this.startMedia(isResume);
+ return this.verifyPlaying().then(() => this.detachFromMediaElement());
+ },
+
+ /**
+ * Starts the media with the associated stream.
+ *
+ * @param {Boolean} isResume specifies if the media element playback
+ * is being resumed from a previous run
+ */
+ startMedia(isResume) {
+ // If we're playing media element for the first time, check that time is zero.
+ if (!isResume) {
+ is(
+ this.mediaElement.currentTime,
+ 0,
+ "Before starting the media element, currentTime = 0"
+ );
+ }
+ this.canPlayThroughFired = listenUntil(
+ this.mediaElement,
+ "canplaythrough",
+ () => true
+ );
+
+ // Hooks up the media stream to the media element and starts playing it
+ this.mediaElement.srcObject = this.mediaStream;
+ this.mediaElement.play();
+ },
+
+ /**
+ * Verifies that media is playing.
+ */
+ verifyPlaying() {
+ var lastElementTime = this.mediaElement.currentTime;
+
+ var mediaTimeProgressed = listenUntil(
+ this.mediaElement,
+ "timeupdate",
+ () => this.mediaElement.currentTime > lastElementTime
+ );
+
+ return timeout(
+ Promise.all([this.canPlayThroughFired, mediaTimeProgressed]),
+ VERIFYPLAYING_TIMEOUT_LENGTH,
+ "verifyPlaying timed out"
+ ).then(() => {
+ is(this.mediaElement.paused, false, "Media element should be playing");
+ is(
+ this.mediaElement.duration,
+ Number.POSITIVE_INFINITY,
+ "Duration should be infinity"
+ );
+
+ // When the media element is playing with a real-time stream, we
+ // constantly switch between having data to play vs. queuing up data,
+ // so we can only check that the ready state is one of those two values
+ ok(
+ this.mediaElement.readyState === HTMLMediaElement.HAVE_ENOUGH_DATA ||
+ this.mediaElement.readyState === HTMLMediaElement.HAVE_CURRENT_DATA,
+ "Ready state shall be HAVE_ENOUGH_DATA or HAVE_CURRENT_DATA"
+ );
+
+ is(this.mediaElement.seekable.length, 0, "Seekable length shall be zero");
+ is(this.mediaElement.buffered.length, 0, "Buffered length shall be zero");
+
+ is(
+ this.mediaElement.seeking,
+ false,
+ "MediaElement is not seekable with MediaStream"
+ );
+ ok(
+ isNaN(this.mediaElement.startOffsetTime),
+ "Start offset time shall not be a number"
+ );
+ is(
+ this.mediaElement.defaultPlaybackRate,
+ 1,
+ "DefaultPlaybackRate should be 1"
+ );
+ is(this.mediaElement.playbackRate, 1, "PlaybackRate should be 1");
+ is(this.mediaElement.preload, "none", 'Preload should be "none"');
+ is(this.mediaElement.src, "", "No src should be defined");
+ is(
+ this.mediaElement.currentSrc,
+ "",
+ "Current src should still be an empty string"
+ );
+ });
+ },
+
+ /**
+ * Detaches from the element without stopping the media.
+ *
+ * Precondition: The media stream and element should both be actively
+ * being played.
+ */
+ detachFromMediaElement() {
+ this.mediaElement.pause();
+ this.mediaElement.srcObject = null;
+ },
+};
+
+// haxx to prevent SimpleTest from failing at window.onload
+function addLoadEvent() {}
+
+/* import-globals-from /testing/mochitest/tests/SimpleTest/SimpleTest.js */
+/* import-globals-from head.js */
+const scriptsReady = Promise.all(
+ ["/tests/SimpleTest/SimpleTest.js", "head.js"].map(script => {
+ const el = document.createElement("script");
+ el.src = script;
+ document.head.appendChild(el);
+ return new Promise(r => (el.onload = r));
+ })
+);
+
+function createHTML(options) {
+ return scriptsReady.then(() => realCreateHTML(options));
+}
+
+async function runTest(testFunction) {
+ await Promise.all([
+ scriptsReady,
+ SpecialPowers.pushPrefEnv({
+ set: [["media.navigator.permission.fake", true]],
+ }),
+ ]);
+ await runTestWhenReady(async (...args) => {
+ await testFunction(...args);
+ await noGum();
+ });
+}
+
+// noGum - Helper to detect whether active guM tracks still exist.
+//
+// Note it relies on the permissions system to detect active tracks, so it won't
+// catch getUserMedia use while media.navigator.permission.disabled is true
+// (which is common in automation), UNLESS we set
+// media.navigator.permission.fake to true also, like runTest() does above.
+async function noGum() {
+ if (!navigator.mediaDevices) {
+ // No mediaDevices, then gUM cannot have been called either.
+ return;
+ }
+ const mediaManagerService = Cc[
+ "@mozilla.org/mediaManagerService;1"
+ ].getService(Ci.nsIMediaManagerService);
+
+ const hasCamera = {};
+ const hasMicrophone = {};
+ mediaManagerService.mediaCaptureWindowState(
+ window,
+ hasCamera,
+ hasMicrophone,
+ {},
+ {},
+ {},
+ {},
+ false
+ );
+ is(
+ hasCamera.value,
+ mediaManagerService.STATE_NOCAPTURE,
+ "Test must leave no active camera gUM tracks behind."
+ );
+ is(
+ hasMicrophone.value,
+ mediaManagerService.STATE_NOCAPTURE,
+ "Test must leave no active microphone gUM tracks behind."
+ );
+}
diff --git a/dom/media/webrtc/tests/mochitests/mochitest.ini b/dom/media/webrtc/tests/mochitests/mochitest.ini
new file mode 100644
index 0000000000..d1a9800984
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/mochitest.ini
@@ -0,0 +1,65 @@
+[DEFAULT]
+tags = mtg webrtc
+subsuite = media
+scheme = https
+support-files =
+ head.js
+ dataChannel.js
+ mediaStreamPlayback.js
+ network.js
+ nonTrickleIce.js
+ pc.js
+ stats.js
+ templates.js
+ test_enumerateDevices_iframe.html
+ test_enumerateDevices_iframe_pre_gum.html
+ test_getUserMedia_permission_iframe.html
+ NetworkPreparationChromeScript.js
+ blacksilence.js
+ turnConfig.js
+ sdpUtils.js
+ addTurnsSelfsignedCert.js
+ parser_rtp.js
+ peerconnection_audio_forced_sample_rate.js
+ iceTestUtils.js
+ simulcast.js
+ helpers_from_wpt/sdp.js
+ !/dom/canvas/test/captureStream_common.js
+ !/dom/canvas/test/webgl-mochitest/webgl-util.js
+ !/dom/media/test/manifest.js
+ !/dom/media/test/seek.webm
+ !/dom/media/test/gizmo.mp4
+ !/docshell/test/navigation/blank.html
+prefs =
+ focusmanager.testmode=true # emulate focus
+ privacy.partition.network_state=false
+ network.proxy.allow_hijacking_localhost=true
+ media.devices.enumerate.legacy.enabled=false
+
+[test_1488832.html]
+skip-if =
+ os == 'linux' # Bug 1714410
+[test_1717318.html]
+[test_a_noOp.html]
+scheme=http
+[test_enumerateDevices.html]
+[test_enumerateDevices_getUserMediaFake.html]
+[test_enumerateDevices_legacy.html]
+[test_enumerateDevices_navigation.html]
+skip-if = true # Disabled because it is a racy test and causes timeouts, see bug 1650932
+[test_fingerprinting_resistance.html]
+skip-if =
+ os == "linux" && asan # Bug 1646309 - low frequency intermittent
+[test_forceSampleRate.html]
+scheme=http
+[test_groupId.html]
+[test_multi_mics.html]
+skip-if = os == 'android'
+[test_ondevicechange.html]
+run-sequentially = sets prefs that may disrupt other tests
+[test_setSinkId.html]
+skip-if =
+ os != 'linux' # the only platform with real devices
+[test_setSinkId_default_addTrack.html]
+[test_setSinkId_preMutedElement.html]
+[test_unfocused_pref.html]
diff --git a/dom/media/webrtc/tests/mochitests/mochitest_datachannel.ini b/dom/media/webrtc/tests/mochitests/mochitest_datachannel.ini
new file mode 100644
index 0000000000..881421af0e
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/mochitest_datachannel.ini
@@ -0,0 +1,52 @@
+[DEFAULT]
+tags = mtg webrtc
+subsuite = media
+scheme = https
+support-files =
+ head.js
+ dataChannel.js
+ mediaStreamPlayback.js
+ network.js
+ nonTrickleIce.js
+ pc.js
+ stats.js
+ templates.js
+ test_enumerateDevices_iframe.html
+ test_getUserMedia_permission_iframe.html
+ NetworkPreparationChromeScript.js
+ blacksilence.js
+ turnConfig.js
+ sdpUtils.js
+ addTurnsSelfsignedCert.js
+ parser_rtp.js
+ peerconnection_audio_forced_sample_rate.js
+ iceTestUtils.js
+ simulcast.js
+ helpers_from_wpt/sdp.js
+ !/dom/canvas/test/captureStream_common.js
+ !/dom/canvas/test/webgl-mochitest/webgl-util.js
+ !/dom/media/test/manifest.js
+ !/dom/media/test/seek.webm
+ !/dom/media/test/gizmo.mp4
+ !/docshell/test/navigation/blank.html
+prefs =
+ focusmanager.testmode=true # emulate focus
+ privacy.partition.network_state=false
+ network.proxy.allow_hijacking_localhost=true
+
+[test_dataChannel_basicAudio.html]
+[test_dataChannel_basicAudioVideo.html]
+[test_dataChannel_basicAudioVideoCombined.html]
+[test_dataChannel_basicAudioVideoNoBundle.html]
+[test_dataChannel_basicDataOnly.html]
+[test_dataChannel_basicVideo.html]
+[test_dataChannel_bug1013809.html]
+[test_dataChannel_dataOnlyBufferedAmountLow.html]
+scheme=http
+[test_dataChannel_dtlsVersions.html]
+[test_dataChannel_hostnameObfuscation.html]
+scheme=http
+[test_dataChannel_noOffer.html]
+scheme=http
+[test_dataChannel_stats.html]
+scheme=http
diff --git a/dom/media/webrtc/tests/mochitests/mochitest_getusermedia.ini b/dom/media/webrtc/tests/mochitests/mochitest_getusermedia.ini
new file mode 100644
index 0000000000..02c8272b5b
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/mochitest_getusermedia.ini
@@ -0,0 +1,105 @@
+[DEFAULT]
+tags = mtg webrtc
+subsuite = media
+scheme = https
+support-files =
+ head.js
+ dataChannel.js
+ mediaStreamPlayback.js
+ network.js
+ nonTrickleIce.js
+ pc.js
+ stats.js
+ templates.js
+ test_enumerateDevices_iframe.html
+ test_getUserMedia_permission_iframe.html
+ NetworkPreparationChromeScript.js
+ blacksilence.js
+ turnConfig.js
+ sdpUtils.js
+ addTurnsSelfsignedCert.js
+ parser_rtp.js
+ peerconnection_audio_forced_sample_rate.js
+ iceTestUtils.js
+ simulcast.js
+ helpers_from_wpt/sdp.js
+ !/dom/canvas/test/captureStream_common.js
+ !/dom/canvas/test/webgl-mochitest/webgl-util.js
+ !/dom/media/test/manifest.js
+ !/dom/media/test/seek.webm
+ !/dom/media/test/gizmo.mp4
+ !/docshell/test/navigation/blank.html
+prefs =
+ focusmanager.testmode=true # emulate focus
+ privacy.partition.network_state=false
+ network.proxy.allow_hijacking_localhost=true
+ media.devices.enumerate.legacy.enabled=false
+
+[test_defaultAudioConstraints.html]
+skip-if = os == 'mac'
+ os == 'win'
+ toolkit == 'android' # Bug 1404995, no loopback devices on some platforms
+[test_getUserMedia_GC_MediaStream.html]
+[test_getUserMedia_active_autoplay.html]
+[test_getUserMedia_addTrackRemoveTrack.html]
+[test_getUserMedia_addtrack_removetrack_events.html]
+[test_getUserMedia_audioCapture.html]
+skip-if = toolkit == 'android'
+ (os == "win" && processor == "aarch64") # android(Bug 1189784, timeouts on 4.3 emulator), android(Bug 1264333), aarch64 due to 1538359
+[test_getUserMedia_audioConstraints.html]
+skip-if = os == 'mac'
+ os == 'win'
+ toolkit == 'android' # Bug 1404995, no loopback devices on some platforms
+[test_getUserMedia_audioConstraints_concurrentIframes.html]
+skip-if = os == 'mac'
+ os == 'win'
+ toolkit == 'android'
+ (os == 'linux' && debug) # Bug 1404995, no loopback devices on some platforms # Bug 1481101
+ os == "linux" && !debug && !fission # bug 1645930, lower frequency intermittent
+[test_getUserMedia_audioConstraints_concurrentStreams.html]
+skip-if = os == 'mac'
+ os == 'win'
+ toolkit == 'android' # Bug 1404995, no loopback devices on some platforms
+[test_getUserMedia_basicAudio.html]
+[test_getUserMedia_basicAudio_loopback.html]
+skip-if = os == 'mac'
+ os == 'win'
+ toolkit == 'android' # Bug 1404995, no loopback devices on some platforms
+[test_getUserMedia_basicScreenshare.html]
+skip-if =
+ toolkit == 'android' # no screenshare on android
+ apple_silicon # bug 1707742
+[test_getUserMedia_basicTabshare.html]
+skip-if =
+ toolkit == 'android' # no windowshare on android
+[test_getUserMedia_basicVideo.html]
+[test_getUserMedia_basicVideoAudio.html]
+[test_getUserMedia_basicVideo_playAfterLoadedmetadata.html]
+[test_getUserMedia_basicWindowshare.html]
+skip-if = toolkit == 'android' # no windowshare on android
+[test_getUserMedia_bug1223696.html]
+[test_getUserMedia_callbacks.html]
+[test_getUserMedia_constraints.html]
+[test_getUserMedia_cubebDisabled.html]
+[test_getUserMedia_cubebDisabledFakeStreams.html]
+[test_getUserMedia_getTrackById.html]
+[test_getUserMedia_gumWithinGum.html]
+[test_getUserMedia_loadedmetadata.html]
+[test_getUserMedia_mediaElementCapture_audio.html]
+[test_getUserMedia_mediaElementCapture_tracks.html]
+[test_getUserMedia_mediaElementCapture_video.html]
+[test_getUserMedia_mediaStreamClone.html]
+[test_getUserMedia_mediaStreamConstructors.html]
+[test_getUserMedia_mediaStreamTrackClone.html]
+[test_getUserMedia_nonDefaultRate.html]
+[test_getUserMedia_peerIdentity.html]
+[test_getUserMedia_permission.html]
+[test_getUserMedia_playAudioTwice.html]
+[test_getUserMedia_playVideoAudioTwice.html]
+[test_getUserMedia_playVideoTwice.html]
+[test_getUserMedia_scarySources.html]
+skip-if = toolkit == 'android' # no screenshare or windowshare on android
+[test_getUserMedia_spinEventLoop.html]
+[test_getUserMedia_trackCloneCleanup.html]
+[test_getUserMedia_trackEnded.html]
+
diff --git a/dom/media/webrtc/tests/mochitests/mochitest_peerconnection.ini b/dom/media/webrtc/tests/mochitests/mochitest_peerconnection.ini
new file mode 100644
index 0000000000..37fb551435
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/mochitest_peerconnection.ini
@@ -0,0 +1,311 @@
+[DEFAULT]
+tags = mtg webrtc
+subsuite = media
+scheme = https
+support-files =
+ head.js
+ dataChannel.js
+ mediaStreamPlayback.js
+ network.js
+ nonTrickleIce.js
+ pc.js
+ stats.js
+ templates.js
+ test_enumerateDevices_iframe.html
+ test_getUserMedia_permission_iframe.html
+ NetworkPreparationChromeScript.js
+ blacksilence.js
+ turnConfig.js
+ sdpUtils.js
+ addTurnsSelfsignedCert.js
+ parser_rtp.js
+ peerconnection_audio_forced_sample_rate.js
+ iceTestUtils.js
+ simulcast.js
+ helpers_from_wpt/sdp.js
+ !/dom/canvas/test/captureStream_common.js
+ !/dom/canvas/test/webgl-mochitest/webgl-util.js
+ !/dom/media/test/manifest.js
+ !/dom/media/test/seek.webm
+ !/dom/media/test/gizmo.mp4
+ !/docshell/test/navigation/blank.html
+prefs =
+ focusmanager.testmode=true # emulate focus
+ privacy.partition.network_state=false
+ network.proxy.allow_hijacking_localhost=true
+ media.devices.enumerate.legacy.enabled=false
+
+[test_peerConnection_addAudioTrackToExistingVideoStream.html]
+[test_peerConnection_addDataChannel.html]
+[test_peerConnection_addDataChannelNoBundle.html]
+[test_peerConnection_addSecondAudioStream.html]
+[test_peerConnection_addSecondAudioStreamNoBundle.html]
+[test_peerConnection_addSecondVideoStream.html]
+[test_peerConnection_addSecondVideoStreamNoBundle.html]
+[test_peerConnection_addtrack_removetrack_events.html]
+[test_peerConnection_answererAddSecondAudioStream.html]
+[test_peerConnection_audioChannels.html]
+[test_peerConnection_audioCodecs.html]
+[test_peerConnection_audioContributingSources.html]
+[test_peerConnection_audioRenegotiationInactiveAnswer.html]
+[test_peerConnection_audioSynchronizationSources.html]
+[test_peerConnection_audioSynchronizationSourcesUnidirectional.html]
+[test_peerConnection_basicAudio.html]
+[test_peerConnection_basicAudioDynamicPtMissingRtpmap.html]
+[test_peerConnection_basicAudioNATRelay.html]
+skip-if =
+ toolkit == 'android' # websockets don't work on android (bug 1266217)
+scheme=http
+[test_peerConnection_basicAudioNATRelayTCP.html]
+skip-if =
+ toolkit == 'android' # websockets don't work on android (bug 1266217)
+ (os == 'win' && os_version == '6.1') # WinError 10048
+scheme=http
+[test_peerConnection_basicAudioNATRelayTCPWithStun300.html]
+skip-if =
+ toolkit == 'android' # websockets don't work on android (bug 1266217)
+ (os == 'win' && os_version == '6.1') # WinError 10048
+scheme=http
+[test_peerConnection_basicAudioNATRelayTLS.html]
+skip-if =
+ toolkit == 'android' # websockets don't work on android (bug 1266217)
+ (os == 'win' && os_version == '6.1') # WinError 10048
+scheme=http
+[test_peerConnection_basicAudioNATRelayWithStun300.html]
+skip-if =
+ toolkit == 'android' # websockets don't work on android (bug 1266217)
+ (os == 'win' && os_version == '6.1') # WinError 10048
+scheme=http
+[test_peerConnection_basicAudioNATSrflx.html]
+skip-if =
+ toolkit == 'android' # websockets don't work on android (bug 1266217)
+ (os == 'win' && os_version == '6.1') # WinError 10048
+scheme=http
+[test_peerConnection_basicAudioNoisyUDPBlock.html]
+skip-if =
+ toolkit == 'android' # websockets don't work on android (bug 1266217)
+ (os == 'win' && os_version == '6.1') # WinError 10048
+scheme=http
+[test_peerConnection_basicAudioPcmaPcmuOnly.html]
+[test_peerConnection_basicAudioRelayPolicy.html]
+skip-if =
+ toolkit == 'android' # websockets don't work on android (bug 1266217)
+ (os == 'win' && os_version == '6.1') # WinError 10048
+scheme=http
+[test_peerConnection_basicAudioRequireEOC.html]
+[test_peerConnection_basicAudioVerifyRtpHeaderExtensions.html]
+[test_peerConnection_basicAudioVideo.html]
+[test_peerConnection_basicAudioVideoCombined.html]
+[test_peerConnection_basicAudioVideoNoBundle.html]
+[test_peerConnection_basicAudioVideoNoBundleNoRtcpMux.html]
+[test_peerConnection_basicAudioVideoNoRtcpMux.html]
+[test_peerConnection_basicAudioVideoTransceivers.html]
+[test_peerConnection_basicAudioVideoVerifyExtmap.html]
+[test_peerConnection_basicAudioVideoVerifyExtmapSendonly.html]
+[test_peerConnection_basicAudioVideoVerifyTooLongMidFails.html]
+[test_peerConnection_basicAudio_forced_higher_rate.html]
+[test_peerConnection_basicAudio_forced_lower_rate.html]
+[test_peerConnection_basicH264Video.html]
+skip-if =
+ toolkit == 'android' && is_emulator # Bug 1355786, No h264 support on android emulator
+[test_peerConnection_basicScreenshare.html]
+skip-if = toolkit == 'android' # no screenshare on android
+[test_peerConnection_basicVideo.html]
+[test_peerConnection_basicVideoVerifyRtpHeaderExtensions.html]
+[test_peerConnection_basicWindowshare.html]
+skip-if = toolkit == 'android' # no screenshare on android
+[test_peerConnection_bug1013809.html]
+[test_peerConnection_bug1042791.html]
+skip-if = (toolkit == 'android' && is_emulator) # Bug 1355786, No h264 support on android emulator
+[test_peerConnection_bug1227781.html]
+scheme=http
+[test_peerConnection_bug1512281.html]
+fail-if = 1
+[test_peerConnection_bug1773067.html]
+[test_peerConnection_bug822674.html]
+scheme=http
+[test_peerConnection_bug825703.html]
+scheme=http
+[test_peerConnection_bug827843.html]
+[test_peerConnection_bug834153.html]
+scheme=http
+[test_peerConnection_callbacks.html]
+[test_peerConnection_captureStream_canvas_2d.html]
+scheme=http
+[test_peerConnection_captureStream_canvas_2d_noSSRC.html]
+scheme=http
+[test_peerConnection_captureStream_canvas_webgl.html]
+scheme=http
+[test_peerConnection_capturedVideo.html]
+tags=capturestream
+skip-if = toolkit == 'android' # android(Bug 1189784, timeouts on 4.3 emulator), Bug 1264340
+[test_peerConnection_certificates.html]
+[test_peerConnection_checkPacketDumpHook.html]
+[test_peerConnection_close.html]
+scheme=http
+[test_peerConnection_closeDuringIce.html]
+[test_peerConnection_codecNegotiationFailure.html]
+[test_peerConnection_constructedStream.html]
+[test_peerConnection_disabledVideoPreNegotiation.html]
+[test_peerConnection_encodingsNegotiation.html]
+[test_peerConnection_errorCallbacks.html]
+scheme=http
+[test_peerConnection_extmapRenegotiation.html]
+[test_peerConnection_forwarding_basicAudioVideoCombined.html]
+skip-if = toolkit == 'android' # Bug 1189784
+[test_peerConnection_gatherWithSetConfiguration.html]
+skip-if =
+ toolkit == 'android' # websockets don't work on android (bug 1266217)
+ (os == 'win' && os_version == '6.1') # WinError 10048
+scheme=http
+[test_peerConnection_gatherWithStun300.html]
+skip-if =
+ toolkit == 'android' # websockets don't work on android (bug 1266217)
+ (os == 'win' && os_version == '6.1') # WinError 10048
+scheme=http
+[test_peerConnection_gatherWithStun300IPv6.html]
+skip-if =
+ toolkit == 'android' # websockets don't work on android (bug 1266217)
+ os == 'mac' # no ipv6 support on OS X testers (bug 1710706)
+ os == 'win' # no ipv6 support on windows testers (bug 1710706)
+scheme=http
+[test_peerConnection_glean.html]
+[test_peerConnection_iceFailure.html]
+skip-if = true # (Bug 1180388 for win, mac and linux), android(Bug 1189784), Bug 1180388
+scheme=http
+[test_peerConnection_insertDTMF.html]
+[test_peerConnection_localReofferRollback.html]
+[test_peerConnection_localRollback.html]
+[test_peerConnection_maxFsConstraint.html]
+[test_peerConnection_multiple_captureStream_canvas_2d.html]
+scheme=http
+[test_peerConnection_noTrickleAnswer.html]
+[test_peerConnection_noTrickleOffer.html]
+[test_peerConnection_noTrickleOfferAnswer.html]
+[test_peerConnection_nonDefaultRate.html]
+[test_peerConnection_offerRequiresReceiveAudio.html]
+[test_peerConnection_offerRequiresReceiveVideo.html]
+[test_peerConnection_offerRequiresReceiveVideoAudio.html]
+[test_peerConnection_portRestrictions.html]
+[test_peerConnection_promiseSendOnly.html]
+[test_peerConnection_recordReceiveTrack.html]
+[test_peerConnection_relayOnly.html]
+disabled=bug 1612063 # test is racy
+[test_peerConnection_remoteReofferRollback.html]
+[test_peerConnection_remoteRollback.html]
+[test_peerConnection_removeAudioTrack.html]
+[test_peerConnection_removeThenAddAudioTrack.html]
+[test_peerConnection_removeThenAddAudioTrackNoBundle.html]
+[test_peerConnection_removeThenAddVideoTrack.html]
+[test_peerConnection_removeThenAddVideoTrackNoBundle.html]
+[test_peerConnection_removeVideoTrack.html]
+[test_peerConnection_renderAfterRenegotiation.html]
+scheme=http
+[test_peerConnection_replaceNullTrackThenRenegotiateAudio.html]
+[test_peerConnection_replaceNullTrackThenRenegotiateVideo.html]
+[test_peerConnection_replaceTrack.html]
+[test_peerConnection_replaceTrack_camera.html]
+skip-if = toolkit == 'android' # Bug 1614460
+[test_peerConnection_replaceTrack_disabled.html]
+skip-if =
+ toolkit == 'android' # Bug 1614460
+[test_peerConnection_replaceTrack_microphone.html]
+[test_peerConnection_replaceVideoThenRenegotiate.html]
+[test_peerConnection_restartIce.html]
+[test_peerConnection_restartIceBadAnswer.html]
+[test_peerConnection_restartIceLocalAndRemoteRollback.html]
+[test_peerConnection_restartIceLocalAndRemoteRollbackNoSubsequentRestart.html]
+[test_peerConnection_restartIceLocalRollback.html]
+[test_peerConnection_restartIceLocalRollbackNoSubsequentRestart.html]
+[test_peerConnection_restartIceNoBundle.html]
+[test_peerConnection_restartIceNoBundleNoRtcpMux.html]
+[test_peerConnection_restartIceNoRtcpMux.html]
+[test_peerConnection_restrictBandwidthTargetBitrate.html]
+[test_peerConnection_restrictBandwidthWithTias.html]
+[test_peerConnection_rtcp_rsize.html]
+[test_peerConnection_scaleResolution.html]
+[test_peerConnection_scaleResolution_oldSetParameters.html]
+[test_peerConnection_sender_and_receiver_stats.html]
+[test_peerConnection_setLocalAnswerInHaveLocalOffer.html]
+[test_peerConnection_setLocalAnswerInStable.html]
+[test_peerConnection_setLocalOfferInHaveRemoteOffer.html]
+[test_peerConnection_setParameters.html]
+[test_peerConnection_setParameters_maxFramerate.html]
+[test_peerConnection_setParameters_maxFramerate_oldSetParameters.html]
+[test_peerConnection_setParameters_oldSetParameters.html]
+[test_peerConnection_setParameters_scaleResolutionDownBy.html]
+skip-if = (os == 'win' && processor == 'aarch64') # aarch64 due to bug 1537567
+[test_peerConnection_setParameters_scaleResolutionDownBy_oldSetParameters.html]
+skip-if = (os == 'win' && processor == 'aarch64') # aarch64 due to bug 1537567
+[test_peerConnection_setRemoteAnswerInHaveRemoteOffer.html]
+[test_peerConnection_setRemoteAnswerInStable.html]
+[test_peerConnection_setRemoteOfferInHaveLocalOffer.html]
+[test_peerConnection_simulcastAnswer.html]
+skip-if = toolkit == 'android' # no simulcast support on android
+[test_peerConnection_simulcastAnswer_lowResFirst.html]
+skip-if = toolkit == 'android' # no simulcast support on android
+[test_peerConnection_simulcastAnswer_lowResFirst_oldSetParameters.html]
+skip-if = toolkit == 'android' # no simulcast support on android
+[test_peerConnection_simulcastAnswer_oldSetParameters.html]
+skip-if = toolkit == 'android' # no simulcast support on android
+[test_peerConnection_simulcastOddResolution.html]
+skip-if = toolkit == 'android' # no simulcast support on android
+[test_peerConnection_simulcastOddResolution_oldSetParameters.html]
+skip-if = toolkit == 'android' # no simulcast support on android
+[test_peerConnection_simulcastOffer.html]
+skip-if = toolkit == 'android' # no simulcast support on android
+[test_peerConnection_simulcastOffer_lowResFirst.html]
+skip-if = toolkit == 'android' # no simulcast support on android
+[test_peerConnection_simulcastOffer_lowResFirst_oldSetParameters.html]
+skip-if = toolkit == 'android' # no simulcast support on android
+[test_peerConnection_simulcastOffer_oldSetParameters.html]
+skip-if = toolkit == 'android' # no simulcast support on android
+[test_peerConnection_stats.html]
+[test_peerConnection_stats_jitter.html]
+skip-if = tsan # Bug 1672590, TSan is just too slow to pass this test
+[test_peerConnection_stats_oneway.html]
+[test_peerConnection_stats_relayProtocol.html]
+skip-if =
+ toolkit == 'android' # android(Bug 1189784, timeouts on 4.3 emulator, Bug 1373858, Bug 1521117)
+ socketprocess_e10s
+ (os == 'win' && os_version == '6.1') # WinError 10048
+scheme=http
+[test_peerConnection_stereoFmtpPref.html]
+[test_peerConnection_syncSetDescription.html]
+[test_peerConnection_telephoneEventFirst.html]
+[test_peerConnection_threeUnbundledConnections.html]
+[test_peerConnection_throwInCallbacks.html]
+[test_peerConnection_toJSON.html]
+scheme=http
+[test_peerConnection_trackDisabling.html]
+skip-if = toolkit == 'android' # Bug 1614460
+[test_peerConnection_trackDisabling_clones.html]
+[test_peerConnection_trackless_sender_stats.html]
+[test_peerConnection_twoAudioStreams.html]
+[test_peerConnection_twoAudioTracksInOneStream.html]
+[test_peerConnection_twoAudioVideoStreams.html]
+[test_peerConnection_twoAudioVideoStreamsCombined.html]
+skip-if = (toolkit == 'android') || (os == 'linux' && asan) # android(Bug 1189784), Bug 1480942 for Linux asan
+[test_peerConnection_twoAudioVideoStreamsCombinedNoBundle.html]
+skip-if =
+ (toolkit == 'android') # Bug 1189784
+ (os == 'linux' && asan) # Bug 1480942
+ (os == 'win' && processor == 'aarch64') # Bug 1777081
+[test_peerConnection_twoVideoStreams.html]
+[test_peerConnection_twoVideoTracksInOneStream.html]
+[test_peerConnection_verifyAudioAfterRenegotiation.html]
+skip-if =
+ os == "android" && processor == "x86_64" && !debug # Bug 1783287
+[test_peerConnection_verifyDescriptions.html]
+[test_peerConnection_verifyVideoAfterRenegotiation.html]
+[test_peerConnection_videoCodecs.html]
+skip-if =
+ toolkit == 'android' # android(Bug 1614460)
+ win10_2004 && !debug # Bug 1777082
+[test_peerConnection_videoRenegotiationInactiveAnswer.html]
+[test_peerConnection_webAudio.html]
+tags = webaudio webrtc
+scheme=http
+[test_selftest.html]
+# Bug 1227781: Crash with bogus TURN server.
+scheme=http
diff --git a/dom/media/webrtc/tests/mochitests/network.js b/dom/media/webrtc/tests/mochitests/network.js
new file mode 100644
index 0000000000..223721b111
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/network.js
@@ -0,0 +1,16 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * A stub function for preparing the network if needed
+ *
+ */
+async function startNetworkAndTest() {}
+
+/**
+ * A stub function to shutdown the network if needed
+ */
+async function networkTestFinished() {}
diff --git a/dom/media/webrtc/tests/mochitests/nonTrickleIce.js b/dom/media/webrtc/tests/mochitests/nonTrickleIce.js
new file mode 100644
index 0000000000..9361944791
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/nonTrickleIce.js
@@ -0,0 +1,97 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function removeTrickleOption(desc) {
+ var sdp = desc.sdp.replace(/\r\na=ice-options:trickle\r\n/, "\r\n");
+ return new RTCSessionDescription({ type: desc.type, sdp });
+}
+
+function makeOffererNonTrickle(chain) {
+ chain.replace("PC_LOCAL_SETUP_ICE_HANDLER", [
+ function PC_LOCAL_SETUP_NOTRICKLE_ICE_HANDLER(test) {
+ // We need to install this callback before calling setLocalDescription
+ // otherwise we might miss callbacks
+ test.pcLocal.setupIceCandidateHandler(test, () => {});
+ // We ignore ICE candidates because we want the full offer
+ },
+ ]);
+ chain.replace("PC_REMOTE_GET_OFFER", [
+ function PC_REMOTE_GET_FULL_OFFER(test) {
+ return test.pcLocal.endOfTrickleIce.then(() => {
+ test._local_offer = removeTrickleOption(test.pcLocal.localDescription);
+ test._offer_constraints = test.pcLocal.constraints;
+ test._offer_options = test.pcLocal.offerOptions;
+ });
+ },
+ ]);
+ chain.insertAfter("PC_REMOTE_SANE_REMOTE_SDP", [
+ function PC_REMOTE_REQUIRE_REMOTE_SDP_CANDIDATES(test) {
+ info(
+ "test.pcLocal.localDescription.sdp: " +
+ JSON.stringify(test.pcLocal.localDescription.sdp)
+ );
+ info("test._local_offer.sdp" + JSON.stringify(test._local_offer.sdp));
+ is(
+ test.pcRemote._pc.canTrickleIceCandidates,
+ false,
+ "Remote thinks that trickle isn't supported"
+ );
+ ok(!test.localRequiresTrickleIce, "Local does NOT require trickle");
+ ok(
+ test._local_offer.sdp.includes("a=candidate"),
+ "offer has ICE candidates"
+ );
+ ok(
+ test._local_offer.sdp.includes("a=end-of-candidates"),
+ "offer has end-of-candidates"
+ );
+ },
+ ]);
+ chain.remove("PC_REMOTE_CHECK_CAN_TRICKLE_SYNC");
+}
+
+function makeAnswererNonTrickle(chain) {
+ chain.replace("PC_REMOTE_SETUP_ICE_HANDLER", [
+ function PC_REMOTE_SETUP_NOTRICKLE_ICE_HANDLER(test) {
+ // We need to install this callback before calling setLocalDescription
+ // otherwise we might miss callbacks
+ test.pcRemote.setupIceCandidateHandler(test, () => {});
+ // We ignore ICE candidates because we want the full offer
+ },
+ ]);
+ chain.replace("PC_LOCAL_GET_ANSWER", [
+ function PC_LOCAL_GET_FULL_ANSWER(test) {
+ return test.pcRemote.endOfTrickleIce.then(() => {
+ test._remote_answer = removeTrickleOption(
+ test.pcRemote.localDescription
+ );
+ test._answer_constraints = test.pcRemote.constraints;
+ });
+ },
+ ]);
+ chain.insertAfter("PC_LOCAL_SANE_REMOTE_SDP", [
+ function PC_LOCAL_REQUIRE_REMOTE_SDP_CANDIDATES(test) {
+ info(
+ "test.pcRemote.localDescription.sdp: " +
+ JSON.stringify(test.pcRemote.localDescription.sdp)
+ );
+ info("test._remote_answer.sdp" + JSON.stringify(test._remote_answer.sdp));
+ is(
+ test.pcLocal._pc.canTrickleIceCandidates,
+ false,
+ "Local thinks that trickle isn't supported"
+ );
+ ok(!test.remoteRequiresTrickleIce, "Remote does NOT require trickle");
+ ok(
+ test._remote_answer.sdp.includes("a=candidate"),
+ "answer has ICE candidates"
+ );
+ ok(
+ test._remote_answer.sdp.includes("a=end-of-candidates"),
+ "answer has end-of-candidates"
+ );
+ },
+ ]);
+ chain.remove("PC_LOCAL_CHECK_CAN_TRICKLE_SYNC");
+}
diff --git a/dom/media/webrtc/tests/mochitests/parser_rtp.js b/dom/media/webrtc/tests/mochitests/parser_rtp.js
new file mode 100644
index 0000000000..2275c1f787
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/parser_rtp.js
@@ -0,0 +1,131 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/*
+ * Parses an RTP packet
+ * @param buffer an ArrayBuffer that contains the packet
+ * @return { type: "rtp", header: {...}, payload: a DataView }
+ */
+var ParseRtpPacket = buffer => {
+ // DataView.getFooInt returns big endian numbers by default
+ let view = new DataView(buffer);
+
+ // Standard Header Fields
+ // https://tools.ietf.org/html/rfc3550#section-5.1
+ // 0 1 2 3
+ // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // |V=2|P|X| CC |M| PT | sequence number |
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // | timestamp |
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // | synchronization source (SSRC) identifier |
+ // +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
+ // | contributing source (CSRC) identifiers |
+ // | .... |
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+ let header = {};
+ let offset = 0;
+ // Note that incrementing the offset happens as close to reading the data as
+ // possible. This simplifies ensuring that the number of read bytes and the
+ // offset increment match. Data may be manipulated between when the offset is
+ // incremented and before the next read.
+ let byte = view.getUint8(offset);
+ offset++;
+ // Version 2 Bit
+ header.version = (0xc0 & byte) >> 6;
+ // Padding 1 Bit
+ header.padding = (0x30 & byte) >> 5;
+ // Extension 1 Bit
+ header.extensionsPresent = (0x10 & byte) >> 4 == 1;
+ // CSRC count 4 Bit
+ header.csrcCount = 0xf & byte;
+
+ byte = view.getUint8(offset);
+ offset++;
+ // Marker 1 Bit
+ header.marker = (0x80 & byte) >> 7;
+ // Payload Type 7 Bit
+ header.payloadType = 0x7f & byte;
+ // Sequence Number 16 Bit
+ header.sequenceNumber = view.getUint16(offset);
+ offset += 2;
+ // Timestamp 32 Bit
+ header.timestamp = view.getUint32(offset);
+ offset += 4;
+ // SSRC 32 Bit
+ header.ssrc = view.getUint32(offset);
+ offset += 4;
+
+ // CSRC 32 Bit
+ header.csrcs = [];
+ for (let c = 0; c < header.csrcCount; c++) {
+ header.csrcs.push(view.getUint32(offset));
+ offset += 4;
+ }
+
+ // Extensions
+ header.extensions = [];
+ header.extensionPaddingBytes = 0;
+ header.extensionsTotalLength = 0;
+ if (header.extensionsPresent) {
+ // https://tools.ietf.org/html/rfc3550#section-5.3.1
+ // 0 1 2 3
+ // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // | defined by profile | length |
+ // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ // | header extension |
+ // | .... |
+ let addExtension = (id, len) =>
+ header.extensions.push({
+ id,
+ data: new DataView(buffer, offset, len),
+ });
+ let extensionId = view.getUint16(offset);
+ offset += 2;
+ // len is in 32 bit units, not bytes
+ header.extensionsTotalLength = view.getUint16(offset) * 4;
+ offset += 2;
+ // Check for https://tools.ietf.org/html/rfc5285
+ if (extensionId != 0xbede) {
+ // No rfc5285
+ addExtension(extensionId, header.extensionsTotalLength);
+ offset += header.extensionsTotalLength;
+ } else {
+ let expectedEnd = offset + header.extensionsTotalLength;
+ while (offset < expectedEnd) {
+ // We only support "one-byte" extension headers ATM
+ // https://tools.ietf.org/html/rfc5285#section-4.2
+ // 0
+ // 0 1 2 3 4 5 6 7
+ // +-+-+-+-+-+-+-+-+
+ // | ID | len |
+ // +-+-+-+-+-+-+-+-+
+ byte = view.getUint8(offset);
+ offset++;
+ // Check for padding which can occur between extensions or at the end
+ if (byte == 0) {
+ header.extensionPaddingBytes++;
+ continue;
+ }
+ let id = (byte & 0xf0) >> 4;
+ // Check for the FORBIDDEN id (15), dun dun dun
+ if (id == 15) {
+ // Ignore bytes until until the end of extensions
+ offset = expectedEnd;
+ break;
+ }
+ // the length of the extention is len + 1
+ let len = (byte & 0x0f) + 1;
+ addExtension(id, len);
+ offset += len;
+ }
+ }
+ }
+ return { type: "rtp", header, payload: new DataView(buffer, offset) };
+};
diff --git a/dom/media/webrtc/tests/mochitests/pc.js b/dom/media/webrtc/tests/mochitests/pc.js
new file mode 100644
index 0000000000..36a923fbed
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/pc.js
@@ -0,0 +1,2495 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const LOOPBACK_ADDR = "127.0.0.";
+
+const iceStateTransitions = {
+ new: ["checking", "closed"], //Note: 'failed' might need to added here
+ // even though it is not in the standard
+ checking: ["new", "connected", "failed", "closed"], //Note: do we need to
+ // allow 'completed' in
+ // here as well?
+ connected: ["new", "completed", "disconnected", "closed"],
+ completed: ["new", "disconnected", "closed"],
+ disconnected: ["new", "connected", "completed", "failed", "closed"],
+ failed: ["new", "disconnected", "closed"],
+ closed: [],
+};
+
+const signalingStateTransitions = {
+ stable: ["have-local-offer", "have-remote-offer", "closed"],
+ "have-local-offer": [
+ "have-remote-pranswer",
+ "stable",
+ "closed",
+ "have-local-offer",
+ ],
+ "have-remote-pranswer": ["stable", "closed", "have-remote-pranswer"],
+ "have-remote-offer": [
+ "have-local-pranswer",
+ "stable",
+ "closed",
+ "have-remote-offer",
+ ],
+ "have-local-pranswer": ["stable", "closed", "have-local-pranswer"],
+ closed: [],
+};
+
+var makeDefaultCommands = () => {
+ return [].concat(
+ commandsPeerConnectionInitial,
+ commandsGetUserMedia,
+ commandsPeerConnectionOfferAnswer
+ );
+};
+
+/**
+ * This class handles tests for peer connections.
+ *
+ * @constructor
+ * @param {object} [options={}]
+ * Optional options for the peer connection test
+ * @param {object} [options.commands=commandsPeerConnection]
+ * Commands to run for the test
+ * @param {bool} [options.is_local=true]
+ * true if this test should run the tests for the "local" side.
+ * @param {bool} [options.is_remote=true]
+ * true if this test should run the tests for the "remote" side.
+ * @param {object} [options.config_local=undefined]
+ * Configuration for the local peer connection instance
+ * @param {object} [options.config_remote=undefined]
+ * Configuration for the remote peer connection instance. If not defined
+ * the configuration from the local instance will be used
+ */
+function PeerConnectionTest(options) {
+ // If no options are specified make it an empty object
+ options = options || {};
+ options.commands = options.commands || makeDefaultCommands();
+ options.is_local = "is_local" in options ? options.is_local : true;
+ options.is_remote = "is_remote" in options ? options.is_remote : true;
+
+ options.h264 = "h264" in options ? options.h264 : false;
+ options.bundle = "bundle" in options ? options.bundle : true;
+ options.rtcpmux = "rtcpmux" in options ? options.rtcpmux : true;
+ options.opus = "opus" in options ? options.opus : true;
+ options.ssrc = "ssrc" in options ? options.ssrc : true;
+
+ options.config_local = options.config_local || {};
+ options.config_remote = options.config_remote || {};
+
+ if (!options.bundle) {
+ // Make sure neither end tries to use bundle-only!
+ options.config_local.bundlePolicy = "max-compat";
+ options.config_remote.bundlePolicy = "max-compat";
+ }
+
+ if (iceServersArray.length) {
+ if (!options.turn_disabled_local && !options.config_local.iceServers) {
+ options.config_local.iceServers = iceServersArray;
+ }
+ if (!options.turn_disabled_remote && !options.config_remote.iceServers) {
+ options.config_remote.iceServers = iceServersArray;
+ }
+ } else if (typeof turnServers !== "undefined") {
+ if (!options.turn_disabled_local && turnServers.local) {
+ if (!options.config_local.hasOwnProperty("iceServers")) {
+ options.config_local.iceServers = turnServers.local.iceServers;
+ }
+ }
+ if (!options.turn_disabled_remote && turnServers.remote) {
+ if (!options.config_remote.hasOwnProperty("iceServers")) {
+ options.config_remote.iceServers = turnServers.remote.iceServers;
+ }
+ }
+ }
+
+ if (options.is_local) {
+ this.pcLocal = new PeerConnectionWrapper("pcLocal", options.config_local);
+ } else {
+ this.pcLocal = null;
+ }
+
+ if (options.is_remote) {
+ this.pcRemote = new PeerConnectionWrapper(
+ "pcRemote",
+ options.config_remote || options.config_local
+ );
+ } else {
+ this.pcRemote = null;
+ }
+
+ // Create command chain instance and assign default commands
+ this.chain = new CommandChain(this, options.commands);
+
+ this.testOptions = options;
+}
+
+/** TODO: consider removing this dependency on timeouts */
+function timerGuard(p, time, message) {
+ return Promise.race([
+ p,
+ wait(time).then(() => {
+ throw new Error("timeout after " + time / 1000 + "s: " + message);
+ }),
+ ]);
+}
+
+/**
+ * Closes the peer connection if it is active
+ */
+PeerConnectionTest.prototype.closePC = function () {
+ info("Closing peer connections");
+
+ var closeIt = pc => {
+ if (!pc || pc.signalingState === "closed") {
+ return Promise.resolve();
+ }
+
+ var promise = Promise.all([
+ Promise.all(
+ pc._pc
+ .getReceivers()
+ .filter(receiver => receiver.track.readyState == "live")
+ .map(receiver => {
+ info(
+ "Waiting for track " +
+ receiver.track.id +
+ " (" +
+ receiver.track.kind +
+ ") to end."
+ );
+ return haveEvent(receiver.track, "ended", wait(50000)).then(
+ event => {
+ is(
+ event.target,
+ receiver.track,
+ "Event target should be the correct track"
+ );
+ info(pc + " ended fired for track " + receiver.track.id);
+ },
+ e =>
+ e
+ ? Promise.reject(e)
+ : ok(
+ false,
+ "ended never fired for track " + receiver.track.id
+ )
+ );
+ })
+ ),
+ ]);
+ pc.close();
+ return promise;
+ };
+
+ return timerGuard(
+ Promise.all([closeIt(this.pcLocal), closeIt(this.pcRemote)]),
+ 60000,
+ "failed to close peer connection"
+ );
+};
+
+/**
+ * Close the open data channels, followed by the underlying peer connection
+ */
+PeerConnectionTest.prototype.close = function () {
+ var allChannels = (this.pcLocal || this.pcRemote).dataChannels;
+ return timerGuard(
+ Promise.all(allChannels.map((channel, i) => this.closeDataChannels(i))),
+ 120000,
+ "failed to close data channels"
+ ).then(() => this.closePC());
+};
+
+/**
+ * Close the specified data channels
+ *
+ * @param {Number} index
+ * Index of the data channels to close on both sides
+ */
+PeerConnectionTest.prototype.closeDataChannels = function (index) {
+ info("closeDataChannels called with index: " + index);
+ var localChannel = null;
+ if (this.pcLocal) {
+ localChannel = this.pcLocal.dataChannels[index];
+ }
+ var remoteChannel = null;
+ if (this.pcRemote) {
+ remoteChannel = this.pcRemote.dataChannels[index];
+ }
+
+ // We need to setup all the close listeners before calling close
+ var setupClosePromise = channel => {
+ if (!channel) {
+ return Promise.resolve();
+ }
+ return new Promise(resolve => {
+ channel.onclose = () => {
+ is(
+ channel.readyState,
+ "closed",
+ name + " channel " + index + " closed"
+ );
+ resolve();
+ };
+ });
+ };
+
+ // make sure to setup close listeners before triggering any actions
+ var allClosed = Promise.all([
+ setupClosePromise(localChannel),
+ setupClosePromise(remoteChannel),
+ ]);
+ var complete = timerGuard(
+ allClosed,
+ 120000,
+ "failed to close data channel pair"
+ );
+
+ // triggering close on one side should suffice
+ if (remoteChannel) {
+ remoteChannel.close();
+ } else if (localChannel) {
+ localChannel.close();
+ }
+
+ return complete;
+};
+
+/**
+ * Send data (message or blob) to the other peer
+ *
+ * @param {String|Blob} data
+ * Data to send to the other peer. For Blobs the MIME type will be lost.
+ * @param {Object} [options={ }]
+ * Options to specify the data channels to be used
+ * @param {DataChannelWrapper} [options.sourceChannel=pcLocal.dataChannels[length - 1]]
+ * Data channel to use for sending the message
+ * @param {DataChannelWrapper} [options.targetChannel=pcRemote.dataChannels[length - 1]]
+ * Data channel to use for receiving the message
+ */
+PeerConnectionTest.prototype.send = async function (data, options) {
+ options = options || {};
+ const source =
+ options.sourceChannel ||
+ this.pcLocal.dataChannels[this.pcLocal.dataChannels.length - 1];
+ const target =
+ options.targetChannel ||
+ this.pcRemote.dataChannels[this.pcRemote.dataChannels.length - 1];
+ source.bufferedAmountLowThreshold = options.bufferedAmountLowThreshold || 0;
+
+ const getSizeInBytes = d => {
+ if (d instanceof Blob) {
+ return d.size;
+ } else if (d instanceof ArrayBuffer) {
+ return d.byteLength;
+ } else if (d instanceof String || typeof d === "string") {
+ return new TextEncoder().encode(d).length;
+ } else {
+ ok(false);
+ }
+ };
+
+ const expectedSizeInBytes = getSizeInBytes(data);
+ const bufferedAmount = source.bufferedAmount;
+
+ source.send(data);
+ is(
+ source.bufferedAmount,
+ expectedSizeInBytes + bufferedAmount,
+ `Buffered amount should be ${expectedSizeInBytes}`
+ );
+
+ await new Promise(resolve => (source.onbufferedamountlow = resolve));
+
+ return new Promise(resolve => {
+ // Register event handler for the target channel
+ target.onmessage = e => {
+ is(
+ getSizeInBytes(e.data),
+ expectedSizeInBytes,
+ `Expected to receive the same number of bytes as we sent (${expectedSizeInBytes})`
+ );
+ resolve({ channel: target, data: e.data });
+ };
+ });
+};
+
+/**
+ * Create a data channel
+ *
+ * @param {Dict} options
+ * Options for the data channel (see nsIPeerConnection)
+ */
+PeerConnectionTest.prototype.createDataChannel = function (options) {
+ var remotePromise;
+ if (!options.negotiated) {
+ this.pcRemote.expectDataChannel("pcRemote expected data channel");
+ remotePromise = this.pcRemote.nextDataChannel;
+ }
+
+ // Create the datachannel
+ var localChannel = this.pcLocal.createDataChannel(options);
+ var localPromise = localChannel.opened;
+
+ if (options.negotiated) {
+ remotePromise = localPromise.then(localChannel => {
+ // externally negotiated - we need to open from both ends
+ options.id = options.id || channel.id; // allow for no id on options
+ var remoteChannel = this.pcRemote.createDataChannel(options);
+ return remoteChannel.opened;
+ });
+ }
+
+ // pcRemote.observedNegotiationNeeded might be undefined if
+ // !options.negotiated, which means we just wait on pcLocal
+ return Promise.all([
+ this.pcLocal.observedNegotiationNeeded,
+ this.pcRemote.observedNegotiationNeeded,
+ ]).then(() => {
+ return Promise.all([localPromise, remotePromise]).then(result => {
+ return { local: result[0], remote: result[1] };
+ });
+ });
+};
+
+/**
+ * Creates an answer for the specified peer connection instance
+ * and automatically handles the failure case.
+ *
+ * @param {PeerConnectionWrapper} peer
+ * The peer connection wrapper to run the command on
+ */
+PeerConnectionTest.prototype.createAnswer = function (peer) {
+ return peer.createAnswer().then(answer => {
+ // make a copy so this does not get updated with ICE candidates
+ this.originalAnswer = new RTCSessionDescription(
+ JSON.parse(JSON.stringify(answer))
+ );
+ return answer;
+ });
+};
+
+/**
+ * Creates an offer for the specified peer connection instance
+ * and automatically handles the failure case.
+ *
+ * @param {PeerConnectionWrapper} peer
+ * The peer connection wrapper to run the command on
+ */
+PeerConnectionTest.prototype.createOffer = function (peer) {
+ return peer.createOffer().then(offer => {
+ // make a copy so this does not get updated with ICE candidates
+ this.originalOffer = new RTCSessionDescription(
+ JSON.parse(JSON.stringify(offer))
+ );
+ return offer;
+ });
+};
+
+/**
+ * Sets the local description for the specified peer connection instance
+ * and automatically handles the failure case.
+ *
+ * @param {PeerConnectionWrapper} peer
+ The peer connection wrapper to run the command on
+ * @param {RTCSessionDescriptionInit} desc
+ * Session description for the local description request
+ */
+PeerConnectionTest.prototype.setLocalDescription = function (
+ peer,
+ desc,
+ stateExpected
+) {
+ var eventFired = new Promise(resolve => {
+ peer.onsignalingstatechange = e => {
+ info(peer + ": 'signalingstatechange' event received");
+ var state = e.target.signalingState;
+ if (stateExpected === state) {
+ peer.setLocalDescStableEventDate = new Date();
+ resolve();
+ } else {
+ ok(
+ false,
+ "This event has either already fired or there has been a " +
+ "mismatch between event received " +
+ state +
+ " and event expected " +
+ stateExpected
+ );
+ }
+ };
+ });
+
+ var stateChanged = peer.setLocalDescription(desc).then(() => {
+ peer.setLocalDescDate = new Date();
+ });
+
+ peer.endOfTrickleSdp = peer.endOfTrickleIce
+ .then(() => {
+ return peer._pc.localDescription;
+ })
+ .catch(e => ok(false, "Sending EOC message failed: " + e));
+
+ return Promise.all([eventFired, stateChanged]);
+};
+
+/**
+ * Sets the media constraints for both peer connection instances.
+ *
+ * @param {object} constraintsLocal
+ * Media constrains for the local peer connection instance
+ * @param constraintsRemote
+ */
+PeerConnectionTest.prototype.setMediaConstraints = function (
+ constraintsLocal,
+ constraintsRemote
+) {
+ if (this.pcLocal) {
+ this.pcLocal.constraints = constraintsLocal;
+ }
+ if (this.pcRemote) {
+ this.pcRemote.constraints = constraintsRemote;
+ }
+};
+
+/**
+ * Sets the media options used on a createOffer call in the test.
+ *
+ * @param {object} options the media constraints to use on createOffer
+ */
+PeerConnectionTest.prototype.setOfferOptions = function (options) {
+ if (this.pcLocal) {
+ this.pcLocal.offerOptions = options;
+ }
+};
+
+/**
+ * Sets the remote description for the specified peer connection instance
+ * and automatically handles the failure case.
+ *
+ * @param {PeerConnectionWrapper} peer
+ The peer connection wrapper to run the command on
+ * @param {RTCSessionDescriptionInit} desc
+ * Session description for the remote description request
+ */
+PeerConnectionTest.prototype.setRemoteDescription = function (
+ peer,
+ desc,
+ stateExpected
+) {
+ var eventFired = new Promise(resolve => {
+ peer.onsignalingstatechange = e => {
+ info(peer + ": 'signalingstatechange' event received");
+ var state = e.target.signalingState;
+ if (stateExpected === state) {
+ peer.setRemoteDescStableEventDate = new Date();
+ resolve();
+ } else {
+ ok(
+ false,
+ "This event has either already fired or there has been a " +
+ "mismatch between event received " +
+ state +
+ " and event expected " +
+ stateExpected
+ );
+ }
+ };
+ });
+
+ var stateChanged = peer.setRemoteDescription(desc).then(() => {
+ peer.setRemoteDescDate = new Date();
+ peer.checkMediaTracks();
+ });
+
+ return Promise.all([eventFired, stateChanged]);
+};
+
+/**
+ * Adds and removes steps to/from the execution chain based on the configured
+ * testOptions.
+ */
+PeerConnectionTest.prototype.updateChainSteps = function () {
+ if (this.testOptions.h264) {
+ this.chain.insertAfterEach("PC_LOCAL_CREATE_OFFER", [
+ PC_LOCAL_REMOVE_ALL_BUT_H264_FROM_OFFER,
+ ]);
+ }
+ if (!this.testOptions.bundle) {
+ this.chain.insertAfterEach("PC_LOCAL_CREATE_OFFER", [
+ PC_LOCAL_REMOVE_BUNDLE_FROM_OFFER,
+ ]);
+ }
+ if (!this.testOptions.rtcpmux) {
+ this.chain.insertAfterEach("PC_LOCAL_CREATE_OFFER", [
+ PC_LOCAL_REMOVE_RTCPMUX_FROM_OFFER,
+ ]);
+ }
+ if (!this.testOptions.ssrc) {
+ this.chain.insertAfterEach("PC_LOCAL_CREATE_OFFER", [
+ PC_LOCAL_REMOVE_SSRC_FROM_OFFER,
+ ]);
+ this.chain.insertAfterEach("PC_REMOTE_CREATE_ANSWER", [
+ PC_REMOTE_REMOVE_SSRC_FROM_ANSWER,
+ ]);
+ }
+ if (!this.testOptions.is_local) {
+ this.chain.filterOut(/^PC_LOCAL/);
+ }
+ if (!this.testOptions.is_remote) {
+ this.chain.filterOut(/^PC_REMOTE/);
+ }
+};
+
+/**
+ * Start running the tests as assigned to the command chain.
+ */
+PeerConnectionTest.prototype.run = async function () {
+ /* We have to modify the chain here to allow tests which modify the default
+ * test chain instantiating a PeerConnectionTest() */
+ this.updateChainSteps();
+ try {
+ await this.chain.execute();
+ await this.close();
+ } catch (e) {
+ const stack =
+ typeof e.stack === "string"
+ ? ` ${e.stack.split("\n").join(" ... ")}`
+ : "";
+ ok(false, `Error in test execution: ${e} (${stack})`);
+ }
+};
+
+/**
+ * Routes ice candidates from one PCW to the other PCW
+ */
+PeerConnectionTest.prototype.iceCandidateHandler = function (
+ caller,
+ candidate
+) {
+ info("Received: " + JSON.stringify(candidate) + " from " + caller);
+
+ var target = null;
+ if (caller.includes("pcLocal")) {
+ if (this.pcRemote) {
+ target = this.pcRemote;
+ }
+ } else if (caller.includes("pcRemote")) {
+ if (this.pcLocal) {
+ target = this.pcLocal;
+ }
+ } else {
+ ok(false, "received event from unknown caller: " + caller);
+ return;
+ }
+
+ if (target) {
+ target.storeOrAddIceCandidate(candidate);
+ } else {
+ info("sending ice candidate to signaling server");
+ send_message({ type: "ice_candidate", ice_candidate: candidate });
+ }
+};
+
+/**
+ * Installs a polling function for the socket.io client to read
+ * all messages from the chat room into a message queue.
+ */
+PeerConnectionTest.prototype.setupSignalingClient = function () {
+ this.signalingMessageQueue = [];
+ this.signalingCallbacks = {};
+ this.signalingLoopRun = true;
+
+ var queueMessage = message => {
+ info("Received signaling message: " + JSON.stringify(message));
+ var fired = false;
+ Object.keys(this.signalingCallbacks).forEach(name => {
+ if (name === message.type) {
+ info("Invoking callback for message type: " + name);
+ this.signalingCallbacks[name](message);
+ fired = true;
+ }
+ });
+ if (!fired) {
+ this.signalingMessageQueue.push(message);
+ info(
+ "signalingMessageQueue.length: " + this.signalingMessageQueue.length
+ );
+ }
+ if (this.signalingLoopRun) {
+ wait_for_message().then(queueMessage);
+ } else {
+ info("Exiting signaling message event loop");
+ }
+ };
+ wait_for_message().then(queueMessage);
+};
+
+/**
+ * Sets a flag to stop reading further messages from the chat room.
+ */
+PeerConnectionTest.prototype.signalingMessagesFinished = function () {
+ this.signalingLoopRun = false;
+};
+
+/**
+ * Register a callback function to deliver messages from the chat room
+ * directly instead of storing them in the message queue.
+ *
+ * @param {string} messageType
+ * For which message types should the callback get invoked.
+ *
+ * @param {function} onMessage
+ * The function which gets invoked if a message of the messageType
+ * has been received from the chat room.
+ */
+PeerConnectionTest.prototype.registerSignalingCallback = function (
+ messageType,
+ onMessage
+) {
+ this.signalingCallbacks[messageType] = onMessage;
+};
+
+/**
+ * Searches the message queue for the first message of a given type
+ * and invokes the given callback function, or registers the callback
+ * function for future messages if the queue contains no such message.
+ *
+ * @param {string} messageType
+ * The type of message to search and register for.
+ */
+PeerConnectionTest.prototype.getSignalingMessage = function (messageType) {
+ var i = this.signalingMessageQueue.findIndex(m => m.type === messageType);
+ if (i >= 0) {
+ info(
+ "invoking callback on message " +
+ i +
+ " from message queue, for message type:" +
+ messageType
+ );
+ return Promise.resolve(this.signalingMessageQueue.splice(i, 1)[0]);
+ }
+ return new Promise(resolve =>
+ this.registerSignalingCallback(messageType, resolve)
+ );
+};
+
+/**
+ * This class acts as a wrapper around a DataChannel instance.
+ *
+ * @param dataChannel
+ * @param peerConnectionWrapper
+ * @constructor
+ */
+function DataChannelWrapper(dataChannel, peerConnectionWrapper) {
+ this._channel = dataChannel;
+ this._pc = peerConnectionWrapper;
+
+ info("Creating " + this);
+
+ /**
+ * Setup appropriate callbacks
+ */
+ createOneShotEventWrapper(this, this._channel, "close");
+ createOneShotEventWrapper(this, this._channel, "error");
+ createOneShotEventWrapper(this, this._channel, "message");
+ createOneShotEventWrapper(this, this._channel, "bufferedamountlow");
+
+ this.opened = timerGuard(
+ new Promise(resolve => {
+ this._channel.onopen = () => {
+ this._channel.onopen = unexpectedEvent(this, "onopen");
+ is(this.readyState, "open", "data channel is 'open' after 'onopen'");
+ resolve(this);
+ };
+ }),
+ 180000,
+ "channel didn't open in time"
+ );
+}
+
+DataChannelWrapper.prototype = {
+ /**
+ * Returns the binary type of the channel
+ *
+ * @returns {String} The binary type
+ */
+ get binaryType() {
+ return this._channel.binaryType;
+ },
+
+ /**
+ * Sets the binary type of the channel
+ *
+ * @param {String} type
+ * The new binary type of the channel
+ */
+ set binaryType(type) {
+ this._channel.binaryType = type;
+ },
+
+ /**
+ * Returns the label of the underlying data channel
+ *
+ * @returns {String} The label
+ */
+ get label() {
+ return this._channel.label;
+ },
+
+ /**
+ * Returns the protocol of the underlying data channel
+ *
+ * @returns {String} The protocol
+ */
+ get protocol() {
+ return this._channel.protocol;
+ },
+
+ /**
+ * Returns the id of the underlying data channel
+ *
+ * @returns {number} The stream id
+ */
+ get id() {
+ return this._channel.id;
+ },
+
+ /**
+ * Returns the reliable state of the underlying data channel
+ *
+ * @returns {bool} The stream's reliable state
+ */
+ get reliable() {
+ return this._channel.reliable;
+ },
+
+ /**
+ * Returns the ordered attribute of the data channel
+ *
+ * @returns {bool} The ordered attribute
+ */
+ get ordered() {
+ return this._channel.ordered;
+ },
+
+ /**
+ * Returns the maxPacketLifeTime attribute of the data channel
+ *
+ * @returns {number} The maxPacketLifeTime attribute
+ */
+ get maxPacketLifeTime() {
+ return this._channel.maxPacketLifeTime;
+ },
+
+ /**
+ * Returns the maxRetransmits attribute of the data channel
+ *
+ * @returns {number} The maxRetransmits attribute
+ */
+ get maxRetransmits() {
+ return this._channel.maxRetransmits;
+ },
+
+ /**
+ * Returns the readyState bit of the data channel
+ *
+ * @returns {String} The state of the channel
+ */
+ get readyState() {
+ return this._channel.readyState;
+ },
+
+ get bufferedAmount() {
+ return this._channel.bufferedAmount;
+ },
+
+ /**
+ * Sets the bufferlowthreshold of the channel
+ *
+ * @param {integer} amoutn
+ * The new threshold for the chanel
+ */
+ set bufferedAmountLowThreshold(amount) {
+ this._channel.bufferedAmountLowThreshold = amount;
+ },
+
+ /**
+ * Close the data channel
+ */
+ close() {
+ info(this + ": Closing channel");
+ this._channel.close();
+ },
+
+ /**
+ * Send data through the data channel
+ *
+ * @param {String|Object} data
+ * Data which has to be sent through the data channel
+ */
+ send(data) {
+ info(this + ": Sending data '" + data + "'");
+ this._channel.send(data);
+ },
+
+ /**
+ * Returns the string representation of the class
+ *
+ * @returns {String} The string representation
+ */
+ toString() {
+ return (
+ "DataChannelWrapper (" + this._pc.label + "_" + this._channel.label + ")"
+ );
+ },
+};
+
+/**
+ * This class acts as a wrapper around a PeerConnection instance.
+ *
+ * @constructor
+ * @param {string} label
+ * Description for the peer connection instance
+ * @param {object} configuration
+ * Configuration for the peer connection instance
+ */
+function PeerConnectionWrapper(label, configuration) {
+ this.configuration = configuration;
+ if (configuration && configuration.label_suffix) {
+ label = label + "_" + configuration.label_suffix;
+ }
+ this.label = label;
+
+ this.constraints = [];
+ this.offerOptions = {};
+
+ this.dataChannels = [];
+
+ this._local_ice_candidates = [];
+ this._remote_ice_candidates = [];
+ this.localRequiresTrickleIce = false;
+ this.remoteRequiresTrickleIce = false;
+ this.localMediaElements = [];
+ this.remoteMediaElements = [];
+ this.audioElementsOnly = false;
+
+ this._sendStreams = [];
+
+ this.expectedLocalTrackInfo = [];
+ this.remoteStreamsByTrackId = new Map();
+
+ this.disableRtpCountChecking = false;
+
+ this.iceConnectedResolve;
+ this.iceConnectedReject;
+ this.iceConnected = new Promise((resolve, reject) => {
+ this.iceConnectedResolve = resolve;
+ this.iceConnectedReject = reject;
+ });
+ this.iceCheckingRestartExpected = false;
+ this.iceCheckingIceRollbackExpected = false;
+
+ info("Creating " + this);
+ this._pc = new RTCPeerConnection(this.configuration);
+
+ /**
+ * Setup callback handlers
+ */
+ // This allows test to register their own callbacks for ICE connection state changes
+ this.ice_connection_callbacks = {};
+
+ this._pc.oniceconnectionstatechange = e => {
+ isnot(
+ typeof this._pc.iceConnectionState,
+ "undefined",
+ "iceConnectionState should not be undefined"
+ );
+ var iceState = this._pc.iceConnectionState;
+ info(
+ this + ": oniceconnectionstatechange fired, new state is: " + iceState
+ );
+ Object.keys(this.ice_connection_callbacks).forEach(name => {
+ this.ice_connection_callbacks[name]();
+ });
+ if (iceState === "connected") {
+ this.iceConnectedResolve();
+ } else if (iceState === "failed") {
+ this.iceConnectedReject(new Error("ICE failed"));
+ }
+ };
+
+ this._pc.onicegatheringstatechange = e => {
+ isnot(
+ typeof this._pc.iceGatheringState,
+ "undefined",
+ "iceGetheringState should not be undefined"
+ );
+ var gatheringState = this._pc.iceGatheringState;
+ info(
+ this +
+ ": onicegatheringstatechange fired, new state is: " +
+ gatheringState
+ );
+ };
+
+ createOneShotEventWrapper(this, this._pc, "datachannel");
+ this._pc.addEventListener("datachannel", e => {
+ var wrapper = new DataChannelWrapper(e.channel, this);
+ this.dataChannels.push(wrapper);
+ });
+
+ createOneShotEventWrapper(this, this._pc, "signalingstatechange");
+ createOneShotEventWrapper(this, this._pc, "negotiationneeded");
+}
+
+PeerConnectionWrapper.prototype = {
+ /**
+ * Returns the senders
+ *
+ * @returns {sequence<RTCRtpSender>} the senders
+ */
+ getSenders() {
+ return this._pc.getSenders();
+ },
+
+ /**
+ * Returns the getters
+ *
+ * @returns {sequence<RTCRtpReceiver>} the receivers
+ */
+ getReceivers() {
+ return this._pc.getReceivers();
+ },
+
+ /**
+ * Returns the local description.
+ *
+ * @returns {object} The local description
+ */
+ get localDescription() {
+ return this._pc.localDescription;
+ },
+
+ /**
+ * Returns the remote description.
+ *
+ * @returns {object} The remote description
+ */
+ get remoteDescription() {
+ return this._pc.remoteDescription;
+ },
+
+ /**
+ * Returns the signaling state.
+ *
+ * @returns {object} The local description
+ */
+ get signalingState() {
+ return this._pc.signalingState;
+ },
+ /**
+ * Returns the ICE connection state.
+ *
+ * @returns {object} The local description
+ */
+ get iceConnectionState() {
+ return this._pc.iceConnectionState;
+ },
+
+ setIdentityProvider(provider, options) {
+ this._pc.setIdentityProvider(provider, options);
+ },
+
+ elementPrefix: direction => {
+ return [this.label, direction].join("_");
+ },
+
+ getMediaElementForTrack(track, direction) {
+ var prefix = this.elementPrefix(direction);
+ return getMediaElementForTrack(track, prefix);
+ },
+
+ createMediaElementForTrack(track, direction) {
+ var prefix = this.elementPrefix(direction);
+ return createMediaElementForTrack(track, prefix);
+ },
+
+ ensureMediaElement(track, direction) {
+ var prefix = this.elementPrefix(direction);
+ var element = this.getMediaElementForTrack(track, direction);
+ if (!element) {
+ element = this.createMediaElementForTrack(track, direction);
+ if (direction == "local") {
+ this.localMediaElements.push(element);
+ } else if (direction == "remote") {
+ this.remoteMediaElements.push(element);
+ }
+ }
+
+ // We do this regardless, because sometimes we end up with a new stream with
+ // an old id (ie; the rollback tests cause the same stream to be added
+ // twice)
+ element.srcObject = new MediaStream([track]);
+ element.play();
+ },
+
+ addSendStream(stream) {
+ // The PeerConnection will not necessarily know about this stream
+ // automatically, because replaceTrack is not told about any streams the
+ // new track might be associated with. Only content really knows.
+ this._sendStreams.push(stream);
+ },
+
+ getStreamForSendTrack(track) {
+ return this._sendStreams.find(str => str.getTrackById(track.id));
+ },
+
+ getStreamForRecvTrack(track) {
+ return this._pc.getRemoteStreams().find(s => !!s.getTrackById(track.id));
+ },
+
+ /**
+ * Attaches a local track to this RTCPeerConnection using
+ * RTCPeerConnection.addTrack().
+ *
+ * Also creates a media element playing a MediaStream containing all
+ * tracks that have been added to `stream` using `attachLocalTrack()`.
+ *
+ * @param {MediaStreamTrack} track
+ * MediaStreamTrack to handle
+ * @param {MediaStream} stream
+ * MediaStream to use as container for `track` on remote side
+ */
+ attachLocalTrack(track, stream) {
+ info("Got a local " + track.kind + " track");
+
+ this.expectNegotiationNeeded();
+ var sender = this._pc.addTrack(track, stream);
+ is(sender.track, track, "addTrack returns sender");
+ is(
+ this._pc.getSenders().pop(),
+ sender,
+ "Sender should be the last element in getSenders()"
+ );
+
+ ok(track.id, "track has id");
+ ok(track.kind, "track has kind");
+ ok(stream.id, "stream has id");
+ this.expectedLocalTrackInfo.push({ track, sender, streamId: stream.id });
+ this.addSendStream(stream);
+
+ // This will create one media element per track, which might not be how
+ // we set up things with the RTCPeerConnection. It's the only way
+ // we can ensure all sent tracks are flowing however.
+ this.ensureMediaElement(track, "local");
+
+ return this.observedNegotiationNeeded;
+ },
+
+ /**
+ * Callback when we get local media. Also an appropriate HTML media element
+ * will be created and added to the content node.
+ *
+ * @param {MediaStream} stream
+ * Media stream to handle
+ */
+ attachLocalStream(stream, useAddTransceiver) {
+ info("Got local media stream: (" + stream.id + ")");
+
+ this.expectNegotiationNeeded();
+ if (useAddTransceiver) {
+ info("Using addTransceiver (on PC).");
+ stream.getTracks().forEach(track => {
+ var transceiver = this._pc.addTransceiver(track, { streams: [stream] });
+ is(transceiver.sender.track, track, "addTransceiver returns sender");
+ });
+ }
+ // In order to test both the addStream and addTrack APIs, we do half one
+ // way, half the other, at random.
+ else if (Math.random() < 0.5) {
+ info("Using addStream.");
+ this._pc.addStream(stream);
+ ok(
+ this._pc
+ .getSenders()
+ .find(sender => sender.track == stream.getTracks()[0]),
+ "addStream returns sender"
+ );
+ } else {
+ info("Using addTrack (on PC).");
+ stream.getTracks().forEach(track => {
+ var sender = this._pc.addTrack(track, stream);
+ is(sender.track, track, "addTrack returns sender");
+ });
+ }
+
+ this.addSendStream(stream);
+
+ stream.getTracks().forEach(track => {
+ ok(track.id, "track has id");
+ ok(track.kind, "track has kind");
+ const sender = this._pc.getSenders().find(s => s.track == track);
+ ok(sender, "track has a sender");
+ this.expectedLocalTrackInfo.push({ track, sender, streamId: stream.id });
+ this.ensureMediaElement(track, "local");
+ });
+
+ return this.observedNegotiationNeeded;
+ },
+
+ removeSender(index) {
+ var sender = this._pc.getSenders()[index];
+ this.expectedLocalTrackInfo = this.expectedLocalTrackInfo.filter(
+ i => i.sender != sender
+ );
+ this.expectNegotiationNeeded();
+ this._pc.removeTrack(sender);
+ return this.observedNegotiationNeeded;
+ },
+
+ senderReplaceTrack(sender, withTrack, stream) {
+ const info = this.expectedLocalTrackInfo.find(i => i.sender == sender);
+ if (!info) {
+ return; // replaceTrack on a null track, probably
+ }
+ info.track = withTrack;
+ this.addSendStream(stream);
+ this.ensureMediaElement(withTrack, "local");
+ return sender.replaceTrack(withTrack);
+ },
+
+ async getUserMedia(constraints) {
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ var stream = await getUserMedia(constraints);
+ if (constraints.audio) {
+ stream.getAudioTracks().forEach(track => {
+ info(
+ this +
+ " gUM local stream " +
+ stream.id +
+ " with audio track " +
+ track.id
+ );
+ });
+ }
+ if (constraints.video) {
+ stream.getVideoTracks().forEach(track => {
+ info(
+ this +
+ " gUM local stream " +
+ stream.id +
+ " with video track " +
+ track.id
+ );
+ });
+ }
+ return stream;
+ },
+
+ /**
+ * Requests all the media streams as specified in the constrains property.
+ *
+ * @param {array} constraintsList
+ * Array of constraints for GUM calls
+ */
+ getAllUserMedia(constraintsList) {
+ if (constraintsList.length === 0) {
+ info("Skipping GUM: no UserMedia requested");
+ return Promise.resolve();
+ }
+
+ info("Get " + constraintsList.length + " local streams");
+ return Promise.all(
+ constraintsList.map(constraints => this.getUserMedia(constraints))
+ );
+ },
+
+ async getAllUserMediaAndAddStreams(constraintsList) {
+ var streams = await this.getAllUserMedia(constraintsList);
+ if (!streams) {
+ return;
+ }
+ return Promise.all(streams.map(stream => this.attachLocalStream(stream)));
+ },
+
+ async getAllUserMediaAndAddTransceivers(constraintsList) {
+ var streams = await this.getAllUserMedia(constraintsList);
+ if (!streams) {
+ return;
+ }
+ return Promise.all(
+ streams.map(stream => this.attachLocalStream(stream, true))
+ );
+ },
+
+ /**
+ * Create a new data channel instance. Also creates a promise called
+ * `this.nextDataChannel` that resolves when the next data channel arrives.
+ */
+ expectDataChannel(message) {
+ this.nextDataChannel = new Promise(resolve => {
+ this.ondatachannel = e => {
+ ok(e.channel, message);
+ is(
+ e.channel.readyState,
+ "open",
+ "data channel in 'open' after 'ondatachannel'"
+ );
+ resolve(e.channel);
+ };
+ });
+ },
+
+ /**
+ * Create a new data channel instance
+ *
+ * @param {Object} options
+ * Options which get forwarded to nsIPeerConnection.createDataChannel
+ * @returns {DataChannelWrapper} The created data channel
+ */
+ createDataChannel(options) {
+ var label = "channel_" + this.dataChannels.length;
+ info(this + ": Create data channel '" + label);
+
+ if (!this.dataChannels.length) {
+ this.expectNegotiationNeeded();
+ }
+ var channel = this._pc.createDataChannel(label, options);
+ is(channel.readyState, "connecting", "initial readyState is 'connecting'");
+ var wrapper = new DataChannelWrapper(channel, this);
+ this.dataChannels.push(wrapper);
+ return wrapper;
+ },
+
+ /**
+ * Creates an offer and automatically handles the failure case.
+ */
+ createOffer() {
+ return this._pc.createOffer(this.offerOptions).then(offer => {
+ info("Got offer: " + JSON.stringify(offer));
+ // note: this might get updated through ICE gathering
+ this._latest_offer = offer;
+ return offer;
+ });
+ },
+
+ /**
+ * Creates an answer and automatically handles the failure case.
+ */
+ createAnswer() {
+ return this._pc.createAnswer().then(answer => {
+ info(this + ": Got answer: " + JSON.stringify(answer));
+ this._last_answer = answer;
+ return answer;
+ });
+ },
+
+ /**
+ * Sets the local description and automatically handles the failure case.
+ *
+ * @param {object} desc
+ * RTCSessionDescriptionInit for the local description request
+ */
+ setLocalDescription(desc) {
+ this.observedNegotiationNeeded = undefined;
+ return this._pc.setLocalDescription(desc).then(() => {
+ info(this + ": Successfully set the local description");
+ });
+ },
+
+ /**
+ * Tries to set the local description and expect failure. Automatically
+ * causes the test case to fail if the call succeeds.
+ *
+ * @param {object} desc
+ * RTCSessionDescriptionInit for the local description request
+ * @returns {Promise}
+ * A promise that resolves to the expected error
+ */
+ setLocalDescriptionAndFail(desc) {
+ return this._pc
+ .setLocalDescription(desc)
+ .then(
+ generateErrorCallback("setLocalDescription should have failed."),
+ err => {
+ info(this + ": As expected, failed to set the local description");
+ return err;
+ }
+ );
+ },
+
+ /**
+ * Sets the remote description and automatically handles the failure case.
+ *
+ * @param {object} desc
+ * RTCSessionDescriptionInit for the remote description request
+ */
+ setRemoteDescription(desc) {
+ this.observedNegotiationNeeded = undefined;
+ // This has to be done before calling sRD, otherwise a candidate in flight
+ // could end up in the PC's operations queue before sRD resolves.
+ if (desc.type == "rollback") {
+ this.holdIceCandidates = new Promise(
+ r => (this.releaseIceCandidates = r)
+ );
+ }
+ return this._pc.setRemoteDescription(desc).then(() => {
+ info(this + ": Successfully set remote description");
+ if (desc.type != "rollback") {
+ this.releaseIceCandidates();
+ }
+ });
+ },
+
+ /**
+ * Tries to set the remote description and expect failure. Automatically
+ * causes the test case to fail if the call succeeds.
+ *
+ * @param {object} desc
+ * RTCSessionDescriptionInit for the remote description request
+ * @returns {Promise}
+ * a promise that resolve to the returned error
+ */
+ setRemoteDescriptionAndFail(desc) {
+ return this._pc
+ .setRemoteDescription(desc)
+ .then(
+ generateErrorCallback("setRemoteDescription should have failed."),
+ err => {
+ info(this + ": As expected, failed to set the remote description");
+ return err;
+ }
+ );
+ },
+
+ /**
+ * Registers a callback for the signaling state change and
+ * appends the new state to an array for logging it later.
+ */
+ logSignalingState() {
+ this.signalingStateLog = [this._pc.signalingState];
+ this._pc.addEventListener("signalingstatechange", e => {
+ var newstate = this._pc.signalingState;
+ var oldstate = this.signalingStateLog[this.signalingStateLog.length - 1];
+ if (Object.keys(signalingStateTransitions).includes(oldstate)) {
+ ok(
+ signalingStateTransitions[oldstate].includes(newstate),
+ this +
+ ": legal signaling state transition from " +
+ oldstate +
+ " to " +
+ newstate
+ );
+ } else {
+ ok(
+ false,
+ this +
+ ": old signaling state " +
+ oldstate +
+ " missing in signaling transition array"
+ );
+ }
+ this.signalingStateLog.push(newstate);
+ });
+ },
+
+ isTrackOnPC(track) {
+ return !!this.getStreamForRecvTrack(track);
+ },
+
+ allExpectedTracksAreObserved(expected, observed) {
+ return Object.keys(expected).every(trackId => observed[trackId]);
+ },
+
+ setupStreamEventHandlers(stream) {
+ const myTrackIds = new Set(stream.getTracks().map(t => t.id));
+
+ stream.addEventListener("addtrack", ({ track }) => {
+ ok(
+ !myTrackIds.has(track.id),
+ "Duplicate addtrack callback: " +
+ `stream id=${stream.id} track id=${track.id}`
+ );
+ myTrackIds.add(track.id);
+ // addtrack events happen before track events, so the track callback hasn't
+ // heard about this yet.
+ let streams = this.remoteStreamsByTrackId.get(track.id);
+ ok(
+ !streams || !streams.has(stream.id),
+ `In addtrack for stream id=${stream.id}` +
+ `there should not have been a track event for track id=${track.id} ` +
+ " containing this stream yet."
+ );
+ ok(
+ stream.getTracks().includes(track),
+ "In addtrack, stream id=" +
+ `${stream.id} should already contain track id=${track.id}`
+ );
+ });
+
+ stream.addEventListener("removetrack", ({ track }) => {
+ ok(
+ myTrackIds.has(track.id),
+ "Duplicate removetrack callback: " +
+ `stream id=${stream.id} track id=${track.id}`
+ );
+ myTrackIds.delete(track.id);
+ // Also remove the association from remoteStreamsByTrackId
+ const streams = this.remoteStreamsByTrackId.get(track.id);
+ ok(
+ streams,
+ `In removetrack for stream id=${stream.id}, track id=` +
+ `${track.id} should have had a track callback for the stream.`
+ );
+ streams.delete(stream.id);
+ ok(
+ !stream.getTracks().includes(track),
+ "In removetrack, stream id=" +
+ `${stream.id} should not contain track id=${track.id}`
+ );
+ });
+ },
+
+ setupTrackEventHandler() {
+ this._pc.addEventListener("track", ({ track, streams }) => {
+ info(`${this}: 'ontrack' event fired for ${track.id}`);
+ ok(this.isTrackOnPC(track), `Found track ${track.id}`);
+
+ let gratuitousEvent = true;
+ let streamsContainingTrack = this.remoteStreamsByTrackId.get(track.id);
+ if (!streamsContainingTrack) {
+ gratuitousEvent = false; // Told us about a new track
+ this.remoteStreamsByTrackId.set(track.id, new Set());
+ streamsContainingTrack = this.remoteStreamsByTrackId.get(track.id);
+ }
+
+ for (const stream of streams) {
+ ok(
+ stream.getTracks().includes(track),
+ `In track event, track id=${track.id}` +
+ ` should already be in stream id=${stream.id}`
+ );
+
+ if (!streamsContainingTrack.has(stream.id)) {
+ gratuitousEvent = false; // Told us about a new stream
+ streamsContainingTrack.add(stream.id);
+ this.setupStreamEventHandlers(stream);
+ }
+ }
+
+ ok(!gratuitousEvent, "track event told us something new");
+
+ // So far, we've verified consistency between the current state of the
+ // streams, addtrack/removetrack events on the streams, and track events
+ // on the peerconnection. We have also verified that we have not gotten
+ // any gratuitous events. We have not done anything to verify that the
+ // current state of affairs matches what we were expecting it to.
+
+ this.ensureMediaElement(track, "remote");
+ });
+ },
+
+ /**
+ * Either adds a given ICE candidate right away or stores it to be added
+ * later, depending on the state of the PeerConnection.
+ *
+ * @param {object} candidate
+ * The RTCIceCandidate to be added or stored
+ */
+ storeOrAddIceCandidate(candidate) {
+ this._remote_ice_candidates.push(candidate);
+ if (this.signalingState === "closed") {
+ info("Received ICE candidate for closed PeerConnection - discarding");
+ return;
+ }
+ this.holdIceCandidates
+ .then(() => {
+ info(this + ": adding ICE candidate " + JSON.stringify(candidate));
+ return this._pc.addIceCandidate(candidate);
+ })
+ .then(() => ok(true, this + " successfully added an ICE candidate"))
+ .catch(e =>
+ // The onicecandidate callback runs independent of the test steps
+ // and therefore errors thrown from in there don't get caught by the
+ // race of the Promises around our test steps.
+ // Note: as long as we are queuing ICE candidates until the success
+ // of sRD() this should never ever happen.
+ ok(false, this + " adding ICE candidate failed with: " + e.message)
+ );
+ },
+
+ /**
+ * Registers a callback for the ICE connection state change and
+ * appends the new state to an array for logging it later.
+ */
+ logIceConnectionState() {
+ this.iceConnectionLog = [this._pc.iceConnectionState];
+ this.ice_connection_callbacks.logIceStatus = () => {
+ var newstate = this._pc.iceConnectionState;
+ var oldstate = this.iceConnectionLog[this.iceConnectionLog.length - 1];
+ if (Object.keys(iceStateTransitions).includes(oldstate)) {
+ if (this.iceCheckingRestartExpected) {
+ is(
+ newstate,
+ "checking",
+ "iceconnectionstate event '" +
+ newstate +
+ "' matches expected state 'checking'"
+ );
+ this.iceCheckingRestartExpected = false;
+ } else if (this.iceCheckingIceRollbackExpected) {
+ is(
+ newstate,
+ "connected",
+ "iceconnectionstate event '" +
+ newstate +
+ "' matches expected state 'connected'"
+ );
+ this.iceCheckingIceRollbackExpected = false;
+ } else {
+ ok(
+ iceStateTransitions[oldstate].includes(newstate),
+ this +
+ ": legal ICE state transition from " +
+ oldstate +
+ " to " +
+ newstate
+ );
+ }
+ } else {
+ ok(
+ false,
+ this +
+ ": old ICE state " +
+ oldstate +
+ " missing in ICE transition array"
+ );
+ }
+ this.iceConnectionLog.push(newstate);
+ };
+ },
+
+ /**
+ * Resets the ICE connected Promise and allows ICE connection state monitoring
+ * to go backwards to 'checking'.
+ */
+ expectIceChecking() {
+ this.iceCheckingRestartExpected = true;
+ this.iceConnected = new Promise((resolve, reject) => {
+ this.iceConnectedResolve = resolve;
+ this.iceConnectedReject = reject;
+ });
+ },
+
+ /**
+ * Waits for ICE to either connect or fail.
+ *
+ * @returns {Promise}
+ * resolves when connected, rejects on failure
+ */
+ waitForIceConnected() {
+ return this.iceConnected;
+ },
+
+ /**
+ * Setup a onicecandidate handler
+ *
+ * @param {object} test
+ * A PeerConnectionTest object to which the ice candidates gets
+ * forwarded.
+ */
+ setupIceCandidateHandler(test, candidateHandler) {
+ candidateHandler = candidateHandler || test.iceCandidateHandler.bind(test);
+
+ var resolveEndOfTrickle;
+ this.endOfTrickleIce = new Promise(r => (resolveEndOfTrickle = r));
+ this.holdIceCandidates = new Promise(r => (this.releaseIceCandidates = r));
+
+ this._pc.onicecandidate = anEvent => {
+ if (!anEvent.candidate) {
+ this._pc.onicecandidate = () =>
+ ok(
+ false,
+ this.label + " received ICE candidate after end of trickle"
+ );
+ info(this.label + ": received end of trickle ICE event");
+ ok(
+ this._pc.iceGatheringState === "complete",
+ "ICE gathering state has reached complete"
+ );
+ resolveEndOfTrickle(this.label);
+ return;
+ }
+
+ info(
+ this.label + ": iceCandidate = " + JSON.stringify(anEvent.candidate)
+ );
+ ok(anEvent.candidate.sdpMid.length, "SDP mid not empty");
+ ok(
+ anEvent.candidate.usernameFragment.length,
+ "usernameFragment not empty"
+ );
+
+ ok(
+ typeof anEvent.candidate.sdpMLineIndex === "number",
+ "SDP MLine Index needs to exist"
+ );
+ this._local_ice_candidates.push(anEvent.candidate);
+ candidateHandler(this.label, anEvent.candidate);
+ };
+ },
+
+ checkLocalMediaTracks() {
+ info(
+ `${this}: Checking local tracks ${JSON.stringify(
+ this.expectedLocalTrackInfo
+ )}`
+ );
+ const sendersWithTrack = this._pc.getSenders().filter(({ track }) => track);
+ is(
+ sendersWithTrack.length,
+ this.expectedLocalTrackInfo.length,
+ "The number of senders with a track should be equal to the number of " +
+ "expected local tracks."
+ );
+
+ // expectedLocalTrackInfo is in the same order that the tracks were added, and
+ // so should the output of getSenders.
+ this.expectedLocalTrackInfo.forEach((info, i) => {
+ const sender = sendersWithTrack[i];
+ is(sender, info.sender, `Sender ${i} should match`);
+ is(sender.track, info.track, `Track ${i} should match`);
+ });
+ },
+
+ /**
+ * Checks that we are getting the media tracks we expect.
+ */
+ checkMediaTracks() {
+ this.checkLocalMediaTracks();
+ },
+
+ checkLocalMsids() {
+ const sdp = this.localDescription.sdp;
+ const msections = sdputils.getMSections(sdp);
+ const expectedStreamIdCounts = new Map();
+ for (const { track, sender, streamId } of this.expectedLocalTrackInfo) {
+ const transceiver = this._pc
+ .getTransceivers()
+ .find(t => t.sender == sender);
+ ok(transceiver, "There should be a transceiver for each sender");
+ if (transceiver.mid) {
+ const midFinder = new RegExp(`^a=mid:${transceiver.mid}$`, "m");
+ const msection = msections.find(m => m.match(midFinder));
+ ok(
+ msection,
+ `There should be a media section for mid = ${transceiver.mid}`
+ );
+ ok(
+ msection.startsWith(`m=${track.kind}`),
+ `Media section should be of type ${track.kind}`
+ );
+ const msidFinder = new RegExp(`^a=msid:${streamId} \\S+$`, "m");
+ ok(
+ msection.match(msidFinder),
+ `Should find a=msid:${streamId} in media section` +
+ " (with any track id for now)"
+ );
+ const count = expectedStreamIdCounts.get(streamId) || 0;
+ expectedStreamIdCounts.set(streamId, count + 1);
+ }
+ }
+
+ // Check for any unexpected msids.
+ const allMsids = sdp.match(new RegExp("^a=msid:\\S+", "mg"));
+ if (!allMsids) {
+ return;
+ }
+ const allStreamIds = allMsids.map(msidAttr =>
+ msidAttr.replace("a=msid:", "")
+ );
+ allStreamIds.forEach(id => {
+ const count = expectedStreamIdCounts.get(id);
+ ok(count, `Unexpected stream id ${id} found in local description.`);
+ if (count) {
+ expectedStreamIdCounts.set(id, count - 1);
+ }
+ });
+ },
+
+ /**
+ * Check that media flow is present for the given media element by checking
+ * that it reaches ready state HAVE_ENOUGH_DATA and progresses time further
+ * than the start of the check.
+ *
+ * This ensures, that the stream being played is producing
+ * data and, in case it contains a video track, that at least one video frame
+ * has been displayed.
+ *
+ * @param {HTMLMediaElement} track
+ * The media element to check
+ * @returns {Promise}
+ * A promise that resolves when media data is flowing.
+ */
+ waitForMediaElementFlow(element) {
+ info("Checking data flow for element: " + element.id);
+ is(
+ element.ended,
+ !element.srcObject.active,
+ "Element ended should be the inverse of the MediaStream's active state"
+ );
+ if (element.ended) {
+ is(
+ element.readyState,
+ element.HAVE_CURRENT_DATA,
+ "Element " + element.id + " is ended and should have had data"
+ );
+ return Promise.resolve();
+ }
+
+ const haveEnoughData = (
+ element.readyState == element.HAVE_ENOUGH_DATA
+ ? Promise.resolve()
+ : haveEvent(
+ element,
+ "canplay",
+ wait(60000, new Error("Timeout for element " + element.id))
+ )
+ ).then(_ => info("Element " + element.id + " has enough data."));
+
+ const startTime = element.currentTime;
+ const timeProgressed = timeout(
+ listenUntil(element, "timeupdate", _ => element.currentTime > startTime),
+ 60000,
+ "Element " + element.id + " should progress currentTime"
+ ).then();
+
+ return Promise.all([haveEnoughData, timeProgressed]);
+ },
+
+ /**
+ * Wait for RTP packet flow for the given MediaStreamTrack.
+ *
+ * @param {object} track
+ * A MediaStreamTrack to wait for data flow on.
+ * @returns {Promise}
+ * Returns a promise which yields a StatsReport object with RTP stats.
+ */
+ async _waitForRtpFlow(target, rtpType) {
+ const { track } = target;
+ info(`_waitForRtpFlow(${track.id}, ${rtpType})`);
+ const packets = `packets${rtpType == "outbound-rtp" ? "Sent" : "Received"}`;
+
+ const retryInterval = 500; // Time between stats checks
+ const timeout = 30000; // Timeout in ms
+ const retries = timeout / retryInterval;
+
+ for (let i = 0; i < retries; i++) {
+ info(`Checking ${rtpType} for ${track.kind} track ${track.id} try ${i}`);
+ for (const rtp of (await target.getStats()).values()) {
+ if (rtp.type != rtpType) {
+ continue;
+ }
+ if (rtp.kind != track.kind) {
+ continue;
+ }
+
+ const numPackets = rtp[packets];
+ info(`Track ${track.id} has ${numPackets} ${packets}.`);
+ if (!numPackets) {
+ continue;
+ }
+
+ ok(true, `RTP flowing for ${track.kind} track ${track.id}`);
+ return;
+ }
+ await wait(retryInterval);
+ }
+ throw new Error(
+ `Checking stats for track ${track.id} timed out after ${timeout} ms`
+ );
+ },
+
+ /**
+ * Wait for inbound RTP packet flow for the given MediaStreamTrack.
+ *
+ * @param {object} receiver
+ * An RTCRtpReceiver to wait for data flow on.
+ * @returns {Promise}
+ * Returns a promise that resolves once data is flowing.
+ */
+ async waitForInboundRtpFlow(receiver) {
+ return this._waitForRtpFlow(receiver, "inbound-rtp");
+ },
+
+ /**
+ * Wait for outbound RTP packet flow for the given MediaStreamTrack.
+ *
+ * @param {object} sender
+ * An RTCRtpSender to wait for data flow on.
+ * @returns {Promise}
+ * Returns a promise that resolves once data is flowing.
+ */
+ async waitForOutboundRtpFlow(sender) {
+ return this._waitForRtpFlow(sender, "outbound-rtp");
+ },
+
+ getExpectedActiveReceivers() {
+ return this._pc
+ .getTransceivers()
+ .filter(
+ t =>
+ !t.stopped &&
+ t.currentDirection &&
+ t.currentDirection != "inactive" &&
+ t.currentDirection != "sendonly"
+ )
+ .filter(({ receiver }) => receiver.track)
+ .map(({ mid, currentDirection, receiver }) => {
+ info(
+ `Found transceiver that should be receiving RTP: mid=${mid}` +
+ ` currentDirection=${currentDirection}` +
+ ` kind=${receiver.track.kind} track-id=${receiver.track.id}`
+ );
+ return receiver;
+ });
+ },
+
+ getExpectedSenders() {
+ return this._pc.getSenders().filter(({ track }) => track);
+ },
+
+ /**
+ * Wait for presence of video flow on all media elements and rtp flow on
+ * all sending and receiving track involved in this test.
+ *
+ * @returns {Promise}
+ * A promise that resolves when media flows for all elements and tracks
+ */
+ waitForMediaFlow() {
+ const receivers = this.getExpectedActiveReceivers();
+ return Promise.all([
+ ...this.localMediaElements.map(el => this.waitForMediaElementFlow(el)),
+ ...this.remoteMediaElements
+ .filter(({ srcObject }) =>
+ receivers.some(({ track }) =>
+ srcObject.getTracks().some(t => t == track)
+ )
+ )
+ .map(el => this.waitForMediaElementFlow(el)),
+ ...receivers.map(receiver => this.waitForInboundRtpFlow(receiver)),
+ ...this.getExpectedSenders().map(sender =>
+ this.waitForOutboundRtpFlow(sender)
+ ),
+ ]);
+ },
+
+ /**
+ * Check that correct audio (typically a flat tone) is flowing to this
+ * PeerConnection for each transceiver that should be receiving. Uses
+ * WebAudio AnalyserNodes to compare input and output audio data in the
+ * frequency domain.
+ *
+ * @param {object} from
+ * A PeerConnectionWrapper whose audio RTPSender we use as source for
+ * the audio flow check.
+ * @returns {Promise}
+ * A promise that resolves when we're receiving the tone/s from |from|.
+ */
+ async checkReceivingToneFrom(
+ audiocontext,
+ from,
+ cancel = wait(60000, new Error("Tone not detected"))
+ ) {
+ let localTransceivers = this._pc
+ .getTransceivers()
+ .filter(t => t.mid)
+ .filter(t => t.receiver.track.kind == "audio")
+ .sort((t1, t2) => t1.mid < t2.mid);
+ let remoteTransceivers = from._pc
+ .getTransceivers()
+ .filter(t => t.mid)
+ .filter(t => t.receiver.track.kind == "audio")
+ .sort((t1, t2) => t1.mid < t2.mid);
+
+ is(
+ localTransceivers.length,
+ remoteTransceivers.length,
+ "Same number of associated audio transceivers on remote and local."
+ );
+
+ for (let i = 0; i < localTransceivers.length; i++) {
+ is(
+ localTransceivers[i].mid,
+ remoteTransceivers[i].mid,
+ "Transceivers at index " + i + " have the same mid."
+ );
+
+ if (!remoteTransceivers[i].sender.track) {
+ continue;
+ }
+
+ if (
+ remoteTransceivers[i].currentDirection == "recvonly" ||
+ remoteTransceivers[i].currentDirection == "inactive"
+ ) {
+ continue;
+ }
+
+ let sendTrack = remoteTransceivers[i].sender.track;
+ let inputElem = from.getMediaElementForTrack(sendTrack, "local");
+ ok(
+ inputElem,
+ "Remote wrapper should have a media element for track id " +
+ sendTrack.id
+ );
+ let inputAudioStream = from.getStreamForSendTrack(sendTrack);
+ ok(
+ inputAudioStream,
+ "Remote wrapper should have a stream for track id " + sendTrack.id
+ );
+ let inputAnalyser = new AudioStreamAnalyser(
+ audiocontext,
+ inputAudioStream
+ );
+
+ let recvTrack = localTransceivers[i].receiver.track;
+ let outputAudioStream = this.getStreamForRecvTrack(recvTrack);
+ ok(
+ outputAudioStream,
+ "Local wrapper should have a stream for track id " + recvTrack.id
+ );
+ let outputAnalyser = new AudioStreamAnalyser(
+ audiocontext,
+ outputAudioStream
+ );
+
+ let error = null;
+ cancel.then(e => (error = e));
+
+ let indexOfMax = data =>
+ data.reduce((max, val, i) => (val >= data[max] ? i : max), 0);
+
+ await outputAnalyser.waitForAnalysisSuccess(() => {
+ if (error) {
+ throw error;
+ }
+
+ let inputData = inputAnalyser.getByteFrequencyData();
+ let outputData = outputAnalyser.getByteFrequencyData();
+
+ let inputMax = indexOfMax(inputData);
+ let outputMax = indexOfMax(outputData);
+ info(
+ `Comparing maxima; input[${inputMax}] = ${inputData[inputMax]},` +
+ ` output[${outputMax}] = ${outputData[outputMax]}`
+ );
+ if (!inputData[inputMax] || !outputData[outputMax]) {
+ return false;
+ }
+
+ // When the input and output maxima are within reasonable distance (2% of
+ // total length, which means ~10 for length 512) from each other, we can
+ // be sure that the input tone has made it through the peer connection.
+ info(`input data length: ${inputData.length}`);
+ return Math.abs(inputMax - outputMax) < inputData.length * 0.02;
+ });
+ }
+ },
+
+ /**
+ * Check that stats are present by checking for known stats.
+ */
+ async getStats(selector) {
+ const stats = await this._pc.getStats(selector);
+ const dict = {};
+ for (const [k, v] of stats.entries()) {
+ dict[k] = v;
+ }
+ info(`${this}: Got stats: ${JSON.stringify(dict)}`);
+ return stats;
+ },
+
+ /**
+ * Checks that we are getting the media streams we expect.
+ *
+ * @param {object} stats
+ * The stats to check from this PeerConnectionWrapper
+ */
+ checkStats(stats) {
+ const isRemote = ({ type }) =>
+ ["remote-outbound-rtp", "remote-inbound-rtp"].includes(type);
+ var counters = {};
+ for (let [key, res] of stats) {
+ info("Checking stats for " + key + " : " + res);
+ // validate stats
+ ok(res.id == key, "Coherent stats id");
+ const now = performance.timeOrigin + performance.now();
+ const minimum = performance.timeOrigin;
+ const type = isRemote(res) ? "rtcp" : "rtp";
+ ok(
+ res.timestamp >= minimum,
+ `Valid ${type} timestamp ${res.timestamp} >= ${minimum} (
+ ${res.timestamp - minimum} ms)`
+ );
+ ok(
+ res.timestamp <= now,
+ `Valid ${type} timestamp ${res.timestamp} <= ${now} (
+ ${res.timestamp - now} ms)`
+ );
+ if (isRemote(res)) {
+ continue;
+ }
+ counters[res.type] = (counters[res.type] || 0) + 1;
+
+ switch (res.type) {
+ case "inbound-rtp":
+ case "outbound-rtp":
+ {
+ // Inbound tracks won't have an ssrc if RTP is not flowing.
+ // (eg; negotiated inactive)
+ ok(
+ res.ssrc || res.type == "inbound-rtp",
+ "Outbound RTP stats has an ssrc."
+ );
+
+ if (res.ssrc) {
+ // ssrc is a 32 bit number returned as an unsigned long
+ ok(!/[^0-9]/.test(`${res.ssrc}`), "SSRC is numeric");
+ ok(parseInt(res.ssrc) < Math.pow(2, 32), "SSRC is within limits");
+ }
+
+ if (res.type == "outbound-rtp") {
+ ok(res.packetsSent !== undefined, "Rtp packetsSent");
+ // We assume minimum payload to be 1 byte (guess from RFC 3550)
+ ok(res.bytesSent >= res.packetsSent, "Rtp bytesSent");
+ } else {
+ ok(res.packetsReceived !== undefined, "Rtp packetsReceived");
+ ok(res.bytesReceived >= res.packetsReceived, "Rtp bytesReceived");
+ }
+ if (res.remoteId) {
+ var rem = stats.get(res.remoteId);
+ ok(isRemote(rem), "Remote is rtcp");
+ ok(rem.localId == res.id, "Remote backlink match");
+ if (res.type == "outbound-rtp") {
+ ok(rem.type == "remote-inbound-rtp", "Rtcp is inbound");
+ if (rem.packetsLost) {
+ ok(
+ rem.packetsLost >= 0,
+ "Rtcp packetsLost " + rem.packetsLost + " >= 0"
+ );
+ ok(
+ rem.packetsLost < 1000,
+ "Rtcp packetsLost " + rem.packetsLost + " < 1000"
+ );
+ }
+ if (!this.disableRtpCountChecking) {
+ // no guarantee which one is newer!
+ // Note: this must change when we add a timestamp field to remote RTCP reports
+ // and make rem.timestamp be the reception time
+ if (res.timestamp < rem.timestamp) {
+ info(
+ "REVERSED timestamps: rec:" +
+ rem.packetsReceived +
+ " time:" +
+ rem.timestamp +
+ " sent:" +
+ res.packetsSent +
+ " time:" +
+ res.timestamp
+ );
+ }
+ }
+ if (rem.jitter) {
+ ok(rem.jitter >= 0, "Rtcp jitter " + rem.jitter + " >= 0");
+ ok(rem.jitter < 5, "Rtcp jitter " + rem.jitter + " < 5 sec");
+ }
+ if (rem.roundTripTime) {
+ ok(
+ rem.roundTripTime >= 0,
+ "Rtcp rtt " + rem.roundTripTime + " >= 0"
+ );
+ ok(
+ rem.roundTripTime < 60,
+ "Rtcp rtt " + rem.roundTripTime + " < 1 min"
+ );
+ }
+ } else {
+ ok(rem.type == "remote-outbound-rtp", "Rtcp is outbound");
+ ok(rem.packetsSent !== undefined, "Rtcp packetsSent");
+ ok(rem.bytesSent !== undefined, "Rtcp bytesSent");
+ }
+ ok(rem.ssrc == res.ssrc, "Remote ssrc match");
+ } else {
+ info("No rtcp info received yet");
+ }
+ }
+ break;
+ }
+ }
+
+ var nin = this._pc.getTransceivers().filter(t => {
+ return (
+ !t.stopped &&
+ t.currentDirection != "inactive" &&
+ t.currentDirection != "sendonly"
+ );
+ }).length;
+ const nout = Object.keys(this.expectedLocalTrackInfo).length;
+ var ndata = this.dataChannels.length;
+
+ // TODO(Bug 957145): Restore stronger inbound-rtp test once Bug 948249 is fixed
+ //is((counters["inbound-rtp"] || 0), nin, "Have " + nin + " inbound-rtp stat(s)");
+ ok(
+ (counters["inbound-rtp"] || 0) >= nin,
+ "Have at least " + nin + " inbound-rtp stat(s) *"
+ );
+
+ is(
+ counters["outbound-rtp"] || 0,
+ nout,
+ "Have " + nout + " outbound-rtp stat(s)"
+ );
+
+ var numLocalCandidates = counters["local-candidate"] || 0;
+ var numRemoteCandidates = counters["remote-candidate"] || 0;
+ // If there are no tracks, there will be no stats either.
+ if (nin + nout + ndata > 0) {
+ ok(numLocalCandidates, "Have local-candidate stat(s)");
+ ok(numRemoteCandidates, "Have remote-candidate stat(s)");
+ } else {
+ is(numLocalCandidates, 0, "Have no local-candidate stats");
+ is(numRemoteCandidates, 0, "Have no remote-candidate stats");
+ }
+ },
+
+ /**
+ * Compares the Ice server configured for this PeerConnectionWrapper
+ * with the ICE candidates received in the RTCP stats.
+ *
+ * @param {object} stats
+ * The stats to be verified for relayed vs. direct connection.
+ */
+ checkStatsIceConnectionType(stats, expectedLocalCandidateType) {
+ let lId;
+ let rId;
+ for (let stat of stats.values()) {
+ if (stat.type == "candidate-pair" && stat.selected) {
+ lId = stat.localCandidateId;
+ rId = stat.remoteCandidateId;
+ break;
+ }
+ }
+ isnot(
+ lId,
+ undefined,
+ "Got local candidate ID " + lId + " for selected pair"
+ );
+ isnot(
+ rId,
+ undefined,
+ "Got remote candidate ID " + rId + " for selected pair"
+ );
+ let lCand = stats.get(lId);
+ let rCand = stats.get(rId);
+ if (!lCand || !rCand) {
+ ok(
+ false,
+ "failed to find candidatepair IDs or stats for local: " +
+ lId +
+ " remote: " +
+ rId
+ );
+ return;
+ }
+
+ info(
+ "checkStatsIceConnectionType verifying: local=" +
+ JSON.stringify(lCand) +
+ " remote=" +
+ JSON.stringify(rCand)
+ );
+ expectedLocalCandidateType = expectedLocalCandidateType || "host";
+ var candidateType = lCand.candidateType;
+ if (lCand.relayProtocol === "tcp" && candidateType === "relay") {
+ candidateType = "relay-tcp";
+ }
+
+ if (lCand.relayProtocol === "tls" && candidateType === "relay") {
+ candidateType = "relay-tls";
+ }
+
+ if (expectedLocalCandidateType === "srflx" && candidateType === "prflx") {
+ // Be forgiving of prflx when expecting srflx, since that can happen due
+ // to timing.
+ candidateType = "srflx";
+ }
+
+ is(
+ candidateType,
+ expectedLocalCandidateType,
+ "Local candidate type is what we expected for selected pair"
+ );
+ },
+
+ /**
+ * Compares amount of established ICE connection according to ICE candidate
+ * pairs in the stats reporting with the expected amount of connection based
+ * on the constraints.
+ *
+ * @param {object} stats
+ * The stats to check for ICE candidate pairs
+ * @param {object} testOptions
+ * The test options object from the PeerConnectionTest
+ */
+ checkStatsIceConnections(stats, testOptions) {
+ var numIceConnections = 0;
+ stats.forEach(stat => {
+ if (stat.type === "candidate-pair" && stat.selected) {
+ numIceConnections += 1;
+ }
+ });
+ info("ICE connections according to stats: " + numIceConnections);
+ isnot(
+ numIceConnections,
+ 0,
+ "Number of ICE connections according to stats is not zero"
+ );
+ if (testOptions.bundle) {
+ if (testOptions.rtcpmux) {
+ is(numIceConnections, 1, "stats reports exactly 1 ICE connection");
+ } else {
+ is(
+ numIceConnections,
+ 2,
+ "stats report exactly 2 ICE connections for media and RTCP"
+ );
+ }
+ } else {
+ var numAudioTransceivers = this._pc
+ .getTransceivers()
+ .filter(transceiver => {
+ return (
+ !transceiver.stopped && transceiver.receiver.track.kind == "audio"
+ );
+ }).length;
+
+ var numVideoTransceivers = this._pc
+ .getTransceivers()
+ .filter(transceiver => {
+ return (
+ !transceiver.stopped && transceiver.receiver.track.kind == "video"
+ );
+ }).length;
+
+ var numExpectedTransports = numAudioTransceivers + numVideoTransceivers;
+ if (!testOptions.rtcpmux) {
+ numExpectedTransports *= 2;
+ }
+
+ if (this.dataChannels.length) {
+ ++numExpectedTransports;
+ }
+
+ info(
+ "expected audio + video + data transports: " + numExpectedTransports
+ );
+ is(
+ numIceConnections,
+ numExpectedTransports,
+ "stats ICE connections matches expected A/V transports"
+ );
+ }
+ },
+
+ expectNegotiationNeeded() {
+ if (!this.observedNegotiationNeeded) {
+ this.observedNegotiationNeeded = new Promise(resolve => {
+ this.onnegotiationneeded = resolve;
+ });
+ }
+ },
+
+ /**
+ * Property-matching function for finding a certain stat in passed-in stats
+ *
+ * @param {object} stats
+ * The stats to check from this PeerConnectionWrapper
+ * @param {object} props
+ * The properties to look for
+ * @returns {boolean} Whether an entry containing all match-props was found.
+ */
+ hasStat(stats, props) {
+ for (let res of stats.values()) {
+ var match = true;
+ for (let prop in props) {
+ if (res[prop] !== props[prop]) {
+ match = false;
+ break;
+ }
+ }
+ if (match) {
+ return true;
+ }
+ }
+ return false;
+ },
+
+ /**
+ * Closes the connection
+ */
+ close() {
+ this._pc.close();
+ this.localMediaElements.forEach(e => e.pause());
+ info(this + ": Closed connection.");
+ },
+
+ /**
+ * Returns the string representation of the class
+ *
+ * @returns {String} The string representation
+ */
+ toString() {
+ return "PeerConnectionWrapper (" + this.label + ")";
+ },
+};
+
+// haxx to prevent SimpleTest from failing at window.onload
+function addLoadEvent() {}
+
+function loadScript(...scripts) {
+ return Promise.all(
+ scripts.map(script => {
+ var el = document.createElement("script");
+ if (typeof scriptRelativePath === "string" && script.charAt(0) !== "/") {
+ script = scriptRelativePath + script;
+ }
+ el.src = script;
+ document.head.appendChild(el);
+ return new Promise(r => {
+ el.onload = r;
+ el.onerror = r;
+ });
+ })
+ );
+}
+
+// Ensure SimpleTest.js is loaded before other scripts.
+/* import-globals-from /testing/mochitest/tests/SimpleTest/SimpleTest.js */
+/* import-globals-from head.js */
+/* import-globals-from templates.js */
+/* import-globals-from turnConfig.js */
+/* import-globals-from dataChannel.js */
+/* import-globals-from network.js */
+/* import-globals-from sdpUtils.js */
+
+var scriptsReady = loadScript("/tests/SimpleTest/SimpleTest.js").then(() => {
+ return loadScript(
+ "head.js",
+ "templates.js",
+ "turnConfig.js",
+ "dataChannel.js",
+ "network.js",
+ "sdpUtils.js"
+ );
+});
+
+function createHTML(options) {
+ return scriptsReady.then(() => realCreateHTML(options));
+}
+
+var iceServerWebsocket;
+var iceServersArray = [];
+
+var addTurnsSelfsignedCerts = () => {
+ var gUrl = SimpleTest.getTestFileURL("addTurnsSelfsignedCert.js");
+ var gScript = SpecialPowers.loadChromeScript(gUrl);
+ var certs = [];
+ // If the ICE server is running TURNS, and includes a "cert" attribute in
+ // its JSON, we set up an override that will forgive things like
+ // self-signed for it.
+ iceServersArray.forEach(iceServer => {
+ if (iceServer.hasOwnProperty("cert")) {
+ iceServer.urls.forEach(url => {
+ if (url.startsWith("turns:")) {
+ // Assumes no port or params!
+ certs.push({ cert: iceServer.cert, hostname: url.substr(6) });
+ }
+ });
+ }
+ });
+
+ return new Promise((resolve, reject) => {
+ gScript.addMessageListener("certs-added", () => {
+ resolve();
+ });
+
+ gScript.sendAsyncMessage("add-turns-certs", certs);
+ });
+};
+
+var setupIceServerConfig = useIceServer => {
+ // We disable ICE support for HTTP proxy when using a TURN server, because
+ // mochitest uses a fake HTTP proxy to serve content, which will eat our STUN
+ // packets for TURN TCP.
+ var enableHttpProxy = enable =>
+ SpecialPowers.pushPrefEnv({
+ set: [["media.peerconnection.disable_http_proxy", !enable]],
+ });
+
+ var spawnIceServer = () =>
+ new Promise((resolve, reject) => {
+ iceServerWebsocket = new WebSocket("ws://localhost:8191/");
+ iceServerWebsocket.onopen = event => {
+ info("websocket/process bridge open, starting ICE Server...");
+ iceServerWebsocket.send("iceserver");
+ };
+
+ iceServerWebsocket.onmessage = event => {
+ // The first message will contain the iceServers configuration, subsequent
+ // messages are just logging.
+ info("ICE Server: " + event.data);
+ resolve(event.data);
+ };
+
+ iceServerWebsocket.onerror = () => {
+ reject("ICE Server error: Is the ICE server websocket up?");
+ };
+
+ iceServerWebsocket.onclose = () => {
+ info("ICE Server websocket closed");
+ reject("ICE Server gone before getting configuration");
+ };
+ });
+
+ if (!useIceServer) {
+ info("Skipping ICE Server for this test");
+ return enableHttpProxy(true);
+ }
+
+ return enableHttpProxy(false)
+ .then(spawnIceServer)
+ .then(iceServersStr => {
+ iceServersArray = JSON.parse(iceServersStr);
+ })
+ .then(addTurnsSelfsignedCerts);
+};
+
+async function runNetworkTest(testFunction, fixtureOptions = {}) {
+ let { AppConstants } = SpecialPowers.ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+ );
+
+ await scriptsReady;
+ await runTestWhenReady(async options => {
+ await startNetworkAndTest();
+ await setupIceServerConfig(fixtureOptions.useIceServer);
+ await testFunction(options);
+ await networkTestFinished();
+ });
+}
diff --git a/dom/media/webrtc/tests/mochitests/peerconnection_audio_forced_sample_rate.js b/dom/media/webrtc/tests/mochitests/peerconnection_audio_forced_sample_rate.js
new file mode 100644
index 0000000000..d0c647be0d
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/peerconnection_audio_forced_sample_rate.js
@@ -0,0 +1,32 @@
+// This function takes a sample-rate, and tests that audio flows correctly when
+// the sampling-rate at which the MTG runs is not one of the sampling-rates that
+// the MediaPipeline can work with.
+// It is in a separate file because we have an MTG per document, and we want to
+// test multiple sample-rates, so we include it in multiple HTML mochitest
+// files.
+async function test_peerconnection_audio_forced_sample_rate(forcedSampleRate) {
+ await scriptsReady;
+ await pushPrefs(["media.cubeb.force_sample_rate", forcedSampleRate]);
+ await runNetworkTest(function (options) {
+ const test = new PeerConnectionTest(options);
+ const ac = new AudioContext();
+ test.setMediaConstraints([{ audio: true }], []);
+ test.chain.replace("PC_LOCAL_GUM", [
+ function PC_LOCAL_WEBAUDIO_SOURCE(test) {
+ const oscillator = ac.createOscillator();
+ oscillator.type = "sine";
+ oscillator.frequency.value = 700;
+ oscillator.start();
+ const dest = ac.createMediaStreamDestination();
+ oscillator.connect(dest);
+ test.pcLocal.attachLocalStream(dest.stream);
+ },
+ ]);
+ test.chain.append([
+ function CHECK_REMOTE_AUDIO_FLOW(test) {
+ return test.pcRemote.checkReceivingToneFrom(ac, test.pcLocal);
+ },
+ ]);
+ return test.run();
+ });
+}
diff --git a/dom/media/webrtc/tests/mochitests/sdpUtils.js b/dom/media/webrtc/tests/mochitests/sdpUtils.js
new file mode 100644
index 0000000000..51cae10dba
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/sdpUtils.js
@@ -0,0 +1,398 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var sdputils = {
+ // Finds the codec id / payload type given a codec format
+ // (e.g., "VP8", "VP9/90000"). `offset` tells us which one to use in case of
+ // multiple matches.
+ findCodecId(sdp, format, offset = 0) {
+ let regex = new RegExp("rtpmap:([0-9]+) " + format, "gi");
+ let match;
+ for (let i = 0; i <= offset; ++i) {
+ match = regex.exec(sdp);
+ if (!match) {
+ throw new Error(
+ "Couldn't find offset " +
+ i +
+ " of codec " +
+ format +
+ " while looking for offset " +
+ offset +
+ " in sdp:\n" +
+ sdp
+ );
+ }
+ }
+ // match[0] is the full matched string
+ // match[1] is the first parenthesis group
+ return match[1];
+ },
+
+ // Returns a list of all payload types, excluding rtx, in an sdp.
+ getPayloadTypes(sdp) {
+ const regex = /^a=rtpmap:([0-9]+) (?:(?!rtx).)*$/gim;
+ const pts = [];
+ for (const [line, pt] of sdp.matchAll(regex)) {
+ pts.push(pt);
+ }
+ return pts;
+ },
+
+ // Finds all the extmap ids in the given sdp. Note that this does NOT
+ // consider m-sections, so a more generic version would need to
+ // look at each m-section separately.
+ findExtmapIds(sdp) {
+ var sdpExtmapIds = [];
+ extmapRegEx = /^a=extmap:([0-9+])/gm;
+ // must call exec on the regex to get each match in the string
+ while ((searchResults = extmapRegEx.exec(sdp)) !== null) {
+ // returned array has the matched text as the first item,
+ // and then one item for each capturing parenthesis that
+ // matched containing the text that was captured.
+ sdpExtmapIds.push(searchResults[1]);
+ }
+ return sdpExtmapIds;
+ },
+
+ findExtmapIdsUrnsDirections(sdp) {
+ var sdpExtmap = [];
+ extmapRegEx = /^a=extmap:([0-9+])([A-Za-z/]*) ([A-Za-z0-9_:\-\/\.]+)/gm;
+ // must call exec on the regex to get each match in the string
+ while ((searchResults = extmapRegEx.exec(sdp)) !== null) {
+ // returned array has the matched text as the first item,
+ // and then one item for each capturing parenthesis that
+ // matched containing the text that was captured.
+ var idUrn = [];
+ idUrn.push(searchResults[1]);
+ idUrn.push(searchResults[3]);
+ idUrn.push(searchResults[2].slice(1));
+ sdpExtmap.push(idUrn);
+ }
+ return sdpExtmap;
+ },
+
+ verify_unique_extmap_ids(sdp) {
+ const sdpExtmapIds = sdputils.findExtmapIdsUrnsDirections(sdp);
+
+ return sdpExtmapIds.reduce(function (result, item, index) {
+ const [id, urn, dir] = item;
+ ok(
+ !(id in result) || (result[id][0] === urn && result[id][1] === dir),
+ "ID " + id + " is unique ID for " + urn + " and direction " + dir
+ );
+ result[id] = [urn, dir];
+ return result;
+ }, {});
+ },
+
+ getMSections(sdp) {
+ return sdp
+ .split(new RegExp("^m=", "gm"))
+ .slice(1)
+ .map(s => "m=" + s);
+ },
+
+ getAudioMSections(sdp) {
+ return this.getMSections(sdp).filter(section =>
+ section.startsWith("m=audio")
+ );
+ },
+
+ getVideoMSections(sdp) {
+ return this.getMSections(sdp).filter(section =>
+ section.startsWith("m=video")
+ );
+ },
+
+ checkSdpAfterEndOfTrickle(description, testOptions, label) {
+ info("EOC-SDP: " + JSON.stringify(description));
+
+ const checkForTransportAttributes = msection => {
+ info("Checking msection: " + msection);
+ ok(
+ msection.includes("a=end-of-candidates"),
+ label + ": SDP contains end-of-candidates"
+ );
+
+ if (!msection.startsWith("m=application")) {
+ if (testOptions.rtcpmux) {
+ ok(
+ msection.includes("a=rtcp-mux"),
+ label + ": SDP contains rtcp-mux"
+ );
+ } else {
+ ok(msection.includes("a=rtcp:"), label + ": SDP contains rtcp port");
+ }
+ }
+ };
+
+ const hasOwnTransport = msection => {
+ const port0Check = new RegExp(/^m=\S+ 0 /).exec(msection);
+ if (port0Check) {
+ return false;
+ }
+ const midMatch = new RegExp(/\r\na=mid:(\S+)/).exec(msection);
+ if (!midMatch) {
+ return true;
+ }
+ const mid = midMatch[1];
+ const bundleGroupMatch = new RegExp(
+ "\\r\\na=group:BUNDLE \\S.* " + mid + "\\s+"
+ ).exec(description.sdp);
+ return bundleGroupMatch == null;
+ };
+
+ const msectionsWithOwnTransports = this.getMSections(
+ description.sdp
+ ).filter(hasOwnTransport);
+
+ ok(
+ msectionsWithOwnTransports.length,
+ "SDP should contain at least one msection with a transport"
+ );
+ msectionsWithOwnTransports.forEach(checkForTransportAttributes);
+
+ if (testOptions.ssrc) {
+ ok(description.sdp.includes("a=ssrc"), label + ": SDP contains a=ssrc");
+ } else {
+ ok(
+ !description.sdp.includes("a=ssrc"),
+ label + ": SDP does not contain a=ssrc"
+ );
+ }
+ },
+
+ // Note, we don't bother removing the fmtp lines, which makes a good test
+ // for some SDP parsing issues.
+ removeCodec(sdp, codec) {
+ var updated_sdp = sdp.replace(
+ new RegExp("a=rtpmap:" + codec + ".*\\/90000\\r\\n", ""),
+ ""
+ );
+ updated_sdp = updated_sdp.replace(
+ new RegExp("(RTP\\/SAVPF.*)( " + codec + ")(.*\\r\\n)", ""),
+ "$1$3"
+ );
+ updated_sdp = updated_sdp.replace(
+ new RegExp("a=rtcp-fb:" + codec + " nack\\r\\n", ""),
+ ""
+ );
+ updated_sdp = updated_sdp.replace(
+ new RegExp("a=rtcp-fb:" + codec + " nack pli\\r\\n", ""),
+ ""
+ );
+ updated_sdp = updated_sdp.replace(
+ new RegExp("a=rtcp-fb:" + codec + " ccm fir\\r\\n", ""),
+ ""
+ );
+ return updated_sdp;
+ },
+
+ removeAllButPayloadType(sdp, pt) {
+ return sdp.replace(
+ new RegExp("m=(\\w+ \\w+) UDP/TLS/RTP/SAVPF .*" + pt + ".*\\r\\n", "gi"),
+ "m=$1 UDP/TLS/RTP/SAVPF " + pt + "\r\n"
+ );
+ },
+
+ removeRtpMapForPayloadType(sdp, pt) {
+ return sdp.replace(new RegExp("a=rtpmap:" + pt + ".*\\r\\n", "gi"), "");
+ },
+
+ removeRtcpMux(sdp) {
+ return sdp.replace(/a=rtcp-mux\r\n/g, "");
+ },
+
+ removeSSRCs(sdp) {
+ return sdp.replace(/a=ssrc.*\r\n/g, "");
+ },
+
+ removeBundle(sdp) {
+ return sdp.replace(/a=group:BUNDLE .*\r\n/g, "");
+ },
+
+ reduceAudioMLineToPcmuPcma(sdp) {
+ return sdp.replace(
+ /m=audio .*\r\n/g,
+ "m=audio 9 UDP/TLS/RTP/SAVPF 0 8\r\n"
+ );
+ },
+
+ setAllMsectionsInactive(sdp) {
+ return sdp
+ .replace(/\r\na=sendrecv/g, "\r\na=inactive")
+ .replace(/\r\na=sendonly/g, "\r\na=inactive")
+ .replace(/\r\na=recvonly/g, "\r\na=inactive");
+ },
+
+ removeAllRtpMaps(sdp) {
+ return sdp.replace(/a=rtpmap:.*\r\n/g, "");
+ },
+
+ reduceAudioMLineToDynamicPtAndOpus(sdp) {
+ return sdp.replace(
+ /m=audio .*\r\n/g,
+ "m=audio 9 UDP/TLS/RTP/SAVPF 101 109\r\n"
+ );
+ },
+
+ addTiasBps(sdp, bps) {
+ return sdp.replace(/c=IN (.*)\r\n/g, "c=IN $1\r\nb=TIAS:" + bps + "\r\n");
+ },
+
+ removeSimulcastProperties(sdp) {
+ return sdp
+ .replace(/a=simulcast:.*\r\n/g, "")
+ .replace(/a=rid:.*\r\n/g, "")
+ .replace(
+ /a=extmap:[^\s]* urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id.*\r\n/g,
+ ""
+ )
+ .replace(
+ /a=extmap:[^\s]* urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id.*\r\n/g,
+ ""
+ );
+ },
+
+ transferSimulcastProperties(offer_sdp, answer_sdp) {
+ if (!offer_sdp.includes("a=simulcast:")) {
+ return answer_sdp;
+ }
+ ok(
+ offer_sdp.includes("a=simulcast:send "),
+ "Offer contains simulcast attribute"
+ );
+ var o_simul = offer_sdp.match(/simulcast:send (.*)([\n$])*/i);
+ var new_answer_sdp = answer_sdp + "a=simulcast:recv " + o_simul[1] + "\r\n";
+ ok(offer_sdp.includes("a=rid:"), "Offer contains RID attribute");
+ var o_rids = offer_sdp.match(/a=rid:(.*)/gi);
+ o_rids.forEach(o_rid => {
+ new_answer_sdp = new_answer_sdp + o_rid.replace(/send/, "recv") + "\r\n";
+ });
+ var extmap_id = offer_sdp.match(
+ "a=extmap:([0-9+])/sendonly urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id"
+ );
+ ok(extmap_id != null, "Offer contains RID RTP header extension");
+ new_answer_sdp =
+ new_answer_sdp +
+ "a=extmap:" +
+ extmap_id[1] +
+ "/recvonly urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id\r\n";
+ var extmap_id = offer_sdp.match(
+ "a=extmap:([0-9+])/sendonly urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id"
+ );
+ if (extmap_id != null) {
+ new_answer_sdp =
+ new_answer_sdp +
+ "a=extmap:" +
+ extmap_id[1] +
+ "/recvonly urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id\r\n";
+ }
+
+ return new_answer_sdp;
+ },
+
+ verifySdp(
+ desc,
+ expectedType,
+ offerConstraintsList,
+ offerOptions,
+ testOptions
+ ) {
+ info("Examining this SessionDescription: " + JSON.stringify(desc));
+ info("offerConstraintsList: " + JSON.stringify(offerConstraintsList));
+ info("offerOptions: " + JSON.stringify(offerOptions));
+ ok(desc, "SessionDescription is not null");
+ is(desc.type, expectedType, "SessionDescription type is " + expectedType);
+ ok(desc.sdp.length > 10, "SessionDescription body length is plausible");
+ ok(desc.sdp.includes("a=ice-ufrag"), "ICE username is present in SDP");
+ ok(desc.sdp.includes("a=ice-pwd"), "ICE password is present in SDP");
+ ok(desc.sdp.includes("a=fingerprint"), "ICE fingerprint is present in SDP");
+ //TODO: update this for loopback support bug 1027350
+ ok(
+ !desc.sdp.includes(LOOPBACK_ADDR),
+ "loopback interface is absent from SDP"
+ );
+ var requiresTrickleIce = !desc.sdp.includes("a=candidate");
+ if (requiresTrickleIce) {
+ info("No ICE candidate in SDP -> requiring trickle ICE");
+ } else {
+ info("at least one ICE candidate is present in SDP");
+ }
+
+ //TODO: how can we check for absence/presence of m=application?
+
+ var audioTracks =
+ sdputils.countTracksInConstraint("audio", offerConstraintsList) ||
+ (offerOptions && offerOptions.offerToReceiveAudio ? 1 : 0);
+
+ info("expected audio tracks: " + audioTracks);
+ if (audioTracks == 0) {
+ ok(!desc.sdp.includes("m=audio"), "audio m-line is absent from SDP");
+ } else {
+ ok(desc.sdp.includes("m=audio"), "audio m-line is present in SDP");
+ is(
+ testOptions.opus,
+ desc.sdp.includes("a=rtpmap:109 opus/48000/2"),
+ "OPUS codec is present in SDP"
+ );
+ //TODO: ideally the rtcp-mux should be for the m=audio, and not just
+ // anywhere in the SDP (JS SDP parser bug 1045429)
+ is(
+ testOptions.rtcpmux,
+ desc.sdp.includes("a=rtcp-mux"),
+ "RTCP Mux is offered in SDP"
+ );
+ }
+
+ var videoTracks =
+ sdputils.countTracksInConstraint("video", offerConstraintsList) ||
+ (offerOptions && offerOptions.offerToReceiveVideo ? 1 : 0);
+
+ info("expected video tracks: " + videoTracks);
+ if (videoTracks == 0) {
+ ok(!desc.sdp.includes("m=video"), "video m-line is absent from SDP");
+ } else {
+ ok(desc.sdp.includes("m=video"), "video m-line is present in SDP");
+ if (testOptions.h264) {
+ ok(
+ desc.sdp.includes("a=rtpmap:126 H264/90000") ||
+ desc.sdp.includes("a=rtpmap:97 H264/90000"),
+ "H.264 codec is present in SDP"
+ );
+ } else {
+ ok(
+ desc.sdp.includes("a=rtpmap:120 VP8/90000") ||
+ desc.sdp.includes("a=rtpmap:121 VP9/90000"),
+ "VP8 or VP9 codec is present in SDP"
+ );
+ }
+ is(
+ testOptions.rtcpmux,
+ desc.sdp.includes("a=rtcp-mux"),
+ "RTCP Mux is offered in SDP"
+ );
+ is(
+ testOptions.ssrc,
+ desc.sdp.includes("a=ssrc"),
+ "a=ssrc signaled in SDP"
+ );
+ }
+
+ return requiresTrickleIce;
+ },
+
+ /**
+ * Counts the amount of audio tracks in a given media constraint.
+ *
+ * @param constraints
+ * The contraint to be examined.
+ */
+ countTracksInConstraint(type, constraints) {
+ if (!Array.isArray(constraints)) {
+ return 0;
+ }
+ return constraints.reduce((sum, c) => sum + (c[type] ? 1 : 0), 0);
+ },
+};
diff --git a/dom/media/webrtc/tests/mochitests/simulcast.js b/dom/media/webrtc/tests/mochitests/simulcast.js
new file mode 100644
index 0000000000..0af36478c4
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/simulcast.js
@@ -0,0 +1,232 @@
+"use strict";
+/* Helper functions to munge SDP and split the sending track into
+ * separate tracks on the receiving end. This can be done in a number
+ * of ways, the one used here uses the fact that the MID and RID header
+ * extensions which are used for packet routing share the same wire
+ * format. The receiver interprets the rids from the sender as mids
+ * which allows receiving the different spatial resolutions on separate
+ * m-lines and tracks.
+ */
+
+// Borrowed from wpt, with some dependencies removed.
+
+const ridExtensions = [
+ "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id",
+ "urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id",
+];
+
+function ridToMid(description, rids) {
+ const sections = SDPUtils.splitSections(description.sdp);
+ const dtls = SDPUtils.getDtlsParameters(sections[1], sections[0]);
+ const ice = SDPUtils.getIceParameters(sections[1], sections[0]);
+ const rtpParameters = SDPUtils.parseRtpParameters(sections[1]);
+ const setupValue = description.sdp.match(/a=setup:(.*)/)[1];
+ const directionValue =
+ description.sdp.match(/a=sendrecv|a=sendonly|a=recvonly|a=inactive/) ||
+ "a=sendrecv";
+ const mline = SDPUtils.parseMLine(sections[1]);
+
+ // Skip mid extension; we are replacing it with the rid extmap
+ rtpParameters.headerExtensions = rtpParameters.headerExtensions.filter(
+ ext => ext.uri != "urn:ietf:params:rtp-hdrext:sdes:mid"
+ );
+
+ for (const ext of rtpParameters.headerExtensions) {
+ if (ext.uri == "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id") {
+ ext.uri = "urn:ietf:params:rtp-hdrext:sdes:mid";
+ }
+ }
+
+ // Filter rtx as we have no way to (re)interpret rrid.
+ // Not doing this makes probing use RTX, it's not understood and ramp-up is slower.
+ rtpParameters.codecs = rtpParameters.codecs.filter(
+ c => c.name.toUpperCase() !== "RTX"
+ );
+
+ if (!rids) {
+ rids = Array.from(description.sdp.matchAll(/a=rid:(.*) send/g)).map(
+ r => r[1]
+ );
+ }
+
+ let sdp =
+ SDPUtils.writeSessionBoilerplate() +
+ SDPUtils.writeDtlsParameters(dtls, setupValue) +
+ SDPUtils.writeIceParameters(ice) +
+ "a=group:BUNDLE " +
+ rids.join(" ") +
+ "\r\n";
+ const baseRtpDescription = SDPUtils.writeRtpDescription(
+ mline.kind,
+ rtpParameters
+ );
+ for (const rid of rids) {
+ sdp +=
+ baseRtpDescription +
+ "a=mid:" +
+ rid +
+ "\r\n" +
+ "a=msid:rid-" +
+ rid +
+ " rid-" +
+ rid +
+ "\r\n";
+ sdp += directionValue + "\r\n";
+ }
+ return sdp;
+}
+
+function midToRid(description, localDescription, rids) {
+ const sections = SDPUtils.splitSections(description.sdp);
+ const dtls = SDPUtils.getDtlsParameters(sections[1], sections[0]);
+ const ice = SDPUtils.getIceParameters(sections[1], sections[0]);
+ const rtpParameters = SDPUtils.parseRtpParameters(sections[1]);
+ const setupValue = description.sdp.match(/a=setup:(.*)/)[1];
+ const directionValue =
+ description.sdp.match(/a=sendrecv|a=sendonly|a=recvonly|a=inactive/) ||
+ "a=sendrecv";
+ const mline = SDPUtils.parseMLine(sections[1]);
+
+ // Skip rid extensions; we are replacing them with the mid extmap
+ rtpParameters.headerExtensions = rtpParameters.headerExtensions.filter(
+ ext => !ridExtensions.includes(ext.uri)
+ );
+
+ for (const ext of rtpParameters.headerExtensions) {
+ if (ext.uri == "urn:ietf:params:rtp-hdrext:sdes:mid") {
+ ext.uri = "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id";
+ }
+ }
+
+ const localMid = localDescription
+ ? SDPUtils.getMid(SDPUtils.splitSections(localDescription.sdp)[1])
+ : "0";
+
+ if (!rids) {
+ rids = [];
+ for (let i = 1; i < sections.length; i++) {
+ rids.push(SDPUtils.getMid(sections[i]));
+ }
+ }
+
+ let sdp =
+ SDPUtils.writeSessionBoilerplate() +
+ SDPUtils.writeDtlsParameters(dtls, setupValue) +
+ SDPUtils.writeIceParameters(ice) +
+ "a=group:BUNDLE " +
+ localMid +
+ "\r\n";
+ sdp += SDPUtils.writeRtpDescription(mline.kind, rtpParameters);
+ // Although we are converting mids to rids, we still need a mid.
+ // The first one will be consistent with trickle ICE candidates.
+ sdp += "a=mid:" + localMid + "\r\n";
+ sdp += directionValue + "\r\n";
+
+ for (const rid of rids) {
+ const stringrid = String(rid); // allow integers
+ const choices = stringrid.split(",");
+ choices.forEach(choice => {
+ sdp += "a=rid:" + choice + " recv\r\n";
+ });
+ }
+ if (rids.length) {
+ sdp += "a=simulcast:recv " + rids.join(";") + "\r\n";
+ }
+
+ return sdp;
+}
+
+async function doOfferToSendSimulcast(offerer, answerer) {
+ await offerer.setLocalDescription();
+
+ // Is this a renegotiation? If so, we cannot remove (or reorder!) any mids,
+ // even if some rids have been removed or reordered.
+ let mids = [];
+ if (answerer.localDescription) {
+ // Renegotiation. Mids must be the same as before, because renegotiation
+ // can never remove or reorder mids, nor can it expand the simulcast
+ // envelope.
+ mids = [...answerer.localDescription.sdp.matchAll(/a=mid:(.*)/g)].map(
+ e => e[1]
+ );
+ } else {
+ // First negotiation; the mids will be exactly the same as the rids
+ const simulcastAttr = offerer.localDescription.sdp.match(
+ /a=simulcast:send (.*)/
+ );
+ if (simulcastAttr) {
+ mids = simulcastAttr[1].split(";");
+ }
+ }
+
+ const nonSimulcastOffer = ridToMid(offerer.localDescription, mids);
+ await answerer.setRemoteDescription({
+ type: "offer",
+ sdp: nonSimulcastOffer,
+ });
+}
+
+async function doAnswerToRecvSimulcast(offerer, answerer, rids) {
+ await answerer.setLocalDescription();
+ const simulcastAnswer = midToRid(
+ answerer.localDescription,
+ offerer.localDescription,
+ rids
+ );
+ await offerer.setRemoteDescription({ type: "answer", sdp: simulcastAnswer });
+}
+
+async function doOfferToRecvSimulcast(offerer, answerer, rids) {
+ await offerer.setLocalDescription();
+ const simulcastOffer = midToRid(
+ offerer.localDescription,
+ answerer.localDescription,
+ rids
+ );
+ await answerer.setRemoteDescription({ type: "offer", sdp: simulcastOffer });
+}
+
+async function doAnswerToSendSimulcast(offerer, answerer) {
+ await answerer.setLocalDescription();
+
+ // See which mids the offerer had; it will barf if we remove or reorder them
+ const mids = [...offerer.localDescription.sdp.matchAll(/a=mid:(.*)/g)].map(
+ e => e[1]
+ );
+
+ const nonSimulcastAnswer = ridToMid(answerer.localDescription, mids);
+ await offerer.setRemoteDescription({
+ type: "answer",
+ sdp: nonSimulcastAnswer,
+ });
+}
+
+async function doOfferToSendSimulcastAndAnswer(offerer, answerer, rids) {
+ await doOfferToSendSimulcast(offerer, answerer);
+ await doAnswerToRecvSimulcast(offerer, answerer, rids);
+}
+
+async function doOfferToRecvSimulcastAndAnswer(offerer, answerer, rids) {
+ await doOfferToRecvSimulcast(offerer, answerer, rids);
+ await doAnswerToSendSimulcast(offerer, answerer);
+}
+
+// This would be useful for cases other than simulcast, but we do not use it
+// anywhere else right now, nor do we have a place for wpt-friendly helpers at
+// the moment.
+function createPlaybackElement(track) {
+ const elem = document.createElement(track.kind);
+ elem.autoplay = true;
+ elem.srcObject = new MediaStream([track]);
+ elem.id = track.id;
+ return elem;
+}
+
+async function getPlaybackWithLoadedMetadata(track) {
+ const elem = createPlaybackElement(track);
+ return new Promise(resolve => {
+ elem.addEventListener("loadedmetadata", () => {
+ resolve(elem);
+ });
+ });
+}
diff --git a/dom/media/webrtc/tests/mochitests/stats.js b/dom/media/webrtc/tests/mochitests/stats.js
new file mode 100644
index 0000000000..475f8eeca9
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/stats.js
@@ -0,0 +1,1596 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const statsExpectedByType = {
+ "inbound-rtp": {
+ expected: [
+ "trackIdentifier",
+ "id",
+ "timestamp",
+ "type",
+ "ssrc",
+ "mediaType",
+ "kind",
+ "codecId",
+ "packetsReceived",
+ "packetsLost",
+ "packetsDiscarded",
+ "bytesReceived",
+ "jitter",
+ "lastPacketReceivedTimestamp",
+ "headerBytesReceived",
+ // Always missing from libwebrtc stats
+ // "estimatedPlayoutTimestamp",
+ "jitterBufferDelay",
+ "jitterBufferEmittedCount",
+ ],
+ optional: ["remoteId", "nackCount", "qpSum"],
+ localVideoOnly: [
+ "firCount",
+ "pliCount",
+ "framesDecoded",
+ "framesDropped",
+ "discardedPackets",
+ "framesPerSecond",
+ "frameWidth",
+ "frameHeight",
+ "framesReceived",
+ "totalDecodeTime",
+ "totalInterFrameDelay",
+ "totalProcessingDelay",
+ "totalSquaredInterFrameDelay",
+ ],
+ localAudioOnly: [
+ "totalSamplesReceived",
+ // libwebrtc doesn't seem to do FEC for video
+ "fecPacketsReceived",
+ "fecPacketsDiscarded",
+ "concealedSamples",
+ "silentConcealedSamples",
+ "concealmentEvents",
+ "insertedSamplesForDeceleration",
+ "removedSamplesForAcceleration",
+ "audioLevel",
+ "totalAudioEnergy",
+ "totalSamplesDuration",
+ ],
+ unimplemented: [
+ "mediaTrackId",
+ "transportId",
+ "associateStatsId",
+ "sliCount",
+ "packetsRepaired",
+ "fractionLost",
+ "burstPacketsLost",
+ "burstLossCount",
+ "burstDiscardCount",
+ "gapDiscardRate",
+ "gapLossRate",
+ ],
+ deprecated: ["mozRtt", "isRemote"],
+ },
+ "outbound-rtp": {
+ expected: [
+ "id",
+ "timestamp",
+ "type",
+ "ssrc",
+ "mediaType",
+ "kind",
+ "codecId",
+ "packetsSent",
+ "bytesSent",
+ "remoteId",
+ "headerBytesSent",
+ "retransmittedPacketsSent",
+ "retransmittedBytesSent",
+ ],
+ optional: ["nackCount", "qpSum"],
+ localAudioOnly: [],
+ localVideoOnly: [
+ "framesEncoded",
+ "firCount",
+ "pliCount",
+ "frameWidth",
+ "frameHeight",
+ "framesSent",
+ "hugeFramesSent",
+ "totalEncodeTime",
+ "totalEncodedBytesTarget",
+ ],
+ unimplemented: ["mediaTrackId", "transportId", "sliCount", "targetBitrate"],
+ deprecated: ["isRemote"],
+ },
+ "remote-inbound-rtp": {
+ expected: [
+ "id",
+ "timestamp",
+ "type",
+ "ssrc",
+ "mediaType",
+ "kind",
+ "codecId",
+ "packetsLost",
+ "jitter",
+ "localId",
+ "totalRoundTripTime",
+ "fractionLost",
+ "roundTripTimeMeasurements",
+ ],
+ optional: ["roundTripTime", "nackCount", "packetsReceived"],
+ unimplemented: [
+ "mediaTrackId",
+ "transportId",
+ "packetsDiscarded",
+ "associateStatsId",
+ "sliCount",
+ "packetsRepaired",
+ "burstPacketsLost",
+ "burstLossCount",
+ "burstDiscardCount",
+ "gapDiscardRate",
+ "gapLossRate",
+ ],
+ deprecated: ["mozRtt", "isRemote"],
+ },
+ "remote-outbound-rtp": {
+ expected: [
+ "id",
+ "timestamp",
+ "type",
+ "ssrc",
+ "mediaType",
+ "kind",
+ "codecId",
+ "packetsSent",
+ "bytesSent",
+ "localId",
+ "remoteTimestamp",
+ ],
+ optional: ["nackCount"],
+ unimplemented: ["mediaTrackId", "transportId", "sliCount", "targetBitrate"],
+ deprecated: ["isRemote"],
+ },
+ "media-source": {
+ expected: ["id", "timestamp", "type", "trackIdentifier", "kind"],
+ unimplemented: [
+ "audioLevel",
+ "totalAudioEnergy",
+ "totalSamplesDuration",
+ "echoReturnLoss",
+ "echoReturnLossEnhancement",
+ "droppedSamplesDuration",
+ "droppedSamplesEvents",
+ "totalCaptureDelay",
+ "totalSamplesCaptured",
+ "width",
+ "height",
+ "frames",
+ "framesPerSecond",
+ ],
+ optional: [],
+ deprecated: [],
+ },
+ csrc: { skip: true },
+ codec: {
+ expected: [
+ "timestamp",
+ "type",
+ "id",
+ "payloadType",
+ "transportId",
+ "mimeType",
+ "clockRate",
+ "sdpFmtpLine",
+ ],
+ optional: ["codecType", "channels"],
+ unimplemented: [],
+ deprecated: [],
+ },
+ "peer-connection": { skip: true },
+ "data-channel": { skip: true },
+ track: { skip: true },
+ transport: { skip: true },
+ "candidate-pair": {
+ expected: [
+ "id",
+ "timestamp",
+ "type",
+ "transportId",
+ "localCandidateId",
+ "remoteCandidateId",
+ "state",
+ "priority",
+ "nominated",
+ "writable",
+ "readable",
+ "bytesSent",
+ "bytesReceived",
+ "lastPacketSentTimestamp",
+ "lastPacketReceivedTimestamp",
+ ],
+ optional: ["selected"],
+ unimplemented: [
+ "totalRoundTripTime",
+ "currentRoundTripTime",
+ "availableOutgoingBitrate",
+ "availableIncomingBitrate",
+ "requestsReceived",
+ "requestsSent",
+ "responsesReceived",
+ "responsesSent",
+ "retransmissionsReceived",
+ "retransmissionsSent",
+ "consentRequestsSent",
+ ],
+ deprecated: [],
+ },
+ "local-candidate": {
+ expected: [
+ "id",
+ "timestamp",
+ "type",
+ "address",
+ "protocol",
+ "port",
+ "candidateType",
+ "priority",
+ ],
+ optional: ["relayProtocol", "proxied"],
+ unimplemented: ["networkType", "url", "transportId"],
+ deprecated: [
+ "candidateId",
+ "portNumber",
+ "ipAddress",
+ "componentId",
+ "mozLocalTransport",
+ "transport",
+ ],
+ },
+ "remote-candidate": {
+ expected: [
+ "id",
+ "timestamp",
+ "type",
+ "address",
+ "protocol",
+ "port",
+ "candidateType",
+ "priority",
+ ],
+ optional: ["relayProtocol", "proxied"],
+ unimplemented: ["networkType", "url", "transportId"],
+ deprecated: [
+ "candidateId",
+ "portNumber",
+ "ipAddress",
+ "componentId",
+ "mozLocalTransport",
+ "transport",
+ ],
+ },
+ certificate: { skip: true },
+};
+
+["in", "out"].forEach(pre => {
+ let s = statsExpectedByType[pre + "bound-rtp"];
+ s.optional = [...s.optional, ...s.localVideoOnly, ...s.localAudioOnly];
+});
+
+//
+// Checks that the fields in a report conform to the expectations in
+// statExpectedByType
+//
+function checkExpectedFields(report) {
+ report.forEach(stat => {
+ let expectations = statsExpectedByType[stat.type];
+ ok(expectations, "Stats type " + stat.type + " was expected");
+ // If the type is not expected or if it is flagged for skipping continue to
+ // the next
+ if (!expectations || expectations.skip) {
+ return;
+ }
+ // Check that all required fields exist
+ expectations.expected.forEach(field => {
+ ok(
+ field in stat,
+ "Expected stat field " + stat.type + "." + field + " exists"
+ );
+ });
+ // Check that each field is either expected or optional
+ let allowed = [...expectations.expected, ...expectations.optional];
+ Object.keys(stat).forEach(field => {
+ ok(
+ allowed.includes(field),
+ "Stat field " +
+ stat.type +
+ "." +
+ field +
+ ` is allowed. ${JSON.stringify(stat)}`
+ );
+ });
+
+ //
+ // Ensure that unimplemented fields are not implemented
+ // note: if a field is implemented it should be moved to expected or
+ // optional.
+ //
+ expectations.unimplemented.forEach(field => {
+ ok(
+ !Object.keys(stat).includes(field),
+ "Unimplemented field " + stat.type + "." + field + " does not exist."
+ );
+ });
+
+ //
+ // Ensure that all deprecated fields are not present
+ //
+ expectations.deprecated.forEach(field => {
+ ok(
+ !Object.keys(stat).includes(field),
+ "Deprecated field " + stat.type + "." + field + " does not exist."
+ );
+ });
+ });
+}
+
+function pedanticChecks(report) {
+ // Check that report is only-maplike
+ [...report.keys()].forEach(key =>
+ is(
+ report[key],
+ undefined,
+ `Report is not dictionary like, it lacks a property for key ${key}`
+ )
+ );
+ report.forEach((statObj, mapKey) => {
+ info(`"${mapKey} = ${JSON.stringify(statObj, null, 2)}`);
+ });
+ // eslint-disable-next-line complexity
+ report.forEach((statObj, mapKey) => {
+ let tested = {};
+ // Record what fields get tested.
+ // To access a field foo without marking it as tested use stat.inner.foo
+ let stat = new Proxy(statObj, {
+ get(stat, key) {
+ if (key == "inner") {
+ return stat;
+ }
+ tested[key] = true;
+ return stat[key];
+ },
+ });
+
+ let expectations = statsExpectedByType[stat.type];
+
+ if (expectations.skip) {
+ return;
+ }
+
+ // All stats share the following attributes inherited from RTCStats
+ is(stat.id, mapKey, stat.type + ".id is the same as the report key.");
+
+ // timestamp
+ ok(stat.timestamp >= 0, stat.type + ".timestamp is not less than 0");
+ // If the timebase for the timestamp is not properly set the timestamp
+ // will appear relative to the year 1970; Bug 1495446
+ const date = new Date(stat.timestamp);
+ ok(
+ date.getFullYear() > 1970,
+ `${stat.type}.timestamp is relative to current time, date=${date}`
+ );
+ //
+ // RTCStreamStats attributes with common behavior
+ //
+ // inbound-rtp, outbound-rtp, remote-inbound-rtp, remote-outbound-rtp
+ // inherit from RTCStreamStats
+ if (
+ [
+ "inbound-rtp",
+ "outbound-rtp",
+ "remote-inbound-rtp",
+ "remote-outbound-rtp",
+ ].includes(stat.type)
+ ) {
+ const isRemote = stat.type.startsWith("remote-");
+ //
+ // Common RTCStreamStats fields
+ //
+
+ // SSRC
+ ok(stat.ssrc, stat.type + ".ssrc has a value");
+
+ // kind
+ ok(
+ ["audio", "video"].includes(stat.kind),
+ stat.type + ".kind is 'audio' or 'video'"
+ );
+
+ // mediaType, renamed to kind but remains for backward compability.
+ ok(
+ ["audio", "video"].includes(stat.mediaType),
+ stat.type + ".mediaType is 'audio' or 'video'"
+ );
+
+ ok(stat.kind == stat.mediaType, "kind equals legacy mediaType");
+
+ // codecId
+ ok(stat.codecId, `${stat.type}.codecId has a value`);
+ ok(report.has(stat.codecId), `codecId ${stat.codecId} exists in report`);
+ is(
+ report.get(stat.codecId).type,
+ "codec",
+ `codecId ${stat.codecId} in report is codec type`
+ );
+ is(
+ report.get(stat.codecId).mimeType.slice(0, 5),
+ stat.kind,
+ `codecId ${stat.codecId} in report is for a mimeType of the same ` +
+ `media type as the referencing rtp stream stat`
+ );
+
+ if (isRemote) {
+ // local id
+ if (stat.localId) {
+ ok(
+ report.has(stat.localId),
+ `localId ${stat.localId} exists in report.`
+ );
+ is(
+ report.get(stat.localId).ssrc,
+ stat.ssrc,
+ "remote ssrc and local ssrc match."
+ );
+ is(
+ report.get(stat.localId).remoteId,
+ stat.id,
+ "local object has remote object as it's own remote object."
+ );
+ }
+ } else {
+ // remote id
+ if (stat.remoteId) {
+ ok(
+ report.has(stat.remoteId),
+ `remoteId ${stat.remoteId} exists in report.`
+ );
+ is(
+ report.get(stat.remoteId).ssrc,
+ stat.ssrc,
+ "remote ssrc and local ssrc match."
+ );
+ is(
+ report.get(stat.remoteId).localId,
+ stat.id,
+ "remote object has local object as it's own local object."
+ );
+ }
+ }
+
+ // nackCount
+ if (stat.nackCount) {
+ ok(
+ stat.nackCount >= 0,
+ `${stat.type}.nackCount is sane (${stat.kind}).`
+ );
+ }
+
+ if (!isRemote && stat.inner.kind == "video") {
+ // firCount
+ ok(
+ stat.firCount >= 0 && stat.firCount < 100,
+ `${stat.type}.firCount is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.firCount}`
+ );
+
+ // pliCount
+ ok(
+ stat.pliCount >= 0 && stat.pliCount < 200,
+ `${stat.type}.pliCount is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.pliCount}`
+ );
+
+ // qpSum
+ if (stat.qpSum !== undefined) {
+ ok(
+ stat.qpSum > 0,
+ `${stat.type}.qpSum is at least 0 ` +
+ `${stat.kind} test. value=${stat.qpSum}`
+ );
+ }
+ } else {
+ is(
+ stat.qpSum,
+ undefined,
+ `${stat.type}.qpSum does not exist when stat.kind != video`
+ );
+ }
+ }
+
+ if (stat.type == "inbound-rtp") {
+ //
+ // Required fields
+ //
+
+ // trackIdentifier
+ is(typeof stat.trackIdentifier, "string");
+ isnot(stat.trackIdentifier, "");
+
+ // packetsReceived
+ ok(
+ stat.packetsReceived >= 0 && stat.packetsReceived < 10 ** 5,
+ `${stat.type}.packetsReceived is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.packetsReceived}`
+ );
+
+ // packetsDiscarded
+ ok(
+ stat.packetsDiscarded >= 0 && stat.packetsDiscarded < 100,
+ `${stat.type}.packetsDiscarded is sane number for a short test. ` +
+ `value=${stat.packetsDiscarded}`
+ );
+ // bytesReceived
+ ok(
+ stat.bytesReceived >= 0 && stat.bytesReceived < 10 ** 9, // Not a magic number, just a guess
+ `${stat.type}.bytesReceived is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.bytesReceived}`
+ );
+
+ // packetsLost
+ ok(
+ stat.packetsLost < 100,
+ `${stat.type}.packetsLost is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.packetsLost}`
+ );
+
+ // This should be much lower for audio, TODO: Bug 1330575
+ let expectedJitter = stat.kind == "video" ? 0.5 : 1;
+ // jitter
+ ok(
+ stat.jitter < expectedJitter,
+ `${stat.type}.jitter is sane number for a ${stat.kind} ` +
+ `local only test. value=${stat.jitter}`
+ );
+
+ // lastPacketReceivedTimestamp
+ ok(
+ stat.lastPacketReceivedTimestamp !== undefined,
+ `${stat.type}.lastPacketReceivedTimestamp has a value`
+ );
+
+ // headerBytesReceived
+ ok(
+ stat.headerBytesReceived >= 0 && stat.headerBytesReceived < 50000,
+ `${stat.type}.headerBytesReceived is sane for a short test. ` +
+ `value=${stat.headerBytesReceived}`
+ );
+
+ // Always missing from libwebrtc stats
+ // estimatedPlayoutTimestamp
+ // ok(
+ // stat.estimatedPlayoutTimestamp !== undefined,
+ // `${stat.type}.estimatedPlayoutTimestamp has a value`
+ // );
+
+ // jitterBufferEmittedCount
+ let expectedJitterBufferEmmitedCount = stat.kind == "video" ? 7 : 1000;
+ ok(
+ stat.jitterBufferEmittedCount > expectedJitterBufferEmmitedCount,
+ `${stat.type}.jitterBufferEmittedCount is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.jitterBufferEmittedCount}`
+ );
+
+ // jitterBufferDelay
+ let avgJitterBufferDelay =
+ stat.jitterBufferDelay / stat.jitterBufferEmittedCount;
+ ok(
+ avgJitterBufferDelay > 0.01 && avgJitterBufferDelay < 10,
+ `${stat.type}.jitterBufferDelay is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.jitterBufferDelay}/${stat.jitterBufferEmittedCount}=${avgJitterBufferDelay}`
+ );
+
+ //
+ // Optional fields
+ //
+
+ //
+ // Local audio only stats
+ //
+ if (stat.inner.kind != "audio") {
+ expectations.localAudioOnly.forEach(field => {
+ ok(
+ stat[field] === undefined,
+ `${stat.type} does not have field ${field}` +
+ ` when kind is not 'audio'`
+ );
+ });
+ } else {
+ expectations.localAudioOnly.forEach(field => {
+ ok(
+ stat.inner[field] !== undefined,
+ stat.type + " has field " + field + " when kind is video"
+ );
+ });
+ // totalSamplesReceived
+ ok(
+ stat.totalSamplesReceived > 1000,
+ `${stat.type}.totalSamplesReceived is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.totalSamplesReceived}`
+ );
+
+ // fecPacketsReceived
+ ok(
+ stat.fecPacketsReceived >= 0 && stat.fecPacketsReceived < 10 ** 5,
+ `${stat.type}.fecPacketsReceived is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.fecPacketsReceived}`
+ );
+
+ // fecPacketsDiscarded
+ ok(
+ stat.fecPacketsDiscarded >= 0 && stat.fecPacketsDiscarded < 100,
+ `${stat.type}.fecPacketsDiscarded is sane number for a short test. ` +
+ `value=${stat.fecPacketsDiscarded}`
+ );
+ // concealedSamples
+ ok(
+ stat.concealedSamples >= 0 &&
+ stat.concealedSamples <= stat.totalSamplesReceived,
+ `${stat.type}.concealedSamples is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.concealedSamples}`
+ );
+
+ // silentConcealedSamples
+ ok(
+ stat.silentConcealedSamples >= 0 &&
+ stat.silentConcealedSamples <= stat.concealedSamples,
+ `${stat.type}.silentConcealedSamples is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.silentConcealedSamples}`
+ );
+
+ // concealmentEvents
+ ok(
+ stat.concealmentEvents >= 0 &&
+ stat.concealmentEvents <= stat.packetsReceived,
+ `${stat.type}.concealmentEvents is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.concealmentEvents}`
+ );
+
+ // insertedSamplesForDeceleration
+ ok(
+ stat.insertedSamplesForDeceleration >= 0 &&
+ stat.insertedSamplesForDeceleration <= stat.totalSamplesReceived,
+ `${stat.type}.insertedSamplesForDeceleration is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.insertedSamplesForDeceleration}`
+ );
+
+ // removedSamplesForAcceleration
+ ok(
+ stat.removedSamplesForAcceleration >= 0 &&
+ stat.removedSamplesForAcceleration <= stat.totalSamplesReceived,
+ `${stat.type}.removedSamplesForAcceleration is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.removedSamplesForAcceleration}`
+ );
+
+ // audioLevel
+ ok(
+ stat.audioLevel >= 0 && stat.audioLevel <= 128,
+ `${stat.type}.bytesReceived is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.audioLevel}`
+ );
+
+ // totalAudioEnergy
+ ok(
+ stat.totalAudioEnergy >= 0 && stat.totalAudioEnergy <= 128,
+ `${stat.type}.totalAudioEnergy is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.totalAudioEnergy}`
+ );
+
+ // totalSamplesDuration
+ ok(
+ stat.totalSamplesDuration >= 0 && stat.totalSamplesDuration <= 300,
+ `${stat.type}.totalSamplesDuration is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.totalSamplesDuration}`
+ );
+ }
+
+ //
+ // Local video only stats
+ //
+ if (stat.inner.kind != "video") {
+ expectations.localVideoOnly.forEach(field => {
+ ok(
+ stat[field] === undefined,
+ `${stat.type} does not have field ${field}` +
+ ` when kind is not 'video'`
+ );
+ });
+ } else {
+ expectations.localVideoOnly.forEach(field => {
+ ok(
+ stat.inner[field] !== undefined,
+ stat.type + " has field " + field + " when kind is video"
+ );
+ });
+ // discardedPackets
+ ok(
+ stat.discardedPackets < 100,
+ `${stat.type}.discardedPackets is a sane number for a short test. ` +
+ `value=${stat.discardedPackets}`
+ );
+ // framesPerSecond
+ ok(
+ stat.framesPerSecond > 0 && stat.framesPerSecond < 70,
+ `${stat.type}.framesPerSecond is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.framesPerSecond}`
+ );
+
+ // framesDecoded
+ ok(
+ stat.framesDecoded > 0 && stat.framesDecoded < 1000000,
+ `${stat.type}.framesDecoded is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.framesDecoded}`
+ );
+
+ // framesDropped
+ ok(
+ stat.framesDropped >= 0 && stat.framesDropped < 100,
+ `${stat.type}.framesDropped is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.framesDropped}`
+ );
+
+ // frameWidth
+ ok(
+ stat.frameWidth > 0 && stat.frameWidth < 100000,
+ `${stat.type}.frameWidth is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.framesSent}`
+ );
+
+ // frameHeight
+ ok(
+ stat.frameHeight > 0 && stat.frameHeight < 100000,
+ `${stat.type}.frameHeight is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.frameHeight}`
+ );
+
+ // totalDecodeTime
+ ok(
+ stat.totalDecodeTime >= 0 && stat.totalDecodeTime < 300,
+ `${stat.type}.totalDecodeTime is sane for a short test. ` +
+ `value=${stat.totalDecodeTime}`
+ );
+
+ // totalProcessingDelay
+ ok(
+ stat.totalProcessingDelay < 100,
+ `${stat.type}.totalProcessingDelay is sane number for a short test ` +
+ `local only test. value=${stat.totalProcessingDelay}`
+ );
+
+ // totalInterFrameDelay
+ ok(
+ stat.totalInterFrameDelay >= 0 && stat.totalInterFrameDelay < 100,
+ `${stat.type}.totalInterFrameDelay is sane for a short test. ` +
+ `value=${stat.totalInterFrameDelay}`
+ );
+
+ // totalSquaredInterFrameDelay
+ ok(
+ stat.totalSquaredInterFrameDelay >= 0 &&
+ stat.totalSquaredInterFrameDelay < 100,
+ `${stat.type}.totalSquaredInterFrameDelay is sane for a short test. ` +
+ `value=${stat.totalSquaredInterFrameDelay}`
+ );
+
+ // framesReceived
+ ok(
+ stat.framesReceived >= 0 && stat.framesReceived < 100000,
+ `${stat.type}.framesReceived is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.framesReceived}`
+ );
+ }
+ } else if (stat.type == "remote-inbound-rtp") {
+ // roundTripTime
+ ok(
+ stat.roundTripTime >= 0,
+ `${stat.type}.roundTripTime is sane with` +
+ `value of: ${stat.roundTripTime} (${stat.kind})`
+ );
+ //
+ // Required fields
+ //
+
+ // packetsLost
+ ok(
+ stat.packetsLost < 100,
+ `${stat.type}.packetsLost is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.packetsLost}`
+ );
+
+ // jitter
+ ok(
+ stat.jitter >= 0,
+ `${stat.type}.jitter is sane number (${stat.kind}). ` +
+ `value=${stat.jitter}`
+ );
+
+ //
+ // Optional fields
+ //
+
+ // packetsReceived
+ if (stat.packetsReceived) {
+ ok(
+ stat.packetsReceived >= 0 && stat.packetsReceived < 10 ** 5,
+ `${stat.type}.packetsReceived is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.packetsReceived}`
+ );
+ }
+
+ // totalRoundTripTime
+ ok(
+ stat.totalRoundTripTime < 50000,
+ `${stat.type}.totalRoundTripTime is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.totalRoundTripTime}`
+ );
+
+ // fractionLost
+ ok(
+ stat.fractionLost < 0.2,
+ `${stat.type}.fractionLost is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.fractionLost}`
+ );
+
+ // roundTripTimeMeasurements
+ ok(
+ stat.roundTripTimeMeasurements >= 1 &&
+ stat.roundTripTimeMeasurements < 500,
+ `${stat.type}.roundTripTimeMeasurements is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.roundTripTimeMeasurements}`
+ );
+ } else if (stat.type == "outbound-rtp") {
+ //
+ // Required fields
+ //
+
+ // packetsSent
+ ok(
+ stat.packetsSent > 0 && stat.packetsSent < 10000,
+ `${stat.type}.packetsSent is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.packetsSent}`
+ );
+
+ // bytesSent
+ const audio1Min = 16000 * 60; // 128kbps
+ const video1Min = 250000 * 60; // 2Mbps
+ ok(
+ stat.bytesSent > 0 &&
+ stat.bytesSent < (stat.kind == "video" ? video1Min : audio1Min),
+ `${stat.type}.bytesSent is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.bytesSent}`
+ );
+
+ // headerBytesSent
+ ok(
+ stat.headerBytesSent > 0 &&
+ stat.headerBytesSent < (stat.kind == "video" ? video1Min : audio1Min),
+ `${stat.type}.headerBytesSent is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.headerBytesSent}`
+ );
+
+ // retransmittedPacketsSent
+ ok(
+ stat.retransmittedPacketsSent >= 0 &&
+ stat.retransmittedPacketsSent <
+ (stat.kind == "video" ? video1Min : audio1Min),
+ `${stat.type}.retransmittedPacketsSent is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.retransmittedPacketsSent}`
+ );
+
+ // retransmittedBytesSent
+ ok(
+ stat.retransmittedBytesSent >= 0 &&
+ stat.retransmittedBytesSent <
+ (stat.kind == "video" ? video1Min : audio1Min),
+ `${stat.type}.retransmittedBytesSent is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.retransmittedBytesSent}`
+ );
+
+ //
+ // Optional fields
+ //
+
+ // qpSum
+ // This is supported for all of our vpx codecs (on the encode side, see
+ // bug 1519590)
+ const mimeType = report.get(stat.codecId).mimeType;
+ if (mimeType.includes("VP")) {
+ ok(
+ stat.qpSum >= 0,
+ `${stat.type}.qpSum is a sane number (${stat.kind}) ` +
+ `for ${report.get(stat.codecId).mimeType}. value=${stat.qpSum}`
+ );
+ } else if (mimeType.includes("H264")) {
+ // OpenH264 encoder records QP so we check for either condition.
+ if (!stat.qpSum && !("qpSum" in stat)) {
+ ok(
+ !stat.qpSum && !("qpSum" in stat),
+ `${stat.type}.qpSum absent for ${report.get(stat.codecId).mimeType}`
+ );
+ } else {
+ ok(
+ stat.qpSum >= 0,
+ `${stat.type}.qpSum is a sane number (${stat.kind}) ` +
+ `for ${report.get(stat.codecId).mimeType}. value=${stat.qpSum}`
+ );
+ }
+ } else {
+ ok(
+ !stat.qpSum && !("qpSum" in stat),
+ `${stat.type}.qpSum absent for ${report.get(stat.codecId).mimeType}`
+ );
+ }
+
+ //
+ // Local video only stats
+ //
+ if (stat.inner.kind != "video") {
+ expectations.localVideoOnly.forEach(field => {
+ ok(
+ stat[field] === undefined,
+ `${stat.type} does not have field ` +
+ `${field} when kind is not 'video'`
+ );
+ });
+ } else {
+ expectations.localVideoOnly.forEach(field => {
+ ok(
+ stat.inner[field] !== undefined,
+ `${stat.type} has field ` +
+ `${field} when kind is video and isRemote is false`
+ );
+ });
+
+ // framesEncoded
+ ok(
+ stat.framesEncoded >= 0 && stat.framesEncoded < 100000,
+ `${stat.type}.framesEncoded is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.framesEncoded}`
+ );
+
+ // frameWidth
+ ok(
+ stat.frameWidth >= 0 && stat.frameWidth < 100000,
+ `${stat.type}.frameWidth is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.frameWidth}`
+ );
+
+ // frameHeight
+ ok(
+ stat.frameHeight >= 0 && stat.frameHeight < 100000,
+ `${stat.type}.frameHeight is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.frameHeight}`
+ );
+
+ // framesSent
+ ok(
+ stat.framesSent >= 0 && stat.framesSent < 100000,
+ `${stat.type}.framesSent is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.framesSent}`
+ );
+
+ // hugeFramesSent
+ ok(
+ stat.hugeFramesSent >= 0 && stat.hugeFramesSent < 100000,
+ `${stat.type}.hugeFramesSent is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.hugeFramesSent}`
+ );
+
+ // totalEncodeTime
+ ok(
+ stat.totalEncodeTime >= 0,
+ `${stat.type}.totalEncodeTime is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.totalEncodeTime}`
+ );
+
+ // totalEncodedBytesTarget
+ ok(
+ stat.totalEncodedBytesTarget > 1000,
+ `${stat.type}.totalEncodedBytesTarget is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.totalEncodedBytesTarget}`
+ );
+ }
+ } else if (stat.type == "remote-outbound-rtp") {
+ //
+ // Required fields
+ //
+
+ // packetsSent
+ ok(
+ stat.packetsSent > 0 && stat.packetsSent < 10000,
+ `${stat.type}.packetsSent is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.packetsSent}`
+ );
+
+ // bytesSent
+ const audio1Min = 16000 * 60; // 128kbps
+ const video1Min = 250000 * 60; // 2Mbps
+ ok(
+ stat.bytesSent > 0 &&
+ stat.bytesSent < (stat.kind == "video" ? video1Min : audio1Min),
+ `${stat.type}.bytesSent is a sane number for a short ` +
+ `${stat.kind} test. value=${stat.bytesSent}`
+ );
+
+ ok(
+ stat.remoteTimestamp !== undefined,
+ `${stat.type}.remoteTimestamp ` + `is not undefined (${stat.kind})`
+ );
+ const ageSeconds = (stat.timestamp - stat.remoteTimestamp) / 1000;
+ // remoteTimestamp is exact (so it can be mapped to a packet), whereas
+ // timestamp has reduced precision. It is possible that
+ // remoteTimestamp occurs a millisecond into the future from
+ // timestamp. We also subtract half a millisecond when reducing
+ // precision on libwebrtc timestamps, to counteract the potential
+ // rounding up that libwebrtc may do since it tends to round its
+ // internal timestamps to whole milliseconds. In the worst case
+ // remoteTimestamp may therefore occur 2 milliseconds ahead of
+ // timestamp.
+ ok(
+ ageSeconds >= -0.002 && ageSeconds < 30,
+ `${stat.type}.remoteTimestamp is on the same timeline as ` +
+ `${stat.type}.timestamp, and no older than 30 seconds. ` +
+ `difference=${ageSeconds}s`
+ );
+ } else if (stat.type == "media-source") {
+ // trackIdentifier
+ is(typeof stat.trackIdentifier, "string");
+ isnot(stat.trackIdentifier, "");
+
+ // kind
+ is(typeof stat.kind, "string");
+ ok(stat.kind == "audio" || stat.kind == "video");
+ } else if (stat.type == "codec") {
+ //
+ // Required fields
+ //
+
+ // mimeType & payloadType
+ switch (stat.mimeType) {
+ case "audio/opus":
+ is(stat.payloadType, 109, "codec.payloadType for opus");
+ break;
+ case "video/VP8":
+ is(stat.payloadType, 120, "codec.payloadType for VP8");
+ break;
+ case "video/VP9":
+ is(stat.payloadType, 121, "codec.payloadType for VP9");
+ break;
+ case "video/H264":
+ ok(
+ stat.payloadType == 97 || stat.payloadType == 126,
+ `codec.payloadType for H264 was ${stat.payloadType}, exp. 97 or 126`
+ );
+ break;
+ default:
+ ok(
+ false,
+ `Unexpected codec.mimeType ${stat.mimeType} for payloadType ` +
+ `${stat.payloadType}`
+ );
+ break;
+ }
+
+ // transportId
+ // (no transport stats yet)
+ ok(stat.transportId, "codec.transportId is set");
+
+ // clockRate
+ if (stat.mimeType.startsWith("audio")) {
+ is(stat.clockRate, 48000, "codec.clockRate for audio/opus");
+ } else if (stat.mimeType.startsWith("video")) {
+ is(stat.clockRate, 90000, "codec.clockRate for video");
+ }
+
+ // sdpFmtpLine
+ // (not technically mandated by spec, but expected here)
+ ok(stat.sdpFmtpLine, "codec.sdpFmtpLine is set");
+ const opusParams = [
+ "maxplaybackrate",
+ "maxaveragebitrate",
+ "usedtx",
+ "stereo",
+ "useinbandfec",
+ "cbr",
+ "ptime",
+ "minptime",
+ "maxptime",
+ ];
+ const vpxParams = ["max-fs", "max-fr"];
+ const h264Params = [
+ "packetization-mode",
+ "level-asymmetry-allowed",
+ "profile-level-id",
+ "max-fs",
+ "max-cpb",
+ "max-dpb",
+ "max-br",
+ "max-mbps",
+ ];
+ for (const param of stat.sdpFmtpLine.split(";")) {
+ const [key, value] = param.split("=");
+ if (stat.payloadType == 109) {
+ ok(
+ opusParams.includes(key),
+ `codec.sdpFmtpLine param ${key}=${value} for opus`
+ );
+ } else if (stat.payloadType == 120 || stat.payloadType == 121) {
+ ok(
+ vpxParams.includes(key),
+ `codec.sdpFmtpLine param ${key}=${value} for VPx`
+ );
+ } else if (stat.payloadType == 97 || stat.payloadType == 126) {
+ ok(
+ h264Params.includes(key),
+ `codec.sdpFmtpLine param ${key}=${value} for H264`
+ );
+ if (key == "packetization-mode") {
+ if (stat.payloadType == 97) {
+ is(value, "0", "codec.sdpFmtpLine: H264 (97) packetization-mode");
+ } else if (stat.payloadType == 126) {
+ is(
+ value,
+ "1",
+ "codec.sdpFmtpLine: H264 (126) packetization-mode"
+ );
+ }
+ }
+ if (key == "profile-level-id") {
+ is(value, "42e01f", "codec.sdpFmtpLine: H264 profile-level-id");
+ }
+ }
+ }
+
+ //
+ // Optional fields
+ //
+
+ // codecType
+ ok(
+ !Object.keys(stat).includes("codecType") ||
+ stat.codecType == "encode" ||
+ stat.codecType == "decode",
+ "codec.codecType (${codec.codecType}) is an expected value or absent"
+ );
+ let numRecvStreams = 0;
+ let numSendStreams = 0;
+ const counts = {
+ "inbound-rtp": 0,
+ "outbound-rtp": 0,
+ "remote-inbound-rtp": 0,
+ "remote-outbound-rtp": 0,
+ };
+ const [kind] = stat.mimeType.split("/");
+ report.forEach(other => {
+ if (other.type == "inbound-rtp" && other.kind == kind) {
+ numRecvStreams += 1;
+ } else if (other.type == "outbound-rtp" && other.kind == kind) {
+ numSendStreams += 1;
+ }
+ if (other.codecId == stat.id) {
+ counts[other.type] += 1;
+ }
+ });
+ const expectedCounts = {
+ encode: {
+ "inbound-rtp": 0,
+ "outbound-rtp": numSendStreams,
+ "remote-inbound-rtp": numSendStreams,
+ "remote-outbound-rtp": 0,
+ },
+ decode: {
+ "inbound-rtp": numRecvStreams,
+ "outbound-rtp": 0,
+ "remote-inbound-rtp": 0,
+ "remote-outbound-rtp": numRecvStreams,
+ },
+ absent: {
+ "inbound-rtp": numRecvStreams,
+ "outbound-rtp": numSendStreams,
+ "remote-inbound-rtp": numSendStreams,
+ "remote-outbound-rtp": numRecvStreams,
+ },
+ };
+ // Note that the logic above assumes at most one sender and at most one
+ // receiver was used to generate this stats report. If more senders or
+ // receivers are present, they'd be referring to not only this codec stat,
+ // skewing `numSendStreams` and `numRecvStreams` above.
+ // This could be fixed when we support `senderId` and `receiverId` in
+ // RTCOutboundRtpStreamStats and RTCInboundRtpStreamStats respectively.
+ for (const [key, value] of Object.entries(counts)) {
+ is(
+ value,
+ expectedCounts[stat.codecType || "absent"][key],
+ `codec.codecType ${stat.codecType || "absent"} ref from ${key} stat`
+ );
+ }
+
+ // channels
+ if (stat.mimeType.startsWith("audio")) {
+ ok(stat.channels, "codec.channels should exist for audio");
+ if (stat.channels) {
+ if (stat.sdpFmtpLine.includes("stereo=1")) {
+ is(stat.channels, 2, "codec.channels for stereo audio");
+ } else {
+ is(stat.channels, 1, "codec.channels for mono audio");
+ }
+ }
+ } else {
+ ok(!stat.channels, "codec.channels should not exist for video");
+ }
+ } else if (stat.type == "candidate-pair") {
+ info("candidate-pair is: " + JSON.stringify(stat));
+ //
+ // Required fields
+ //
+
+ // transportId
+ ok(
+ stat.transportId,
+ `${stat.type}.transportId has a value. value=` +
+ `${stat.transportId} (${stat.kind})`
+ );
+
+ // localCandidateId
+ ok(
+ stat.localCandidateId,
+ `${stat.type}.localCandidateId has a value. value=` +
+ `${stat.localCandidateId} (${stat.kind})`
+ );
+
+ // remoteCandidateId
+ ok(
+ stat.remoteCandidateId,
+ `${stat.type}.remoteCandidateId has a value. value=` +
+ `${stat.remoteCandidateId} (${stat.kind})`
+ );
+
+ // priority
+ ok(
+ stat.priority,
+ `${stat.type}.priority has a value. value=` +
+ `${stat.priority} (${stat.kind})`
+ );
+
+ // readable
+ ok(
+ stat.readable,
+ `${stat.type}.readable is true. value=${stat.readable} ` +
+ `(${stat.kind})`
+ );
+
+ // writable
+ ok(
+ stat.writable,
+ `${stat.type}.writable is true. value=${stat.writable} ` +
+ `(${stat.kind})`
+ );
+
+ // state
+ if (
+ stat.state == "succeeded" &&
+ stat.selected !== undefined &&
+ stat.selected
+ ) {
+ info("candidate-pair state is succeeded and selected is true");
+ // nominated
+ ok(
+ stat.nominated,
+ `${stat.type}.nominated is true. value=${stat.nominated} ` +
+ `(${stat.kind})`
+ );
+
+ // bytesSent
+ ok(
+ stat.bytesSent > 1000,
+ `${stat.type}.bytesSent is a sane number (>1,000) for a short ` +
+ `${stat.kind} test. value=${stat.bytesSent}`
+ );
+
+ // bytesReceived
+ ok(
+ stat.bytesReceived > 500,
+ `${stat.type}.bytesReceived is a sane number (>500) for a short ` +
+ `${stat.kind} test. value=${stat.bytesReceived}`
+ );
+
+ // lastPacketSentTimestamp
+ ok(
+ stat.lastPacketSentTimestamp,
+ `${stat.type}.lastPacketSentTimestamp has a value. value=` +
+ `${stat.lastPacketSentTimestamp} (${stat.kind})`
+ );
+
+ // lastPacketReceivedTimestamp
+ ok(
+ stat.lastPacketReceivedTimestamp,
+ `${stat.type}.lastPacketReceivedTimestamp has a value. value=` +
+ `${stat.lastPacketReceivedTimestamp} (${stat.kind})`
+ );
+ } else {
+ info("candidate-pair is _not_ both state == succeeded and selected");
+ // nominated
+ ok(
+ stat.nominated !== undefined,
+ `${stat.type}.nominated exists. value=${stat.nominated} ` +
+ `(${stat.kind})`
+ );
+ ok(
+ stat.bytesSent !== undefined,
+ `${stat.type}.bytesSent exists. value=${stat.bytesSent} ` +
+ `(${stat.kind})`
+ );
+ ok(
+ stat.bytesReceived !== undefined,
+ `${stat.type}.bytesReceived exists. value=${stat.bytesReceived} ` +
+ `(${stat.kind})`
+ );
+ ok(
+ stat.lastPacketSentTimestamp !== undefined,
+ `${stat.type}.lastPacketSentTimestamp exists. value=` +
+ `${stat.lastPacketSentTimestamp} (${stat.kind})`
+ );
+ ok(
+ stat.lastPacketReceivedTimestamp !== undefined,
+ `${stat.type}.lastPacketReceivedTimestamp exists. value=` +
+ `${stat.lastPacketReceivedTimestamp} (${stat.kind})`
+ );
+ }
+
+ //
+ // Optional fields
+ //
+ // selected
+ ok(
+ stat.selected === undefined ||
+ (stat.state == "succeeded" && stat.selected) ||
+ !stat.selected,
+ `${stat.type}.selected is undefined, true when state is succeeded, ` +
+ `or false. value=${stat.selected} (${stat.kind})`
+ );
+ } else if (
+ stat.type == "local-candidate" ||
+ stat.type == "remote-candidate"
+ ) {
+ info(`candidate is ${JSON.stringify(stat)}`);
+
+ // address
+ ok(
+ stat.address,
+ `${stat.type} has address. value=${stat.address} ` + `(${stat.kind})`
+ );
+
+ // protocol
+ ok(
+ stat.protocol,
+ `${stat.type} has protocol. value=${stat.protocol} ` + `(${stat.kind})`
+ );
+
+ // port
+ ok(
+ stat.port >= 0,
+ `${stat.type} has port >= 0. value=${stat.port} ` + `(${stat.kind})`
+ );
+ ok(
+ stat.port <= 65535,
+ `${stat.type} has port <= 65535. value=${stat.port} ` + `(${stat.kind})`
+ );
+
+ // candidateType
+ ok(
+ stat.candidateType,
+ `${stat.type} has candidateType. value=${stat.candidateType} ` +
+ `(${stat.kind})`
+ );
+
+ // priority
+ ok(
+ stat.priority > 0 && stat.priority < 2 ** 32 - 1,
+ `${stat.type} has priority between 1 and 2^32 - 1 inc. ` +
+ `value=${stat.priority} (${stat.kind})`
+ );
+
+ // relayProtocol
+ if (stat.type == "local-candidate" && stat.candidateType == "relay") {
+ ok(
+ stat.relayProtocol,
+ `relay ${stat.type} has relayProtocol. value=${stat.relayProtocol} ` +
+ `(${stat.kind})`
+ );
+ } else {
+ is(
+ stat.relayProtocol,
+ undefined,
+ `relayProtocol is undefined for candidates that are not relay and ` +
+ `local. value=${stat.relayProtocol} (${stat.kind})`
+ );
+ }
+
+ // proxied
+ if (stat.proxied) {
+ ok(
+ stat.proxied == "proxied" || stat.proxied == "non-proxied",
+ `${stat.type} has proxied. value=${stat.proxied} (${stat.kind})`
+ );
+ }
+ }
+
+ //
+ // Ensure everything was tested
+ //
+ [...expectations.expected, ...expectations.optional].forEach(field => {
+ ok(
+ Object.keys(tested).includes(field),
+ `${stat.type}.${field} was tested.`
+ );
+ });
+ });
+}
+
+function dumpStats(stats) {
+ const dict = {};
+ for (const [k, v] of stats.entries()) {
+ dict[k] = v;
+ }
+ info(`Got stats: ${JSON.stringify(dict)}`);
+}
+
+async function waitForSyncedRtcp(pc) {
+ // Ensures that RTCP is present
+ let ensureSyncedRtcp = async () => {
+ let report = await pc.getStats();
+ for (const v of report.values()) {
+ if (v.type.endsWith("bound-rtp") && !(v.remoteId || v.localId)) {
+ info(`${v.id} is missing remoteId or localId: ${JSON.stringify(v)}`);
+ return null;
+ }
+ if (v.type == "remote-inbound-rtp" && v.roundTripTime === undefined) {
+ info(`${v.id} is missing roundTripTime: ${JSON.stringify(v)}`);
+ return null;
+ }
+ }
+ return report;
+ };
+ // Returns true if there is proof in aStats of rtcp flow for all remote stats
+ // objects, compared to baseStats.
+ const hasAllRtcpUpdated = (baseStats, stats) => {
+ let hasRtcpStats = false;
+ for (const v of stats.values()) {
+ if (v.type == "remote-outbound-rtp") {
+ hasRtcpStats = true;
+ if (!v.remoteTimestamp) {
+ // `remoteTimestamp` is 0 or not present.
+ return false;
+ }
+ if (v.remoteTimestamp <= baseStats.get(v.id)?.remoteTimestamp) {
+ // `remoteTimestamp` has not advanced further than the base stats,
+ // i.e., no new sender report has been received.
+ return false;
+ }
+ } else if (v.type == "remote-inbound-rtp") {
+ hasRtcpStats = true;
+ // The ideal thing here would be to check `reportsReceived`, but it's
+ // not yet implemented.
+ if (!v.packetsReceived) {
+ // `packetsReceived` is 0 or not present.
+ return false;
+ }
+ if (v.packetsReceived <= baseStats.get(v.id)?.packetsReceived) {
+ // `packetsReceived` has not advanced further than the base stats,
+ // i.e., no new receiver report has been received.
+ return false;
+ }
+ }
+ }
+ return hasRtcpStats;
+ };
+ let attempts = 0;
+ const baseStats = await pc.getStats();
+ // Time-units are MS
+ const waitPeriod = 100;
+ const maxTime = 20000;
+ for (let totalTime = maxTime; totalTime > 0; totalTime -= waitPeriod) {
+ try {
+ let syncedStats = await ensureSyncedRtcp();
+ if (syncedStats && hasAllRtcpUpdated(baseStats, syncedStats)) {
+ dumpStats(syncedStats);
+ return syncedStats;
+ }
+ } catch (e) {
+ info(e);
+ info(e.stack);
+ throw e;
+ }
+ attempts += 1;
+ info(`waitForSyncedRtcp: no sync on attempt ${attempts}, retrying.`);
+ await wait(waitPeriod);
+ }
+ throw Error(
+ "Waiting for synced RTCP timed out after at least " + maxTime + "ms"
+ );
+}
+
+function checkSenderStats(senderStats, streamCount) {
+ const outboundRtpReports = [];
+ const remoteInboundRtpReports = [];
+ for (const v of senderStats.values()) {
+ if (v.type == "outbound-rtp") {
+ outboundRtpReports.push(v);
+ } else if (v.type == "remote-inbound-rtp") {
+ remoteInboundRtpReports.push(v);
+ }
+ }
+ is(
+ outboundRtpReports.length,
+ streamCount,
+ `Sender with ${streamCount} simulcast streams has ${streamCount} outbound-rtp reports`
+ );
+ is(
+ remoteInboundRtpReports.length,
+ streamCount,
+ `Sender with ${streamCount} simulcast streams has ${streamCount} remote-inbound-rtp reports`
+ );
+ for (const outboundRtpReport of outboundRtpReports) {
+ is(
+ outboundRtpReports.filter(r => r.ssrc == outboundRtpReport.ssrc).length,
+ 1,
+ "Simulcast send track SSRCs are distinct"
+ );
+ const remoteReports = remoteInboundRtpReports.filter(
+ r => r.id == outboundRtpReport.remoteId
+ );
+ is(
+ remoteReports.length,
+ 1,
+ "Simulcast send tracks have exactly one remote counterpart"
+ );
+ const remoteInboundRtpReport = remoteReports[0];
+ is(
+ outboundRtpReport.ssrc,
+ remoteInboundRtpReport.ssrc,
+ "SSRC matches for outbound-rtp and remote-inbound-rtp"
+ );
+ }
+}
+
+function PC_LOCAL_TEST_LOCAL_STATS(test) {
+ return waitForSyncedRtcp(test.pcLocal._pc).then(stats => {
+ checkExpectedFields(stats);
+ pedanticChecks(stats);
+ return Promise.all([
+ test.pcLocal._pc.getSenders().map(async s => {
+ checkSenderStats(
+ await s.getStats(),
+ Math.max(1, s.getParameters()?.encodings?.length ?? 0)
+ );
+ }),
+ ]);
+ });
+}
+
+function PC_REMOTE_TEST_REMOTE_STATS(test) {
+ return waitForSyncedRtcp(test.pcRemote._pc).then(stats => {
+ checkExpectedFields(stats);
+ pedanticChecks(stats);
+ return Promise.all([
+ test.pcRemote._pc.getSenders().map(async s => {
+ checkSenderStats(
+ await s.getStats(),
+ s.track ? Math.max(1, s.getParameters()?.encodings?.length ?? 0) : 0
+ );
+ }),
+ ]);
+ });
+}
diff --git a/dom/media/webrtc/tests/mochitests/templates.js b/dom/media/webrtc/tests/mochitests/templates.js
new file mode 100644
index 0000000000..6b7750fd2c
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/templates.js
@@ -0,0 +1,615 @@
+/**
+ * Default list of commands to execute for a PeerConnection test.
+ */
+
+const STABLE = "stable";
+const HAVE_LOCAL_OFFER = "have-local-offer";
+const HAVE_REMOTE_OFFER = "have-remote-offer";
+const CLOSED = "closed";
+
+const ICE_NEW = "new";
+const GATH_NEW = "new";
+const GATH_GATH = "gathering";
+const GATH_COMPLETE = "complete";
+
+function deltaSeconds(date1, date2) {
+ return (date2.getTime() - date1.getTime()) / 1000;
+}
+
+function dumpSdp(test) {
+ if (typeof test._local_offer !== "undefined") {
+ dump("ERROR: SDP offer: " + test._local_offer.sdp.replace(/[\r]/g, ""));
+ }
+ if (typeof test._remote_answer !== "undefined") {
+ dump("ERROR: SDP answer: " + test._remote_answer.sdp.replace(/[\r]/g, ""));
+ }
+
+ if (
+ test.pcLocal &&
+ typeof test.pcLocal._local_ice_candidates !== "undefined"
+ ) {
+ dump(
+ "pcLocal._local_ice_candidates: " +
+ JSON.stringify(test.pcLocal._local_ice_candidates) +
+ "\n"
+ );
+ dump(
+ "pcLocal._remote_ice_candidates: " +
+ JSON.stringify(test.pcLocal._remote_ice_candidates) +
+ "\n"
+ );
+ dump(
+ "pcLocal._ice_candidates_to_add: " +
+ JSON.stringify(test.pcLocal._ice_candidates_to_add) +
+ "\n"
+ );
+ }
+ if (
+ test.pcRemote &&
+ typeof test.pcRemote._local_ice_candidates !== "undefined"
+ ) {
+ dump(
+ "pcRemote._local_ice_candidates: " +
+ JSON.stringify(test.pcRemote._local_ice_candidates) +
+ "\n"
+ );
+ dump(
+ "pcRemote._remote_ice_candidates: " +
+ JSON.stringify(test.pcRemote._remote_ice_candidates) +
+ "\n"
+ );
+ dump(
+ "pcRemote._ice_candidates_to_add: " +
+ JSON.stringify(test.pcRemote._ice_candidates_to_add) +
+ "\n"
+ );
+ }
+
+ if (test.pcLocal && typeof test.pcLocal.iceConnectionLog !== "undefined") {
+ dump(
+ "pcLocal ICE connection state log: " +
+ test.pcLocal.iceConnectionLog +
+ "\n"
+ );
+ }
+ if (test.pcRemote && typeof test.pcRemote.iceConnectionLog !== "undefined") {
+ dump(
+ "pcRemote ICE connection state log: " +
+ test.pcRemote.iceConnectionLog +
+ "\n"
+ );
+ }
+
+ if (
+ test.pcLocal &&
+ test.pcRemote &&
+ typeof test.pcLocal.setRemoteDescDate !== "undefined" &&
+ typeof test.pcRemote.setLocalDescDate !== "undefined"
+ ) {
+ var delta = deltaSeconds(
+ test.pcLocal.setRemoteDescDate,
+ test.pcRemote.setLocalDescDate
+ );
+ dump(
+ "Delay between pcLocal.setRemote <-> pcRemote.setLocal: " + delta + "\n"
+ );
+ }
+ if (
+ test.pcLocal &&
+ test.pcRemote &&
+ typeof test.pcLocal.setRemoteDescDate !== "undefined" &&
+ typeof test.pcLocal.setRemoteDescStableEventDate !== "undefined"
+ ) {
+ var delta = deltaSeconds(
+ test.pcLocal.setRemoteDescDate,
+ test.pcLocal.setRemoteDescStableEventDate
+ );
+ dump(
+ "Delay between pcLocal.setRemote <-> pcLocal.signalingStateStable: " +
+ delta +
+ "\n"
+ );
+ }
+ if (
+ test.pcLocal &&
+ test.pcRemote &&
+ typeof test.pcRemote.setLocalDescDate !== "undefined" &&
+ typeof test.pcRemote.setLocalDescStableEventDate !== "undefined"
+ ) {
+ var delta = deltaSeconds(
+ test.pcRemote.setLocalDescDate,
+ test.pcRemote.setLocalDescStableEventDate
+ );
+ dump(
+ "Delay between pcRemote.setLocal <-> pcRemote.signalingStateStable: " +
+ delta +
+ "\n"
+ );
+ }
+}
+
+// We need to verify that at least one candidate has been (or will be) gathered.
+function waitForAnIceCandidate(pc) {
+ return new Promise(resolve => {
+ if (!pc.localRequiresTrickleIce || pc._local_ice_candidates.length) {
+ resolve();
+ } else {
+ // In some circumstances, especially when both PCs are on the same
+ // browser, even though we are connected, the connection can be
+ // established without receiving a single candidate from one or other
+ // peer. So we wait for at least one...
+ pc._pc.addEventListener("icecandidate", resolve);
+ }
+ }).then(() => {
+ ok(
+ pc._local_ice_candidates.length,
+ pc + " received local trickle ICE candidates"
+ );
+ isnot(
+ pc._pc.iceGatheringState,
+ GATH_NEW,
+ pc + " ICE gathering state is not 'new'"
+ );
+ });
+}
+
+async function checkTrackStats(pc, track, outbound) {
+ const audio = track.kind == "audio";
+ const msg =
+ `${pc} stats ${outbound ? "outbound " : "inbound "}` +
+ `${audio ? "audio" : "video"} rtp track id ${track.id}`;
+ const stats = await pc.getStats(track);
+ ok(
+ pc.hasStat(stats, {
+ type: outbound ? "outbound-rtp" : "inbound-rtp",
+ kind: audio ? "audio" : "video",
+ }),
+ `${msg} - found expected stats`
+ );
+ ok(
+ !pc.hasStat(stats, {
+ type: outbound ? "inbound-rtp" : "outbound-rtp",
+ }),
+ `${msg} - did not find extra stats with wrong direction`
+ );
+ ok(
+ !pc.hasStat(stats, {
+ kind: audio ? "video" : "audio",
+ }),
+ `${msg} - did not find extra stats with wrong media type`
+ );
+}
+
+function checkAllTrackStats(pc) {
+ return Promise.all([
+ ...pc
+ .getExpectedActiveReceivers()
+ .map(({ track }) => checkTrackStats(pc, track, false)),
+ ...pc
+ .getExpectedSenders()
+ .map(({ track }) => checkTrackStats(pc, track, true)),
+ ]);
+}
+
+// Commands run once at the beginning of each test, even when performing a
+// renegotiation test.
+var commandsPeerConnectionInitial = [
+ function PC_LOCAL_SETUP_ICE_LOGGER(test) {
+ test.pcLocal.logIceConnectionState();
+ },
+
+ function PC_REMOTE_SETUP_ICE_LOGGER(test) {
+ test.pcRemote.logIceConnectionState();
+ },
+
+ function PC_LOCAL_SETUP_SIGNALING_LOGGER(test) {
+ test.pcLocal.logSignalingState();
+ },
+
+ function PC_REMOTE_SETUP_SIGNALING_LOGGER(test) {
+ test.pcRemote.logSignalingState();
+ },
+
+ function PC_LOCAL_SETUP_TRACK_HANDLER(test) {
+ test.pcLocal.setupTrackEventHandler();
+ },
+
+ function PC_REMOTE_SETUP_TRACK_HANDLER(test) {
+ test.pcRemote.setupTrackEventHandler();
+ },
+
+ function PC_LOCAL_CHECK_INITIAL_SIGNALINGSTATE(test) {
+ is(
+ test.pcLocal.signalingState,
+ STABLE,
+ "Initial local signalingState is 'stable'"
+ );
+ },
+
+ function PC_REMOTE_CHECK_INITIAL_SIGNALINGSTATE(test) {
+ is(
+ test.pcRemote.signalingState,
+ STABLE,
+ "Initial remote signalingState is 'stable'"
+ );
+ },
+
+ function PC_LOCAL_CHECK_INITIAL_ICE_STATE(test) {
+ is(
+ test.pcLocal.iceConnectionState,
+ ICE_NEW,
+ "Initial local ICE connection state is 'new'"
+ );
+ },
+
+ function PC_REMOTE_CHECK_INITIAL_ICE_STATE(test) {
+ is(
+ test.pcRemote.iceConnectionState,
+ ICE_NEW,
+ "Initial remote ICE connection state is 'new'"
+ );
+ },
+
+ function PC_LOCAL_CHECK_INITIAL_CAN_TRICKLE_SYNC(test) {
+ is(
+ test.pcLocal._pc.canTrickleIceCandidates,
+ null,
+ "Local trickle status should start out unknown"
+ );
+ },
+
+ function PC_REMOTE_CHECK_INITIAL_CAN_TRICKLE_SYNC(test) {
+ is(
+ test.pcRemote._pc.canTrickleIceCandidates,
+ null,
+ "Remote trickle status should start out unknown"
+ );
+ },
+];
+
+var commandsGetUserMedia = [
+ function PC_LOCAL_GUM(test) {
+ return test.pcLocal.getAllUserMediaAndAddStreams(test.pcLocal.constraints);
+ },
+
+ function PC_REMOTE_GUM(test) {
+ return test.pcRemote.getAllUserMediaAndAddStreams(
+ test.pcRemote.constraints
+ );
+ },
+];
+
+var commandsPeerConnectionOfferAnswer = [
+ function PC_LOCAL_SETUP_ICE_HANDLER(test) {
+ test.pcLocal.setupIceCandidateHandler(test);
+ },
+
+ function PC_REMOTE_SETUP_ICE_HANDLER(test) {
+ test.pcRemote.setupIceCandidateHandler(test);
+ },
+
+ function PC_LOCAL_CREATE_OFFER(test) {
+ return test.createOffer(test.pcLocal).then(offer => {
+ is(
+ test.pcLocal.signalingState,
+ STABLE,
+ "Local create offer does not change signaling state"
+ );
+ });
+ },
+
+ function PC_LOCAL_SET_LOCAL_DESCRIPTION(test) {
+ return test
+ .setLocalDescription(test.pcLocal, test.originalOffer, HAVE_LOCAL_OFFER)
+ .then(() => {
+ is(
+ test.pcLocal.signalingState,
+ HAVE_LOCAL_OFFER,
+ "signalingState after local setLocalDescription is 'have-local-offer'"
+ );
+ });
+ },
+
+ function PC_REMOTE_GET_OFFER(test) {
+ test._local_offer = test.originalOffer;
+ test._offer_constraints = test.pcLocal.constraints;
+ test._offer_options = test.pcLocal.offerOptions;
+ return Promise.resolve();
+ },
+
+ function PC_REMOTE_SET_REMOTE_DESCRIPTION(test) {
+ return test
+ .setRemoteDescription(test.pcRemote, test._local_offer, HAVE_REMOTE_OFFER)
+ .then(() => {
+ is(
+ test.pcRemote.signalingState,
+ HAVE_REMOTE_OFFER,
+ "signalingState after remote setRemoteDescription is 'have-remote-offer'"
+ );
+ });
+ },
+
+ function PC_REMOTE_CHECK_CAN_TRICKLE_SYNC(test) {
+ is(
+ test.pcRemote._pc.canTrickleIceCandidates,
+ true,
+ "Remote thinks that local can trickle"
+ );
+ },
+
+ function PC_LOCAL_SANE_LOCAL_SDP(test) {
+ test.pcLocal.localRequiresTrickleIce = sdputils.verifySdp(
+ test._local_offer,
+ "offer",
+ test._offer_constraints,
+ test._offer_options,
+ test.testOptions
+ );
+ },
+
+ function PC_REMOTE_SANE_REMOTE_SDP(test) {
+ test.pcRemote.remoteRequiresTrickleIce = sdputils.verifySdp(
+ test._local_offer,
+ "offer",
+ test._offer_constraints,
+ test._offer_options,
+ test.testOptions
+ );
+ },
+
+ function PC_REMOTE_CREATE_ANSWER(test) {
+ return test.createAnswer(test.pcRemote).then(answer => {
+ is(
+ test.pcRemote.signalingState,
+ HAVE_REMOTE_OFFER,
+ "Remote createAnswer does not change signaling state"
+ );
+ });
+ },
+
+ function PC_REMOTE_SET_LOCAL_DESCRIPTION(test) {
+ return test
+ .setLocalDescription(test.pcRemote, test.originalAnswer, STABLE)
+ .then(() => {
+ is(
+ test.pcRemote.signalingState,
+ STABLE,
+ "signalingState after remote setLocalDescription is 'stable'"
+ );
+ });
+ },
+
+ function PC_LOCAL_GET_ANSWER(test) {
+ test._remote_answer = test.originalAnswer;
+ test._answer_constraints = test.pcRemote.constraints;
+ return Promise.resolve();
+ },
+
+ function PC_LOCAL_SET_REMOTE_DESCRIPTION(test) {
+ return test
+ .setRemoteDescription(test.pcLocal, test._remote_answer, STABLE)
+ .then(() => {
+ is(
+ test.pcLocal.signalingState,
+ STABLE,
+ "signalingState after local setRemoteDescription is 'stable'"
+ );
+ });
+ },
+
+ function PC_REMOTE_SANE_LOCAL_SDP(test) {
+ test.pcRemote.localRequiresTrickleIce = sdputils.verifySdp(
+ test._remote_answer,
+ "answer",
+ test._offer_constraints,
+ test._offer_options,
+ test.testOptions
+ );
+ },
+ function PC_LOCAL_SANE_REMOTE_SDP(test) {
+ test.pcLocal.remoteRequiresTrickleIce = sdputils.verifySdp(
+ test._remote_answer,
+ "answer",
+ test._offer_constraints,
+ test._offer_options,
+ test.testOptions
+ );
+ },
+
+ function PC_LOCAL_CHECK_CAN_TRICKLE_SYNC(test) {
+ is(
+ test.pcLocal._pc.canTrickleIceCandidates,
+ true,
+ "Local thinks that remote can trickle"
+ );
+ },
+
+ function PC_LOCAL_WAIT_FOR_ICE_CONNECTED(test) {
+ return test.pcLocal.waitForIceConnected().then(() => {
+ info(
+ test.pcLocal +
+ ": ICE connection state log: " +
+ test.pcLocal.iceConnectionLog
+ );
+ });
+ },
+
+ function PC_REMOTE_WAIT_FOR_ICE_CONNECTED(test) {
+ return test.pcRemote.waitForIceConnected().then(() => {
+ info(
+ test.pcRemote +
+ ": ICE connection state log: " +
+ test.pcRemote.iceConnectionLog
+ );
+ });
+ },
+
+ function PC_LOCAL_VERIFY_ICE_GATHERING(test) {
+ return waitForAnIceCandidate(test.pcLocal);
+ },
+
+ function PC_REMOTE_VERIFY_ICE_GATHERING(test) {
+ return waitForAnIceCandidate(test.pcRemote);
+ },
+
+ function PC_LOCAL_WAIT_FOR_MEDIA_FLOW(test) {
+ return test.pcLocal.waitForMediaFlow();
+ },
+
+ function PC_REMOTE_WAIT_FOR_MEDIA_FLOW(test) {
+ return test.pcRemote.waitForMediaFlow();
+ },
+
+ function PC_LOCAL_CHECK_STATS(test) {
+ return test.pcLocal.getStats().then(stats => {
+ test.pcLocal.checkStats(stats);
+ });
+ },
+
+ function PC_REMOTE_CHECK_STATS(test) {
+ return test.pcRemote.getStats().then(stats => {
+ test.pcRemote.checkStats(stats);
+ });
+ },
+
+ function PC_LOCAL_CHECK_ICE_CONNECTION_TYPE(test) {
+ return test.pcLocal.getStats().then(stats => {
+ test.pcLocal.checkStatsIceConnectionType(
+ stats,
+ test.testOptions.expectedLocalCandidateType
+ );
+ });
+ },
+
+ function PC_REMOTE_CHECK_ICE_CONNECTION_TYPE(test) {
+ return test.pcRemote.getStats().then(stats => {
+ test.pcRemote.checkStatsIceConnectionType(
+ stats,
+ test.testOptions.expectedRemoteCandidateType
+ );
+ });
+ },
+
+ function PC_LOCAL_CHECK_ICE_CONNECTIONS(test) {
+ return test.pcLocal.getStats().then(stats => {
+ test.pcLocal.checkStatsIceConnections(stats, test.testOptions);
+ });
+ },
+
+ function PC_REMOTE_CHECK_ICE_CONNECTIONS(test) {
+ return test.pcRemote.getStats().then(stats => {
+ test.pcRemote.checkStatsIceConnections(stats, test.testOptions);
+ });
+ },
+
+ function PC_LOCAL_CHECK_MSID(test) {
+ return test.pcLocal.checkLocalMsids();
+ },
+ function PC_REMOTE_CHECK_MSID(test) {
+ return test.pcRemote.checkLocalMsids();
+ },
+
+ function PC_LOCAL_CHECK_TRACK_STATS(test) {
+ return checkAllTrackStats(test.pcLocal);
+ },
+ function PC_REMOTE_CHECK_TRACK_STATS(test) {
+ return checkAllTrackStats(test.pcRemote);
+ },
+ function PC_LOCAL_VERIFY_SDP_AFTER_END_OF_TRICKLE(test) {
+ if (test.pcLocal.endOfTrickleSdp) {
+ /* In case the endOfTrickleSdp promise is resolved already it will win the
+ * race because it gets evaluated first. But if endOfTrickleSdp is still
+ * pending the rejection will win the race. */
+ return Promise.race([
+ test.pcLocal.endOfTrickleSdp,
+ Promise.reject("No SDP"),
+ ]).then(
+ sdp =>
+ sdputils.checkSdpAfterEndOfTrickle(
+ sdp,
+ test.testOptions,
+ test.pcLocal.label
+ ),
+ () =>
+ info(
+ "pcLocal: Gathering is not complete yet, skipping post-gathering SDP check"
+ )
+ );
+ }
+ },
+ function PC_REMOTE_VERIFY_SDP_AFTER_END_OF_TRICKLE(test) {
+ if (test.pcRemote.endOfTrickleSdp) {
+ /* In case the endOfTrickleSdp promise is resolved already it will win the
+ * race because it gets evaluated first. But if endOfTrickleSdp is still
+ * pending the rejection will win the race. */
+ return Promise.race([
+ test.pcRemote.endOfTrickleSdp,
+ Promise.reject("No SDP"),
+ ]).then(
+ sdp =>
+ sdputils.checkSdpAfterEndOfTrickle(
+ sdp,
+ test.testOptions,
+ test.pcRemote.label
+ ),
+ () =>
+ info(
+ "pcRemote: Gathering is not complete yet, skipping post-gathering SDP check"
+ )
+ );
+ }
+ },
+];
+
+function PC_LOCAL_REMOVE_ALL_BUT_H264_FROM_OFFER(test) {
+ isnot(
+ test.originalOffer.sdp.search("H264/90000"),
+ -1,
+ "H.264 should be present in the SDP offer"
+ );
+ test.originalOffer.sdp = sdputils.removeCodec(
+ sdputils.removeCodec(
+ sdputils.removeCodec(test.originalOffer.sdp, 120),
+ 121,
+ 97
+ )
+ );
+ info("Updated H264 only offer: " + JSON.stringify(test.originalOffer));
+}
+
+function PC_LOCAL_REMOVE_BUNDLE_FROM_OFFER(test) {
+ test.originalOffer.sdp = sdputils.removeBundle(test.originalOffer.sdp);
+ info("Updated no bundle offer: " + JSON.stringify(test.originalOffer));
+}
+
+function PC_LOCAL_REMOVE_RTCPMUX_FROM_OFFER(test) {
+ test.originalOffer.sdp = sdputils.removeRtcpMux(test.originalOffer.sdp);
+ info("Updated no RTCP-Mux offer: " + JSON.stringify(test.originalOffer));
+}
+
+function PC_LOCAL_REMOVE_SSRC_FROM_OFFER(test) {
+ test.originalOffer.sdp = sdputils.removeSSRCs(test.originalOffer.sdp);
+ info("Updated no SSRCs offer: " + JSON.stringify(test.originalOffer));
+}
+
+function PC_REMOTE_REMOVE_SSRC_FROM_ANSWER(test) {
+ test.originalAnswer.sdp = sdputils.removeSSRCs(test.originalAnswer.sdp);
+ info("Updated no SSRCs answer: " + JSON.stringify(test.originalAnswer));
+}
+
+var addRenegotiation = (chain, commands, checks) => {
+ chain.append(commands);
+ chain.append(commandsPeerConnectionOfferAnswer);
+ if (checks) {
+ chain.append(checks);
+ }
+};
+
+var addRenegotiationAnswerer = (chain, commands, checks) => {
+ chain.append(function SWAP_PC_LOCAL_PC_REMOTE(test) {
+ var temp = test.pcLocal;
+ test.pcLocal = test.pcRemote;
+ test.pcRemote = temp;
+ });
+ addRenegotiation(chain, commands, checks);
+};
diff --git a/dom/media/webrtc/tests/mochitests/test_1488832.html b/dom/media/webrtc/tests/mochitests/test_1488832.html
new file mode 100644
index 0000000000..8798994b24
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_1488832.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<iframe id="testframe"></iframe>
+<script>
+"use strict";
+
+createHTML({
+ title: "gUM shutdown race",
+ bug: "1488832"
+});
+
+runTest(async () => {
+ testframe.srcdoc = `
+ <html>
+ <head>
+ <script>
+ function start() {
+ for (let i = 0; i < 16; i++) {
+ window.navigator.mediaDevices.getUserMedia({video: true})
+ setTimeout('location.reload()', 100)
+ }
+ }
+ document.addEventListener('DOMContentLoaded', start)
+ </` + `script>
+ </head>
+ </html>`;
+
+ await wait(10000);
+ testframe.srcdoc = "";
+});
+</script>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_1717318.html b/dom/media/webrtc/tests/mochitests/test_1717318.html
new file mode 100644
index 0000000000..425bd29e7e
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_1717318.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>PC construct with no global object (bug 1717318)</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script class="testbody" type="text/javascript">
+"use strict";
+
+// nsIArray is not special here, it could be pretty much any interface.
+// We do this outside the try block just in case someday the interface is
+// removed.
+const dummyInterface = SpecialPowers.Components.interfaces.nsIArray;
+ok(dummyInterface, "nsIArray should exist");
+try {
+ // Just don't crash.
+ SpecialPowers.Components.classes["@mozilla.org/peerconnection;1"]
+ .createInstance(dummyInterface);
+} catch (e) {}
+
+</script>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_a_noOp.html b/dom/media/webrtc/tests/mochitests/test_a_noOp.html
new file mode 100644
index 0000000000..971f5d7666
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_a_noOp.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1264772
+-->
+<head>
+ <title>Test for Bug 1264772</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1264772">Mozilla Bug 1264772</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 1264772 **/
+// The WebRTC tests seem to have more problems with intermittents (at
+// least on Android) if they run first in a test run. This is a dummy test
+// to ensure that the browser is ready prior to running any actual WebRTC
+// tests.
+//
+// Note: mochitests are run in alphabetical order, so it is not sufficient
+// for this test to appear first in the manifest.
+ok(true, "test passed");
+
+</script>
+</pre>
+</body>
diff --git a/dom/media/webrtc/tests/mochitests/test_dataChannel_basicAudio.html b/dom/media/webrtc/tests/mochitests/test_dataChannel_basicAudio.html
new file mode 100644
index 0000000000..06ca9562ad
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_dataChannel_basicAudio.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "796895",
+ title: "Basic data channel audio connection"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ test = new PeerConnectionTest(options);
+ addInitialDataChannel(test.chain);
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ return test.run();
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_dataChannel_basicAudioVideo.html b/dom/media/webrtc/tests/mochitests/test_dataChannel_basicAudioVideo.html
new file mode 100644
index 0000000000..ea534ca2e7
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_dataChannel_basicAudioVideo.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "796891",
+ title: "Basic data channel audio/video connection"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ test = new PeerConnectionTest(options);
+ addInitialDataChannel(test.chain);
+ test.setMediaConstraints([{audio: true}, {video: true}],
+ [{audio: true}, {video: true}]);
+ return test.run();
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_dataChannel_basicAudioVideoCombined.html b/dom/media/webrtc/tests/mochitests/test_dataChannel_basicAudioVideoCombined.html
new file mode 100644
index 0000000000..d5409986ec
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_dataChannel_basicAudioVideoCombined.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "796891",
+ title: "Basic data channel audio/video connection"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ test = new PeerConnectionTest(options);
+ addInitialDataChannel(test.chain);
+ test.setMediaConstraints([{audio: true, video: true}],
+ [{audio: true, video: true}]);
+ return test.run();
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_dataChannel_basicAudioVideoNoBundle.html b/dom/media/webrtc/tests/mochitests/test_dataChannel_basicAudioVideoNoBundle.html
new file mode 100644
index 0000000000..7dc22d86ad
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_dataChannel_basicAudioVideoNoBundle.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1016476",
+ title: "Basic data channel audio/video connection without bundle"
+ });
+
+var test;
+runNetworkTest(function (options) {
+ options = options || { };
+ options.bundle = false;
+ test = new PeerConnectionTest(options);
+ addInitialDataChannel(test.chain);
+ test.setMediaConstraints([{audio: true}, {video: true}],
+ [{audio: true}, {video: true}]);
+ return test.run();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_dataChannel_basicDataOnly.html b/dom/media/webrtc/tests/mochitests/test_dataChannel_basicDataOnly.html
new file mode 100644
index 0000000000..98e72f7a21
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_dataChannel_basicDataOnly.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "796894",
+ title: "Basic datachannel only connection"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ test = new PeerConnectionTest(options);
+ addInitialDataChannel(test.chain);
+ return test.run();
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_dataChannel_basicVideo.html b/dom/media/webrtc/tests/mochitests/test_dataChannel_basicVideo.html
new file mode 100644
index 0000000000..90f2d7caff
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_dataChannel_basicVideo.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "796889",
+ title: "Basic data channel video connection"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ test = new PeerConnectionTest(options);
+ addInitialDataChannel(test.chain);
+ test.setMediaConstraints([{video: true}], [{video: true}]);
+ return test.run();
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_dataChannel_bug1013809.html b/dom/media/webrtc/tests/mochitests/test_dataChannel_bug1013809.html
new file mode 100644
index 0000000000..e36caebab4
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_dataChannel_bug1013809.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "796895",
+ title: "Basic data channel audio connection"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ test = new PeerConnectionTest(options);
+ addInitialDataChannel(test.chain);
+ var sld = test.chain.remove("PC_REMOTE_SET_LOCAL_DESCRIPTION");
+ test.chain.insertAfter("PC_LOCAL_SET_REMOTE_DESCRIPTION", sld);
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ return test.run();
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_dataChannel_dataOnlyBufferedAmountLow.html b/dom/media/webrtc/tests/mochitests/test_dataChannel_dataOnlyBufferedAmountLow.html
new file mode 100644
index 0000000000..26767e0865
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_dataChannel_dataOnlyBufferedAmountLow.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1051685",
+ title: "Verify bufferedAmountLowThreshold works"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ test = new PeerConnectionTest(options);
+ addInitialDataChannel(test.chain);
+ test.chain.insertAfter('PC_REMOTE_CHECK_ICE_CONNECTIONS', commandsCheckLargeXfer);
+ return test.run();
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_dataChannel_dtlsVersions.html b/dom/media/webrtc/tests/mochitests/test_dataChannel_dtlsVersions.html
new file mode 100644
index 0000000000..6f0cbc5d3d
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_dataChannel_dtlsVersions.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1284103",
+ title: "Test basic data channel audio connection for supported DTLS versions"
+ });
+
+ async function testDtlsVersion(options, version) {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["media.peerconnection.dtls.version.min", version],
+ ["media.peerconnection.dtls.version.max", version]
+ ]
+ });
+
+ const test = new PeerConnectionTest(options);
+ addInitialDataChannel(test.chain);
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+
+ await test.run();
+ }
+
+ runNetworkTest(async (options) => {
+ // 770 = DTLS 1.0, 771 = DTLS 1.2, 772 = DTLS 1.3
+ for (var version = 770; version <= 772; version++) {
+ await testDtlsVersion(options, version);
+ }
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_dataChannel_hostnameObfuscation.html b/dom/media/webrtc/tests/mochitests/test_dataChannel_hostnameObfuscation.html
new file mode 100644
index 0000000000..d0790fb9c9
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_dataChannel_hostnameObfuscation.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1592620",
+ title: "Blocklist to disable hostname obfuscation"
+ });
+
+ async function testBlocklist(options, blocklistEntry, shouldBeObfuscated) {
+ let test = new PeerConnectionTest(options);
+ addInitialDataChannel(test.chain);
+
+ if (blocklistEntry !== null) {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["media.peerconnection.ice.obfuscate_host_addresses.blocklist",
+ blocklistEntry]
+ ]
+ });
+ }
+
+ test.chain.insertAfter('PC_LOCAL_WAIT_FOR_ICE_CONNECTED', [
+ async function CHECK_LOCAL_CANDIDATES() {
+ const stats = await test.pcLocal.getStats();
+ stats.forEach(s => {
+ if (s.type === 'local-candidate') {
+ if (shouldBeObfuscated) {
+ ok(s.address.includes(".local"), "address should be obfuscated");
+ } else {
+ ok(!s.address.includes(".local"), "address should not be obfuscated");
+ }
+ }
+ });
+ }]);
+
+ await test.run();
+ }
+
+ runNetworkTest(async (options) => {
+ await SpecialPowers.pushPrefEnv({
+ set: [["media.peerconnection.ice.obfuscate_host_addresses", true]]
+ });
+ await testBlocklist(options, null, true);
+ await testBlocklist(options, "", true);
+ await testBlocklist(options, "example.com", true);
+ await testBlocklist(options, "mochi.test", false);
+ await testBlocklist(options, "example.com,mochi.test", false);
+ await testBlocklist(options, "*.test", false);
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_dataChannel_noOffer.html b/dom/media/webrtc/tests/mochitests/test_dataChannel_noOffer.html
new file mode 100644
index 0000000000..a6e9fa5214
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_dataChannel_noOffer.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "856319",
+ title: "Don't offer m=application unless createDataChannel is called first"
+ });
+
+ runNetworkTest(async function () {
+ const pc = new RTCPeerConnection();
+
+ // necessary to circumvent bug 864109
+ const options = { offerToReceiveAudio: true };
+
+ const errorCallback = generateErrorCallback();
+ try {
+ const offer = await pc.createOffer(options);
+ ok(!offer.sdp.includes("m=application"),
+ "m=application is not contained in the SDP");
+ } catch(e) {
+ errorCallback(e);
+ }
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_dataChannel_stats.html b/dom/media/webrtc/tests/mochitests/test_dataChannel_stats.html
new file mode 100644
index 0000000000..4498e2d23a
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_dataChannel_stats.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1218356",
+ title: "DataChannel stats"
+ });
+
+ runNetworkTest(function (options) {
+ const test = new PeerConnectionTest(options);
+ test.chain.remove('PC_LOCAL_CHECK_STATS');
+ test.chain.remove('PC_REMOTE_CHECK_STATS');
+ addInitialDataChannel(test.chain);
+ test.chain.removeAfter("PC_REMOTE_CHECK_ICE_CONNECTIONS");
+ test.chain.insertAfter("PC_REMOTE_CHECK_ICE_CONNECTIONS",
+ async function TEST_DATA_CHANNEL_STATS(test) {
+ const channel = test.pcLocal.dataChannels[0];
+ test.pcRemote.dataChannels[0].onbufferedamountlow = () => {};
+ test.pcRemote.dataChannels[0].send(`Sending Message`);
+ channel.onbufferedamountlow = () => {};
+ const event = await new Promise( r => channel.onmessage = r);
+ info(`Received message: "${event.data}"`);
+ const report = await test.pcLocal.getStats();
+ info(`Received Stats ${JSON.stringify([...report.values()], null, 2)}\n`);
+ const stats = [...report.values()].find(block => block.type == "data-channel");
+ info(`DataChannel stats ${JSON.stringify(stats, null, 2)}`);
+ is(stats.label, channel.label, 'DataChannel stats has correct label');
+ is(stats.protocol, channel.protocol,
+ 'DataChannel stats has correct protocol');
+ is(stats.dataChannelIdentifier, channel.id,
+ 'DataChannel stats has correct dataChannelIdentifier');
+ is(stats.state, channel.readyState, 'DataChannel has correct state');
+ is(stats.bytesReceived, 15, 'DataChannel has correct bytesReceived');
+ is(stats.bytesSent, 0, 'DataChannel has correct bytesSent');
+ is(stats.messagesReceived, 1,
+ 'DataChannel has correct messagesReceived');
+ is(stats.messagesSent, 0, 'DataChannel has correct messagesSent');
+ });
+ return test.run();
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_defaultAudioConstraints.html b/dom/media/webrtc/tests/mochitests/test_defaultAudioConstraints.html
new file mode 100644
index 0000000000..8e0db48fff
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_defaultAudioConstraints.html
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+createHTML({
+ title: "Test that the audio constraints that observe at the audio constraints we expect.",
+ bug: "1509842"
+});
+
+runTest(async () => {
+ // We need a real device to get a MediaEngine supporting constraints
+ let audioDevice = SpecialPowers.getCharPref("media.audio_loopback_dev", "");
+ if (!audioDevice) {
+ todo(false, "No device set by framework. Try --use-test-media-devices");
+ return;
+ }
+
+ // Get a gUM track with the default settings, check that they are what we
+ // expect.
+ let stream = await navigator.mediaDevices.getUserMedia({ audio: true });
+ let track = stream.getAudioTracks()[0];
+ let defaultSettings = track.getSettings();
+
+ is(defaultSettings.echoCancellation, true,
+ "Echo cancellation should be ON by default.");
+ is(defaultSettings.noiseSuppression, true,
+ "Noise suppression should be ON by default.");
+ is(defaultSettings.autoGainControl, true,
+ "Automatic gain control should be ON by default.");
+
+ track.stop();
+
+ // This is UA-dependant, and belongs in a Mochitest, not in a WPT.
+ // When a gUM track has been requested with `echoCancellation` OFF, check that
+ // `noiseSuppression` and `autoGainControl` are off as well.
+ stream =
+ await navigator.mediaDevices.getUserMedia({audio:{echoCancellation: false}});
+ track = stream.getAudioTracks()[0];
+ defaultSettings = track.getSettings();
+
+ is(defaultSettings.echoCancellation, false,
+ "Echo cancellation should be OFF when requested.");
+ is(defaultSettings.noiseSuppression, false,
+ `Noise suppression should be OFF when echoCancellation is the only
+ constraint and is OFF.`);
+ is(defaultSettings.autoGainControl, false,
+ `Automatic gain control should be OFF when echoCancellation is the only
+ constraint and is OFF.`);
+
+ track.stop();
+
+ // When a gUM track has been requested with `echoCancellation` OFF, check that
+ // `noiseSuppression` and `autoGainControl` are not OFF as well if another
+ // constraint has been specified.
+ stream =
+ await navigator.mediaDevices.getUserMedia({audio:{echoCancellation: false,
+ autoGainControl: true}});
+ track = stream.getAudioTracks()[0];
+ defaultSettings = track.getSettings();
+
+ is(defaultSettings.echoCancellation, false,
+ "Echo cancellation should be OFF when requested.");
+ is(defaultSettings.noiseSuppression, false,
+ `Noise suppression should be OFF when echoCancellation is OFF and another
+ constraint has been specified.`);
+ is(defaultSettings.autoGainControl, true,
+ "Auto gain control should be ON when requested.");
+
+ track.stop();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_enumerateDevices.html b/dom/media/webrtc/tests/mochitests/test_enumerateDevices.html
new file mode 100644
index 0000000000..48bec0006a
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_enumerateDevices.html
@@ -0,0 +1,141 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({ title: "Run enumerateDevices code", bug: "1046245" });
+/**
+ Tests covering enumerateDevices API and deviceId constraint. Exercise code.
+*/
+
+async function mustSucceedWithStream(msg, f) {
+ try {
+ const stream = await f();
+ for (const track of stream.getTracks()) {
+ track.stop();
+ }
+ ok(true, msg + " must succeed");
+ } catch (e) {
+ is(e.name, null, msg + " must succeed: " + e.message);
+ }
+}
+
+async function mustFailWith(msg, reason, constraint, f) {
+ try {
+ await f();
+ ok(false, msg + " must fail");
+ } catch(e) {
+ is(e.name, reason, msg + " must fail: " + e.message);
+ if (constraint) {
+ is(e.constraint, constraint, msg + " must fail w/correct constraint.");
+ }
+ }
+}
+
+const gUM = c => navigator.mediaDevices.getUserMedia(c);
+
+const kinds = ["videoinput", "audioinput", "audiooutput"];
+
+function validateDevice({kind, label, deviceId, groupId}) {
+ ok(kinds.includes(kind), "Known device kind");
+ is(deviceId.length, 44, "deviceId length id as expected for Firefox");
+ ok(label.length !== undefined, "Device label: " + label);
+ isnot(groupId, "", "groupId must be present.");
+}
+
+runTest(async () => {
+ await pushPrefs(["media.navigator.streams.fake", true]);
+
+ // Validate enumerated devices after gUM.
+ for (const track of (await gUM({video: true, audio: true})).getTracks()) {
+ track.stop();
+ }
+
+ let devices = await navigator.mediaDevices.enumerateDevices();
+ ok(devices.length, "At least one device found");
+ const jsoned = JSON.parse(JSON.stringify(devices));
+ is(jsoned[0].kind, devices[0].kind, "kind survived serializer");
+ is(jsoned[0].deviceId, devices[0].deviceId, "deviceId survived serializer");
+ for (const device of devices) {
+ validateDevice(device);
+ if (device.kind == "audiooutput") continue;
+ // Test deviceId constraint
+ let deviceId = device.deviceId;
+ let constraints = (device.kind == "videoinput") ? { video: { deviceId } }
+ : { audio: { deviceId } };
+ for (const track of (await gUM(constraints)).getTracks()) {
+ is(typeof(track.label), "string", "Track label is a string");
+ is(track.label, device.label, "Track label is the device label");
+ track.stop();
+ }
+ }
+
+ const unknownId = "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=";
+
+ // Check deviceId failure paths for video.
+
+ await mustSucceedWithStream("unknown plain deviceId on video",
+ () => gUM({ video: { deviceId: unknownId } }));
+ await mustSucceedWithStream("unknown plain deviceId on audio",
+ () => gUM({ audio: { deviceId: unknownId } }));
+ await mustFailWith("unknown exact deviceId on video",
+ "OverconstrainedError", "deviceId",
+ () => gUM({ video: { deviceId: { exact: unknownId } } }));
+ await mustFailWith("unknown exact deviceId on audio",
+ "OverconstrainedError", "deviceId",
+ () => gUM({ audio: { deviceId: { exact: unknownId } } }));
+
+ // Check that deviceIds are stable for same origin and differ across origins.
+
+ const path = "/tests/dom/media/webrtc/tests/mochitests/test_enumerateDevices_iframe.html";
+ const origins = ["https://example.com", "https://test1.example.com"];
+ info(window.location);
+
+ const haveDevicesMap = new Promise(resolve => {
+ const map = new Map();
+ window.addEventListener("message", ({origin, data}) => {
+ ok(origins.includes(origin), "Got message from expected origin");
+ map.set(origin, JSON.parse(data));
+ if (map.size < origins.length) return;
+ resolve(map);
+ });
+ });
+
+ await Promise.all(origins.map(origin => {
+ const iframe = document.createElement("iframe");
+ iframe.src = origin + path;
+ iframe.allow = "camera;microphone;speaker-selection";
+ info(iframe.src);
+ document.documentElement.appendChild(iframe);
+ return new Promise(resolve => iframe.onload = resolve);
+ }));
+ let devicesMap = await haveDevicesMap;
+ let [sameOriginDevices, differentOriginDevices] = origins.map(o => devicesMap.get(o));
+
+ is(sameOriginDevices.length, devices.length, "same origin same devices");
+ is(differentOriginDevices.length, devices.length, "cross origin same devices");
+ [...sameOriginDevices, ...differentOriginDevices].forEach(d => validateDevice(d));
+
+ for (const device of sameOriginDevices) {
+ ok(devices.find(d => d.deviceId == device.deviceId),
+ "Same origin deviceId for " + device.label + " must match");
+ }
+ for (const device of differentOriginDevices) {
+ ok(!devices.find(d => d.deviceId == device.deviceId),
+ "Different origin deviceId for " + device.label + " must be different");
+ }
+
+ // Check the special case of no devices found.
+ await pushPrefs(["media.navigator.streams.fake", false],
+ ["media.audio_loopback_dev", "none"],
+ ["media.video_loopback_dev", "none"]);
+ devices = await navigator.mediaDevices.enumerateDevices();
+ is(devices.length, 0, "No devices");
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_enumerateDevices_getUserMediaFake.html b/dom/media/webrtc/tests/mochitests/test_enumerateDevices_getUserMediaFake.html
new file mode 100644
index 0000000000..7952bcba1b
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_enumerateDevices_getUserMediaFake.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+ <script>
+"use strict";
+
+createHTML({
+ title: "Test labeled devices or speakers aren't exposed in enumerateDevices() after fake getUserMedia()",
+ bug: "1743524"
+});
+
+runTest(async () => {
+ await pushPrefs(
+ ["media.setsinkid.enabled", true],
+ // This test uses real devices because fake devices are not grouped with
+ // audiooutput devices.
+ ["media.navigator.streams.fake", false]);
+ const devices = navigator.mediaDevices;
+ {
+ // `fake:true` means that getUserMedia() resolves without any permission
+ // check, and so this should not be sufficient to expose real device info.
+ const stream = await devices.getUserMedia({ audio: true, fake: true });
+ stream.getTracks()[0].stop();
+ const list = await devices.enumerateDevices();
+ const labeledDevices = list.filter(({label}) => label != "");
+ is(labeledDevices.length, 0, "must be zero labeled devices after fake gUM");
+ const outputDevices = list.filter(({kind}) => kind == "audiooutput");
+ is(outputDevices.length, 0, "must be zero output devices after fake gUM");
+ }
+ {
+ // Check without `fake:true` to verify assumptions about existing devices.
+ let stream;
+ try {
+ stream = await devices.getUserMedia({ audio: true });
+ stream.getTracks()[0].stop();
+ } catch (e) {
+ if (e.name == "NotFoundError" &&
+ navigator.userAgent.includes("Mac OS X")) {
+ todo(false, "Expecting no real audioinput device on Mac test machines");
+ return;
+ }
+ throw e;
+ }
+ {
+ const list = await devices.enumerateDevices();
+ const audioDevices = list.filter(({kind}) => kind.includes("audio"));
+ ok(audioDevices.length, "have audio devices after real gUM");
+ const unlabeledAudioDevices = audioDevices.filter(({label}) => !label);
+ is(unlabeledAudioDevices.length, 0,
+ "must be zero unlabeled audio devices after real gUM");
+
+ const outputDevices = list.filter(({kind}) => kind == "audiooutput");
+ isnot(outputDevices.length, 0, "have output devices after real gUM");
+ }
+ }
+});
+ </script>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_enumerateDevices_iframe.html b/dom/media/webrtc/tests/mochitests/test_enumerateDevices_iframe.html
new file mode 100644
index 0000000000..beea3a4f97
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_enumerateDevices_iframe.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<pre id="test">
+<script type="application/javascript">
+/**
+ Runs inside iframe in test_enumerateDevices.html.
+*/
+
+const pushPrefs = (...p) => SpecialPowers.pushPrefEnv({set: p});
+const gUM = c => navigator.mediaDevices.getUserMedia(c);
+
+(async () => {
+ await pushPrefs(["media.navigator.streams.fake", true]);
+
+ // Validate enumerated devices after gUM.
+ for (const track of (await gUM({video: true, audio: true})).getTracks()) {
+ track.stop();
+ }
+
+ const devices = await navigator.mediaDevices.enumerateDevices();
+ parent.postMessage(JSON.stringify(devices), "https://example.com:443");
+
+})().catch(e => setTimeout(() => { throw e; }));
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_enumerateDevices_iframe_pre_gum.html b/dom/media/webrtc/tests/mochitests/test_enumerateDevices_iframe_pre_gum.html
new file mode 100644
index 0000000000..f2dc2d1f65
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_enumerateDevices_iframe_pre_gum.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<pre id="test">
+<script type="application/javascript">
+/**
+ Runs inside iframe in test_enumerateDevices_legacy.html.
+*/
+
+const pushPrefs = (...p) => SpecialPowers.pushPrefEnv({set: p});
+
+(async () => {
+ await pushPrefs(["media.navigator.streams.fake", true]);
+
+ const devices = await navigator.mediaDevices.enumerateDevices();
+ parent.postMessage(JSON.stringify(devices), "https://example.com:443");
+
+})().catch(e => setTimeout(() => { throw e; }));
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_enumerateDevices_legacy.html b/dom/media/webrtc/tests/mochitests/test_enumerateDevices_legacy.html
new file mode 100644
index 0000000000..c599f2b599
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_enumerateDevices_legacy.html
@@ -0,0 +1,147 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({ title: "Run enumerateDevices code", bug: "1046245" });
+/**
+ This is a modified copy of test_enumerateDevices.html testing the
+ enumerateDevices() legacy version and deviceId constraint.
+*/
+
+async function mustSucceedWithStream(msg, f) {
+ try {
+ const stream = await f();
+ for (const track of stream.getTracks()) {
+ track.stop();
+ }
+ ok(true, msg + " must succeed");
+ } catch (e) {
+ is(e.name, null, msg + " must succeed: " + e.message);
+ }
+}
+
+async function mustFailWith(msg, reason, constraint, f) {
+ try {
+ await f();
+ ok(false, msg + " must fail");
+ } catch(e) {
+ is(e.name, reason, msg + " must fail: " + e.message);
+ if (constraint) {
+ is(e.constraint, constraint, msg + " must fail w/correct constraint.");
+ }
+ }
+}
+
+const gUM = c => navigator.mediaDevices.getUserMedia(c);
+
+const kinds = ["videoinput", "audioinput", "audiooutput"];
+
+function validateDevice({kind, label, deviceId, groupId}) {
+ ok(kinds.includes(kind), "Known device kind");
+ is(deviceId.length, 44, "deviceId length id as expected for Firefox");
+ ok(label.length !== undefined, "Device label: " + label);
+ isnot(groupId, "", "groupId must be present.");
+}
+
+runTest(async () => {
+ await pushPrefs(["media.navigator.streams.fake", true],
+ ["media.devices.enumerate.legacy.enabled", true]);
+
+ // Validate enumerated devices before gUM (legacy).
+
+ let devices = await navigator.mediaDevices.enumerateDevices();
+ ok(devices.length, "At least one device found");
+ const jsoned = JSON.parse(JSON.stringify(devices));
+ is(jsoned[0].kind, devices[0].kind, "kind survived serializer");
+ is(jsoned[0].deviceId, devices[0].deviceId, "deviceId survived serializer");
+ for (const device of devices) {
+ validateDevice(device);
+ if (device.kind == "audiooutput") continue;
+ is(device.label, "", "Device label is empty");
+ // Test deviceId constraint
+ let deviceId = device.deviceId;
+ let constraints = (device.kind == "videoinput") ? { video: { deviceId } }
+ : { audio: { deviceId } };
+ let namedDevices;
+ for (const track of (await gUM(constraints)).getTracks()) {
+ is(typeof(track.label), "string", "Track label is a string");
+ isnot(track.label.length, 0, "Track label is not empty");
+ if (!namedDevices) {
+ namedDevices = await navigator.mediaDevices.enumerateDevices();
+ }
+ const namedDevice = namedDevices.find(d => d.deviceId == device.deviceId);
+ is(track.label, namedDevice.label, "Track label is the device label");
+ track.stop();
+ }
+ }
+
+ const unknownId = "unknown9qHr8B0JIbcHlbl9xR+jMbZZ8WyoPfpCXPfc=";
+
+ // Check deviceId failure paths for video.
+
+ await mustSucceedWithStream("unknown plain deviceId on video",
+ () => gUM({ video: { deviceId: unknownId } }));
+ await mustSucceedWithStream("unknown plain deviceId on audio",
+ () => gUM({ audio: { deviceId: unknownId } }));
+ await mustFailWith("unknown exact deviceId on video",
+ "OverconstrainedError", "deviceId",
+ () => gUM({ video: { deviceId: { exact: unknownId } } }));
+ await mustFailWith("unknown exact deviceId on audio",
+ "OverconstrainedError", "deviceId",
+ () => gUM({ audio: { deviceId: { exact: unknownId } } }));
+
+ // Check that deviceIds are stable for same origin and differ across origins.
+
+ const path = "/tests/dom/media/webrtc/tests/mochitests/test_enumerateDevices_iframe_pre_gum.html";
+ const origins = ["https://example.com", "https://test1.example.com"];
+ info(window.location);
+
+ const haveDevicesMap = new Promise(resolve => {
+ const map = new Map();
+ window.addEventListener("message", ({origin, data}) => {
+ ok(origins.includes(origin), "Got message from expected origin");
+ map.set(origin, JSON.parse(data));
+ if (map.size < origins.length) return;
+ resolve(map);
+ });
+ });
+
+ await Promise.all(origins.map(origin => {
+ const iframe = document.createElement("iframe");
+ iframe.src = origin + path;
+ iframe.allow = "camera;microphone;speaker-selection";
+ info(iframe.src);
+ document.documentElement.appendChild(iframe);
+ return new Promise(resolve => iframe.onload = resolve);
+ }));
+ let devicesMap = await haveDevicesMap;
+ let [sameOriginDevices, differentOriginDevices] = origins.map(o => devicesMap.get(o));
+
+ is(sameOriginDevices.length, devices.length, "same origin same devices");
+ is(differentOriginDevices.length, devices.length, "cross origin same devices");
+ [...sameOriginDevices, ...differentOriginDevices].forEach(d => validateDevice(d));
+
+ for (const device of sameOriginDevices) {
+ ok(devices.find(d => d.deviceId == device.deviceId),
+ "Same origin deviceId for " + device.label + " must match");
+ }
+ for (const device of differentOriginDevices) {
+ ok(!devices.find(d => d.deviceId == device.deviceId),
+ "Different origin deviceId for " + device.label + " must be different");
+ }
+
+ // Check the special case of no devices found.
+ await pushPrefs(["media.navigator.streams.fake", false],
+ ["media.audio_loopback_dev", "none"],
+ ["media.video_loopback_dev", "none"]);
+ devices = await navigator.mediaDevices.enumerateDevices();
+ is(devices.length, 0, "No devices");
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_enumerateDevices_navigation.html b/dom/media/webrtc/tests/mochitests/test_enumerateDevices_navigation.html
new file mode 100644
index 0000000000..bf7650223f
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_enumerateDevices_navigation.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<iframe id="iframe" srcdoc="<script>
+ window.enumerateDevices = () =>
+ navigator.mediaDevices.enumerateDevices();
+ </script>"
+ width="100%" height="50%" frameborder="1">
+</iframe>
+<pre id="test">
+<script type="application/javascript">
+createHTML({ title: "Suspend enumerateDevices code ", bug: "1479840" });
+/**
+ This test covers the case that the enumerateDevices method is suspended by
+ navigating away the current window. In order to implement that the enumeration
+ is executed in an iframe which is cleared before the enumeration has been resolved
+*/
+
+runTest(async () => {
+ // Run enumerate devices and mesure the time it will take.
+ const start = new Date().getTime();
+ try {
+ await iframe.contentWindow.enumerateDevices();
+ } catch (e) {
+ info("Failed to enumerate devices, error: " + e);
+ }
+ const elapsed = new Date().getTime() - start;
+
+ // Run again and navigate away. Expected to remain pending.
+ let p = iframe.contentWindow.enumerateDevices()
+ p.then( devices => {
+ ok(false, "Enumerate devices promise resolved unexpectedly, found " + devices.length + " devices.");
+ })
+ .catch ( error => {
+ ok(false, "Enumerate devices promise rejected unexpectedly: " + error);
+ });
+ iframe.srcdoc = "";
+
+ // Wait enough time.
+ try {
+ await timeout(p, 5 * elapsed, "timeout");
+ ok(false, "Enumerate devices promise resolved unexpectedly");
+ } catch (e) {
+ is(e.message, "timeout", "We should time out without enumerateDevices rejecting");
+ }
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_fingerprinting_resistance.html b/dom/media/webrtc/tests/mochitests/test_fingerprinting_resistance.html
new file mode 100644
index 0000000000..7e9cd5a219
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_fingerprinting_resistance.html
@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<script src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<script>
+/* global SimpleTest SpecialPowers */
+
+async function testEnumerateDevices(expectDevices) {
+ let devices = await navigator.mediaDevices.enumerateDevices();
+ if (!expectDevices) {
+ SimpleTest.is(devices.length, 0, "testEnumerateDevices: No devices");
+ return;
+ }
+ let cams = devices.filter((device) => device.kind == "videoinput");
+ let mics = devices.filter((device) => device.kind == "audioinput");
+ SimpleTest.ok((cams.length == 1) && (mics.length == 1),
+ "testEnumerateDevices: a microphone and a camera");
+}
+
+async function testGetUserMedia(expectDevices) {
+ const constraints = [
+ {audio: true},
+ {video: true},
+ {audio: true, video: true},
+ {video: {width: {min: 1e9}}}, // impossible
+ {audio: {channelCount: {exact: 1e3}}}, // impossible
+ ];
+ for (let constraint of constraints) {
+ let message = "getUserMedia(" + JSON.stringify(constraint) + ")";
+ try {
+ let stream = await navigator.mediaDevices.getUserMedia(constraint);
+ SimpleTest.ok(expectDevices, message + " resolved");
+ if (!expectDevices) {
+ continue;
+ }
+
+ // We only do testGetUserMedia(true) when privacy.resistFingerprinting
+ // is true, test if MediaStreamTrack.label is spoofed.
+ for (let track of stream.getTracks()) {
+ switch (track.kind) {
+ case "audio":
+ SimpleTest.is(track.label, "Internal Microphone", "AudioStreamTrack.label");
+ break;
+ case "video":
+ SimpleTest.is(track.label, "Internal Camera", "VideoStreamTrack.label");
+ break;
+ default:
+ SimpleTest.ok(false, "Unknown kind: " + track.kind);
+ break;
+ }
+ track.stop();
+ }
+ } catch (e) {
+ if (!expectDevices) {
+ SimpleTest.is(e.name, "NotAllowedError", message + " throws NotAllowedError");
+ } else {
+ SimpleTest.ok(false, message + " failed: " + e);
+ }
+ }
+ }
+}
+
+async function testDevices() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.resistFingerprinting", true],
+ ["media.navigator.streams.fake", true]
+ ]
+ });
+ await testEnumerateDevices(true); // should list a microphone and a camera
+ await testGetUserMedia(true); // should get audio and video streams
+}
+
+async function testNoDevices() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.resistFingerprinting", false],
+ ["media.navigator.permission.device", false],
+ ["media.navigator.streams.fake", false],
+ ["media.audio_loopback_dev", "foo"],
+ ["media.video_loopback_dev", "bar"]
+ ]
+ });
+ await testEnumerateDevices(false); // should list nothing
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.resistFingerprinting", true]
+ ]
+ });
+ await testEnumerateDevices(true); // should list a microphone and a camera
+ await testGetUserMedia(false); // should reject with NotAllowedError
+}
+
+createHTML({
+ title: "Neutralize the threat of fingerprinting of media devices API when 'privacy.resistFingerprinting' is true",
+ bug: "1372073"
+});
+
+runTest(async () => {
+ // Make sure enumerateDevices and getUserMedia work when
+ // privacy.resistFingerprinting is true.
+ await testDevices();
+
+ // Test that absence of devices can't be detected.
+ await testNoDevices();
+});
+</script>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_forceSampleRate.html b/dom/media/webrtc/tests/mochitests/test_forceSampleRate.html
new file mode 100644
index 0000000000..c5a9820aaa
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_forceSampleRate.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test the pref media.cubeb.force_sample_rate</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script class="testbody" type="text/javascript">
+const WEIRD_SAMPLE_RATE = 44101;
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({"set": [
+ ["media.cubeb.force_sample_rate", WEIRD_SAMPLE_RATE]
+]}).then(function() {
+ var ac = new AudioContext();
+ is(ac.sampleRate, WEIRD_SAMPLE_RATE, "Forced sample-rate set successfully.");
+ SimpleTest.finish();
+});
+</script>
+</pre>
+</body>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_GC_MediaStream.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_GC_MediaStream.html
new file mode 100644
index 0000000000..5aa0e64947
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_GC_MediaStream.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ "use strict";
+
+ createHTML({
+ title: "MediaStreams can be garbage collected",
+ bug: "1407542"
+ });
+
+ let SpecialStream = SpecialPowers.wrap(MediaStream);
+
+ async function testGC(stream, numCopies, copy) {
+ let startStreams = await SpecialStream.countUnderlyingStreams();
+
+ let copies = new Array(numCopies).fill(0).map(() => copy(stream));
+ ok(await SpecialStream.countUnderlyingStreams() > startStreams,
+ "MediaStreamTrack constructor creates more underlying streams");
+
+ copies = [];
+ await new Promise(r => SpecialPowers.exactGC(r));
+ is(await SpecialStream.countUnderlyingStreams(), startStreams,
+ "MediaStreamTracks should have been collected");
+ }
+
+ runTest(async () => {
+ // We do not need LoopbackTone because it is not used
+ // and creates extra streams that affect the result
+ DISABLE_LOOPBACK_TONE = true;
+
+ let gUMStream = await getUserMedia({video: true});
+ info("Testing GC of track-array constructor with cloned tracks");
+ await testGC(gUMStream, 10, s => new MediaStream(s.getTracks().map(t => t.clone())));
+
+ info("Testing GC of empty constructor plus addTrack with cloned tracks");
+ await testGC(gUMStream, 10, s => {
+ let s2 = new MediaStream();
+ s.getTracks().forEach(t => s2.addTrack(t.clone()));
+ return s2;
+ });
+
+ info("Testing GC of cloned stream");
+ await testGC(gUMStream, 10, s => s.clone());
+
+ info("Testing GC of gUM stream");
+ gUMStream = null;
+ await new Promise(r => SpecialPowers.exactGC(r));
+ is(await SpecialStream.countUnderlyingStreams(), 0,
+ "Original gUM stream should be collectable");
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_active_autoplay.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_active_autoplay.html
new file mode 100644
index 0000000000..c1a39cdd4c
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_active_autoplay.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<video id="testAutoplay" autoplay></video>
+<script type="application/javascript">
+"use strict";
+
+const video = document.getElementById("testAutoplay");
+var stream;
+var otherVideoTrack;
+var otherAudioTrack;
+
+createHTML({
+ title: "MediaStream can be autoplayed in media element after going inactive and then active",
+ bug: "1208316"
+});
+
+runTest(() => getUserMedia({audio: true, video: true}).then(s => {
+ stream = s;
+ otherVideoTrack = stream.getVideoTracks()[0].clone();
+ otherAudioTrack = stream.getAudioTracks()[0].clone();
+
+ video.srcObject = stream;
+ return haveEvent(video, "playing", wait(5000, new Error("Timeout")));
+})
+.then(() => {
+ ok(!video.ended, "Video element should be playing after adding a gUM stream");
+ stream.getTracks().forEach(t => t.stop());
+ return haveEvent(video, "ended", wait(5000, new Error("Timeout")));
+})
+.then(() => {
+ ok(video.ended, "Video element should be ended");
+ stream.addTrack(otherVideoTrack);
+ return haveEvent(video, "playing", wait(5000, new Error("Timeout")));
+})
+.then(() => {
+ ok(!video.ended, "Video element should be playing after adding a video track");
+ stream.getTracks().forEach(t => t.stop());
+ return haveEvent(video, "ended", wait(5000, new Error("Timeout")));
+})
+.then(() => {
+ ok(video.ended, "Video element should be ended");
+ stream.addTrack(otherAudioTrack);
+ return haveEvent(video, "playing", wait(5000, new Error("Timeout")));
+})
+.then(() => {
+ ok(!video.ended, "Video element should be playing after adding a audio track");
+ stream.getTracks().forEach(t => t.stop());
+ return haveEvent(video, "ended", wait(5000, new Error("Timeout")));
+})
+.then(() => {
+ ok(video.ended, "Video element should be ended");
+}));
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_addTrackRemoveTrack.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_addTrackRemoveTrack.html
new file mode 100644
index 0000000000..27dad2519f
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_addTrackRemoveTrack.html
@@ -0,0 +1,169 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ "use strict";
+
+ createHTML({
+ title: "MediaStream's addTrack() and removeTrack() with getUserMedia streams Test",
+ bug: "1103188"
+ });
+
+ runTest(() => Promise.resolve()
+ .then(() => getUserMedia({audio: true})).then(stream =>
+ getUserMedia({video: true}).then(otherStream => {
+ info("Test addTrack()ing a video track to an audio-only gUM stream");
+ var track = stream.getTracks()[0];
+ var otherTrack = otherStream.getTracks()[0];
+
+ stream.addTrack(track);
+ checkMediaStreamContains(stream, [track], "Re-added audio");
+
+ stream.addTrack(otherTrack);
+ checkMediaStreamContains(stream, [track, otherTrack], "Added video");
+
+ var testElem = createMediaElement('video', 'testAddTrackAudioVideo');
+ var playback = new MediaStreamPlayback(testElem, stream);
+ return playback.playMedia(false);
+ }))
+ .then(() => getUserMedia({video: true})).then(stream =>
+ getUserMedia({video: true}).then(otherStream => {
+ info("Test addTrack()ing a video track to a video-only gUM stream");
+ var track = stream.getTracks()[0];
+ var otherTrack = otherStream.getTracks()[0];
+
+ stream.addTrack(track);
+ checkMediaStreamContains(stream, [track], "Re-added video");
+
+ stream.addTrack(otherTrack);
+ checkMediaStreamContains(stream, [track, otherTrack], "Added video");
+
+ var test = createMediaElement('video', 'testAddTrackDoubleVideo');
+ var playback = new MediaStreamPlayback(test, stream);
+ return playback.playMedia(false);
+ }))
+ .then(() => getUserMedia({video: true})).then(stream =>
+ getUserMedia({video: true}).then(otherStream => {
+ info("Test removeTrack() existing and added video tracks from a video-only gUM stream");
+ var track = stream.getTracks()[0];
+ var otherTrack = otherStream.getTracks()[0];
+
+ stream.removeTrack(otherTrack);
+ checkMediaStreamContains(stream, [track], "Removed non-existing video");
+
+ stream.addTrack(otherTrack);
+ checkMediaStreamContains(stream, [track, otherTrack], "Added video");
+
+ stream.removeTrack(otherTrack);
+ checkMediaStreamContains(stream, [track], "Removed added video");
+
+ stream.removeTrack(otherTrack);
+ checkMediaStreamContains(stream, [track], "Re-removed added video");
+
+ stream.removeTrack(track);
+ checkMediaStreamContains(stream, [], "Removed original video");
+
+ var elem = createMediaElement('video', 'testRemoveAllVideo');
+ var loadeddata = false;
+ elem.onloadeddata = () => { loadeddata = true; elem.onloadeddata = null; };
+ elem.srcObject = stream;
+ elem.play();
+ return wait(500).then(() => {
+ ok(!loadeddata, "Stream without tracks shall not raise 'loadeddata' on media element");
+ elem.pause();
+ elem.srcObject = null;
+ })
+ .then(() => {
+ stream.addTrack(track);
+ checkMediaStreamContains(stream, [track], "Re-added added-then-removed track");
+ var playback = new MediaStreamPlayback(elem, stream);
+ return playback.playMedia(false);
+ })
+ .then(() => otherTrack.stop());
+ }))
+ .then(() => getUserMedia({ audio: true })).then(audioStream =>
+ getUserMedia({ video: true }).then(videoStream => {
+ info("Test adding track and removing the original");
+ var audioTrack = audioStream.getTracks()[0];
+ var videoTrack = videoStream.getTracks()[0];
+ videoStream.removeTrack(videoTrack);
+ audioStream.addTrack(videoTrack);
+
+ checkMediaStreamContains(videoStream, [], "1, Removed original track");
+ checkMediaStreamContains(audioStream, [audioTrack, videoTrack],
+ "2, Added external track");
+
+ var elem = createMediaElement('video', 'testAddRemoveOriginalTrackVideo');
+ var playback = new MediaStreamPlayback(elem, audioStream);
+ return playback.playMedia(false);
+ }))
+ .then(() => getUserMedia({ audio: true, video: true })).then(stream => {
+ info("Test removing stopped tracks");
+ stream.getTracks().forEach(t => {
+ t.stop();
+ stream.removeTrack(t);
+ });
+ checkMediaStreamContains(stream, [], "Removed stopped tracks");
+ })
+ .then(() => {
+ var ac = new AudioContext();
+
+ var osc1k = createOscillatorStream(ac, 1000);
+ var audioTrack1k = osc1k.getTracks()[0];
+
+ var osc5k = createOscillatorStream(ac, 5000);
+ var audioTrack5k = osc5k.getTracks()[0];
+
+ var osc10k = createOscillatorStream(ac, 10000);
+ var audioTrack10k = osc10k.getTracks()[0];
+
+ var stream = osc1k;
+ return Promise.resolve().then(() => {
+ info("Analysing audio output with original 1k track");
+ var analyser = new AudioStreamAnalyser(ac, stream);
+ return analyser.waitForAnalysisSuccess(array =>
+ array[analyser.binIndexForFrequency(50)] < 50 &&
+ array[analyser.binIndexForFrequency(1000)] > 200 &&
+ array[analyser.binIndexForFrequency(5000)] < 50 &&
+ array[analyser.binIndexForFrequency(10000)] < 50);
+ }).then(() => {
+ info("Analysing audio output with removed original 1k track and added 5k track");
+ stream.removeTrack(audioTrack1k);
+ stream.addTrack(audioTrack5k);
+ var analyser = new AudioStreamAnalyser(ac, stream);
+ return analyser.waitForAnalysisSuccess(array =>
+ array[analyser.binIndexForFrequency(1000)] < 50 &&
+ array[analyser.binIndexForFrequency(5000)] > 200 &&
+ array[analyser.binIndexForFrequency(10000)] < 50);
+ }).then(() => {
+ info("Analysing audio output with removed 5k track and added 10k track");
+ stream.removeTrack(audioTrack5k);
+ stream.addTrack(audioTrack10k);
+ var analyser = new AudioStreamAnalyser(ac, stream);
+ return analyser.waitForAnalysisSuccess(array =>
+ array[analyser.binIndexForFrequency(1000)] < 50 &&
+ array[analyser.binIndexForFrequency(5000)] < 50 &&
+ array[analyser.binIndexForFrequency(10000)] > 200);
+ }).then(() => {
+ info("Analysing audio output with re-added 1k, 5k and added 10k tracks");
+ stream.addTrack(audioTrack1k);
+ stream.addTrack(audioTrack5k);
+ var analyser = new AudioStreamAnalyser(ac, stream);
+ return analyser.waitForAnalysisSuccess(array =>
+ array[analyser.binIndexForFrequency(50)] < 50 &&
+ array[analyser.binIndexForFrequency(1000)] > 200 &&
+ array[analyser.binIndexForFrequency(2500)] < 50 &&
+ array[analyser.binIndexForFrequency(5000)] > 200 &&
+ array[analyser.binIndexForFrequency(7500)] < 50 &&
+ array[analyser.binIndexForFrequency(10000)] > 200 &&
+ array[analyser.binIndexForFrequency(11000)] < 50);
+ });
+ }));
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_addtrack_removetrack_events.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_addtrack_removetrack_events.html
new file mode 100644
index 0000000000..833653ebb2
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_addtrack_removetrack_events.html
@@ -0,0 +1,110 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+createHTML({
+ title: "MediaStream's 'addtrack' and 'removetrack' events shouldn't fire on manual operations",
+ bug: "1208328"
+});
+
+var spinEventLoop = () => new Promise(r => setTimeout(r, 0));
+
+var stream;
+var clone;
+var newStream;
+var tracks = [];
+
+var addTrack = track => {
+ info("Adding track " + track.id);
+ stream.addTrack(track);
+};
+var removeTrack = track => {
+ info("Removing track " + track.id);
+ stream.removeTrack(track);
+};
+var stopTrack = track => {
+ if (track.readyState == "live") {
+ info("Stopping track " + track.id);
+ }
+ track.stop();
+};
+
+runTest(() => getUserMedia({audio: true, video: true})
+ .then(s => {
+ stream = s;
+ clone = s.clone();
+ stream.addEventListener("addtrack", function onAddtrack(event) {
+ ok(false, "addtrack fired unexpectedly for track " + event.track.id);
+ });
+ stream.addEventListener("removetrack", function onRemovetrack(event) {
+ ok(false, "removetrack fired unexpectedly for track " + event.track.id);
+ });
+
+ return getUserMedia({audio: true, video: true});
+ })
+ .then(s => {
+ newStream = s;
+ info("Stopping an original track");
+ stopTrack(stream.getTracks()[0]);
+
+ return spinEventLoop();
+ })
+ .then(() => {
+ info("Removing original tracks");
+ stream.getTracks().forEach(t => (stream.removeTrack(t), tracks.push(t)));
+
+ return spinEventLoop();
+ })
+ .then(() => {
+ info("Adding other gUM tracks");
+ newStream.getTracks().forEach(t => addTrack(t))
+
+ return spinEventLoop();
+ })
+ .then(() => {
+ info("Adding cloned tracks");
+ let clone = stream.clone();
+ clone.getTracks().forEach(t => addTrack(t));
+
+ return spinEventLoop();
+ })
+ .then(() => {
+ info("Removing a clone");
+ removeTrack(clone.getTracks()[0]);
+
+ return spinEventLoop();
+ })
+ .then(() => {
+ info("Stopping clones");
+ clone.getTracks().forEach(t => stopTrack(t));
+
+ return spinEventLoop();
+ })
+ .then(() => {
+ info("Stopping originals");
+ stream.getTracks().forEach(t => stopTrack(t));
+ tracks.forEach(t => stopTrack(t));
+
+ return spinEventLoop();
+ })
+ .then(() => {
+ info("Removing remaining tracks");
+ stream.getTracks().forEach(t => removeTrack(t));
+
+ return spinEventLoop();
+ })
+ .then(() => {
+ // Test MediaStreamTrackEvent required args here.
+ mustThrowWith("MediaStreamTrackEvent without required args",
+ "TypeError", () => new MediaStreamTrackEvent("addtrack", {}));
+ }));
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_audioCapture.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_audioCapture.html
new file mode 100644
index 0000000000..2cc649a321
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_audioCapture.html
@@ -0,0 +1,104 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test AudioCapture </title>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script>
+
+(async () => {
+ // Get an opus file containing a sine wave at maximum amplitude, of duration
+ // `lengthSeconds`, and of frequency `frequency`.
+ async function getSineWaveFile(frequency, lengthSeconds) {
+ const off = new OfflineAudioContext(1, lengthSeconds * 48000, 48000);
+ const osc = off.createOscillator();
+ const rec = new MediaRecorder(off.destination,
+ {mimeType: "audio/ogg; codecs=opus"});
+ osc.frequency.value = frequency;
+ osc.connect(off.destination);
+ osc.start();
+ rec.start();
+ off.startRendering();
+ const {data} = await new Promise(r => rec.ondataavailable = r);
+ return data;
+ }
+
+ await createHTML({
+ bug: "1156472",
+ title: "Test AudioCapture with regular HTMLMediaElement, AudioContext, " +
+ "and HTMLMediaElement playing a MediaStream",
+ visible: true
+ });
+
+ await runTestWhenReady(async () => {
+ /**
+ * Get two HTMLMediaElements:
+ * - One playing a sine tone from a blob (of an opus file created on the fly)
+ * - One being the output for an AudioContext's OscillatorNode, connected to
+ * a MediaSourceDestinationNode.
+ *
+ * Also, use the AudioContext playing through its AudioDestinationNode another
+ * tone, using another OscillatorNode.
+ *
+ * Capture the output of the document, feed that back into the AudioContext,
+ * with an AnalyserNode, and check the frequency content to make sure we
+ * have recorded the three sources.
+ *
+ * The three sine tones have frequencies far apart from each other, so that we
+ * can check that the spectrum of the capture stream contains three
+ * components with a high magnitude.
+ */
+ const wavtone = createMediaElement("audio", "WaveTone");
+ const acTone = createMediaElement("audio", "audioContextTone");
+ const ac = new AudioContext();
+
+ const oscThroughMediaElement = ac.createOscillator();
+ oscThroughMediaElement.frequency.value = 1000;
+ const oscThroughAudioDestinationNode = ac.createOscillator();
+ oscThroughAudioDestinationNode.frequency.value = 5000;
+ const msDest = ac.createMediaStreamDestination();
+
+ oscThroughMediaElement.connect(msDest);
+ oscThroughAudioDestinationNode.connect(ac.destination);
+
+ acTone.srcObject = msDest.stream;
+
+ const blob = await getSineWaveFile(10000, 10);
+ wavtone.src = URL.createObjectURL(blob);
+ oscThroughMediaElement.start();
+ oscThroughAudioDestinationNode.start();
+ wavtone.loop = true;
+ wavtone.play();
+ acTone.play();
+
+ const constraints = {audio: {mediaSource: "audioCapture"}};
+
+ const stream = await getUserMedia(constraints);
+ try {
+ const analyser = new AudioStreamAnalyser(ac, stream);
+ analyser.enableDebugCanvas();
+ await analyser.waitForAnalysisSuccess(array => {
+ // We want to find three frequency components here, around 1000, 5000
+ // and 10000Hz. Frequency are logarithmic. Also make sure we have low
+ // energy in between, not just a flat white noise.
+ return (array[analyser.binIndexForFrequency(50)] < 50 &&
+ array[analyser.binIndexForFrequency(1000)] > 200 &&
+ array[analyser.binIndexForFrequency(2500)] < 50 &&
+ array[analyser.binIndexForFrequency(5000)] > 200 &&
+ array[analyser.binIndexForFrequency(7500)] < 50 &&
+ array[analyser.binIndexForFrequency(10000)] > 200);
+ });
+ } finally {
+ for (let t of stream.getTracks()) {
+ t.stop();
+ }
+ ac.close();
+ }
+ });
+})();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_audioConstraints.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_audioConstraints.html
new file mode 100644
index 0000000000..162e83063a
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_audioConstraints.html
@@ -0,0 +1,93 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+createHTML({
+ title: "Test that microphone getSettings report correct settings after applyConstraints",
+ bug: "1447982",
+});
+
+function testTrackAgainstAudioConstraints(track, audioConstraints) {
+ let constraints = track.getConstraints();
+ is(constraints.autoGainControl, audioConstraints.autoGainControl,
+ "Should report correct autoGainControl constraint");
+ is(constraints.echoCancellation, audioConstraints.echoCancellation,
+ "Should report correct echoCancellation constraint");
+ is(constraints.noiseSuppression, audioConstraints.noiseSuppression,
+ "Should report correct noiseSuppression constraint");
+
+ let settings = track.getSettings();
+ is(settings.autoGainControl, audioConstraints.autoGainControl,
+ "Should report correct autoGainControl setting");
+ is(settings.echoCancellation, audioConstraints.echoCancellation,
+ "Should report correct echoCancellation setting");
+ is(settings.noiseSuppression, audioConstraints.noiseSuppression,
+ "Should report correct noiseSuppression setting");
+}
+
+async function testAudioConstraints(track, audioConstraints) {
+ // We applyConstraints() first and do a fresh gUM later, to avoid
+ // testing multiple concurrent captures at different settings.
+
+ info(`Testing applying constraints ${JSON.stringify(audioConstraints)} ` +
+ `to track with settings ${JSON.stringify(track.getSettings())}`);
+ await track.applyConstraints(audioConstraints);
+ testTrackAgainstAudioConstraints(track, audioConstraints);
+
+ info("Testing fresh gUM request with audio constraints " +
+ JSON.stringify(audioConstraints));
+ let stream = await getUserMedia({audio: audioConstraints});
+ testTrackAgainstAudioConstraints(stream.getTracks()[0], audioConstraints);
+ stream.getTracks().forEach(t => t.stop());
+}
+
+runTest(async () => {
+ let audioDevice = SpecialPowers.getCharPref("media.audio_loopback_dev", "");
+ if (!audioDevice) {
+ ok(false, "No device set by framework. Try --use-test-media-devices");
+ return;
+ }
+
+ let supportedConstraints = navigator.mediaDevices.getSupportedConstraints();
+ is(supportedConstraints.autoGainControl, true,
+ "autoGainControl constraint should be supported");
+ is(supportedConstraints.echoCancellation, true,
+ "echoCancellation constraint should be supported");
+ is(supportedConstraints.noiseSuppression, true,
+ "noiseSuppression constraint should be supported");
+
+ let egn = (e, g, n) => ({
+ echoCancellation: e,
+ autoGainControl: g,
+ noiseSuppression: n
+ });
+
+ let stream = await getUserMedia({
+ audio: egn(true, true, true),
+ });
+ let track = stream.getTracks()[0];
+ let audioConstraintsToTest = [
+ egn(false, true, true),
+ egn(true, false, true),
+ egn(true, true, false),
+ egn(false, false, true),
+ egn(false, true, false),
+ egn(true, false, false),
+ egn(false, false, false),
+ egn(true, true, true),
+ ];
+ for (let audioConstraints of audioConstraintsToTest) {
+ await testAudioConstraints(track, audioConstraints);
+ }
+ track.stop();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_audioConstraints_concurrentIframes.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_audioConstraints_concurrentIframes.html
new file mode 100644
index 0000000000..d07dbc41f1
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_audioConstraints_concurrentIframes.html
@@ -0,0 +1,157 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ title: "getUserMedia in multiple iframes with different constraints",
+ bug: "1404977"
+});
+/**
+ * Verify that we can successfully call getUserMedia for the same device in
+ * multiple iframes concurrently. This is checked by creating a number of
+ * iframes and performing a separate getUserMedia call in each. We verify the
+ * stream returned by that call has the same constraints as requested both
+ * immediately after the call and after all gUM calls have been made. The test
+ * then verifies the streams can be played.
+ */
+runTest(async function() {
+ // Compare constraints and return a string with the differences in
+ // echoCancellation, autoGainControl, and noiseSuppression. The string
+ // will be empty if there are no differences.
+ function getConstraintDifferenceString(constraints, otherConstraints) {
+ let diffString = "";
+ if (constraints.echoCancellation != otherConstraints.echoCancellation) {
+ diffString += "echoCancellation different: " +
+ `${constraints.echoCancellation} != ${otherConstraints.echoCancellation}, `;
+ }
+ if (constraints.autoGainControl != otherConstraints.autoGainControl) {
+ diffString += "autoGainControl different: " +
+ `${constraints.autoGainControl} != ${otherConstraints.autoGainControl}, `;
+ }
+ if (constraints.noiseSuppression != otherConstraints.noiseSuppression) {
+ diffString += "noiseSuppression different: " +
+ `${constraints.noiseSuppression} != ${otherConstraints.noiseSuppression}, `;
+ }
+ // Replace trailing comma and space if any
+ return diffString.replace(/, $/, "");
+ }
+
+ // We need a real device to get a MediaEngine supporting constraints
+ let audioDevice = SpecialPowers.getCharPref("media.audio_loopback_dev", "");
+ if (!audioDevice) {
+ todo(false, "No device set by framework. Try --use-test-media-devices");
+ return;
+ }
+
+ let egn = (e, g, n) => ({
+ echoCancellation: e,
+ autoGainControl: g,
+ noiseSuppression: n
+ });
+
+ let allConstraintCombinations = [
+ egn(false, false, false),
+ egn(true, false, false),
+ egn(false, true, false),
+ egn(false, false, true),
+ egn(true, true, false),
+ egn(true, false, true),
+ egn(false, true, true),
+ egn(true, true, true),
+ ];
+
+ // TODO: We would like to be able to perform an arbitrary number of gUM calls
+ // at once, but issues with pulse and audio IPC mean on some systems we're
+ // limited to as few as 2 concurrent calls. To avoid issues we chunk test runs
+ // to only two calls at a time. The while, splice and GC lines can be removed,
+ // the extra scope removed and allConstraintCombinations can be renamed to
+ // constraintCombinations once this issue is resolved. See bug 1480489.
+ while (allConstraintCombinations.length) {
+ {
+ let constraintCombinations = allConstraintCombinations.splice(0, 2);
+ // Array to store objects that associate information used in our test such as
+ // constraints, iframes, gum streams, and various promises.
+ let testCases = [];
+
+ for (let constraints of constraintCombinations) {
+ let testCase = {requestedConstraints: constraints};
+ // Provide an id for logging, labeling related elements.
+ testCase.id = `testCase.` +
+ `e=${constraints.echoCancellation}.` +
+ `g=${constraints.noiseSuppression}.` +
+ `n=${constraints.noiseSuppression}`;
+ testCases.push(testCase);
+ testCase.iframe = document.createElement("iframe");
+ testCase.iframeLoadedPromise = new Promise((resolve, reject) => {
+ testCase.iframe.onload = () => { resolve(); };
+ });
+ document.body.appendChild(testCase.iframe);
+ }
+ is(testCases.length,
+ constraintCombinations.length,
+ "Should have created a testcase for each constraint");
+
+ // Wait for all iframes to be loaded
+ await Promise.all(testCases.map(tc => tc.iframeLoadedPromise));
+
+ // Start a tone at our top level page so the gUM calls will record something
+ // should we wish to verify their recording in future.
+ let tone = new LoopbackTone(new AudioContext, TEST_AUDIO_FREQ);
+ tone.start();
+
+ // One by one see if we can grab a gUM stream per iframe
+ for (let testCase of testCases) {
+ // Use normal gUM rather than our test helper as the test harness was
+ // not made to be used inside iframes.
+ testCase.gumStream =
+ await testCase.iframe.contentWindow.navigator.mediaDevices.getUserMedia({audio: testCase.requestedConstraints})
+ .catch(e => Promise.reject(`getUserMedia calls should not fail! Failed at ${testCase.id} with: ${e}!`));
+ let differenceString = getConstraintDifferenceString(
+ testCase.requestedConstraints,
+ testCase.gumStream.getAudioTracks()[0].getSettings());
+ ok(!differenceString,
+ `gUM stream for ${testCase.id} should have the same constraints as were ` +
+ `requested from gUM. Differences: ${differenceString}`);
+ }
+
+ // Once all streams are collected, make sure the constraints haven't been
+ // mutated by another gUM call.
+ for (let testCase of testCases) {
+ let differenceString = getConstraintDifferenceString(
+ testCase.requestedConstraints,
+ testCase.gumStream.getAudioTracks()[0].getSettings());
+ ok(!differenceString,
+ `gUM stream for ${testCase.id} should not have had constraints altered after ` +
+ `all gUM calls are done. Differences: ${differenceString}`);
+ }
+
+ // We do not currently have tests to verify the behaviour of the different
+ // constraints. Once we do we should do further verification here. See
+ // bug 1406372, bug 1406376, and bug 1406377.
+
+ for (let testCase of testCases) {
+ let testAudio = createMediaElement("audio", `testAudio.${testCase.id}`);
+ let playback = new MediaStreamPlayback(testAudio, testCase.gumStream);
+ await playback.playMediaWithoutStoppingTracks(false);
+ }
+
+ // Stop the tracks for each stream, we left them running above via
+ // playMediaWithoutStoppingTracks to make sure they can play concurrently.
+ for (let testCase of testCases) {
+ testCase.gumStream.getTracks().map(t => t.stop());
+ document.body.removeChild(testCase.iframe);
+ }
+
+ tone.stop();
+ }
+ await new Promise(r => SpecialPowers.exactGC(r));
+ }
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_audioConstraints_concurrentStreams.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_audioConstraints_concurrentStreams.html
new file mode 100644
index 0000000000..f5b5e784ea
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_audioConstraints_concurrentStreams.html
@@ -0,0 +1,123 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ title: "getUserMedia multiple times, concurrently, and with different constraints",
+ bug: "1404977"
+});
+/**
+ * Verify that we can successfully call getUserMedia multiple times for the
+ * same device, concurrently. This is checked by calling getUserMedia a number
+ * of times with different constraints. We verify that the stream returned by
+ * that call has the same constraints as requested both immediately after the
+ * call and after all gUM calls have been made. The test then verifies the
+ * streams can be played.
+ */
+runTest(async function() {
+ // Compare constraints and return a string with the differences in
+ // echoCancellation, autoGainControl, and noiseSuppression. The string
+ // will be empty if there are no differences.
+ function getConstraintDifferenceString(constraints, otherConstraints) {
+ let diffString = "";
+ if (constraints.echoCancellation != otherConstraints.echoCancellation) {
+ diffString += "echoCancellation different: " +
+ `${constraints.echoCancellation} != ${otherConstraints.echoCancellation}, `;
+ }
+ if (constraints.autoGainControl != otherConstraints.autoGainControl) {
+ diffString += "autoGainControl different: " +
+ `${constraints.autoGainControl} != ${otherConstraints.autoGainControl}, `;
+ }
+ if (constraints.noiseSuppression != otherConstraints.noiseSuppression) {
+ diffString += "noiseSuppression different: " +
+ `${constraints.noiseSuppression} != ${otherConstraints.noiseSuppression}, `;
+ }
+ // Replace trailing comma and space if any
+ return diffString.replace(/, $/, "");
+ }
+
+ // We need a real device to get a MediaEngine supporting constraints
+ let audioDevice = SpecialPowers.getCharPref("media.audio_loopback_dev", "");
+ if (!audioDevice) {
+ todo(false, "No device set by framework. Try --use-test-media-devices");
+ return;
+ }
+
+ let egn = (e, g, n) => ({
+ echoCancellation: e,
+ autoGainControl: g,
+ noiseSuppression: n
+ });
+
+ let constraintCombinations = [
+ egn(false, false, false),
+ egn(true, false, false),
+ egn(false, true, false),
+ egn(false, false, true),
+ egn(true, true, false),
+ egn(true, false, true),
+ egn(false, true, true),
+ egn(true, true, true),
+ ];
+
+ // Array to store objects that associate information used in our test such as
+ // constraints, gum streams, and various promises.
+ let testCases = [];
+
+ for (let constraints of constraintCombinations) {
+ let testCase = {requestedConstraints: constraints};
+ // Provide an id for logging, labeling related elements.
+ testCase.id = `testCase.` +
+ `e=${constraints.echoCancellation}.` +
+ `g=${constraints.noiseSuppression}.` +
+ `n=${constraints.noiseSuppression}`;
+ testCases.push(testCase);
+ testCase.gumStream =
+ await getUserMedia({audio: testCase.requestedConstraints})
+ .catch(e => Promise.reject(`getUserMedia calls should not fail! Failed at ${testCase.id} with: ${e}!`));
+ let differenceString = getConstraintDifferenceString(
+ testCase.requestedConstraints,
+ testCase.gumStream.getAudioTracks()[0].getSettings());
+ ok(!differenceString,
+ `gUM stream for ${testCase.id} should have the same constraints as were ` +
+ `requested from gUM. Differences: ${differenceString}`);
+ }
+ is(testCases.length,
+ constraintCombinations.length,
+ "Should have a stream for each constraint");
+
+ // Once all streams are collected, make sure the constraints haven't been
+ // mutated by another gUM call.
+ for (let testCase of testCases) {
+ let differenceString = getConstraintDifferenceString(
+ testCase.requestedConstraints,
+ testCase.gumStream.getAudioTracks()[0].getSettings());
+ ok(!differenceString,
+ `gUM stream for ${testCase.id} should not have had constraints altered after ` +
+ `all gUM calls are done. Differences: ${differenceString}`);
+ }
+
+ // We do not currently have tests to verify the behaviour of the different
+ // constraints. Once we do we should do further verificaiton here. See
+ // bug 1406372, bug 1406376, and bug 1406377.
+
+ for (let testCase of testCases) {
+ let testAudio = createMediaElement("audio", `testAudio.${testCase.id}`);
+ let playback = new MediaStreamPlayback(testAudio, testCase.gumStream);
+ await playback.playMediaWithoutStoppingTracks(false);
+ }
+
+ // Stop the tracks for each stream, we left them running above via
+ // playMediaWithoutStoppingTracks to make sure they can play concurrently.
+ for (let testCase of testCases) {
+ testCase.gumStream.getTracks().map(t => t.stop());
+ }
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicAudio.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicAudio.html
new file mode 100644
index 0000000000..b4775b4244
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicAudio.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({ title: "getUserMedia Basic Audio Test", bug: "781534" });
+ /**
+ * Run a test to verify that we can complete a start and stop media playback
+ * cycle for an audio MediaStream on an audio HTMLMediaElement.
+ */
+ runTest(function () {
+ var testAudio = createMediaElement('audio', 'testAudio');
+ var constraints = {audio: true};
+
+ return getUserMedia(constraints).then(stream => {
+ var playback = new MediaStreamPlayback(testAudio, stream);
+ return playback.playMedia(false);
+ });
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicAudio_loopback.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicAudio_loopback.html
new file mode 100644
index 0000000000..10bf669c00
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicAudio_loopback.html
@@ -0,0 +1,99 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+
+<script>
+ createHTML({
+ title: "getUserMedia Basic Audio Test Loopback",
+ bug: "1406350",
+ visible: true
+ });
+
+ /**
+ * Run a test to verify the use of LoopbackTone as audio input.
+ */
+ runTest(async () => {
+ if (!SpecialPowers.getCharPref("media.audio_loopback_dev", "")) {
+ todo(false, "No loopback device set by framework. Try --use-test-media-devices");
+ return;
+ }
+
+ // At this point DefaultLoopbackTone has been instantiated
+ // automatically on frequency TEST_AUDIO_FREQ (440 Hz). Verify
+ // that a tone is detected on that frequency.
+ info("Capturing at default frequency");
+ const stream = await getUserMedia({audio: true});
+
+ try {
+ const audioContext = new AudioContext();
+ const analyser = new AudioStreamAnalyser(audioContext, stream);
+ analyser.enableDebugCanvas();
+ await analyser.waitForAnalysisSuccess(array => {
+ // High energy on 1000 Hz low energy around that
+ const freg_50Hz = array[analyser.binIndexForFrequency(50)];
+ const freq = array[analyser.binIndexForFrequency(TEST_AUDIO_FREQ)];
+ const freq_2000Hz = array[analyser.binIndexForFrequency(2000)];
+
+ info("Analysing audio frequency - low:target:high = "
+ + freg_50Hz + ':' + freq + ':' + freq_2000Hz);
+ return freg_50Hz < 50 && freq > 200 && freq_2000Hz < 50;
+ });
+
+ // Use the LoopbackTone API to change the frequency of the default tone.
+ // Verify that a tone is detected on the new frequency (800 Hz).
+ info("Change loopback tone frequency");
+ DefaultLoopbackTone.changeFrequency(800);
+ await analyser.waitForAnalysisSuccess(array => {
+ const freg_50Hz = array[analyser.binIndexForFrequency(50)];
+ const freq = array[analyser.binIndexForFrequency(800)];
+ const freq_2000Hz = array[analyser.binIndexForFrequency(2000)];
+
+ info("Analysing audio frequency - low:target:high = "
+ + freg_50Hz + ':' + freq + ':' + freq_2000Hz);
+ return freg_50Hz < 50 && freq > 200 && freq_2000Hz < 50;
+ });
+
+ // Create a second tone at a different frequency.
+ // Verify that both tones are detected.
+ info("Multiple loopback tones");
+ DefaultLoopbackTone.changeFrequency(TEST_AUDIO_FREQ);
+ const second_tone = new LoopbackTone(audioContext, 2000);
+ second_tone.start();
+ await analyser.waitForAnalysisSuccess(array => {
+ const freg_50Hz = array[analyser.binIndexForFrequency(50)];
+ const freq = array[analyser.binIndexForFrequency(TEST_AUDIO_FREQ)];
+ const freq_2000Hz = array[analyser.binIndexForFrequency(2000)];
+ const freq_4000Hz = array[analyser.binIndexForFrequency(4000)];
+
+ info("Analysing audio frequency - low:target1:target2:high = "
+ + freg_50Hz + ':' + freq + ':' + freq_2000Hz + ':' + freq_4000Hz);
+ return freg_50Hz < 50 && freq > 200 && freq_2000Hz > 200 && freq_4000Hz < 50;
+ });
+
+ // Stop all tones and verify that there is no audio on the given frequencies.
+ info("Stop all loopback tones");
+ DefaultLoopbackTone.stop();
+ second_tone.stop()
+ await analyser.waitForAnalysisSuccess(array => {
+ const freg_50Hz = array[analyser.binIndexForFrequency(50)];
+ const freq = array[analyser.binIndexForFrequency(TEST_AUDIO_FREQ)];
+ const freq_2000Hz = array[analyser.binIndexForFrequency(2000)];
+
+ info("Analysing audio frequency - low:target:high = "
+ + freg_50Hz + ':' + freq + ':' + freq_2000Hz);
+ return freg_50Hz < 50 && freq < 50 && freq_2000Hz < 50;
+ });
+ } finally {
+ for (let t of stream.getTracks()) {
+ t.stop();
+ }
+ }
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicScreenshare.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicScreenshare.html
new file mode 100644
index 0000000000..cc73de77da
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicScreenshare.html
@@ -0,0 +1,260 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+ <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ title: "getUserMedia Basic Screenshare Test",
+ bug: "1211656",
+ visible: true,
+ });
+
+ const {AppConstants} =
+ SpecialPowers.ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+
+ // Since the MacOS backend captures in the wrong rgb color profile we need
+ // large thresholds there, and they vary greatly by color. We define
+ // thresholds per platform and color here to still allow the test to run.
+ // Since the colors used (red, green, blue, white) consist only of "pure"
+ // components (0 or 255 for each component), the high thresholds on Mac will
+ // still be able to catch an error where the image is corrupt, or if frames
+ // don't flow.
+ const thresholds = {
+ // macos: captures in the display rgb color profile, which we treat as
+ // sRGB, which is most likely wrong. These thresholds are needed currently
+ // in CI. See bug 1827606.
+ "macosx": { "red": 120, "green": 135, "blue": 35, "white": 10 },
+ // windows: rounding errors in 1) conversion to I420 (the capture),
+ // 2) downscaling, 3) conversion to RGB (for rendering).
+ "win": { "red": 5, "green": 5, "blue": 10, "white": 5 },
+ // linux: rounding errors in 1) conversion to I420 (the capture),
+ // 2) downscaling, 3) conversion to RGB (for rendering).
+ "linux": { "red": 5, "green": 5, "blue": 10, "white": 5 },
+ // android: we don't have a screen capture backend.
+ "android": { "red": 0, "green": 0, "blue": 0, "white": 0 },
+ // other: here just because it's supported by AppConstants.platform.
+ "other": { "red": 0, "green": 0, "blue": 0, "white": 0 },
+ };
+
+ const verifyScreenshare =
+ async (video, helper, upleft, upright, downleft, downright) => {
+ if (video.readyState < video.HAVE_CURRENT_DATA) {
+ info("Waiting for data");
+ await new Promise(r => video.onloadeddata = r);
+ }
+
+ // We assume video size will not change. Offsets help to account for a
+ // square fullscreen-canvas, while the screen is rectangular.
+ const offsetX = Math.max(0, video.videoWidth - video.videoHeight) / 2;
+ const offsetY = Math.max(0, video.videoHeight - video.videoWidth) / 2;
+
+ const verifyAround = async (internalX, internalY, color) => {
+ // Pick a couple of samples around a coordinate to check for a color.
+ // We check multiple rows and columns, to avoid most artifact issues.
+ let areaSamples = [
+ {dx: 0, dy: 0},
+ {dx: 1, dy: 3},
+ {dx: 8, dy: 5},
+ ];
+ const threshold = thresholds[AppConstants.platform][color.name];
+ for (let {dx, dy} of areaSamples) {
+ const x = offsetX + dx + internalX;
+ const y = offsetY + dy + internalY;
+ info(`Checking pixel (${[x,y]}) of total resolution `
+ + `${video.videoWidth}x${video.videoHeight} against ${color.name}.`);
+ let lastPixel = [-1, -1, -1, -1];
+ await helper.waitForPixel(video, px => {
+ lastPixel = Array.from(px);
+ return helper.isPixel(px, color, threshold);
+ }, {
+ offsetX: x,
+ offsetY: y,
+ cancel: wait(30000).then(_ =>
+ new Error(`Checking ${[x,y]} against ${color.name} timed out. ` +
+ `Got [${lastPixel}]. Threshold ${threshold}.`)),
+ });
+ ok(true, `Pixel (${[x,y]}) passed. Got [${lastPixel}].`);
+ }
+ };
+
+ const screenSizeSq = Math.min(video.videoWidth, video.videoHeight);
+
+ info("Waiting for upper left quadrant to become " + upleft.name);
+ await verifyAround(screenSizeSq / 4, screenSizeSq / 4, upleft);
+
+ info("Waiting for upper right quadrant to become " + upright.name);
+ await verifyAround(screenSizeSq * 3 / 4, screenSizeSq / 4, upright);
+
+ info("Waiting for lower left quadrant to become " + downleft.name);
+ await verifyAround(screenSizeSq / 4, screenSizeSq * 3 / 4, downleft);
+
+ info("Waiting for lower right quadrant to become " + downright.name);
+ await verifyAround(screenSizeSq * 3 / 4, screenSizeSq * 3 / 4, downright);
+ };
+
+ /**
+ * Run a test to verify that we can complete a start and stop media playback
+ * cycle for a screenshare MediaStream on a video HTMLMediaElement.
+ */
+ runTest(async function () {
+ await pushPrefs(
+ ["full-screen-api.enabled", true],
+ ["full-screen-api.allow-trusted-requests-only", false],
+ ["full-screen-api.transition-duration.enter", "0 0"],
+ ["full-screen-api.transition-duration.leave", "0 0"],
+ );
+
+ // Improve real estate for screenshots
+ const test = document.getElementById("test");
+ test.setAttribute("style", "height:0;margin:0;");
+ const display = document.getElementById("display");
+ display.setAttribute("style", "margin:0;");
+ const testVideo = createMediaElement('video', 'testVideo');
+ testVideo.removeAttribute("width");
+ testVideo.removeAttribute("height");
+ testVideo.setAttribute("style", "max-height:240px;");
+
+ const canvas = document.createElement("canvas");
+ canvas.width = canvas.height = 20;
+ document.getElementById("content").appendChild(canvas);
+ const draw = ([upleft, upright, downleft, downright]) => {
+ helper.drawColor(canvas, helper[upleft], {offsetX: 0, offsetY: 0});
+ helper.drawColor(canvas, helper[upright], {offsetX: 10, offsetY: 0});
+ helper.drawColor(canvas, helper[downleft], {offsetX: 0, offsetY: 10});
+ helper.drawColor(canvas, helper[downright], {offsetX: 10, offsetY: 10});
+ };
+ const helper = new CaptureStreamTestHelper2D(1, 1);
+
+ const doVerify = async (stream, [upleft, upright, downleft, downright]) => {
+ // Reset from potential earlier verification runs.
+ testVideo.srcObject = null;
+ const playback = new MediaStreamPlayback(testVideo, stream);
+ playback.startMedia();
+ await playback.verifyPlaying();
+ const settings = stream.getTracks()[0].getSettings();
+ is(settings.width, testVideo.videoWidth,
+ "Width setting should match video width");
+ is(settings.height, testVideo.videoHeight,
+ "Height setting should match video height");
+ await SpecialPowers.wrap(canvas).requestFullscreen();
+ try {
+ await verifyScreenshare(testVideo, helper, helper[upleft], helper[upright],
+ helper[downleft], helper[downright]);
+ } finally {
+ await playback.stopTracksForStreamInMediaPlayback();
+ await SpecialPowers.wrap(document).exitFullscreen();
+ // We wait a bit extra here to make sure we have completely left
+ // fullscreen when the --screenshot-on-fail screenshot is captured.
+ await wait(300);
+ }
+ };
+
+ info("Testing screenshare without constraints");
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ let stream = await getUserMedia({video: {mediaSource: "screen"}});
+ let settings = stream.getTracks()[0].getSettings();
+ ok(settings.width <= 8192,
+ `Width setting ${settings.width} should be set after gUM (or 0 per bug 1453247)`);
+ ok(settings.height <= 8192,
+ `Height setting ${settings.height} should be set after gUM (or 0 per bug 1453247)`);
+ let colors = ["red", "blue", "green", "white"];
+ draw(colors);
+ await doVerify(stream, colors);
+ const screenWidth = testVideo.videoWidth;
+ const screenHeight = testVideo.videoHeight;
+
+ info("Testing screenshare with size and framerate constraints");
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ for (const track of stream.getTracks()) {
+ track.stop();
+ }
+ stream = await getUserMedia({
+ video: {
+ mediaSource: 'screen',
+ width: {
+ min: '10',
+ max: '100'
+ },
+ height: {
+ min: '10',
+ max: '100'
+ },
+ frameRate: {
+ min: '10',
+ max: '15'
+ },
+ },
+ });
+ settings = stream.getTracks()[0].getSettings();
+ ok(settings.width == 0 || (settings.width >= 10 && settings.width <= 100),
+ `Width setting ${settings.width} should be correct after gUM (or 0 per bug 1453247)`);
+ ok(settings.height == 0 || (settings.height >= 10 && settings.height <= 100),
+ `Height setting ${settings.height} should be correct after gUM (or 0 per bug 1453247)`);
+ colors = ["green", "red", "white", "blue"];
+ draw(colors);
+ const streamClone = stream.clone();
+ await doVerify(streamClone, colors);
+ settings = stream.getTracks()[0].getSettings();
+ ok(settings.width >= 10 && settings.width <= 100,
+ `Width setting ${settings.width} should be within constraints`);
+ ok(settings.height >= 10 && settings.height <= 100,
+ `Height setting ${settings.height} should be within constraints`);
+ is(settings.width, testVideo.videoWidth,
+ "Width setting should match video width");
+ is(settings.height, testVideo.videoHeight,
+ "Height setting should match video height");
+ let expectedHeight = (screenHeight * settings.width) / screenWidth;
+ ok(Math.abs(expectedHeight - settings.height) <= 1,
+ "Aspect ratio after constrained gUM should be close enough");
+
+ info("Testing modifying screenshare with applyConstraints");
+ testVideo.srcObject = stream;
+ testVideo.play();
+ await new Promise(r => testVideo.onloadeddata = r);
+ const resize = haveEvent(
+ testVideo, "resize", wait(5000, new Error("Timeout waiting for resize")));
+ await stream.getVideoTracks()[0].applyConstraints({
+ mediaSource: 'screen',
+ width: 200,
+ height: 200,
+ frameRate: {
+ min: '5',
+ max: '10'
+ }
+ });
+ // getSettings() should report correct size as soon as applyConstraints()
+ // resolves - bug 1453259. Until fixed, check that we at least report
+ // something sane.
+ const newSettings = stream.getTracks()[0].getSettings();
+ ok(newSettings.width > settings.width && newSettings.width < screenWidth,
+ `Width setting ${newSettings.width} should have increased after applyConstraints`);
+ ok(newSettings.height > settings.height && newSettings.height < screenHeight,
+ `Height setting ${newSettings.height} should have increased after applyConstraints`);
+ await resize;
+ settings = stream.getTracks()[0].getSettings();
+ ok(settings.width > 100 && settings.width < screenWidth,
+ `Width setting ${settings.width} should have increased after first frame after applyConstraints`);
+ ok(settings.height > 100 && settings.height < screenHeight,
+ `Height setting ${settings.height} should have increased after first frame after applyConstraints`);
+ is(settings.width, testVideo.videoWidth,
+ "Width setting should match video width");
+ is(settings.height, testVideo.videoHeight,
+ "Height setting should match video height");
+ expectedHeight = (screenHeight * settings.width) / screenWidth;
+ ok(Math.abs(expectedHeight - settings.height) <= 1,
+ "Aspect ratio after applying constraints should be close enough");
+ colors = ["white", "green", "blue", "red"];
+ draw(colors);
+ await doVerify(stream, colors);
+ for (const track of [...stream.getTracks(), ...streamClone.getTracks()]) {
+ track.stop();
+ }
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicTabshare.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicTabshare.html
new file mode 100644
index 0000000000..635cf387d4
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicTabshare.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ title: "getUserMedia Basic Tabshare Test",
+ bug: "1193075"
+ });
+ /**
+ * Run a test to verify that we can complete a start and stop media playback
+ * cycle for a tabshare MediaStream on a video HTMLMediaElement.
+ *
+ * Additionally, exercise applyConstraints code for tabshare viewport offset.
+ */
+ runTest(function () {
+ var testVideo = createMediaElement('video', 'testVideo');
+
+ return Promise.resolve()
+ .then(() => pushPrefs(["media.getusermedia.browser.enabled", true]))
+ .then(() => {
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ return getUserMedia({
+ video: { mediaSource: "browser",
+ scrollWithPage: true },
+ });
+ })
+ .then(stream => {
+ var playback = new MediaStreamPlayback(testVideo, stream);
+ return playback.playMedia(false);
+ })
+ .then(() => getUserMedia({
+ video: {
+ mediaSource: "browser",
+ viewportOffsetX: 0,
+ viewportOffsetY: 0,
+ viewportWidth: 100,
+ viewportHeight: 100
+ },
+ }))
+ .then(stream => {
+ var playback = new MediaStreamPlayback(testVideo, stream);
+ playback.startMedia(false);
+ return playback.verifyPlaying()
+ .then(() => Promise.all([
+ () => testVideo.srcObject.getVideoTracks()[0].applyConstraints({
+ mediaSource: "browser",
+ viewportOffsetX: 10,
+ viewportOffsetY: 50,
+ viewportWidth: 90,
+ viewportHeight: 50
+ }),
+ () => listenUntil(testVideo, "resize", () => true)
+ ]))
+ .then(() => playback.verifyPlaying()) // still playing
+ .then(() => playback.stopTracksForStreamInMediaPlayback())
+ .then(() => playback.detachFromMediaElement());
+ });
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicVideo.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicVideo.html
new file mode 100644
index 0000000000..786d9f2e4b
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicVideo.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ title: "getUserMedia Basic Video Test",
+ bug: "781534"
+ });
+ /**
+ * Run a test to verify that we can complete a start and stop media playback
+ * cycle for an video MediaStream on a video HTMLMediaElement.
+ */
+ runTest(function () {
+ var testVideo = createMediaElement('video', 'testVideo');
+ var constraints = {video: true};
+
+ return getUserMedia(constraints).then(stream => {
+ var playback = new MediaStreamPlayback(testVideo, stream);
+ return playback.playMedia(false);
+ });
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicVideoAudio.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicVideoAudio.html
new file mode 100644
index 0000000000..5218bf7301
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicVideoAudio.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ title: "getUserMedia Basic Video & Audio Test",
+ bug: "781534"
+ });
+ /**
+ * Run a test to verify that we can complete a start and stop media playback
+ * cycle for a video and audio MediaStream on a video HTMLMediaElement.
+ */
+ runTest(function () {
+ var testVideoAudio = createMediaElement('video', 'testVideoAudio');
+ var constraints = {video: true, audio: true};
+
+ return getUserMedia(constraints).then(stream => {
+ var playback = new MediaStreamPlayback(testVideoAudio, stream);
+ return playback.playMedia(false);
+ });
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicVideo_playAfterLoadedmetadata.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicVideo_playAfterLoadedmetadata.html
new file mode 100644
index 0000000000..fbab1b4357
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicVideo_playAfterLoadedmetadata.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ title: "getUserMedia Basic Video shall receive 'loadedmetadata' without play()ing",
+ bug: "1149494"
+ });
+ /**
+ * Run a test to verify that we will always get 'loadedmetadata' from a video
+ * HTMLMediaElement playing a gUM MediaStream.
+ */
+ runTest(() => {
+ var testVideo = createMediaElement('video', 'testVideo');
+ var constraints = {video: true};
+
+ return getUserMedia(constraints).then(stream => {
+ var playback = new MediaStreamPlayback(testVideo, stream);
+ var video = playback.mediaElement;
+
+ video.srcObject = stream;
+ return new Promise(resolve => {
+ ok(playback.mediaElement.paused,
+ "Media element should be paused before play()ing");
+ video.addEventListener('loadedmetadata', function() {
+ ok(video.videoWidth > 0, "Expected nonzero video width");
+ ok(video.videoHeight > 0, "Expected nonzero video width");
+ resolve();
+ });
+ })
+ .then(() => stream.getTracks().forEach(t => t.stop()));
+ });
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicWindowshare.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicWindowshare.html
new file mode 100644
index 0000000000..7b27944bdc
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_basicWindowshare.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ title: "getUserMedia Basic Windowshare Test",
+ bug: "1038926"
+ });
+ /**
+ * Run a test to verify that we can complete a start and stop media playback
+ * cycle for an screenshare MediaStream on a video HTMLMediaElement.
+ */
+ runTest(async function () {
+ const testVideo = createMediaElement('video', 'testVideo');
+ const constraints = {
+ video: { mediaSource: "window" },
+ };
+
+ try {
+ await getUserMedia(constraints);
+ ok(false, "Should require user gesture");
+ } catch (e) {
+ is(e.name, "InvalidStateError");
+ }
+
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ const stream = await getUserMedia(constraints);
+ const playback = new MediaStreamPlayback(testVideo, stream);
+ return playback.playMedia(false);
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_bug1223696.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_bug1223696.html
new file mode 100644
index 0000000000..6af7b69d70
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_bug1223696.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+ <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ "use strict";
+
+ createHTML({
+ title: "Testing that removeTrack+addTrack of video tracks still render the correct track in a media element",
+ bug: "1223696",
+ visible: true
+ });
+
+ runTest(async function() {
+ const stream = await getUserMedia({audio:true, video: true});
+ info("Test addTrack()ing a video track to an audio-only gUM stream");
+
+ const video = createMediaElement("video", "test_video_track");
+ video.srcObject = stream;
+ video.play();
+
+ await haveEvent(video, "loadeddata", wait(5000, new Error("Timeout")));
+ info("loadeddata");
+
+ const removedTrack = stream.getVideoTracks()[0];
+ stream.removeTrack(removedTrack);
+
+ const h = new CaptureStreamTestHelper2D();
+ const emitter = new VideoFrameEmitter(h.grey, h.grey);
+ emitter.start();
+
+ stream.addTrack(emitter.stream().getVideoTracks()[0]);
+
+ checkMediaStreamContains(stream, [stream.getAudioTracks()[0],
+ emitter.stream().getVideoTracks()[0]]);
+
+ await h.pixelMustBecome(video, h.grey, {
+ threshold: 5,
+ infoString: "The canvas track should be rendered by the media element",
+ });
+
+ emitter.stop();
+ for (const t of [removedTrack, ...stream.getAudioTracks()]) {
+ t.stop();
+ }
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_callbacks.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_callbacks.html
new file mode 100644
index 0000000000..14c6cc7e7f
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_callbacks.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ title: "navigator.mozGetUserMedia Callback Test",
+ bug: "1119593"
+ });
+ /**
+ * Check that the old fashioned callback-based function works.
+ */
+ runTest(function () {
+ var testAudio = createMediaElement('audio', 'testAudio');
+ var constraints = {audio: true};
+
+ SimpleTest.waitForExplicitFinish();
+ return new Promise(resolve =>
+ navigator.mozGetUserMedia(constraints, stream => {
+ checkMediaStreamTracks(constraints, stream);
+
+ var playback = new MediaStreamPlayback(testAudio, stream);
+ return playback.playMedia(false)
+ .then(() => resolve(), generateErrorCallback());
+ }, generateErrorCallback())
+ );
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_constraints.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_constraints.html
new file mode 100644
index 0000000000..d6439ce9d6
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_constraints.html
@@ -0,0 +1,166 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script src="mediaStreamPlayback.js"></script>
+ <script src="constraints.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({ title: "Test getUserMedia constraints", bug: "882145" });
+/**
+ Tests covering gUM constraints API for audio, video and fake video. Exercise
+ successful parsing code and ensure that unknown required constraints and
+ overconstraining cases produce appropriate errors.
+*/
+var tests = [
+ // Each test here tests a different constraint or codepath.
+ { message: "unknown required constraint on video ignored",
+ constraints: { video: { somethingUnknown: { exact: 0 } } },
+ error: null },
+ { message: "unknown required constraint on audio ignored",
+ constraints: { audio: { somethingUnknown: { exact: 0 } } },
+ error: null },
+ { message: "audio overconstrained by facingMode ignored",
+ constraints: { audio: { facingMode: { exact: 'left' } } },
+ error: null },
+ { message: "full screensharing requires permission",
+ constraints: { video: { mediaSource: 'screen' } },
+ error: "NotAllowedError" },
+ { message: "application screensharing no longer exists",
+ constraints: { video: { mediaSource: 'application' } },
+ error: "OverconstrainedError" },
+ { message: "window screensharing requires permission",
+ constraints: { video: { mediaSource: 'window' } },
+ error: "NotAllowedError" },
+ { message: "browser screensharing requires permission",
+ constraints: { video: { mediaSource: 'browser' } },
+ error: "NotAllowedError" },
+ { message: "unknown mediaSource in video fails",
+ constraints: { video: { mediaSource: 'uncle' } },
+ error: "OverconstrainedError",
+ constraint: "mediaSource" },
+ { message: "unknown mediaSource in audio fails",
+ constraints: { audio: { mediaSource: 'uncle' } },
+ error: "OverconstrainedError",
+ constraint: "mediaSource" },
+ { message: "emtpy constraint fails",
+ constraints: { },
+ error: "TypeError" },
+ { message: "Triggering mock failure in default video device fails",
+ constraints: { video: { deviceId: 'bad device' } },
+ error: "NotReadableError" },
+ { message: "Triggering mock failure in default audio device fails",
+ constraints: { audio: { deviceId: 'bad device' } },
+ error: "NotReadableError" },
+ { message: "Success-path: optional video facingMode + audio ignoring facingMode",
+ constraints: { audio: { mediaSource: 'microphone',
+ facingMode: 'left',
+ foo: 0,
+ advanced: [{ facingMode: 'environment' },
+ { facingMode: 'user' },
+ { bar: 0 }] },
+ video: { mediaSource: 'camera',
+ foo: 0,
+ advanced: [{ facingMode: 'environment' },
+ { facingMode: ['user'] },
+ { facingMode: ['left', 'right', 'user'] },
+ { bar: 0 }] } },
+ error: null },
+ { message: "legacy facingMode ignored",
+ constraints: { video: { mandatory: { facingMode: 'left' } } },
+ error: null },
+];
+
+var mustSupport = [
+ 'width', 'height', 'frameRate', 'facingMode', 'deviceId', 'groupId',
+ 'echoCancellation', 'noiseSuppression', 'autoGainControl', 'channelCount',
+
+ // Yet to add:
+ // 'aspectRatio', 'volume', 'sampleRate', 'sampleSize', 'latency'
+
+ // http://fluffy.github.io/w3c-screen-share/#screen-based-video-constraints
+ // OBE by http://w3c.github.io/mediacapture-screen-share
+ 'mediaSource',
+
+ // Experimental https://bugzilla.mozilla.org/show_bug.cgi?id=1131568#c3
+ 'browserWindow', 'scrollWithPage',
+ 'viewportOffsetX', 'viewportOffsetY', 'viewportWidth', 'viewportHeight',
+];
+
+var mustFailWith = (msg, reason, constraint, f) =>
+ f().then(() => ok(false, msg + " must fail"), e => {
+ is(e.name, reason, msg + " must fail: " + e.message);
+ if (constraint !== undefined) {
+ is(e.constraint, constraint, msg + " must fail w/correct constraint.");
+ }
+ });
+
+/**
+ * Starts the test run by running through each constraint
+ * test by verifying that the right resolution and rejection is fired.
+ */
+
+runTest(() => pushPrefs(
+ // This test expects fake devices, particularly for the 'triggering mock
+ // failure *' steps. So explicitly disable loopback and setup fakes
+ ['media.audio_loopback_dev', ''],
+ ['media.video_loopback_dev', ''],
+ ['media.navigator.streams.fake', true]
+ )
+ .then(() => {
+ // Check supported constraints first.
+ var dict = navigator.mediaDevices.getSupportedConstraints();
+ var supported = Object.keys(dict);
+
+ mustSupport.forEach(key => ok(supported.includes(key) && dict[key],
+ "Supports " + key));
+
+ var unexpected = supported.filter(key => !mustSupport.includes(key));
+ is(unexpected.length, 0,
+ "Unanticipated support (please update test): " + unexpected);
+ })
+ .then(() => pushPrefs(["media.getusermedia.browser.enabled", false],
+ ["media.getusermedia.screensharing.enabled", false]))
+ .then(() => tests.reduce((p, test) => p.then(
+ () => {
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ return getUserMedia(test.constraints);
+ })
+ .then(stream => {
+ is(null, test.error, test.message);
+ stream.getTracks().forEach(t => t.stop());
+ }, e => {
+ is(e.name, test.error, test.message + ": " + e.message);
+ if (test.constraint) {
+ is(e.constraint, test.constraint,
+ test.message + " w/correct constraint.");
+ }
+ }), Promise.resolve()))
+ .then(() => getUserMedia({video: true, audio: true}))
+ .then(stream => stream.getVideoTracks()[0].applyConstraints({ width: 320 })
+ .then(() => stream.getAudioTracks()[0].applyConstraints({ }))
+ .then(() => {
+ stream.getTracks().forEach(track => track.stop());
+ ok(true, "applyConstraints code exercised");
+ }))
+ // TODO: Test outcome once fake devices support constraints (Bug 1088621)
+ .then(() => mustFailWith("applyConstraints fails on non-Gum tracks",
+ "OverconstrainedError", "",
+ () => (new AudioContext())
+ .createMediaStreamDestination().stream
+ .getAudioTracks()[0].applyConstraints()))
+ .then(() => mustFailWith(
+ "getUserMedia with unsatisfied required constraint",
+ "OverconstrainedError", "deviceId",
+ () => getUserMedia({ audio: true,
+ video: { deviceId: { exact: "unheardof" } } })))
+ .then(() => mustFailWith(
+ "getUserMedia with unsatisfied required constraint array",
+ "OverconstrainedError", "deviceId",
+ () => getUserMedia({ audio: true,
+ video: { deviceId: { exact: ["a", "b"] } } }))));
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_cubebDisabled.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_cubebDisabled.html
new file mode 100644
index 0000000000..54142aeb77
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_cubebDisabled.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ title: "getUserMedia with Cubeb Disabled Test",
+ bug: "1443525"
+ });
+ /**
+ * Run a test to verify we fail gracefully if we cannot fetch a cubeb context
+ * during a gUM call.
+ */
+ runTest(async function () {
+ info("Get user media with cubeb disabled starting");
+ // Push prefs to ensure no cubeb context and no fake streams.
+ await pushPrefs(["media.cubeb.force_null_context", true],
+ ["media.navigator.permission.device", false],
+ ["media.navigator.streams.fake", false]);
+
+ // Request audio only, to avoid cams
+ let constraints = {audio: true, video: false};
+ let stream;
+ try {
+ stream = await getUserMedia(constraints);
+ } catch (e) {
+ // We've got no audio backend, so we expect gUM to fail.
+ ok(e.name == "NotFoundError", "Expected NotFoundError due to no audio tracks!");
+ return;
+ }
+ // If we're not on android we should not have gotten a stream without a cubeb context!
+ ok(false, "getUserMedia not expected to succeed when cubeb is disabled, but it did!");
+ });
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_cubebDisabledFakeStreams.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_cubebDisabledFakeStreams.html
new file mode 100644
index 0000000000..f8150cc4c1
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_cubebDisabledFakeStreams.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ title: "getUserMedia fake stream with Cubeb Disabled Test",
+ bug: "1443525"
+ });
+ /**
+ * Run a test to verify we can still return a fake stream even if we cannot
+ * get a cubeb context. See also Bug 1434477
+ */
+ runTest(async function () {
+ info("Get user media with cubeb disabled and fake tracks starting");
+ // Push prefs to ensure no cubeb context and fake streams
+ await pushPrefs(["media.cubeb.force_null_context", true],
+ ["media.navigator.streams.fake", true],
+ ['media.audio_loopback_dev', '']);
+ let testAudio = createMediaElement('audio', 'testAudio');
+ // Request audio only, to avoid cams
+ let constraints = {audio: true, video: false};
+ let stream;
+ try {
+ stream = await getUserMedia(constraints);
+ } catch (e) {
+ // We've got no audio backend, so we expect gUM to fail
+ ok(false, `Did not expect to fail, but got ${e}`);
+ return;
+ }
+ ok(stream, "getUserMedia should get a stream!");
+ let playback = new MediaStreamPlayback(testAudio, stream);
+ return playback.playMedia(false);
+ });
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_getTrackById.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_getTrackById.html
new file mode 100644
index 0000000000..161bf631e3
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_getTrackById.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ title: "Basic getTrackById test of gUM stream",
+ bug: "1208390",
+ });
+
+ runTest(() => {
+ var constraints = {audio: true, video: true};
+ return getUserMedia(constraints).then(stream => {
+ is(stream.getTrackById(""), null,
+ "getTrackById of non-matching string should return null");
+
+ let audioTrack = stream.getAudioTracks()[0];
+ is(stream.getTrackById(audioTrack.id), audioTrack,
+ "getTrackById with matching id should return the track");
+
+ let videoTrack = stream.getVideoTracks()[0];
+ is(stream.getTrackById(videoTrack.id), videoTrack,
+ "getTrackById with matching id should return the track");
+
+ stream.removeTrack(audioTrack);
+ is(stream.getTrackById(audioTrack.id), null,
+ "getTrackById with id of removed track should return null");
+
+ let newStream = new MediaStream();
+ is(newStream.getTrackById(videoTrack.id), null,
+ "getTrackById with id of track in other stream should return null");
+
+ newStream.addTrack(audioTrack);
+ is(newStream.getTrackById(audioTrack.id), audioTrack,
+ "getTrackByid with matching id should return the track");
+
+ newStream.addTrack(videoTrack);
+ is(newStream.getTrackById(videoTrack.id), videoTrack,
+ "getTrackByid with matching id should return the track");
+ [audioTrack, videoTrack].forEach(t => t.stop());
+ });
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_gumWithinGum.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_gumWithinGum.html
new file mode 100644
index 0000000000..86a7aa5606
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_gumWithinGum.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({title: "getUserMedia within getUserMedia", bug: "822109" });
+ /**
+ * Run a test that we can complete a playback cycle for a video,
+ * then upon completion, do a playback cycle with audio, such that
+ * the audio gum call happens within the video gum call.
+ */
+ runTest(function () {
+ return getUserMedia({video: true})
+ .then(videoStream => {
+ var testVideo = createMediaElement('video', 'testVideo');
+ var videoPlayback = new MediaStreamPlayback(testVideo,
+ videoStream);
+
+ return videoPlayback.playMediaWithoutStoppingTracks(false)
+ .then(() => getUserMedia({audio: true}))
+ .then(audioStream => {
+ var testAudio = createMediaElement('audio', 'testAudio');
+ var audioPlayback = new MediaStreamPlayback(testAudio,
+ audioStream);
+
+ return audioPlayback.playMedia(false);
+ })
+ .then(() => videoStream.getTracks().forEach(t => t.stop()));
+ });
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_loadedmetadata.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_loadedmetadata.html
new file mode 100644
index 0000000000..d6efac4650
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_loadedmetadata.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ title: "getUserMedia in media element should have video dimensions on loadedmetadata",
+ bug: "1240478"
+ });
+ /**
+ * Tests that assigning a stream to a media element results in the
+ * "loadedmetadata" event without having to play() the media element.
+ *
+ * Also makes sure that the video size has been set on "loadedmetadata".
+ */
+ runTest(function () {
+ var v = document.createElement("video");
+ document.body.appendChild(v);
+ v.preload = "metadata";
+
+ var constraints = {video: true, audio: true};
+ return getUserMedia(constraints).then(stream => new Promise(resolve => {
+ v.srcObject = stream;
+ v.onloadedmetadata = () => {
+ isnot(v.videoWidth, 0, "videoWidth shall be set on 'loadedmetadata'");
+ isnot(v.videoHeight, 0, "videoHeight shall be set on 'loadedmetadata'");
+ resolve();
+ };
+ })
+ .then(() => stream.getTracks().forEach(t => t.stop())));
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaElementCapture_audio.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaElementCapture_audio.html
new file mode 100644
index 0000000000..3b9e00896c
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaElementCapture_audio.html
@@ -0,0 +1,116 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script>
+
+createHTML({
+ bug: "1259788",
+ title: "Test CaptureStream audio content on HTMLMediaElement playing a gUM MediaStream",
+ visible: true
+});
+
+var audioContext;
+var gUMAudioElement;
+var analyser;
+runTest(() => getUserMedia({audio: { echoCancellation: false }})
+ .then(stream => {
+ gUMAudioElement = createMediaElement("audio", "gUMAudio");
+ gUMAudioElement.srcObject = stream;
+
+ audioContext = new AudioContext();
+ info("Capturing");
+
+ analyser = new AudioStreamAnalyser(audioContext,
+ gUMAudioElement.mozCaptureStream());
+ analyser.enableDebugCanvas();
+ return analyser.waitForAnalysisSuccess(array =>
+ array[analyser.binIndexForFrequency(50)] < 50 &&
+ array[analyser.binIndexForFrequency(TEST_AUDIO_FREQ)] > 200 &&
+ array[analyser.binIndexForFrequency(2500)] < 50);
+ })
+ .then(() => {
+ info("Audio flowing. Pausing.");
+ gUMAudioElement.pause();
+
+ return analyser.waitForAnalysisSuccess(array =>
+ array[analyser.binIndexForFrequency(50)] < 50 &&
+ array[analyser.binIndexForFrequency(TEST_AUDIO_FREQ)] < 50 &&
+ array[analyser.binIndexForFrequency(2500)] < 50);
+ })
+ .then(() => {
+ info("Audio stopped flowing. Playing.");
+ gUMAudioElement.play();
+
+ return analyser.waitForAnalysisSuccess(array =>
+ array[analyser.binIndexForFrequency(50)] < 50 &&
+ array[analyser.binIndexForFrequency(TEST_AUDIO_FREQ)] > 200 &&
+ array[analyser.binIndexForFrequency(2500)] < 50);
+ })
+ .then(() => {
+ info("Audio flowing. Removing source.");
+ var stream = gUMAudioElement.srcObject;
+ gUMAudioElement.srcObject = null;
+
+ return analyser.waitForAnalysisSuccess(array =>
+ array[analyser.binIndexForFrequency(50)] < 50 &&
+ array[analyser.binIndexForFrequency(TEST_AUDIO_FREQ)] < 50 &&
+ array[analyser.binIndexForFrequency(2500)] < 50)
+ .then(() => stream);
+ })
+ .then(stream => {
+ info("Audio stopped flowing. Setting source.");
+ gUMAudioElement.srcObject = stream;
+
+ return analyser.waitForAnalysisSuccess(array =>
+ array[analyser.binIndexForFrequency(50)] < 50 &&
+ array[analyser.binIndexForFrequency(TEST_AUDIO_FREQ)] > 200 &&
+ array[analyser.binIndexForFrequency(2500)] < 50);
+ })
+ .then(() => {
+ info("Audio flowing from new source. Adding a track.");
+ let oscillator = audioContext.createOscillator();
+ oscillator.type = 'sine';
+ oscillator.frequency.value = 2000;
+ oscillator.start();
+
+ let oscOut = audioContext.createMediaStreamDestination();
+ oscillator.connect(oscOut);
+
+ gUMAudioElement.srcObject.addTrack(oscOut.stream.getTracks()[0]);
+
+ return analyser.waitForAnalysisSuccess(array =>
+ array[analyser.binIndexForFrequency(50)] < 50 &&
+ array[analyser.binIndexForFrequency(TEST_AUDIO_FREQ)] > 200 &&
+ array[analyser.binIndexForFrequency(1500)] < 50 &&
+ array[analyser.binIndexForFrequency(2000)] > 200 &&
+ array[analyser.binIndexForFrequency(2500)] < 50);
+ })
+ .then(() => {
+ info("Audio flowing from new track. Removing a track.");
+
+ const gUMTrack = gUMAudioElement.srcObject.getTracks()[0];
+ gUMAudioElement.srcObject.removeTrack(gUMTrack);
+
+ is(gUMAudioElement.srcObject.getTracks().length, 1,
+ "A track should have been removed");
+
+ return analyser.waitForAnalysisSuccess(array =>
+ array[analyser.binIndexForFrequency(50)] < 50 &&
+ array[analyser.binIndexForFrequency(TEST_AUDIO_FREQ)] < 50 &&
+ array[analyser.binIndexForFrequency(1500)] < 50 &&
+ array[analyser.binIndexForFrequency(2000)] > 200 &&
+ array[analyser.binIndexForFrequency(2500)] < 50)
+ .then(() => [gUMTrack, ...gUMAudioElement.srcObject.getTracks()]
+ .forEach(t => t.stop()));
+ })
+ .then(() => ok(true, "Test passed."))
+ .catch(e => ok(false, "Test failed: " + e + (e.stack ? "\n" + e.stack : ""))));
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaElementCapture_tracks.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaElementCapture_tracks.html
new file mode 100644
index 0000000000..a747e75de9
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaElementCapture_tracks.html
@@ -0,0 +1,179 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script>
+
+createHTML({
+ bug: "1259788",
+ title: "Test CaptureStream track output on HTMLMediaElement playing a gUM MediaStream",
+ visible: true
+});
+
+let audioElement;
+let audioCaptureStream;
+let videoElement;
+let videoCaptureStream;
+let untilEndedElement;
+let streamUntilEnded;
+const tracks = [];
+runTest(async () => {
+ try {
+ let stream = await getUserMedia({audio: true, video: true});
+ // We need to test with multiple tracks. We add an extra of each kind.
+ for (const track of stream.getTracks()) {
+ stream.addTrack(track.clone());
+ }
+
+ audioElement = createMediaElement("audio", "gUMAudio");
+ audioElement.srcObject = stream;
+
+ await haveEvent(audioElement, "loadedmetadata", wait(50000, new Error("Timeout")));
+
+ info("Capturing audio element (loadedmetadata -> captureStream)");
+ audioCaptureStream = audioElement.mozCaptureStream();
+
+ is(audioCaptureStream.getAudioTracks().length, 2,
+ "audio element should capture two audio tracks");
+ is(audioCaptureStream.getVideoTracks().length, 0,
+ "audio element should not capture any video tracks");
+
+ await haveNoEvent(audioCaptureStream, "addtrack");
+
+ videoElement = createMediaElement("video", "gUMVideo");
+
+ info("Capturing video element (captureStream -> loadedmetadata)");
+ videoCaptureStream = videoElement.mozCaptureStream();
+ videoElement.srcObject = audioElement.srcObject.clone();
+
+ is(videoCaptureStream.getTracks().length, 0,
+ "video element should have no tracks before metadata known");
+
+ await haveEventsButNoMore(
+ videoCaptureStream, "addtrack", 3, wait(50000, new Error("No event")));
+
+ is(videoCaptureStream.getAudioTracks().length, 2,
+ "video element should capture two audio tracks");
+ is(videoCaptureStream.getVideoTracks().length, 1,
+ "video element should capture one video track at most");
+
+ info("Testing dynamically adding audio track to audio element");
+ audioElement.srcObject.addTrack(
+ audioElement.srcObject.getAudioTracks()[0].clone());
+ await haveEventsButNoMore(
+ audioCaptureStream, "addtrack", 1, wait(50000, new Error("No event")));
+
+ is(audioCaptureStream.getAudioTracks().length, 3,
+ "Audio element should have three audio tracks captured.");
+
+ info("Testing dynamically adding video track to audio element");
+ audioElement.srcObject.addTrack(
+ audioElement.srcObject.getVideoTracks()[0].clone());
+ await haveNoEvent(audioCaptureStream, "addtrack");
+
+ is(audioCaptureStream.getVideoTracks().length, 0,
+ "Audio element should have no video tracks captured.");
+
+ info("Testing dynamically adding audio track to video element");
+ videoElement.srcObject.addTrack(
+ videoElement.srcObject.getAudioTracks()[0].clone());
+ await haveEventsButNoMore(
+ videoCaptureStream, "addtrack", 1, wait(50000, new Error("Timeout")));
+
+ is(videoCaptureStream.getAudioTracks().length, 3,
+ "Captured video stream should have three audio tracks captured.");
+
+ info("Testing dynamically adding video track to video element");
+ videoElement.srcObject.addTrack(
+ videoElement.srcObject.getVideoTracks()[0].clone());
+ await haveNoEvent(videoCaptureStream, "addtrack");
+
+ is(videoCaptureStream.getVideoTracks().length, 1,
+ "Captured video stream should have at most one video tracks captured.");
+
+ info("Testing track removal.");
+ tracks.push(...videoElement.srcObject.getTracks());
+ for (const track of videoElement.srcObject.getVideoTracks().reverse()) {
+ videoElement.srcObject.removeTrack(track);
+ }
+ is(videoCaptureStream.getVideoTracks().length, 1,
+ "Captured video should have still have one video track.");
+
+ await haveEvent(videoCaptureStream.getVideoTracks()[0], "ended",
+ wait(50000, new Error("Timeout")));
+ await haveEvent(videoCaptureStream, "removetrack",
+ wait(50000, new Error("Timeout")));
+
+ is(videoCaptureStream.getVideoTracks().length, 0,
+ "Captured video stream should have no video tracks after removal.");
+
+
+ info("Testing source reset.");
+ stream = await getUserMedia({audio: true, video: true});
+ videoElement.srcObject = stream;
+ for (const track of videoCaptureStream.getTracks()) {
+ await Promise.race(videoCaptureStream.getTracks().map(
+ t => haveEvent(t, "ended", wait(50000, new Error("Timeout"))))
+ );
+ await haveEvent(videoCaptureStream, "removetrack", wait(50000, new Error("Timeout")));
+ }
+ await haveEventsButNoMore(
+ videoCaptureStream, "addtrack", 2, wait(50000, new Error("Timeout")));
+ is(videoCaptureStream.getAudioTracks().length, 1,
+ "Captured video stream should have one audio track");
+
+ is(videoCaptureStream.getVideoTracks().length, 1,
+ "Captured video stream should have one video track");
+
+ info("Testing CaptureStreamUntilEnded");
+ untilEndedElement =
+ createMediaElement("video", "gUMVideoUntilEnded");
+ untilEndedElement.srcObject = audioElement.srcObject;
+
+ await haveEvent(untilEndedElement, "loadedmetadata",
+ wait(50000, new Error("Timeout")));
+
+ streamUntilEnded = untilEndedElement.mozCaptureStreamUntilEnded();
+
+ is(streamUntilEnded.getAudioTracks().length, 3,
+ "video element should capture all 3 audio tracks until ended");
+ is(streamUntilEnded.getVideoTracks().length, 1,
+ "video element should capture only 1 video track until ended");
+
+ for (const track of untilEndedElement.srcObject.getTracks()) {
+ track.stop();
+ }
+
+ await haveEvent(untilEndedElement, "ended", wait(50000, new Error("Timeout")));
+ for (const track of streamUntilEnded.getTracks()) {
+ await Promise.race(streamUntilEnded.getTracks().map(
+ t => haveEvent(t, "ended", wait(50000, new Error("Timeout"))))
+ );
+ await haveEvent(streamUntilEnded, "removetrack", wait(50000, new Error("Timeout")));
+ }
+
+ info("Element and tracks ended. Ensuring that new tracks aren't created.");
+ untilEndedElement.srcObject = videoElement.srcObject;
+ await haveEventsButNoMore(
+ untilEndedElement, "loadedmetadata", 1, wait(50000, new Error("Timeout")));
+
+ is(streamUntilEnded.getTracks().length, 0, "Should have no tracks");
+ } catch(e) {
+ ok(false, "Test failed: " + e + (e && e.stack ? "\n" + e.stack : ""));
+ } finally {
+ if (videoElement) {
+ tracks.push(...videoElement.srcObject.getTracks());
+ }
+ for(const track of tracks) {
+ track.stop();
+ }
+ }
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaElementCapture_video.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaElementCapture_video.html
new file mode 100644
index 0000000000..d177e93bfb
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaElementCapture_video.html
@@ -0,0 +1,91 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script>
+
+createHTML({
+ bug: "1259788",
+ title: "Test CaptureStream video content on HTMLMediaElement playing a gUM MediaStream",
+ visible: true
+});
+
+var gUMVideoElement;
+var captureStreamElement;
+
+const pausedTimeout = 1000;
+let h;
+
+runTest(async () => {
+ try {
+ await pushPrefs(
+ // This test expects fake video devices, as it expects captured frames to
+ // shift over time, which is not currently provided by loopback devices
+ ['media.video_loopback_dev', ''],
+ ['media.navigator.streams.fake', true]);
+
+ let stream = await getUserMedia({video: true});
+ h = new VideoStreamHelper();
+ gUMVideoElement =
+ createMediaElement("video", "gUMVideo");
+ gUMVideoElement.srcObject = stream;
+ gUMVideoElement.play();
+
+ info("Capturing");
+ captureStreamElement =
+ createMediaElement("video", "captureStream");
+ captureStreamElement.srcObject = gUMVideoElement.mozCaptureStream();
+ captureStreamElement.play();
+
+ await h.checkVideoPlaying(captureStreamElement);
+
+ // Adding a dummy audio track to the stream will keep a consuming media
+ // element from ending.
+ // We could also solve it by repeatedly play()ing or autoplay, but then we
+ // wouldn't be sure the media element stopped rendering video because it
+ // went to the ended state or because there were no frames for the track.
+ let osc = createOscillatorStream(new AudioContext(), 1000);
+ captureStreamElement.srcObject.addTrack(osc.getTracks()[0]);
+
+ info("Video flowing. Pausing.");
+ gUMVideoElement.pause();
+ await h.checkVideoPaused(captureStreamElement, { time: pausedTimeout });
+
+ info("Video stopped flowing. Playing.");
+ gUMVideoElement.play();
+ await h.checkVideoPlaying(captureStreamElement);
+
+ info("Video flowing. Removing source.");
+ stream = gUMVideoElement.srcObject;
+ gUMVideoElement.srcObject = null;
+ await h.checkVideoPaused(captureStreamElement, { time: pausedTimeout });
+
+ info("Video stopped flowing. Setting source.");
+ gUMVideoElement.srcObject = stream;
+ await h.checkVideoPlaying(captureStreamElement);
+
+ info("Video flowing. Changing source by track manipulation. Remove first.");
+ let track = gUMVideoElement.srcObject.getTracks()[0];
+ gUMVideoElement.srcObject.removeTrack(track);
+ await h.checkVideoPaused(captureStreamElement, { time: pausedTimeout });
+
+ info("Video paused. Changing source by track manipulation. Add first.");
+ gUMVideoElement.srcObject.addTrack(track);
+ gUMVideoElement.play();
+ await h.checkVideoPlaying(captureStreamElement);
+
+ gUMVideoElement.srcObject.getTracks().forEach(t => t.stop());
+ ok(true, "Test passed.");
+ } catch (e) {
+ ok(false, "Test failed: " + e + (e.stack ? "\n" + e.stack : ""));
+ }
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaStreamClone.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaStreamClone.html
new file mode 100644
index 0000000000..029ce77dd0
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaStreamClone.html
@@ -0,0 +1,258 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+createHTML({
+ title: "MediaStream.clone()",
+ bug: "1208371"
+});
+
+runTest(async () => {
+ await pushPrefs(
+ ["media.getusermedia.camera.stop_on_disable.enabled", true],
+ ["media.getusermedia.camera.stop_on_disable.delay_ms", 0],
+ ["media.getusermedia.microphone.stop_on_disable.enabled", true],
+ ["media.getusermedia.microphone.stop_on_disable.delay_ms", 0]);
+
+ let gUMStream = await getUserMedia({audio: true, video: true});
+ {
+ info("Test clone()ing an audio/video gUM stream");
+ let clone = gUMStream.clone();
+
+ checkMediaStreamCloneAgainstOriginal(clone, gUMStream);
+ checkMediaStreamTrackCloneAgainstOriginal(clone.getAudioTracks()[0],
+ gUMStream.getAudioTracks()[0]);
+ checkMediaStreamTrackCloneAgainstOriginal(clone.getVideoTracks()[0],
+ gUMStream.getVideoTracks()[0]);
+
+ isnot(clone.id.length, 0, "Stream clone should have an id string");
+ isnot(clone.getAudioTracks()[0].id.length, 0,
+ "Audio track clone should have an id string");
+ isnot(clone.getVideoTracks()[0].id.length, 0,
+ "Audio track clone should have an id string");
+
+ info("Playing from track clones");
+ let test = createMediaElement('video', 'testClonePlayback');
+ let playback = new MediaStreamPlayback(test, clone);
+ await playback.playMedia(false);
+ }
+
+ {
+ info("Test addTrack()ing a video track to a stream without affecting its clone");
+ let stream = new MediaStream(gUMStream.getVideoTracks());
+ let otherStream = await getUserMedia({video: true});
+ let track = stream.getTracks()[0];
+ let otherTrack = otherStream.getTracks()[0];
+
+ let streamClone = stream.clone();
+ let trackClone = streamClone.getTracks()[0];
+ checkMediaStreamContains(streamClone, [trackClone], "Initial clone");
+
+ stream.addTrack(otherTrack);
+ checkMediaStreamContains(stream, [track, otherTrack],
+ "Added video to original");
+ checkMediaStreamContains(streamClone, [trackClone],
+ "Clone not affected");
+
+ stream.removeTrack(track);
+ streamClone.addTrack(track);
+ checkMediaStreamContains(streamClone, [trackClone, track],
+ "Added video to clone");
+ checkMediaStreamContains(stream, [otherTrack],
+ "Original not affected");
+
+ // Not part of streamClone. Does not get stopped by the playback test.
+ otherTrack.stop();
+
+ let test = createMediaElement('video', 'testClonePlayback');
+ let playback = new MediaStreamPlayback(test, streamClone);
+ await playback.playMedia(false);
+ }
+
+ {
+ info("Test cloning a stream into inception");
+ let stream = gUMStream.clone()
+ let clone = stream;
+ let clones = Array(10).fill().map(() => clone = clone.clone());
+ let inceptionClone = clones.pop();
+ checkMediaStreamCloneAgainstOriginal(inceptionClone, stream);
+ stream.getTracks().forEach(t => (stream.removeTrack(t),
+ inceptionClone.addTrack(t)));
+ is(inceptionClone.getAudioTracks().length, 2,
+ "The inception clone should contain the original audio track and a track clone");
+ is(inceptionClone.getVideoTracks().length, 2,
+ "The inception clone should contain the original video track and a track clone");
+
+ let test = createMediaElement('video', 'testClonePlayback');
+ let playback = new MediaStreamPlayback(test, inceptionClone);
+ await playback.playMedia(false);
+ clones.forEach(c => c.getTracks().forEach(t => t.stop()));
+ stream.getTracks().forEach(t => t.stop());
+ }
+
+ {
+ info("Test adding tracks from many stream clones to the original stream");
+ let stream = gUMStream.clone();
+
+ const LOOPS = 3;
+ for (let i = 0; i < LOOPS; i++) {
+ stream.clone().getTracks().forEach(t => stream.addTrack(t));
+ }
+ is(stream.getAudioTracks().length, Math.pow(2, LOOPS),
+ "The original track should contain the original audio track and all the audio clones");
+ is(stream.getVideoTracks().length, Math.pow(2, LOOPS),
+ "The original track should contain the original video track and all the video clones");
+ stream.getTracks().forEach(t1 => is(stream.getTracks()
+ .filter(t2 => t1.id == t2.id)
+ .length,
+ 1, "Each track should be unique"));
+
+ let test = createMediaElement('video', 'testClonePlayback');
+ let playback = new MediaStreamPlayback(test, stream);
+ await playback.playMedia(false);
+ }
+
+ {
+ info("Testing audio content routing with MediaStream.clone()");
+ let ac = new AudioContext();
+
+ let osc1kOriginal = createOscillatorStream(ac, 1000);
+ let audioTrack1kOriginal = osc1kOriginal.getTracks()[0];
+ let audioTrack1kClone = osc1kOriginal.clone().getTracks()[0];
+
+ let osc5kOriginal = createOscillatorStream(ac, 5000);
+ let audioTrack5kOriginal = osc5kOriginal.getTracks()[0];
+ let audioTrack5kClone = osc5kOriginal.clone().getTracks()[0];
+
+ info("Analysing audio output of original stream (1k + 5k)");
+ let stream = new MediaStream();
+ stream.addTrack(audioTrack1kOriginal);
+ stream.addTrack(audioTrack5kOriginal);
+
+ let analyser = new AudioStreamAnalyser(ac, stream);
+ await analyser.waitForAnalysisSuccess(array =>
+ array[analyser.binIndexForFrequency(50)] < 50 &&
+ array[analyser.binIndexForFrequency(1000)] > 200 &&
+ array[analyser.binIndexForFrequency(3000)] < 50 &&
+ array[analyser.binIndexForFrequency(5000)] > 200 &&
+ array[analyser.binIndexForFrequency(10000)] < 50);
+
+ info("Waiting for original tracks to stop");
+ stream.getTracks().forEach(t => t.stop());
+ await analyser.waitForAnalysisSuccess(array =>
+ array[analyser.binIndexForFrequency(50)] < 50 &&
+ // WebAudioDestination streams do not handle stop()
+ // XXX Should they? Plan to resolve that in bug 1208384.
+ // array[analyser.binIndexForFrequency(1000)] < 50 &&
+ array[analyser.binIndexForFrequency(3000)] < 50 &&
+ // array[analyser.binIndexForFrequency(5000)] < 50 &&
+ array[analyser.binIndexForFrequency(10000)] < 50);
+ analyser.disconnect();
+
+ info("Analysing audio output of stream clone (1k + 5k)");
+ stream = new MediaStream();
+ stream.addTrack(audioTrack1kClone);
+ stream.addTrack(audioTrack5kClone);
+
+ analyser = new AudioStreamAnalyser(ac, stream);
+ await analyser.waitForAnalysisSuccess(array =>
+ array[analyser.binIndexForFrequency(50)] < 50 &&
+ array[analyser.binIndexForFrequency(1000)] > 200 &&
+ array[analyser.binIndexForFrequency(3000)] < 50 &&
+ array[analyser.binIndexForFrequency(5000)] > 200 &&
+ array[analyser.binIndexForFrequency(10000)] < 50);
+ analyser.disconnect();
+
+ info("Analysing audio output of clone of clone (1k + 5k)");
+ stream = new MediaStream([audioTrack1kClone, audioTrack5kClone]).clone();
+
+ analyser = new AudioStreamAnalyser(ac, stream);
+ await analyser.waitForAnalysisSuccess(array =>
+ array[analyser.binIndexForFrequency(50)] < 50 &&
+ array[analyser.binIndexForFrequency(1000)] > 200 &&
+ array[analyser.binIndexForFrequency(3000)] < 50 &&
+ array[analyser.binIndexForFrequency(5000)] > 200 &&
+ array[analyser.binIndexForFrequency(10000)] < 50);
+ analyser.disconnect();
+
+ info("Analysing audio output of clone() + addTrack()ed tracks (1k + 5k)");
+ stream = new MediaStream(new MediaStream([ audioTrack1kClone
+ , audioTrack5kClone
+ ]).clone().getTracks());
+
+ analyser = new AudioStreamAnalyser(ac, stream);
+ await analyser.waitForAnalysisSuccess(array =>
+ array[analyser.binIndexForFrequency(50)] < 50 &&
+ array[analyser.binIndexForFrequency(1000)] > 200 &&
+ array[analyser.binIndexForFrequency(3000)] < 50 &&
+ array[analyser.binIndexForFrequency(5000)] > 200 &&
+ array[analyser.binIndexForFrequency(10000)] < 50);
+ analyser.disconnect();
+
+ info("Analysing audio output of clone()d tracks in original stream (1k) " +
+ "and clone()d tracks in stream clone (5k)");
+ stream = new MediaStream([audioTrack1kClone, audioTrack5kClone]);
+ let streamClone = stream.clone();
+
+ stream.getTracks().forEach(t => stream.removeTrack(t));
+ stream.addTrack(streamClone.getTracks()[0]);
+ streamClone.removeTrack(streamClone.getTracks()[0]);
+
+ analyser = new AudioStreamAnalyser(ac, stream);
+ await analyser.waitForAnalysisSuccess(array =>
+ array[analyser.binIndexForFrequency(50)] < 50 &&
+ array[analyser.binIndexForFrequency(1000)] > 200 &&
+ array[analyser.binIndexForFrequency(3000)] < 50 &&
+ array[analyser.binIndexForFrequency(5000)] < 50);
+ analyser.disconnect();
+
+ let cloneAnalyser = new AudioStreamAnalyser(ac, streamClone);
+ await cloneAnalyser.waitForAnalysisSuccess(array =>
+ array[cloneAnalyser.binIndexForFrequency(1000)] < 50 &&
+ array[cloneAnalyser.binIndexForFrequency(3000)] < 50 &&
+ array[cloneAnalyser.binIndexForFrequency(5000)] > 200 &&
+ array[cloneAnalyser.binIndexForFrequency(10000)] < 50);
+ cloneAnalyser.disconnect();
+
+ info("Analysing audio output enabled and disabled tracks that don't affect each other");
+ stream = new MediaStream([audioTrack1kClone, audioTrack5kClone]);
+ let clone = stream.clone();
+
+ stream.getTracks()[0].enabled = true;
+ stream.getTracks()[1].enabled = false;
+
+ clone.getTracks()[0].enabled = false;
+ clone.getTracks()[1].enabled = true;
+
+ analyser = new AudioStreamAnalyser(ac, stream);
+ await analyser.waitForAnalysisSuccess(array =>
+ array[analyser.binIndexForFrequency(50)] < 50 &&
+ array[analyser.binIndexForFrequency(1000)] > 200 &&
+ array[analyser.binIndexForFrequency(3000)] < 50 &&
+ array[analyser.binIndexForFrequency(5000)] < 50);
+ analyser.disconnect();
+
+ cloneAnalyser = new AudioStreamAnalyser(ac, clone);
+ await cloneAnalyser.waitForAnalysisSuccess(array =>
+ array[cloneAnalyser.binIndexForFrequency(1000)] < 50 &&
+ array[cloneAnalyser.binIndexForFrequency(3000)] < 50 &&
+ array[cloneAnalyser.binIndexForFrequency(5000)] > 200 &&
+ array[cloneAnalyser.binIndexForFrequency(10000)] < 50);
+ cloneAnalyser.disconnect();
+
+ // Restore original tracks
+ stream.getTracks().forEach(t => t.enabled = true);
+ }
+
+ gUMStream.getTracks().forEach(t => t.stop());
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaStreamConstructors.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaStreamConstructors.html
new file mode 100644
index 0000000000..4ea6e3f444
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaStreamConstructors.html
@@ -0,0 +1,171 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ "use strict";
+
+ createHTML({
+ title: "MediaStream constructors with getUserMedia streams Test",
+ bug: "1070216"
+ });
+
+ var audioContext = new AudioContext();
+ var videoElement;
+
+ runTest(() => Promise.resolve()
+ .then(() => videoElement = createMediaElement('video', 'constructorsTest'))
+ .then(() => getUserMedia({video: true})).then(gUMStream => {
+ info("Test default constructor with video");
+ ok(gUMStream.active, "gUMStream with one track should be active");
+ var track = gUMStream.getTracks()[0];
+
+ var stream = new MediaStream();
+ ok(!stream.active, "New MediaStream should be inactive");
+ checkMediaStreamContains(stream, [], "Default constructed stream");
+
+ stream.addTrack(track);
+ ok(stream.active, "MediaStream should be active after adding a track");
+ checkMediaStreamContains(stream, [track], "Added video track");
+
+ var playback = new MediaStreamPlayback(videoElement, stream);
+ return playback.playMedia(false).then(() => {
+ ok(!gUMStream.active, "gUMStream should be inactive after stopping");
+ ok(!stream.active, "stream with stopped tracks should be inactive");
+ });
+ })
+ .then(() => getUserMedia({video: true})).then(gUMStream => {
+ info("Test copy constructor with gUM stream");
+ ok(gUMStream.active, "gUMStream with one track should be active");
+ var track = gUMStream.getTracks()[0];
+
+ var stream = new MediaStream(gUMStream);
+ ok(stream.active, "List constructed MediaStream should be active");
+ checkMediaStreamContains(stream, [track], "Copy constructed video track");
+
+ var playback = new MediaStreamPlayback(videoElement, stream);
+ return playback.playMedia(false).then(() => {
+ ok(!gUMStream.active, "gUMStream should be inactive after stopping");
+ ok(!stream.active, "stream with stopped tracks should be inactive");
+ });
+ })
+ .then(() => getUserMedia({video: true})).then(gUMStream => {
+ info("Test list constructor with empty list");
+ ok(gUMStream.active, "gUMStream with one track should be active");
+ var track = gUMStream.getTracks()[0];
+
+ var stream = new MediaStream([]);
+ ok(!stream.active, "Empty-list constructed MediaStream should be inactive");
+ checkMediaStreamContains(stream, [], "Empty-list constructed stream");
+
+ stream.addTrack(track);
+ ok(stream.active, "MediaStream should be active after adding a track");
+ checkMediaStreamContains(stream, [track], "Added video track");
+
+ var playback = new MediaStreamPlayback(videoElement, stream);
+ return playback.playMedia(false).then(() => {
+ ok(!gUMStream.active, "gUMStream should be inactive after stopping");
+ ok(!stream.active, "stream with stopped tracks should be inactive");
+ });
+ })
+ .then(() => getUserMedia({audio: true, video: true})).then(gUMStream => {
+ info("Test list constructor with a gUM audio/video stream");
+ ok(gUMStream.active, "gUMStream with two tracks should be active");
+ var audioTrack = gUMStream.getAudioTracks()[0];
+ var videoTrack = gUMStream.getVideoTracks()[0];
+
+ var stream = new MediaStream([audioTrack, videoTrack]);
+ ok(stream.active, "List constructed MediaStream should be active");
+ checkMediaStreamContains(stream, [audioTrack, videoTrack],
+ "List constructed audio and video tracks");
+
+ var playback = new MediaStreamPlayback(videoElement, stream);
+ return playback.playMedia(false).then(() => {
+ ok(!gUMStream.active, "gUMStream should be inactive after stopping");
+ ok(!stream.active, "stream with stopped tracks should be inactive");
+ });
+ })
+ .then(() => getUserMedia({video: true})).then(gUMStream => {
+ info("Test list constructor with gUM-video and WebAudio tracks");
+ ok(gUMStream.active, "gUMStream with one track should be active");
+ var audioStream = createOscillatorStream(audioContext, 2000);
+ ok(audioStream.active, "WebAudio stream should be active");
+
+ var audioTrack = audioStream.getTracks()[0];
+ var videoTrack = gUMStream.getTracks()[0];
+
+ var stream = new MediaStream([audioTrack, videoTrack]);
+ ok(stream.active, "List constructed MediaStream should be active");
+ checkMediaStreamContains(stream, [audioTrack, videoTrack],
+ "List constructed WebAudio and gUM-video tracks");
+
+ var playback = new MediaStreamPlayback(videoElement, stream);
+ return playback.playMedia(false).then(() => {
+ gUMStream.getTracks().forEach(t => t.stop());
+ ok(!gUMStream.active, "gUMStream should be inactive after stopping");
+ ok(!stream.active, "stream with stopped tracks should be inactive");
+ });
+ })
+ .then(() => {
+ var osc1k = createOscillatorStream(audioContext, 1000);
+ var audioTrack1k = osc1k.getTracks()[0];
+
+ var osc5k = createOscillatorStream(audioContext, 5000);
+ var audioTrack5k = osc5k.getTracks()[0];
+
+ var osc10k = createOscillatorStream(audioContext, 10000);
+ var audioTrack10k = osc10k.getTracks()[0];
+
+ return Promise.resolve().then(() => {
+ info("Analysing audio output with empty default constructed stream");
+ var stream = new MediaStream();
+ var analyser = new AudioStreamAnalyser(audioContext, stream);
+ return analyser.waitForAnalysisSuccess(array =>
+ array[analyser.binIndexForFrequency(1000)] < 50 &&
+ array[analyser.binIndexForFrequency(5000)] < 50 &&
+ array[analyser.binIndexForFrequency(10000)] < 50)
+ .then(() => analyser.disconnect());
+ }).then(() => {
+ info("Analysing audio output with copy constructed 5k stream");
+ var stream = new MediaStream(osc5k);
+ is(stream.active, osc5k.active,
+ "Copy constructed MediaStream should preserve active state");
+ var analyser = new AudioStreamAnalyser(audioContext, stream);
+ return analyser.waitForAnalysisSuccess(array =>
+ array[analyser.binIndexForFrequency(1000)] < 50 &&
+ array[analyser.binIndexForFrequency(5000)] > 200 &&
+ array[analyser.binIndexForFrequency(10000)] < 50)
+ .then(() => analyser.disconnect());
+ }).then(() => {
+ info("Analysing audio output with empty-list constructed stream");
+ var stream = new MediaStream([]);
+ var analyser = new AudioStreamAnalyser(audioContext, stream);
+ return analyser.waitForAnalysisSuccess(array =>
+ array[analyser.binIndexForFrequency(1000)] < 50 &&
+ array[analyser.binIndexForFrequency(5000)] < 50 &&
+ array[analyser.binIndexForFrequency(10000)] < 50)
+ .then(() => analyser.disconnect());
+ }).then(() => {
+ info("Analysing audio output with list constructed 1k, 5k and 10k tracks");
+ var stream = new MediaStream([audioTrack1k, audioTrack5k, audioTrack10k]);
+ ok(stream.active,
+ "List constructed MediaStream from WebAudio should be active");
+ var analyser = new AudioStreamAnalyser(audioContext, stream);
+ return analyser.waitForAnalysisSuccess(array =>
+ array[analyser.binIndexForFrequency(50)] < 50 &&
+ array[analyser.binIndexForFrequency(1000)] > 200 &&
+ array[analyser.binIndexForFrequency(2500)] < 50 &&
+ array[analyser.binIndexForFrequency(5000)] > 200 &&
+ array[analyser.binIndexForFrequency(7500)] < 50 &&
+ array[analyser.binIndexForFrequency(10000)] > 200 &&
+ array[analyser.binIndexForFrequency(11000)] < 50)
+ .then(() => analyser.disconnect());
+ });
+ }));
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaStreamTrackClone.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaStreamTrackClone.html
new file mode 100644
index 0000000000..e5e0764427
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_mediaStreamTrackClone.html
@@ -0,0 +1,170 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ "use strict";
+
+ createHTML({
+ title: "MediaStreamTrack.clone()",
+ bug: "1208371"
+ });
+
+ var testSingleTrackClonePlayback = constraints =>
+ getUserMedia(constraints).then(stream => {
+ info("Test clone()ing an " + constraints + " gUM track");
+ var track = stream.getTracks()[0];
+ var clone = track.clone();
+
+ checkMediaStreamTrackCloneAgainstOriginal(clone, track);
+
+ info("Stopping original track");
+ track.stop();
+
+ info("Creating new stream for clone");
+ var cloneStream = new MediaStream([clone]);
+ checkMediaStreamContains(cloneStream, [clone]);
+
+ info("Testing playback of track clone");
+ var test = createMediaElement('video', 'testClonePlayback');
+ var playback = new MediaStreamPlayback(test, cloneStream);
+ return playback.playMedia(false);
+ });
+
+ runTest(() => Promise.resolve()
+ .then(() => testSingleTrackClonePlayback({audio: true}))
+ .then(() => testSingleTrackClonePlayback({video: true}))
+ .then(() => getUserMedia({video: true})).then(stream => {
+ info("Test cloning a track into inception");
+ var track = stream.getTracks()[0];
+ var clone = track;
+ var clones = Array(10).fill().map(() => clone = clone.clone());
+ var inceptionClone = clones.pop();
+ checkMediaStreamTrackCloneAgainstOriginal(inceptionClone, track);
+
+ var cloneStream = new MediaStream();
+ cloneStream.addTrack(inceptionClone);
+
+ // cloneStream is now essentially the same as stream.clone();
+ checkMediaStreamCloneAgainstOriginal(cloneStream, stream);
+
+ var test = createMediaElement('video', 'testClonePlayback');
+ var playback = new MediaStreamPlayback(test, cloneStream);
+ return playback.playMedia(false).then(() => {
+ info("Testing that clones of ended tracks are ended");
+ cloneStream.clone().getTracks().forEach(t =>
+ is(t.readyState, "ended", "Track " + t.id + " should be ended"));
+ })
+ .then(() => {
+ clones.forEach(t => t.stop());
+ track.stop();
+ });
+ })
+ .then(() => getUserMedia({audio: true, video: true})).then(stream => {
+ info("Test adding many track clones to the original stream");
+
+ const LOOPS = 3;
+ for (var i = 0; i < LOOPS; i++) {
+ stream.getTracks().forEach(t => stream.addTrack(t.clone()));
+ }
+ is(stream.getVideoTracks().length, Math.pow(2, LOOPS),
+ "The original track should contain the original video track and all the video clones");
+ stream.getTracks().forEach(t1 => is(stream.getTracks()
+ .filter(t2 => t1.id == t2.id)
+ .length,
+ 1, "Each track should be unique"));
+
+ var test = createMediaElement('video', 'testClonePlayback');
+ var playback = new MediaStreamPlayback(test, stream);
+ return playback.playMedia(false);
+ })
+ .then(() => {
+ info("Testing audio content routing with MediaStreamTrack.clone()");
+ var ac = new AudioContext();
+
+ var osc1kOriginal = createOscillatorStream(ac, 1000);
+ var audioTrack1kOriginal = osc1kOriginal.getTracks()[0];
+ var audioTrack1kClone = audioTrack1kOriginal.clone();
+
+ var osc5kOriginal = createOscillatorStream(ac, 5000);
+ var audioTrack5kOriginal = osc5kOriginal.getTracks()[0];
+ var audioTrack5kClone = audioTrack5kOriginal.clone();
+
+ return Promise.resolve().then(() => {
+ info("Analysing audio output enabled and disabled tracks that don't affect each other");
+ audioTrack1kOriginal.enabled = true;
+ audioTrack5kOriginal.enabled = false;
+
+ audioTrack1kClone.enabled = false;
+ audioTrack5kClone.enabled = true;
+
+ var analyser =
+ new AudioStreamAnalyser(ac, new MediaStream([audioTrack1kOriginal,
+ audioTrack5kOriginal]));
+ return analyser.waitForAnalysisSuccess(array =>
+ array[analyser.binIndexForFrequency(50)] < 50 &&
+ array[analyser.binIndexForFrequency(1000)] > 200 &&
+ array[analyser.binIndexForFrequency(3000)] < 50 &&
+ array[analyser.binIndexForFrequency(5000)] < 50)
+ .then(() => analyser.disconnect())
+ .then(() => {
+ var cloneAnalyser =
+ new AudioStreamAnalyser(ac, new MediaStream([audioTrack1kClone,
+ audioTrack5kClone]));
+ return cloneAnalyser.waitForAnalysisSuccess(array =>
+ array[cloneAnalyser.binIndexForFrequency(1000)] < 50 &&
+ array[cloneAnalyser.binIndexForFrequency(3000)] < 50 &&
+ array[cloneAnalyser.binIndexForFrequency(5000)] > 200 &&
+ array[cloneAnalyser.binIndexForFrequency(10000)] < 50)
+ .then(() => cloneAnalyser.disconnect());
+ })
+ // Restore original tracks
+ .then(() => [audioTrack1kOriginal,
+ audioTrack5kOriginal,
+ audioTrack1kClone,
+ audioTrack5kClone].forEach(t => t.enabled = true));
+ }).then(() => {
+ info("Analysing audio output of 1k original and 5k clone.");
+ var stream = new MediaStream();
+ stream.addTrack(audioTrack1kOriginal);
+ stream.addTrack(audioTrack5kClone);
+
+ var analyser = new AudioStreamAnalyser(ac, stream);
+ return analyser.waitForAnalysisSuccess(array =>
+ array[analyser.binIndexForFrequency(50)] < 50 &&
+ array[analyser.binIndexForFrequency(1000)] > 200 &&
+ array[analyser.binIndexForFrequency(3000)] < 50 &&
+ array[analyser.binIndexForFrequency(5000)] > 200 &&
+ array[analyser.binIndexForFrequency(10000)] < 50)
+ .then(() => {
+ info("Waiting for tracks to stop");
+ stream.getTracks().forEach(t => t.stop());
+ return analyser.waitForAnalysisSuccess(array =>
+ array[analyser.binIndexForFrequency(50)] < 50 &&
+ array[analyser.binIndexForFrequency(1000)] < 50 &&
+ array[analyser.binIndexForFrequency(3000)] < 50 &&
+ array[analyser.binIndexForFrequency(5000)] < 50 &&
+ array[analyser.binIndexForFrequency(10000)] < 50);
+ }).then(() => analyser.disconnect());
+ }).then(() => {
+ info("Analysing audio output of clones of clones (1kx2 + 5kx4)");
+ var stream = new MediaStream([audioTrack1kClone.clone(),
+ audioTrack5kOriginal.clone().clone().clone().clone()]);
+
+ var analyser = new AudioStreamAnalyser(ac, stream);
+ return analyser.waitForAnalysisSuccess(array =>
+ array[analyser.binIndexForFrequency(50)] < 50 &&
+ array[analyser.binIndexForFrequency(1000)] > 200 &&
+ array[analyser.binIndexForFrequency(3000)] < 50 &&
+ array[analyser.binIndexForFrequency(5000)] > 200 &&
+ array[analyser.binIndexForFrequency(10000)] < 50)
+ .then(() => analyser.disconnect());
+ });
+ }));
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_nonDefaultRate.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_nonDefaultRate.html
new file mode 100644
index 0000000000..8a6ac8c62b
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_nonDefaultRate.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ title: "getUserMedia feed to a graph with non default rate",
+ bug: "1387454",
+ });
+
+ /**
+ * Run a test to verify that when we use the streem from a gUM to an AudioContext
+ * with non default rate the connection fails. (gUM is always on default rate).
+ */
+ runTest(async () => {
+ // Since we do not examine the stream we do not need loopback.
+ DISABLE_LOOPBACK_TONE = true;
+ const stream = await getUserMedia({audio: true});
+ const nonDefaultRate = 32000;
+ const ac = new AudioContext({sampleRate: nonDefaultRate});
+ mustThrowWith(
+ "Connect stream with graph of different sample rate",
+ "NotSupportedError", () => {
+ ac.createMediaStreamSource(stream);
+ }
+ );
+ for (let t of stream.getTracks()) {
+ t.stop();
+ }
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_peerIdentity.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_peerIdentity.html
new file mode 100644
index 0000000000..c4dfb9acb8
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_peerIdentity.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+ <script type="application/javascript" src="blacksilence.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ title: "Test getUserMedia peerIdentity Constraint",
+ bug: "942367"
+});
+async function theTest() {
+ async function testPeerIdentityConstraint(withConstraint) {
+ const config = { audio: true, video: true };
+ if (withConstraint) {
+ config.peerIdentity = 'user@example.com';
+ }
+ info('getting media with constraints: ' + JSON.stringify(config));
+ const stream = await getUserMedia(config);
+ for (const track of stream.getTracks()) {
+ const recorder = new MediaRecorder(new MediaStream([track]));
+ try {
+ recorder.start();
+ ok(!withConstraint,
+ `gUM ${track.kind} track without peerIdentity must not throw`);
+ recorder.stop();
+ } catch(e) {
+ ok(withConstraint,
+ `gUM ${track.kind} track with peerIdentity must throw`);
+ }
+ }
+ await Promise.all([
+ audioIsSilence(withConstraint, stream),
+ videoIsBlack(withConstraint, stream),
+ ]);
+ stream.getTracks().forEach(t => t.stop());
+ };
+
+ // both without and with the constraint
+ await testPeerIdentityConstraint(false);
+ await testPeerIdentityConstraint(true);
+}
+
+runTest(theTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_permission.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_permission.html
new file mode 100644
index 0000000000..cd02c7326c
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_permission.html
@@ -0,0 +1,104 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({ title: "Test getUserMedia in iframes", bug: "1371741" });
+/**
+ Tests covering enumerateDevices API and deviceId constraint. Exercise code.
+*/
+
+// Call gUM in iframe.
+async function iframeGum(dict, iframe = document.createElement("iframe")) {
+ Object.assign(iframe, dict);
+ if (dict.src) {
+ info(`<iframe src="${dict.src}" sandbox="${dict.sandbox}">`);
+ } else {
+ info(`<iframe srcdoc sandbox="${dict.sandbox}">`);
+ }
+ document.documentElement.appendChild(iframe);
+
+ const once = (t, msg) => new Promise(r => t.addEventListener(msg, r, { once: true }));
+ const haveMessage = once(window, "message");
+ await new Promise(resolve => iframe.onload = resolve);
+ return (await haveMessage).data;
+};
+
+runTest(async () => {
+ const path = "/tests/dom/media/webrtc/tests/mochitests/test_getUserMedia_permission_iframe.html";
+
+ async function sourceFn() {
+ try {
+ const gUM = c => navigator.mediaDevices.getUserMedia(c);
+ let message;
+ let stream;
+ try {
+ stream = await gUM({ video: true });
+ message = 'success';
+ } catch(e) {
+ message = e.name;
+ }
+ parent.postMessage(message, 'https://example.com:443');
+
+ if (message == "success") {
+ stream.getTracks().forEach(track => track.stop());
+ }
+ } catch (e) {
+ setTimeout(() => { throw e; });
+ }
+ }
+
+ const source = `<html\><script\>(${sourceFn.toString()})()</script\></html\>`;
+
+ // Test gUM in sandboxed vs. regular iframe.
+
+ for (const origin of [window.location.origin, "https://test1.example.com"]) {
+ const src = origin + path;
+ is(await iframeGum({ src, sandbox: "allow-scripts" }),
+ "NotAllowedError", "gUM fails in sandboxed iframe " + origin);
+ }
+ is(await iframeGum({
+ src: path,
+ sandbox: "allow-scripts allow-same-origin",
+ }),
+ "success", "gUM works in regular same-origin iframe");
+ is(await iframeGum({
+ src: `https://test1.example.com${path}`,
+ sandbox: "allow-scripts allow-same-origin",
+ }),
+ "NotAllowedError", "gUM fails in regular cross-origin iframe");
+
+ // Test gUM in sandboxed vs regular srcdoc iframe
+
+ const iframeSrcdoc = document.createElement("iframe");
+ iframeSrcdoc.srcdoc = source;
+ is(await iframeGum({ sandbox: "allow-scripts" }, iframeSrcdoc),
+ "NotAllowedError", "gUM fails in sandboxed srcdoc iframe");
+ is(await iframeGum({ sandbox: "allow-scripts allow-same-origin" }, iframeSrcdoc),
+ "success", "gUM works in regular srcdoc iframe");
+
+ // Test gUM in sandboxed vs regular blob iframe
+
+ const blob = new Blob([source], {type : "text/html"});
+ let src = URL.createObjectURL(blob);
+ is(await iframeGum({ src, sandbox: "allow-scripts" }),
+ "NotAllowedError", "gUM fails in sandboxed blob iframe");
+ is(await iframeGum({ src, sandbox: "allow-scripts allow-same-origin"}),
+ "success", "gUM works in regular blob iframe");
+ URL.revokeObjectURL(src);
+
+ // data iframes always have null-principals
+
+ src = `data:text/html;base64,${btoa(source)}`;
+ is(await iframeGum({ src, sandbox: "allow-scripts" }),
+ "NotAllowedError", "gUM fails in sandboxed data iframe");
+ is(await iframeGum({ src, sandbox: "allow-scripts allow-same-origin"}),
+ "NotAllowedError", "gUM fails in regular data iframe");
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_permission_iframe.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_permission_iframe.html
new file mode 100644
index 0000000000..732c2cf98c
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_permission_iframe.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<pre id="test">
+<script type="application/javascript">
+/**
+ Runs inside iframe in test_getUserMedia_permission.html.
+*/
+
+const gUM = c => navigator.mediaDevices.getUserMedia(c);
+
+(async () => {
+ let message;
+ let stream;
+ try {
+ stream = await gUM({ video: true });
+ message = "success";
+ } catch(e) {
+ message = e.name;
+ }
+ parent.postMessage(message, "https://example.com:443");
+
+ if (message == "success") {
+ stream.getTracks().forEach(track => track.stop());
+ }
+})().catch(e => setTimeout(() => { throw e; }));
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_playAudioTwice.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_playAudioTwice.html
new file mode 100644
index 0000000000..30d168bf38
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_playAudioTwice.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({title: "getUserMedia Play Audio Twice", bug: "822109" });
+ /**
+ * Run a test that we can complete an audio playback cycle twice in a row.
+ */
+ runTest(function () {
+ return getUserMedia({audio: true}).then(audioStream => {
+ var testAudio = createMediaElement('audio', 'testAudio');
+ var playback = new MediaStreamPlayback(testAudio, audioStream);
+
+ return playback.playMediaWithoutStoppingTracks(false)
+ .then(() => playback.playMedia(true));
+ });
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_playVideoAudioTwice.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_playVideoAudioTwice.html
new file mode 100644
index 0000000000..7b5e6effd1
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_playVideoAudioTwice.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({title: "getUserMedia Play Video and Audio Twice", bug: "822109" });
+ /**
+ * Run a test that we can complete a video playback cycle twice in a row.
+ */
+ runTest(function () {
+ return getUserMedia({video: true, audio: true}).then(stream => {
+ var testVideo = createMediaElement('video', 'testVideo');
+ var playback = new MediaStreamPlayback(testVideo, stream);
+
+ return playback.playMediaWithoutStoppingTracks(false)
+ .then(() => playback.playMedia(true));
+ });
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_playVideoTwice.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_playVideoTwice.html
new file mode 100644
index 0000000000..2890f45eab
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_playVideoTwice.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({ title: "getUserMedia Play Video Twice", bug: "822109" });
+ /**
+ * Run a test that we can complete a video playback cycle twice in a row.
+ */
+ runTest(function () {
+ return getUserMedia({video: true}).then(stream => {
+ var testVideo = createMediaElement('video', 'testVideo');
+ var streamPlayback = new MediaStreamPlayback(testVideo, stream);
+
+ return streamPlayback.playMediaWithoutStoppingTracks(false)
+ .then(() => streamPlayback.playMedia(true));
+ });
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_scarySources.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_scarySources.html
new file mode 100644
index 0000000000..782110823e
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_scarySources.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+
+createHTML({title: "Detect screensharing sources that are firefox", bug: "1311048"});
+
+const Services = SpecialPowers.Services;
+
+let observe = topic => new Promise(r => Services.obs.addObserver(function o(...args) {
+ Services.obs.removeObserver(o, topic);
+ r(args.map(x => SpecialPowers.wrap(x)));
+}, topic));
+
+let getDevices = async constraints => {
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ let [{ windowID, innerWindowID, callID, devices }] = await Promise.race([
+ getUserMedia(constraints),
+ observe("getUserMedia:request")
+ ]);
+ let window = Services.wm.getOuterWindowWithId(windowID);
+ return devices.map(SpecialPowers.wrapCallback(d => d.QueryInterface(Ci.nsIMediaDevice)));
+};
+
+runTest(async () => {
+ await pushPrefs(["media.navigator.permission.disabled", true],
+ ["media.navigator.permission.force", true]);
+ let devices = await getDevices({video: { mediaSource: "window" }});
+ ok(devices.length, "Found one or more windows.");
+ devices = Array.prototype.filter.call(devices, d => d.scary);
+ ok(devices.length, "Found one or more scary windows (our own counts).");
+ devices = devices.filter(d => d.rawName.includes("MochiTest"));
+ ok(devices.length,
+ "Our own window is among the scary: "
+ + devices.map(d => `"${d.rawName}"`));
+
+ devices = await getDevices({video: { mediaSource: "screen" }});
+ let numScreens = devices.length;
+ ok(numScreens, "Found one or more screens.");
+ devices = Array.prototype.filter.call(devices, d => d.scary);
+ is(devices.length, numScreens, "All screens are scary.");
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_spinEventLoop.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_spinEventLoop.html
new file mode 100644
index 0000000000..ae691785f5
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_spinEventLoop.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({ title: "getUserMedia Basic Audio Test", bug: "1208656" });
+ /**
+ * Run a test to verify that we can spin the event loop from within a mozGUM callback.
+ */
+ runTest(() => {
+ var testAudio = createMediaElement('audio', 'testAudio');
+ return new Promise((resolve, reject) => {
+ navigator.mozGetUserMedia({ audio: true }, stream => {
+ SpecialPowers.spinEventLoop(window);
+ ok(true, "Didn't crash");
+ stream.getTracks().forEach(t => t.stop());
+ resolve();
+ }, () => {});
+ });
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_trackCloneCleanup.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_trackCloneCleanup.html
new file mode 100644
index 0000000000..60077ec73b
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_trackCloneCleanup.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ "use strict";
+
+ createHTML({
+ title: "Stopping a MediaStreamTrack and its clones should deallocate the device",
+ bug: "1294605"
+ });
+
+ runTest(async () => {
+ await pushPrefs(["media.navigator.permission.fake", true]);
+ const stream = await getUserMedia({audio: true, video: true, fake: true});
+ const clone = stream.clone();
+ stream.getTracks().forEach(t => t.stop());
+ stream.clone().getTracks().forEach(t => stream.addTrack(t));
+ is(stream.getTracks().filter(t => t.readyState == "live").length, 0,
+ "Cloning ended tracks should make them ended");
+ [...stream.getTracks(), ...clone.getTracks()].forEach(t => t.stop());
+
+ // Bug 1295352: better to be explicit about noGum here wrt future refactoring.
+ return noGum();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_getUserMedia_trackEnded.html b/dom/media/webrtc/tests/mochitests/test_getUserMedia_trackEnded.html
new file mode 100644
index 0000000000..b275f4555f
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_getUserMedia_trackEnded.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<iframe id="iframe" onload="iframeLoaded()" srcdoc="
+ <script type='application/javascript'>
+ document.gUM = (constraints, success, failure) =>
+ navigator.mediaDevices.getUserMedia(constraints).then(success, failure);
+ </script>">
+</iframe>
+<script type="application/javascript">
+ "use strict";
+ let iframeLoadedPromise = {};
+ iframeLoadedPromise.promise = new Promise(r => {
+ iframeLoadedPromise.resolve = r;
+ });;
+ function iframeLoaded() {
+ iframeLoadedPromise.resolve();
+ }
+
+ createHTML({
+ title: "getUserMedia MediaStreamTrack 'ended' event on navigating",
+ bug: "1208373",
+ });
+
+ runTest(async () => {
+ await iframeLoadedPromise.promise;
+ let iframe = document.getElementById("iframe");
+ let stream;
+ // We're passing callbacks into a method in the iframe here, because
+ // a Promise created in the iframe is unusable after the iframe has
+ // navigated away (see bug 1269400 for details).
+ return new Promise((resolve, reject) =>
+ iframe.contentDocument.gUM({audio: true, video: true}, resolve, reject))
+ .then(s => {
+ // We're cloning a stream containing identical tracks (an original
+ // and its clone) to test that ended works both for originals
+ // clones when they're both owned by the same MediaStream.
+ // (Bug 1274221)
+ stream = new MediaStream([].concat(s.getTracks(), s.getTracks())
+ .map(t => t.clone())).clone();
+ var allTracksEnded = Promise.all(stream.getTracks().map(t => {
+ info("Set up ended handler for track " + t.id);
+ return haveEvent(t, "ended", wait(50000))
+ .then(event => {
+ info("ended handler invoked for track " + t.id);
+ is(event.target, t, "Target should be correct");
+ }, e => e ? Promise.reject(e)
+ : ok(false, "ended event never raised for track " + t.id));
+ }));
+ stream.getTracks().forEach(t =>
+ is(t.readyState, "live",
+ "Non-ended track should have readyState 'live'"));
+ iframe.srcdoc = "";
+ info("iframe has been reset. Waiting for tracks to end.");
+ return allTracksEnded;
+ })
+ .then(() => stream.getTracks().forEach(t =>
+ is(t.readyState, "ended",
+ "Ended track should have readyState 'ended'")));
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_groupId.html b/dom/media/webrtc/tests/mochitests/test_groupId.html
new file mode 100644
index 0000000000..f2aefe5e80
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_groupId.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({ title: "Test group id of MediaDeviceInfo", bug: "1213453" });
+
+async function getDefaultDevices() {
+ const devices = await navigator.mediaDevices.enumerateDevices();
+ is(devices.length, 2, "Two fake devices found.");
+
+ devices.forEach(d => isnot(d.groupId, "", "GroupId is included in every device"));
+
+ const videos = devices.filter(d => d.kind == "videoinput");
+ is(videos.length, 1, "One video device found.");
+ const audios = devices.filter(d => d.kind == "audioinput");
+ is(audios.length, 1, "One microphone device found.");
+
+ return {audio: audios[0], video: videos[0]};
+}
+
+runTest(async () => {
+ // Force fake devices in order to be able to change camera name by pref.
+ await pushPrefs(["media.navigator.streams.fake", true],
+ ["media.audio_loopback_dev", ""],
+ ["media.video_loopback_dev", ""]);
+
+ const afterGum = await navigator.mediaDevices.getUserMedia({
+ video: true, audio: true
+ });
+ afterGum.getTracks().forEach(track => track.stop());
+
+ let {audio, video} = await getDefaultDevices();
+
+ /* The low level method to correlate groupIds is by device names.
+ * Use a similar comparison here to verify that it works.
+ * Multiple devices of the same device name are not expected in
+ * automation. */
+ isnot(audio.label, video.label, "Audio label differs from video");
+ isnot(audio.groupId, video.groupId, "Not the same groupIds");
+ // Change video name to match.
+ await pushPrefs(["media.getusermedia.fake-camera-name", audio.label]);
+ ({audio, video} = await getDefaultDevices());
+ is(audio.label, video.label, "Audio label matches video");
+ is(audio.groupId, video.groupId, "GroupIds should be the same");
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_multi_mics.html b/dom/media/webrtc/tests/mochitests/test_multi_mics.html
new file mode 100644
index 0000000000..95bcbfd3e4
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_multi_mics.html
@@ -0,0 +1,66 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+createHTML({
+ title: "Test the ability of opening multiple microphones via gUM",
+ bug: "1238038",
+});
+
+runTest(async () => {
+ // Ensure we use the real microphones by disabling loopback devices and fake devices.
+ await pushPrefs(["media.audio_loopback_dev", ""], ["media.navigator.streams.fake", false]);
+
+ try {
+ let devices = await navigator.mediaDevices.enumerateDevices();
+ // Create constraints
+ let constraints = [];
+ devices.forEach((device) => {
+ if (device.kind === "audioinput") {
+ constraints.push({
+ audio: { deviceId: { exact: device.deviceId } },
+ });
+ }
+ });
+ if (constraints.length >= 2) {
+ // Create constraints
+ let constraints = [];
+ devices.forEach((device) => {
+ if (device.kind === "audioinput") {
+ constraints.push({
+ audio: { deviceId: { exact: device.deviceId } },
+ });
+ }
+ });
+ // Open microphones by the constraints
+ let mediaStreams = [];
+ for (let c of constraints) {
+ let stream = await navigator.mediaDevices.getUserMedia(c);
+ dump("MediaStream: " + stream.id + " for device: " + c.audio.deviceId.exact + " is created\n");
+ mediaStreams.push(stream);
+ }
+ // Close microphones
+ for (let stream of mediaStreams) {
+ for (let track of stream.getTracks()) {
+ track.stop();
+ }
+ dump("Stop all tracks in MediaStream: " + stream.id + "\n");
+ }
+ mediaStreams = [];
+ } else {
+ dump("Skip test since we need at least two microphones\n");
+ }
+ } catch (e) {
+ ok(false, e.name + ": " + e.message);
+ }
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_ondevicechange.html b/dom/media/webrtc/tests/mochitests/test_ondevicechange.html
new file mode 100644
index 0000000000..4358d9d748
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_ondevicechange.html
@@ -0,0 +1,180 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<script type="application/javascript">
+"use strict";
+
+createHTML({
+ title: "ondevicechange tests",
+ bug: "1152383"
+});
+
+async function resolveOnEvent(target, name) {
+ return new Promise(r => target.addEventListener(name, r, {once: true}));
+}
+let eventCount = 0;
+async function triggerVideoDevicechange() {
+ ++eventCount;
+ // "media.getusermedia.fake-camera-name" specifies the name of the single
+ // fake video camera.
+ // Changing the pref imitates replacing one device with another.
+ return pushPrefs(["media.getusermedia.fake-camera-name",
+ `devicechange ${eventCount}`])
+}
+function addIframe() {
+ const iframe = document.createElement("iframe");
+ // Workaround for bug 1743933
+ iframe.loadPromise = resolveOnEvent(iframe, "load");
+ document.documentElement.appendChild(iframe);
+ return iframe;
+}
+
+runTest(async () => {
+ // A toplevel Window and an iframe Windows are compared for devicechange
+ // events.
+ const iframe1 = addIframe();
+ const iframe2 = addIframe();
+ await Promise.all([
+ iframe1.loadPromise,
+ iframe2.loadPromise,
+ pushPrefs(
+ // Use the fake video backend to trigger devicechange events.
+ ["media.navigator.streams.fake", true],
+ // Loopback would override fake.
+ ["media.video_loopback_dev", ""],
+ // Make fake devices count as real, permission-wise, or devicechange
+ // events won't be exposed
+ ["media.navigator.permission.fake", true],
+ // For gUM.
+ ["media.navigator.permission.disabled", true]
+ ),
+ ]);
+ const topDevices = navigator.mediaDevices;
+ const frame1Devices = iframe1.contentWindow.navigator.mediaDevices;
+ const frame2Devices = iframe2.contentWindow.navigator.mediaDevices;
+ // Initialization of MediaDevices::mLastPhysicalDevices is triggered when
+ // ondevicechange is set but tests "media.getusermedia.fake-camera-name"
+ // asynchronously. Wait for getUserMedia() completion to ensure that the
+ // pref has been read before doDevicechanges() changes it.
+ frame1Devices.ondevicechange = () => {};
+ const topEventPromise = resolveOnEvent(topDevices, "devicechange");
+ const frame2EventPromise = resolveOnEvent(frame2Devices, "devicechange");
+ (await frame1Devices.getUserMedia({video: true})).getTracks()[0].stop();
+
+ await Promise.all([
+ resolveOnEvent(frame1Devices, "devicechange"),
+ triggerVideoDevicechange(),
+ ]);
+ ok(true,
+ "devicechange event is fired when gUM has been in use");
+ // The number of devices has not changed. Race a settled Promise to check
+ // that no devicechange event has been received in frame2.
+ const racer = {};
+ is(await Promise.race([frame2EventPromise, racer]), racer,
+ "devicechange event is NOT fired in iframe2 for replaced device when " +
+ "gUM has NOT been in use");
+ // getUserMedia() is invoked on frame2Devices after a first device list
+ // change but before returning to the previous state, in order to test that
+ // the device set is compared with the set after previous device list
+ // changes regardless of whether a "devicechange" event was previously
+ // dispatched.
+ (await frame2Devices.getUserMedia({video: true})).getTracks()[0].stop();
+ // Revert device list change.
+ await Promise.all([
+ resolveOnEvent(frame1Devices, "devicechange"),
+ resolveOnEvent(frame2Devices, "devicechange"),
+ SpecialPowers.popPrefEnv(),
+ ]);
+ ok(true,
+ "devicechange event is fired on return to previous list " +
+ "after gUM has been is use");
+
+ const frame1EventPromise1 = resolveOnEvent(frame1Devices, "devicechange");
+ while (true) {
+ const racePromise = Promise.race([
+ frame1EventPromise1,
+ // 100ms is half the coalescing time in MediaManager::DeviceListChanged().
+ wait(100, {type: "wait done"}),
+ ]);
+ await triggerVideoDevicechange();
+ if ((await racePromise).type == "devicechange") {
+ ok(true,
+ "devicechange event is fired even when hardware changes continue");
+ break;
+ }
+ }
+
+ is(await Promise.race([topEventPromise, racer]), racer,
+ "devicechange event is NOT fired for device replacements when " +
+ "gUM has NOT been in use");
+
+ if (navigator.userAgent.includes("Android")) {
+ todo(false, "test assumes Firefox-for-Desktop specific API and behavior");
+ return;
+ }
+ // Open a new tab, which is expected to receive focus and hide the first tab.
+ const tab = window.open();
+ SimpleTest.registerCleanupFunction(() => tab.close());
+ await Promise.all([
+ resolveOnEvent(document, 'visibilitychange'),
+ resolveOnEvent(tab, 'focus'),
+ ]);
+ ok(tab.document.hasFocus(), "tab.document.hasFocus()");
+ await Promise.all([
+ resolveOnEvent(tab, 'blur'),
+ SpecialPowers.spawnChrome([], function focusUrlBar() {
+ this.browsingContext.topChromeWindow.gURLBar.focus();
+ }),
+ ]);
+ ok(!tab.document.hasFocus(), "!tab.document.hasFocus()");
+ is(document.visibilityState, 'hidden', 'visibilityState')
+ const frame1EventPromise2 = resolveOnEvent(frame1Devices, "devicechange");
+ const tabDevices = tab.navigator.mediaDevices;
+ tabDevices.ondevicechange = () => {};
+ const tabStream = await tabDevices.getUserMedia({video: true});
+ // Trigger and await two devicechanges on tabDevices to wait long enough to
+ // provide that a devicechange on another MediaDevices would be received.
+ for (let i = 0; i < 2; ++i) {
+ await Promise.all([
+ resolveOnEvent(tabDevices, "devicechange"),
+ triggerVideoDevicechange(),
+ ]);
+ };
+ is(await Promise.race([frame1EventPromise2, racer]), racer,
+ "devicechange event is NOT fired while tab is in background");
+ tab.close();
+ await resolveOnEvent(document, 'visibilitychange');
+ is(document.visibilityState, 'visible', 'visibilityState')
+ await frame1EventPromise2;
+ ok(true, "devicechange event IS fired when tab returns to foreground");
+
+ const audioLoopbackDev =
+ SpecialPowers.getCharPref("media.audio_loopback_dev", "");
+ if (!navigator.userAgent.includes("Linux")) {
+ todo_isnot(audioLoopbackDev, "", "audio_loopback_dev");
+ return;
+ }
+ isnot(audioLoopbackDev, "", "audio_loopback_dev");
+ await Promise.all([
+ resolveOnEvent(topDevices, "devicechange"),
+ pushPrefs(["media.audio_loopback_dev", "none"]),
+ ]);
+ ok(true,
+ "devicechange event IS fired when last audio device is removed and " +
+ "gUM has NOT been in use");
+ await Promise.all([
+ resolveOnEvent(topDevices, "devicechange"),
+ pushPrefs(["media.audio_loopback_dev", audioLoopbackDev]),
+ ]);
+ ok(true,
+ "devicechange event IS fired when first audio device is added and " +
+ "gUM has NOT been in use");
+});
+
+</script>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_addAudioTrackToExistingVideoStream.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_addAudioTrackToExistingVideoStream.html
new file mode 100644
index 0000000000..b09d7ffeb5
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_addAudioTrackToExistingVideoStream.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1246310",
+ title: "Renegotiation: add audio track to existing video-only stream",
+ });
+
+ runNetworkTest(function (options) {
+ SimpleTest.requestCompleteLog();
+ const test = new PeerConnectionTest(options);
+ test.chain.replace("PC_LOCAL_GUM",
+ [
+ function PC_LOCAL_GUM_ATTACH_VIDEO_ONLY(test) {
+ var localConstraints = {audio: true, video: true};
+ test.setMediaConstraints([{video: true}], []);
+ return getUserMedia(localConstraints)
+ .then(s => test.originalGumStream = s)
+ .then(() => is(test.originalGumStream.getAudioTracks().length, 1,
+ "Should have 1 audio track"))
+ .then(() => is(test.originalGumStream.getVideoTracks().length, 1,
+ "Should have 1 video track"))
+ .then(() => test.pcLocal.attachLocalTrack(
+ test.originalGumStream.getVideoTracks()[0],
+ test.originalGumStream));
+ },
+ ]
+ );
+ addRenegotiation(test.chain,
+ [
+ function PC_LOCAL_ATTACH_SECOND_TRACK_AUDIO(test) {
+ test.setMediaConstraints([{audio: true, video: true}], []);
+ return test.pcLocal.attachLocalTrack(
+ test.originalGumStream.getAudioTracks()[0],
+ test.originalGumStream);
+ },
+ ],
+ [
+ function PC_CHECK_REMOTE_AUDIO_FLOW(test) {
+ return test.pcRemote.checkReceivingToneFrom(new AudioContext(), test.pcLocal);
+ }
+ ]
+ );
+
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_addDataChannel.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_addDataChannel.html
new file mode 100644
index 0000000000..c7536214e5
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_addDataChannel.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1017888",
+ title: "Renegotiation: add DataChannel"
+ });
+
+ runNetworkTest(function (options) {
+ const test = new PeerConnectionTest(options);
+ addRenegotiation(test.chain,
+ commandsCreateDataChannel,
+ commandsCheckDataChannel);
+
+ // Insert before the second PC_LOCAL_WAIT_FOR_MEDIA_FLOW
+ test.chain.insertBefore('PC_LOCAL_WAIT_FOR_MEDIA_FLOW',
+ commandsWaitForDataChannel,
+ false,
+ 1);
+
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ return test.run();
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_addDataChannelNoBundle.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_addDataChannelNoBundle.html
new file mode 100644
index 0000000000..6ad754336c
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_addDataChannelNoBundle.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1017888",
+ title: "Renegotiation: add DataChannel"
+ });
+
+ runNetworkTest(function (options) {
+ options = options || { };
+ options.bundle = false;
+ const test = new PeerConnectionTest(options);
+ addRenegotiation(test.chain,
+ commandsCreateDataChannel.concat(
+ [
+ function PC_LOCAL_EXPECT_ICE_CHECKING(test) {
+ test.pcLocal.expectIceChecking();
+ },
+ function PC_REMOTE_EXPECT_ICE_CHECKING(test) {
+ test.pcRemote.expectIceChecking();
+ },
+ ]
+ ),
+ commandsCheckDataChannel);
+
+ // Insert before the second PC_LOCAL_WAIT_FOR_MEDIA_FLOW
+ test.chain.insertBefore('PC_LOCAL_WAIT_FOR_MEDIA_FLOW',
+ commandsWaitForDataChannel,
+ false,
+ 1);
+
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ return test.run();
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_addSecondAudioStream.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_addSecondAudioStream.html
new file mode 100644
index 0000000000..61a0250887
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_addSecondAudioStream.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1017888",
+ title: "Renegotiation: add second audio stream"
+ });
+
+ runNetworkTest(function (options) {
+ const test = new PeerConnectionTest(options);
+ addRenegotiation(test.chain,
+ [
+ function PC_LOCAL_ADD_SECOND_STREAM(test) {
+ test.setMediaConstraints([{audio: true}, {audio: true}],
+ [{audio: true}]);
+ return test.pcLocal.getAllUserMediaAndAddStreams([{audio: true}]);
+ },
+ ],
+ [
+ function PC_REMOTE_CHECK_ADDED_TRACK(test) {
+ // We test both tracks to avoid an ordering problem
+ is(test.pcRemote._pc.getReceivers().length, 2,
+ "pcRemote should have two receivers");
+ return Promise.all(test.pcRemote._pc.getReceivers().map(r => {
+ const analyser = new AudioStreamAnalyser(
+ new AudioContext(), new MediaStream([r.track]));
+ const freq = analyser.binIndexForFrequency(TEST_AUDIO_FREQ);
+ return analyser.waitForAnalysisSuccess(arr => arr[freq] > 200);
+ }));
+ },
+ ]
+ );
+
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_addSecondAudioStreamNoBundle.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_addSecondAudioStreamNoBundle.html
new file mode 100644
index 0000000000..32d0564717
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_addSecondAudioStreamNoBundle.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1017888",
+ title: "Renegotiation: add second audio stream, no bundle"
+ });
+
+ runNetworkTest(function (options = {}) {
+ options.bundle = false;
+ const test = new PeerConnectionTest(options);
+ addRenegotiation(test.chain,
+ [
+ function PC_LOCAL_ADD_SECOND_STREAM(test) {
+ test.setMediaConstraints([{audio: true}, {audio: true}],
+ [{audio: true}]);
+ // Since this is a NoBundle variant, adding a track will cause us to
+ // go back to checking.
+ test.pcLocal.expectIceChecking();
+ return test.pcLocal.getAllUserMediaAndAddStreams([{audio: true}]);
+ },
+ function PC_REMOTE_EXPECT_ICE_CHECKING(test) {
+ test.pcRemote.expectIceChecking();
+ },
+ ],
+ [
+ function PC_REMOTE_CHECK_ADDED_TRACK(test) {
+ // We test both tracks to avoid an ordering problem
+ is(test.pcRemote._pc.getReceivers().length, 2,
+ "pcRemote should have two receivers");
+ return Promise.all(test.pcRemote._pc.getReceivers().map(r => {
+ const analyser = new AudioStreamAnalyser(
+ new AudioContext(), new MediaStream([r.track]));
+ const freq = analyser.binIndexForFrequency(TEST_AUDIO_FREQ);
+ return analyser.waitForAnalysisSuccess(arr => arr[freq] > 200);
+ }));
+ },
+ ]
+ );
+
+ // TODO(bug 1093835): figure out how to verify if media flows through the new stream
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_addSecondVideoStream.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_addSecondVideoStream.html
new file mode 100644
index 0000000000..1565958d01
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_addSecondVideoStream.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1017888",
+ title: "Renegotiation: add second video stream"
+ });
+
+ runNetworkTest(async function (options) {
+ // Use fake video here since the native fake device on linux doesn't
+ // change color as needed by checkVideoPlaying() below.
+ await pushPrefs(
+ ['media.video_loopback_dev', ''],
+ ['media.navigator.streams.fake', true]);
+ // [TODO] re-enable HW decoder after bug 1526207 is fixed.
+ if (navigator.userAgent.includes("Android")) {
+ await pushPrefs(["media.navigator.mediadatadecoder_vpx_enabled", false],
+ ["media.webrtc.hw.h264.enabled", false]);
+ }
+
+ const test = new PeerConnectionTest(options);
+ addRenegotiation(test.chain,
+ [
+ function PC_LOCAL_ADD_SECOND_STREAM(test) {
+ test.setMediaConstraints([{video: true}, {video: true}],
+ [{video: true}]);
+ return test.pcLocal.getAllUserMediaAndAddStreams([{video: true}]);
+ },
+ ],
+ [
+ function PC_REMOTE_CHECK_VIDEO_FLOW(test) {
+ const h = new VideoStreamHelper();
+ is(test.pcRemote.remoteMediaElements.length, 2,
+ "Should have two remote media elements after renegotiation");
+ return Promise.all(test.pcRemote.remoteMediaElements.map(video =>
+ h.checkVideoPlaying(video)));
+ },
+ ]
+ );
+
+ test.setMediaConstraints([{video: true, fake: true}], [{video: true}]);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_addSecondVideoStreamNoBundle.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_addSecondVideoStreamNoBundle.html
new file mode 100644
index 0000000000..2857100998
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_addSecondVideoStreamNoBundle.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1017888",
+ title: "Renegotiation: add second video stream, no bundle"
+ });
+
+ runNetworkTest(async function (options = {}) {
+ // Use fake video here since the native fake device on linux doesn't
+ // change color as needed by checkVideoPlaying() below.
+ await pushPrefs(
+ ['media.video_loopback_dev', ''],
+ ['media.navigator.streams.fake', true]);
+ // [TODO] re-enable HW decoder after bug 1526207 is fixed.
+ if (navigator.userAgent.includes("Android")) {
+ await pushPrefs(["media.navigator.mediadatadecoder_vpx_enabled", false],
+ ["media.webrtc.hw.h264.enabled", false]);
+ }
+
+ options.bundle = false;
+ const test = new PeerConnectionTest(options);
+ addRenegotiation(test.chain,
+ [
+ function PC_LOCAL_ADD_SECOND_STREAM(test) {
+ test.setMediaConstraints([{video: true}, {video: true}],
+ [{video: true}]);
+ // Since this is a NoBundle variant, adding a track will cause us to
+ // go back to checking.
+ test.pcLocal.expectIceChecking();
+ return test.pcLocal.getAllUserMediaAndAddStreams([{video: true}]);
+ },
+ function PC_REMOTE_EXPECT_ICE_CHECKING(test) {
+ test.pcRemote.expectIceChecking();
+ },
+ ],
+ [
+ function PC_REMOTE_CHECK_VIDEO_FLOW(test) {
+ const h = new VideoStreamHelper();
+ is(test.pcRemote.remoteMediaElements.length, 2,
+ "Should have two remote media elements after renegotiation");
+ return Promise.all(test.pcRemote.remoteMediaElements.map(video =>
+ h.checkVideoPlaying(video)));
+ },
+ ]
+ );
+
+ test.setMediaConstraints([{video: true}], [{video: true}]);
+ await test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_addtrack_removetrack_events.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_addtrack_removetrack_events.html
new file mode 100644
index 0000000000..ff9ca9a772
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_addtrack_removetrack_events.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+createHTML({
+ title: "MediaStream's 'addtrack' and 'removetrack' events with gUM",
+ bug: "1208328"
+});
+
+runNetworkTest(function (options) {
+ let test = new PeerConnectionTest(options);
+ let eventsPromise;
+ addRenegotiation(test.chain,
+ [
+ function PC_LOCAL_SWAP_VIDEO_TRACKS(test) {
+ return getUserMedia({video: true}).then(stream => {
+ var videoTransceiver = test.pcLocal._pc.getTransceivers()[1];
+ is(videoTransceiver.currentDirection, "sendonly",
+ "Video transceiver's current direction is sendonly");
+ is(videoTransceiver.direction, "sendrecv",
+ "Video transceiver's desired direction is sendrecv");
+
+ const localStream = test.pcLocal._pc.getLocalStreams()[0];
+ ok(localStream, "Should have local stream");
+
+ const remoteStream = test.pcRemote._pc.getRemoteStreams()[0];
+ ok(remoteStream, "Should have remote stream");
+
+ const newTrack = stream.getTracks()[0];
+
+ const videoSenderIndex =
+ test.pcLocal._pc.getSenders().findIndex(s => s.track.kind == "video");
+ isnot(videoSenderIndex, -1, "Should have video sender");
+
+ test.pcLocal.removeSender(videoSenderIndex);
+ is(videoTransceiver.direction, "recvonly",
+ "Video transceiver should be recvonly after removeTrack");
+ test.pcLocal.attachLocalTrack(stream.getTracks()[0], localStream);
+ is(videoTransceiver.direction, "recvonly",
+ "Video transceiver should be recvonly after addTrack");
+
+ eventsPromise = haveEvent(remoteStream, "addtrack",
+ wait(50000, new Error("No addtrack event for " + newTrack.id)))
+ .then(trackEvent => {
+ ok(trackEvent instanceof MediaStreamTrackEvent,
+ "Expected event to be instance of MediaStreamTrackEvent");
+ is(trackEvent.type, "addtrack",
+ "Expected addtrack event type");
+ is(trackEvent.track.readyState, "live",
+ "added track should be live");
+ })
+ .then(() => haveNoEvent(remoteStream, "addtrack"));
+ });
+ },
+ ],
+ [
+ function PC_REMOTE_CHECK_EVENTS(test) {
+ return eventsPromise;
+ },
+ ]
+ );
+
+ test.setMediaConstraints([{audio: true, video: true}], []);
+ return test.run();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_answererAddSecondAudioStream.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_answererAddSecondAudioStream.html
new file mode 100644
index 0000000000..d9b01bf722
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_answererAddSecondAudioStream.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1017888",
+ title: "Renegotiation: answerer adds second audio stream"
+ });
+
+ runNetworkTest(function (options) {
+ const test = new PeerConnectionTest(options);
+ addRenegotiationAnswerer(test.chain,
+ [
+ function PC_LOCAL_ADD_SECOND_STREAM(test) {
+ test.setMediaConstraints([{audio: true}, {audio: true}],
+ [{audio: true}]);
+ return test.pcLocal.getAllUserMediaAndAddStreams([{audio: true}]);
+ },
+ ]
+ );
+
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_audioChannels.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_audioChannels.html
new file mode 100644
index 0000000000..f6e77f8271
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_audioChannels.html
@@ -0,0 +1,102 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+
+createHTML({
+ bug: "1765005",
+ title: "Verify audio channel limits for each negotiated audio codec",
+});
+
+const matchesChannels = (sdp, codec, channels) =>
+ !!sdp.match(new RegExp(`a=rtpmap:\\d* ${codec}\/\\d*\/${channels}\r\n`, "g")) ||
+ (channels <= 1 &&
+ !!sdp.match(new RegExp(`a=rtpmap:\\d* ${codec}\/\\d*\r\n`, "g")));
+
+async function testAudioChannels(track, codec, channels, accepted, expectedChannels) {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+ try {
+ pc1.addTrack(track);
+ pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate);
+ pc2.onicecandidate = e => pc1.addIceCandidate(e.candidate);
+ await pc1.setLocalDescription();
+ await pc2.setRemoteDescription(pc1.localDescription);
+ let {type, sdp} = await pc2.createAnswer();
+ sdp = sdp.replace(new RegExp(`a=rtpmap:(\\d*) ${codec}\/(\\d*)\/?\\d*\r\n`, "g"),
+ `a=rtpmap:$1 ${codec}/$2/${channels}\r\n`);
+ const payloadType = Number(sdputils.findCodecId(sdp, codec));
+ sdp = sdputils.removeAllButPayloadType(sdp, payloadType);
+ ok(matchesChannels(sdp, codec, channels), "control");
+ await pc2.setLocalDescription({type, sdp});
+ is(matchesChannels(pc2.localDescription.sdp, codec, channels),
+ accepted,
+ `test pc2.localDescription`);
+ try {
+ await pc1.setRemoteDescription(pc2.localDescription);
+ ok(expectedChannels, "SRD should succeed iff we're expecting channels");
+ const [receiver] = pc2.getReceivers();
+ await new Promise(r => receiver.track.onunmute = r);
+ let stats = await receiver.getStats();
+ let inboundStat = [...stats.values()].find(({type}) => type == "inbound-rtp");
+ if (!inboundStat) {
+ info("work around bug 1765215"); // TODO bug 1765215
+ await new Promise(r => setTimeout(r, 200));
+ stats = await receiver.getStats();
+ inboundStat = [...stats.values()].find(({type}) => type == "inbound-rtp");
+ }
+ ok(inboundStat, "has inbound-rtp stats after track unmute (w/workaround)");
+ const codecStat = stats.get(inboundStat.codecId);
+ ok(codecStat.mimeType.includes(codec), "correct codec");
+ is(codecStat.payloadType, payloadType, "correct payloadType");
+ is(codecStat.channels, expectedChannels, "expected channels");
+ } catch (e) {
+ ok(!expectedChannels, "SRD should fail iff we're not expecting channels");
+ }
+ } finally {
+ pc1.close();
+ pc2.close();
+ }
+}
+
+runNetworkTest(async () => {
+ const [track] = (await navigator.mediaDevices.getUserMedia({audio: true}))
+ .getAudioTracks();
+ try {
+ for (let [codec, channels, accepted, expectedChannels] of [
+ ["opus", 2, true, 2],
+ ["opus", 1, true, 0],
+ ["opus", 1000, true, 0],
+ ["G722", 1, true, 1],
+ ["G722", 2, true, 0],
+ ["G722", 1000, true, 0],
+ ["PCMU", 1, true, 1],
+ ["PCMU", 2, false, 1],
+ ["PCMU", 1000, false, 1],
+ ["PCMA", 1, true, 1],
+ ["PCMA", 2, false, 1],
+ ["PCMA", 1000, false, 1]
+ ]) {
+ const testName = `${codec} with ${channels} channel(s) is ` +
+ `${accepted? "accepted" : "ignored"} and produces ` +
+ `${expectedChannels || "no"} channels`;
+ try {
+ info(`Testing that ${testName}`);
+ await testAudioChannels(track, codec, channels, accepted, expectedChannels);
+ } catch (e) {
+ ok(false, `Error testing that ${testName}: ${e}\n${e.stack}`);
+ }
+ }
+ } finally {
+ track.stop();
+ }
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_audioCodecs.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_audioCodecs.html
new file mode 100644
index 0000000000..8874436e3b
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_audioCodecs.html
@@ -0,0 +1,81 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1395853",
+ title: "Verify audio content over WebRTC for every audio codec",
+ });
+
+ // We match the format member against the sdp to figure out the payload type,
+ // So all other present codecs can be removed.
+ const codecs = [ "opus", "G722", "PCMU", "PCMA" ];
+
+ async function testAudioCodec(options = {}, codec) {
+ // sdputils checks for opus as part of its sdp sanity test
+ options.opus = codec == "opus";
+
+ let test = new PeerConnectionTest(options);
+ test.setMediaConstraints([{audio: true}], []);
+
+ test.chain.insertBefore("PC_LOCAL_SET_LOCAL_DESCRIPTION", [
+ function PC_LOCAL_FILTER_OUT_CODECS() {
+ let otherCodec = codecs.find(c => c != codec);
+ let otherId = sdputils.findCodecId(test.originalOffer.sdp, otherCodec);
+
+ let id = sdputils.findCodecId(test.originalOffer.sdp, codec);
+ test.originalOffer.sdp =
+ sdputils.removeAllButPayloadType(test.originalOffer.sdp, id);
+
+ ok(!test.originalOffer.sdp.match(new RegExp(`m=.*UDP/TLS/RTP/SAVPF.* ${otherId}[^0-9]`, "gi")),
+ `Other codec ${otherId} should be removed after filtering`);
+ ok(test.originalOffer.sdp.match(new RegExp(`m=.*UDP/TLS/RTP/SAVPF.* ${id}[^0-9]`, "gi")),
+ `Tested codec ${id} should remain after filtering`);
+
+ for (let c of codecs.filter(c => c != codec)) {
+ // Remove rtpmaps for the other codecs so sdp sanity tests pass.
+ let id = sdputils.findCodecId(test.originalOffer.sdp, c);
+ test.originalOffer.sdp =
+ sdputils.removeRtpMapForPayloadType(test.originalOffer.sdp, id);
+ }
+
+ ok(!test.originalOffer.sdp.match(new RegExp(`a=rtpmap:${otherId}.*\\r\\n`, "gi")),
+ `Rtpmap of other codec ${otherId} should be removed after filtering`);
+ ok(test.originalOffer.sdp.match(new RegExp(`a=rtpmap:${id}.*\\r\\n`, "gi")),
+ `Rtpmap of tested codec should remain after filtering`);
+ },
+ ]);
+
+ test.chain.append([
+ async function CHECK_AUDIO_FLOW() {
+ try {
+ await test.pcRemote.checkReceivingToneFrom(new AudioContext(), test.pcLocal);
+ ok(true, "input and output audio data matches");
+ } catch(e) {
+ ok(false, `No audio flow: ${e}`);
+ }
+ },
+ ]);
+
+ await test.run();
+ }
+
+ runNetworkTest(async (options) => {
+ for (let codec of codecs) {
+ info(`Testing audio for codec ${codec}`);
+ try {
+ await testAudioCodec(options, codec);
+ } catch(e) {
+ ok(false, `Error in test for codec ${codec}: ${e}\n${e.stack}`);
+ }
+ info(`Tested audio for codec ${codec}`);
+ }
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_audioContributingSources.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_audioContributingSources.html
new file mode 100644
index 0000000000..333b40a888
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_audioContributingSources.html
@@ -0,0 +1,144 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1363667",
+ title: "Test audio receiver getContributingSources"
+ });
+
+ // test_peerConnection_audioSynchronizationSources.html tests
+ // much of the functionality of getContributingSources as the implementation
+ // is shared.
+ var testGetContributingSources = async (test) => {
+ const remoteReceiver = test.pcRemote.getReceivers()[0];
+ const localReceiver = test.pcLocal.getReceivers()[0];
+
+ // Check that getContributingSources is empty as there is no MCU
+ is(remoteReceiver.getContributingSources().length, 0,
+ "remote contributing sources is empty");
+ is(localReceiver.getContributingSources().length, 0,
+ "local contributing sources is empty");
+ // Wait for the next JS event loop iteration, to clear the cache
+ await Promise.resolve().then();
+ // Insert new entries as if there were an MCU
+ const csrc0 = 124756;
+ const timestamp0 = performance.now() + performance.timeOrigin;
+ const rtpTimestamp0 = 11111;
+ const hasAudioLevel0 = true;
+ // Audio level as expected to be received in RTP
+ const audioLevel0 = 34;
+ // Audio level as expected to be returned
+ const expectedAudioLevel0 = 10 ** (-audioLevel0 / 20);
+
+ SpecialPowers.wrap(remoteReceiver).mozInsertAudioLevelForContributingSource(
+ csrc0,
+ timestamp0,
+ rtpTimestamp0,
+ hasAudioLevel0,
+ audioLevel0);
+
+ const csrc1 = 5786;
+ const timestamp1 = timestamp0 - 200;
+ const rtpTimestamp1 = 22222;
+ const hasAudioLevel1 = false;
+ const audioLevel1 = 0;
+
+ SpecialPowers.wrap(remoteReceiver).mozInsertAudioLevelForContributingSource(
+ csrc1,
+ timestamp1,
+ rtpTimestamp1,
+ hasAudioLevel1,
+ audioLevel1);
+
+ const csrc2 = 93487;
+ const timestamp2 = timestamp0 - 200;
+ const rtpTimestamp2 = 333333;
+ const hasAudioLevel2 = true;
+ const audioLevel2 = 127;
+
+ SpecialPowers.wrap(remoteReceiver).mozInsertAudioLevelForContributingSource(
+ csrc2,
+ timestamp2,
+ rtpTimestamp2,
+ hasAudioLevel2,
+ audioLevel2);
+
+ const contributingSources = remoteReceiver.getContributingSources();
+ is(contributingSources.length, 3,
+ "Expected number of contributing sources");
+
+ // Check that both inserted were returned
+ const source0 = contributingSources.find(c => c.source == csrc0);
+ ok(source0, "first csrc was found");
+
+ const source1 = contributingSources.find(c => c.source == csrc1);
+ ok(source1, "second csrsc was found");
+
+ // Add a small margin of error in the timestamps
+ const compareTimestamps = (ts1, ts2) => Math.abs(ts1 - ts2) < 100;
+
+ // Check the CSRC with audioLevel
+ const isWithinErr = Math.abs(source0.audioLevel - expectedAudioLevel0)
+ < expectedAudioLevel0 / 50;
+ ok(isWithinErr,
+ `Contributing source has correct audio level. (${source0.audioLevel})`);
+ ok(compareTimestamps(source0.timestamp, timestamp0),
+ `Contributing source has correct timestamp (got ${source0.timestamp}), expected ${timestamp0}`);
+ is(source0.rtpTimestamp, rtpTimestamp0,
+ `Contributing source has correct RTP timestamp (${source0.rtpTimestamp}`);
+ // Check the CSRC without audioLevel
+ is(source1.audioLevel, undefined,
+ `Contributing source has no audio level. (${source1.audioLevel})`);
+ ok(compareTimestamps(source1.timestamp, timestamp1),
+ `Contributing source has correct timestamp (got ${source1.timestamp}, expected ${timestamp1})`);
+ is(source1.rtpTimestamp, rtpTimestamp1,
+ `Contributing source has correct RTP timestamp (${source1.rtpTimestamp}`);
+ // Check that a received RTP audio level 127 is exactly 0
+ const source2 = contributingSources.find(c => c.source == csrc2);
+ ok(source2, "third csrc was found");
+ is(source2.audioLevel, 0,
+ `Contributing source has audio level of 0 when RTP audio level is 127`);
+ // Check caching
+ is(JSON.stringify(contributingSources),
+ JSON.stringify(remoteReceiver.getContributingSources()),
+ "getContributingSources is cached");
+ // Check that sources are sorted in descending order by time stamp
+ const timestamp3 = performance.now() + performance.timeOrigin;
+ const rtpTimestamp3 = 44444;
+ // Larger offsets are further back in time
+ const testOffsets = [3, 7, 5, 6, 1, 4];
+ for (const offset of testOffsets) {
+ SpecialPowers.wrap(localReceiver).mozInsertAudioLevelForContributingSource(
+ offset, // Using offset for SSRC for convenience
+ timestamp3 - offset,
+ rtpTimestamp3,
+ true,
+ offset);
+ }
+ const sources = localReceiver.getContributingSources();
+ const sourceOffsets = sources.map(s => s.source);
+ is(JSON.stringify(sourceOffsets),
+ JSON.stringify([...testOffsets].sort((a, b) => a - b)),
+ `Contributing sources are sorted in descending order by timestamp:`
+ + ` ${JSON.stringify(sources)}`);
+ };
+
+ var test;
+ runNetworkTest(async function(options) {
+ test = new PeerConnectionTest(options);
+ test.chain.insertAfter("PC_REMOTE_WAIT_FOR_MEDIA_FLOW",
+ [testGetContributingSources]);
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ test.pcLocal.audioElementsOnly = true;
+ await pushPrefs(["privacy.reduceTimerPrecision", false]);
+ await test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_audioRenegotiationInactiveAnswer.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_audioRenegotiationInactiveAnswer.html
new file mode 100644
index 0000000000..6d3a23b57a
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_audioRenegotiationInactiveAnswer.html
@@ -0,0 +1,69 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="sdpUtils.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1213773",
+ title: "Renegotiation: answerer uses a=inactive for audio"
+ });
+
+ runNetworkTest(function (options) {
+ const helper = new AudioStreamHelper();
+
+ const test = new PeerConnectionTest(options);
+ test.setMediaConstraints([{audio: true}], []);
+ let haveFirstUnmuteEvent;
+
+ test.chain.insertBefore("PC_REMOTE_SET_LOCAL_DESCRIPTION", [
+ function PC_REMOTE_SETUP_ONUNMUTE_1() {
+ haveFirstUnmuteEvent = haveEvent(test.pcRemote._pc.getReceivers()[0].track, "unmute");
+ }
+ ]);
+
+ test.chain.append([
+ function PC_REMOTE_CHECK_AUDIO_UNMUTED() {
+ return haveFirstUnmuteEvent;
+ },
+ function PC_REMOTE_CHECK_AUDIO_FLOWING() {
+ return helper.checkAudioFlowing(test.pcRemote._pc.getRemoteStreams()[0]);
+ }
+ ]);
+
+ addRenegotiation(test.chain, []);
+
+ test.chain.insertAfter("PC_LOCAL_GET_ANSWER", [
+ function PC_LOCAL_REWRITE_REMOTE_SDP_INACTIVE(test) {
+ test._remote_answer.sdp =
+ sdputils.setAllMsectionsInactive(test._remote_answer.sdp);
+ }
+ ], false, 1);
+
+ test.chain.append([
+ function PC_REMOTE_CHECK_AUDIO_NOT_FLOWING() {
+ return helper.checkAudioNotFlowing(test.pcRemote._pc.getRemoteStreams()[0]);
+ }
+ ]);
+
+ test.chain.remove("PC_REMOTE_CHECK_STATS", 1);
+ test.chain.remove("PC_LOCAL_CHECK_STATS", 1);
+ test.chain.remove("PC_REMOTE_WAIT_FOR_MEDIA_FLOW", 1);
+
+ addRenegotiation(test.chain, []);
+
+ test.chain.append([
+ function PC_REMOTE_CHECK_AUDIO_FLOWING_2() {
+ return helper.checkAudioFlowing(test.pcRemote._pc.getRemoteStreams()[0]);
+ }
+ ]);
+
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_audioSynchronizationSources.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_audioSynchronizationSources.html
new file mode 100644
index 0000000000..32603b2e40
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_audioSynchronizationSources.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1363667",
+ title: "Test audio receiver getSynchronizationSources"
+ });
+
+ var waitForSyncSources = async (test) => {
+ let receivers = [...test.pcRemote.getReceivers(),
+ ...test.pcLocal.getReceivers()];
+ is(receivers.length, 2, "Expected number of receivers");
+ // Wait for sync sources
+ while (true) {
+ if (receivers[0].getSynchronizationSources().length &&
+ receivers[1].getSynchronizationSources().length) {
+ break;
+ }
+ await wait(250);
+ }
+ };
+
+ var testGetSynchronizationSources = async (test) => {
+ await waitForSyncSources(test);
+ let receivers = [...test.pcRemote.getReceivers(),
+ ...test.pcLocal.getReceivers()];
+ is(receivers.length, 2,
+ `Expected number of receivers is 2. (${receivers.length})`);
+ for (let recv of receivers) {
+ let syncSources = recv.getSynchronizationSources();
+ ok(syncSources,
+ "Receiver has Synchronization sources " + JSON.stringify(syncSources));
+ is(syncSources.length, 1, "Each receiver has only a single sync source");
+ let source = recv.getSynchronizationSources()[0];
+ ok(source.audioLevel !== null,
+ `Synchronization source has audio level. (${source.audioLevel})`);
+ ok(source.audioLevel >= 0.0,
+ `Synchronization source audio level >= 0.0 (${source.audioLevel})`);
+ ok(source.audioLevel <= 1.0,
+ `Synchronization source audio level <= 1.0 (${source.audioLevel})`);
+ ok(source.timestamp,
+ `Synchronization source has timestamp (${source.timestamp})`);
+ const ageSeconds =
+ (window.performance.now() + window.performance.timeOrigin -
+ source.timestamp) / 1000;
+ ok(ageSeconds >= 0,
+ `Synchronization source timestamp is in the past`);
+ ok(ageSeconds < 2.5,
+ `Synchronization source timestamp is close to now`);
+ is(source.voiceActivityFlag, undefined,
+ "Synchronization source unsupported voiceActivity is undefined");
+ }
+ };
+
+ var testSynchronizationSourceCached = async (test) => {
+ await waitForSyncSources(test);
+ let receivers = [...test.pcRemote.getReceivers(),
+ ...test.pcLocal.getReceivers()];
+ is(receivers.length, 2,
+ `Expected number of receivers is 2. (${receivers.length})`);
+ let sourceSets = [[],[]];
+ for (let sourceSet of sourceSets) {
+ for (let recv of receivers) {
+ let sources = recv.getSynchronizationSources();
+ is(sources.length, 1,
+ `Expected number of sources is 1. (${sources.length})`);
+ sourceSet.push(sources);
+ }
+ // Busy wait 1s before trying again
+ let endTime = performance.now() + 1000;
+ while (performance.now() < endTime) {};
+ }
+ is(JSON.stringify(sourceSets[0]), JSON.stringify(sourceSets[1]),
+ "Subsequent getSynchronizationSources calls are cached.");
+ };
+
+ var test;
+ runNetworkTest(function(options) {
+ test = new PeerConnectionTest(options);
+ test.chain.insertAfter("PC_REMOTE_WAIT_FOR_MEDIA_FLOW",
+ [testGetSynchronizationSources,
+ testSynchronizationSourceCached]);
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ test.pcLocal.audioElementsOnly = true;
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_audioSynchronizationSourcesUnidirectional.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_audioSynchronizationSourcesUnidirectional.html
new file mode 100644
index 0000000000..6d66614e91
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_audioSynchronizationSourcesUnidirectional.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1439001",
+ title: "Test audio unidirectional getSynchronizationSources"
+ });
+
+ var waitForSyncSources = async (test) => {
+ let receiver = test.pcRemote.getReceivers()[0];
+ ok(receiver, "Remote has a receiver");
+ // Wait for remote sync source
+ while (!receiver.getSynchronizationSources().length) {
+ await wait(250);
+ }
+ is(receiver.getSynchronizationSources().length, 1,
+ "Remote receiver has a synchronization source");
+ // Make sure local has no sync source
+ is(test.pcLocal.getReceivers()[0].getSynchronizationSources().length, 0,
+ "Local receiver has no synchronization source");
+ };
+ /*
+ * Test to make sure that in unidirectional calls, the receiving end has
+ * synchronization sources with audio levels, and the sending end has none.
+ */
+ var testGetSynchronizationSourcesUnidirectional = async (test) => {
+ await waitForSyncSources(test);
+ let receiver = test.pcRemote.getReceivers()[0];
+ let syncSources = receiver.getSynchronizationSources();
+ ok(syncSources.length,
+ "Receiver has Synchronization sources " + JSON.stringify(syncSources));
+ is(syncSources.length, 1, "Receiver has only a single sync source");
+ let syncSource = syncSources[0];
+ ok(syncSource.audioLevel !== undefined, "SynchronizationSource has audioLevel");
+ };
+
+ var test;
+ runNetworkTest(function(options) {
+ test = new PeerConnectionTest(options);
+ test.chain.insertAfter("PC_REMOTE_WAIT_FOR_MEDIA_FLOW",
+ [testGetSynchronizationSourcesUnidirectional]);
+ test.setMediaConstraints([{audio: true}], []);
+ test.pcLocal.audioElementsOnly = true;
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudio.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudio.html
new file mode 100644
index 0000000000..5fd10a67f9
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudio.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "796892",
+ title: "Basic audio-only peer connection"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ test = new PeerConnectionTest(options);
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ // pc.js uses video elements by default, we want to test audio elements here
+ test.pcLocal.audioElementsOnly = true;
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioDynamicPtMissingRtpmap.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioDynamicPtMissingRtpmap.html
new file mode 100644
index 0000000000..a076bf80f1
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioDynamicPtMissingRtpmap.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1246011",
+ title: "Offer with dynamic PT but missing rtpmap"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ options = options || { };
+ // we want Opus to get selected and 101 to be ignored
+ options.opus = true;
+ test = new PeerConnectionTest(options);
+ test.chain.insertBefore("PC_REMOTE_GET_OFFER", [
+ function PC_LOCAL_REDUCE_MLINE_REMOVE_RTPMAPS(test) {
+ test.originalOffer.sdp =
+ sdputils.reduceAudioMLineToDynamicPtAndOpus(test.originalOffer.sdp);
+ test.originalOffer.sdp =
+ sdputils.removeAllRtpMaps(test.originalOffer.sdp);
+ test.originalOffer.sdp = test.originalOffer.sdp + "a=rtpmap:109 opus/48000/2\r\n";
+ info("SDP with dyn PT and no Rtpmap: " + JSON.stringify(test.originalOffer));
+ }
+ ]);
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelay.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelay.html
new file mode 100644
index 0000000000..180abc075a
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelay.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="nonTrickleIce.js"></script>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ bug: "1231975",
+ title: "Basic audio-only peer connection with port dependent NAT, for verifying UDP relay"
+});
+
+// This test uses the NAT simulator, which doesn't work in https, so we turn
+// on getUserMedia in http, which requires a reload.
+if (!("mediaDevices" in navigator)) {
+ SpecialPowers.pushPrefEnv({set: [['media.devices.insecure.enabled', true]]},
+ () => location.reload());
+} else {
+ runNetworkTest(async (options = {}) => {
+ await pushPrefs(
+ ['media.peerconnection.ice.obfuscate_host_addresses', false],
+ ['media.peerconnection.nat_simulator.filtering_type', 'PORT_DEPENDENT'],
+ ['media.peerconnection.nat_simulator.mapping_type', 'PORT_DEPENDENT'],
+ ['media.peerconnection.nat_simulator.block_tcp', true],
+ ['media.getusermedia.insecure.enabled', true]);
+ options.expectedLocalCandidateType = "srflx";
+ options.expectedRemoteCandidateType = "relay";
+ // If both have TURN, it is a toss-up which one will end up using a
+ // relay.
+ options.turn_disabled_local = true;
+ const test = new PeerConnectionTest(options);
+ // Make sure we don't end up choosing the wrong thing due to delays in
+ // trickle. Once we are willing to accept trickle after ICE success, we
+ // can maybe wait a bit to allow things to stabilize.
+ // TODO(bug 1238249)
+ makeOffererNonTrickle(test.chain);
+ makeAnswererNonTrickle(test.chain);
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ await test.run();
+ }, { useIceServer: true });
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTCP.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTCP.html
new file mode 100644
index 0000000000..7bb51764bd
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTCP.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ bug: "1231975",
+ title: "Basic audio-only peer connection with port dependent NAT that blocks UDP"
+});
+
+// This test uses the NAT simulator, which doesn't work in https, so we turn
+// on getUserMedia in http, which requires a reload.
+if (!("mediaDevices" in navigator)) {
+ SpecialPowers.pushPrefEnv({set: [['media.devices.insecure.enabled', true]]},
+ () => location.reload());
+} else {
+ runNetworkTest(async (options = {}) => {
+ await pushPrefs(
+ ['media.peerconnection.ice.obfuscate_host_addresses', false],
+ ['media.peerconnection.nat_simulator.filtering_type', 'PORT_DEPENDENT'],
+ ['media.peerconnection.nat_simulator.mapping_type', 'PORT_DEPENDENT'],
+ ['media.peerconnection.nat_simulator.block_udp', true],
+ ['media.peerconnection.nat_simulator.block_tcp', false],
+ ['media.peerconnection.nat_simulator.block_tls', true],
+ ['media.peerconnection.ice.loopback', true],
+ ['media.getusermedia.insecure.enabled', true]);
+ options.expectedLocalCandidateType = "relay-tcp";
+ options.expectedRemoteCandidateType = "relay-tcp";
+ // No reason to wait for gathering to complete like the other NAT tests,
+ // since relayed-tcp is the only thing that can work.
+ const test = new PeerConnectionTest(options);
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ await test.run();
+ }, { useIceServer: true });
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTCPWithStun300.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTCPWithStun300.html
new file mode 100644
index 0000000000..43ea6aaea7
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTCPWithStun300.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="nonTrickleIce.js"></script>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ bug: "857668",
+ title: "Basic audio-only peer connection with UDP-blocking NAT, for verifying TCP relay with STUN 300 responses"
+});
+
+// This test uses the NAT simulator, which doesn't work in https, so we turn
+// on getUserMedia in http, which requires a reload.
+if (!("mediaDevices" in navigator)) {
+ SpecialPowers.pushPrefEnv({set: [['media.devices.insecure.enabled', true]]},
+ () => location.reload());
+} else {
+ runNetworkTest(async (options = {}) => {
+ await pushPrefs(
+ ['media.peerconnection.ice.obfuscate_host_addresses', false],
+ ['media.peerconnection.nat_simulator.filtering_type', 'ENDPOINT_INDEPENDENT'],
+ ['media.peerconnection.nat_simulator.mapping_type', 'ENDPOINT_INDEPENDENT'],
+ ['media.peerconnection.nat_simulator.block_udp', true],
+ ['media.peerconnection.nat_simulator.block_tcp', false],
+ ['media.peerconnection.ice.loopback', true],
+ ['media.getusermedia.insecure.enabled', true]);
+ options.expectedLocalCandidateType = "relay-tcp";
+ options.expectedRemoteCandidateType = "relay-tcp";
+ const turnServer = iceServersArray.find(server => "username" in server);
+ const turnRedirectPort = turnServer.turn_redirect_port;
+ const turnHostname = getTurnHostname(turnServer.urls[0]);
+ turnServer.urls = [`turn:${turnHostname}:${turnRedirectPort}?transport=tcp`];
+ // Override turn servers so we can test simulated redirects
+ options.config_local = {iceServers: [turnServer]};
+ options.config_remote = {iceServers: [turnServer]};
+ const test = new PeerConnectionTest(options);
+ // Make sure we don't end up choosing the wrong thing due to delays in
+ // trickle. Once we are willing to accept trickle after ICE success, we
+ // can maybe wait a bit to allow things to stabilize.
+ // TODO(bug 1238249)
+ makeOffererNonTrickle(test.chain);
+ makeAnswererNonTrickle(test.chain);
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ await test.run();
+ await SpecialPowers.popPrefEnv();
+ }, { useIceServer: true });
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTLS.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTLS.html
new file mode 100644
index 0000000000..7446401f87
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTLS.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ bug: "1231975",
+ title: "Basic audio-only peer connection with port dependent NAT that blocks STUN"
+});
+
+// This test uses the NAT simulator, which doesn't work in https, so we turn
+// on getUserMedia in http, which requires a reload.
+if (!("mediaDevices" in navigator)) {
+ SpecialPowers.pushPrefEnv({set: [['media.devices.insecure.enabled', true]]},
+ () => location.reload());
+} else {
+ runNetworkTest(async (options = {}) => {
+ await pushPrefs(
+ ['media.peerconnection.ice.obfuscate_host_addresses', false],
+ ['media.peerconnection.nat_simulator.filtering_type', 'PORT_DEPENDENT'],
+ ['media.peerconnection.nat_simulator.mapping_type', 'PORT_DEPENDENT'],
+ ['media.peerconnection.nat_simulator.block_udp', true],
+ ['media.peerconnection.nat_simulator.block_tcp', true],
+ ['media.peerconnection.ice.loopback', true],
+ ['media.getusermedia.insecure.enabled', true]);
+ options.expectedLocalCandidateType = "relay-tls";
+ options.expectedRemoteCandidateType = "relay-tls";
+ // No reason to wait for gathering to complete like the other NAT tests,
+ // since relayed-tcp is the only thing that can work.
+ const test = new PeerConnectionTest(options);
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ await test.run();
+ }, { useIceServer: true });
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayWithStun300.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayWithStun300.html
new file mode 100644
index 0000000000..286e67bc2f
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayWithStun300.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="nonTrickleIce.js"></script>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ bug: "857668",
+ title: "Basic audio-only peer connection with port dependent NAT, for verifying UDP relay with STUN 300 responses"
+});
+
+// This test uses the NAT simulator, which doesn't work in https, so we turn
+// on getUserMedia in http, which requires a reload.
+if (!("mediaDevices" in navigator)) {
+ SpecialPowers.pushPrefEnv({set: [['media.devices.insecure.enabled', true]]},
+ () => location.reload());
+} else {
+ runNetworkTest(async (options = {}) => {
+ await pushPrefs(
+ ['media.peerconnection.ice.obfuscate_host_addresses', false],
+ ['media.peerconnection.nat_simulator.filtering_type', 'PORT_DEPENDENT'],
+ ['media.peerconnection.nat_simulator.mapping_type', 'PORT_DEPENDENT'],
+ ['media.peerconnection.nat_simulator.block_tcp', true],
+ ['media.getusermedia.insecure.enabled', true]);
+ options.expectedLocalCandidateType = "srflx";
+ options.expectedRemoteCandidateType = "relay";
+ const turnServer = iceServersArray.find(server => "username" in server);
+ const turnRedirectPort = turnServer.turn_redirect_port;
+ const turnHostname = getTurnHostname(turnServer.urls[0]);
+ turnServer.urls = [`turn:${turnHostname}:${turnRedirectPort}`];
+ // Override turn servers so we can test redirects
+ options.config_remote = {iceServers: [turnServer]};
+ // If both have TURN, it is a toss-up which one will end up using a
+ // relay, so we disable TURN for one side.
+ options.turn_disabled_local = true;
+ const test = new PeerConnectionTest(options);
+ // Make sure we don't end up choosing the wrong thing due to delays in
+ // trickle. Once we are willing to accept trickle after ICE success, we
+ // can maybe wait a bit to allow things to stabilize.
+ // TODO(bug 1238249)
+ makeOffererNonTrickle(test.chain);
+ makeAnswererNonTrickle(test.chain);
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ await test.run();
+ }, { useIceServer: true });
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATSrflx.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATSrflx.html
new file mode 100644
index 0000000000..78fa8bcb2c
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATSrflx.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="nonTrickleIce.js"></script>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ bug: "1231975",
+ title: "Basic audio-only peer connection with endpoint independent NAT, for verifying UDP srflx"
+});
+
+// This test uses the NAT simulator, which doesn't work in https, so we turn
+// on getUserMedia in http, which requires a reload.
+if (!("mediaDevices" in navigator)) {
+ SpecialPowers.pushPrefEnv({set: [['media.devices.insecure.enabled', true]]},
+ () => location.reload());
+} else {
+ runNetworkTest(async (options = {}) => {
+ await pushPrefs(
+ ['media.peerconnection.ice.obfuscate_host_addresses', false],
+ ['media.peerconnection.nat_simulator.filtering_type', 'ENDPOINT_INDEPENDENT'],
+ ['media.peerconnection.nat_simulator.mapping_type', 'ENDPOINT_INDEPENDENT'],
+ ['media.peerconnection.nat_simulator.block_tcp', true],
+ ['media.getusermedia.insecure.enabled', true]);
+ options.expectedLocalCandidateType = "srflx";
+ options.expectedRemoteCandidateType = "srflx";
+ const test = new PeerConnectionTest(options);
+ // Make sure we don't end up choosing the wrong thing due to delays in
+ // trickle. Once we are willing to accept trickle after ICE success, we
+ // can maybe wait a bit to allow things to stabilize.
+ // TODO(bug 1238249)
+ makeOffererNonTrickle(test.chain);
+ makeAnswererNonTrickle(test.chain);
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ await test.run();
+ }, { useIceServer: true });
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNoisyUDPBlock.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNoisyUDPBlock.html
new file mode 100644
index 0000000000..297121cd94
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNoisyUDPBlock.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ bug: "1231975",
+ title: "Basic audio-only peer connection where UDP sockets return errors on send"
+});
+
+// This test uses the NAT simulator, which doesn't work in https, so we turn
+// on getUserMedia in http, which requires a reload.
+if (!("mediaDevices" in navigator)) {
+ SpecialPowers.pushPrefEnv({set: [['media.devices.insecure.enabled', true]]},
+ () => location.reload());
+} else {
+ runNetworkTest(async (options = {}) => {
+ await pushPrefs(
+ ['media.peerconnection.ice.obfuscate_host_addresses', false],
+ ['media.peerconnection.nat_simulator.filtering_type', 'PORT_DEPENDENT'],
+ ['media.peerconnection.nat_simulator.mapping_type', 'PORT_DEPENDENT'],
+ ['media.peerconnection.nat_simulator.block_udp', true],
+ ['media.peerconnection.nat_simulator.error_code_for_drop', 3 /*R_INTERNAL*/],
+ ['media.peerconnection.nat_simulator.block_tls', true],
+ ['media.getusermedia.insecure.enabled', true]);
+ options.expectedLocalCandidateType = "relay-tcp";
+ options.expectedRemoteCandidateType = "relay-tcp";
+ // No reason to wait for gathering to complete like the other NAT tests,
+ // since relayed-tcp is the only thing that can work.
+ const test = new PeerConnectionTest(options);
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ await test.run();
+ }, { useIceServer: true });
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioPcmaPcmuOnly.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioPcmaPcmuOnly.html
new file mode 100644
index 0000000000..f0fe721b8e
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioPcmaPcmuOnly.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1221837",
+ title: "Only offer PCMA and PMCU in mline (no rtpmaps)"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ options = options || { };
+ options.opus = false;
+ test = new PeerConnectionTest(options);
+ test.chain.insertBefore("PC_REMOTE_GET_OFFER", [
+ function PC_LOCAL_REDUCE_MLINE_REMOVE_RTPMAPS(test) {
+ test.originalOffer.sdp =
+ sdputils.reduceAudioMLineToPcmuPcma(test.originalOffer.sdp);
+ test.originalOffer.sdp =
+ sdputils.removeAllRtpMaps(test.originalOffer.sdp);
+ info("SDP without Rtpmaps: " + JSON.stringify(test.originalOffer));
+ }
+ ]);
+ test.chain.insertAfter("PC_REMOTE_SANE_LOCAL_SDP", [
+ function PC_REMOTE_VERIFY_PCMU(test) {
+ ok(test._remote_answer.sdp.includes("a=rtpmap:0 PCMU/8000"), "PCMU codec is present in SDP");
+ }
+ ]);
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioRelayPolicy.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioRelayPolicy.html
new file mode 100644
index 0000000000..ced57ff8a3
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioRelayPolicy.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ bug: "1663746",
+ title: "Basic tests for relay ice policy"
+});
+
+runNetworkTest(async () => {
+ await pushPrefs(
+ // Enable mDNS, since there are some checks we want to run with that
+ ['media.peerconnection.ice.obfuscate_host_addresses', true]);
+
+ const offerer = new RTCPeerConnection({iceServers: iceServersArray, iceTransportPolicy: 'relay'});
+ const answerer = new RTCPeerConnection({iceServers: iceServersArray});
+
+ offerer.onicecandidate = e => {
+ if (e.candidate) {
+ ok(!e.candidate.candidate.includes(' host '), 'IceTransportPolicy \"relay\" should prevent the advertisement of host candidates');
+ ok(!e.candidate.candidate.includes(' srflx '), 'IceTransportPolicy \"relay\" should prevent the advertisement of srflx candidates');
+ }
+ answerer.addIceCandidate(e.candidate);
+ };
+
+ answerer.onicecandidate = e => {
+ if (e.candidate && e.candidate.candidate.includes(' host ')) {
+ ok(e.candidate.candidate.includes('.local'), 'When obfuscate_host_addresses is true, we expect host candidates to use mDNS');
+ }
+ offerer.addIceCandidate(e.candidate);
+ };
+
+ const offererConnected = new Promise(r => {
+ offerer.oniceconnectionstatechange = () => {
+ if (offerer.iceConnectionState == 'connected') {
+ r();
+ }
+ };
+ });
+
+ const answererConnected = new Promise(r => {
+ answerer.oniceconnectionstatechange = () => {
+ if (answerer.iceConnectionState == 'connected') {
+ r();
+ }
+ };
+ });
+
+ const offer = await offerer.createOffer({offerToReceiveAudio: true});
+ await Promise.all([offerer.setLocalDescription(offer), answerer.setRemoteDescription(offer)]);
+ const answer = await answerer.createAnswer();
+ await Promise.all([answerer.setLocalDescription(answer), offerer.setRemoteDescription(answer)]);
+
+ info('Waiting for ICE to connect');
+ await Promise.all([offererConnected, answererConnected]);
+
+ const offererStats = await offerer.getStats();
+ const localCandidates = [...offererStats.values()].filter(stat => stat.type == 'local-candidate');
+ const remoteCandidates = [...offererStats.values()].filter(stat => stat.type == 'remote-candidate');
+ isnot(localCandidates, []);
+ isnot(remoteCandidates, []);
+
+ const localNonRelayCandidates =
+ localCandidates.filter(cand => cand.candidateType != 'relay');
+ is(localNonRelayCandidates.length, 0, `There should only be local relay candidates, because we are using the "relay" IceTransportPolicy, but we got ${JSON.stringify(localNonRelayCandidates)}`);
+
+ const remoteHostCandidates =
+ remoteCandidates.filter(cand => cand.candidateType == 'host');
+ is(remoteHostCandidates.length, 0, `There should be no remote host candidates in the stats, because mDNS resolution should have been disabled by the "relay" IceTransportPolicy, but we got ${JSON.stringify(remoteHostCandidates)}`);
+
+ offerer.close();
+ answerer.close();
+
+ await SpecialPowers.popPrefEnv();
+}, { useIceServer: true });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioRequireEOC.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioRequireEOC.html
new file mode 100644
index 0000000000..afad4550d4
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioRequireEOC.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1167443",
+ title: "Basic audio-only peer connection which waits for end-of-candidates"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ test = new PeerConnectionTest(options);
+ test.chain.replace("PC_LOCAL_VERIFY_SDP_AFTER_END_OF_TRICKLE", [
+ function PC_LOCAL_REQUIRE_SDP_AFTER_END_OF_TRICKLE(test) {
+ return test.pcLocal.endOfTrickleSdp.then(sdp =>
+ sdputils.checkSdpAfterEndOfTrickle(sdp, test.testOptions, test.pcLocal.label));
+ }
+ ]);
+ test.chain.replace("PC_REMOTE_VERIFY_SDP_AFTER_END_OF_TRICKLE", [
+ function PC_REMOTE_REQUIRE_SDP_AFTER_END_OF_TRICKLE(test) {
+ return test.pcRemote.endOfTrickleSdp.then(sdp =>
+ sdputils.checkSdpAfterEndOfTrickle(sdp, test.testOptions, test.pcRemote.label));
+ }
+ ]);
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVerifyRtpHeaderExtensions.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVerifyRtpHeaderExtensions.html
new file mode 100644
index 0000000000..f28a990bd2
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVerifyRtpHeaderExtensions.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="parser_rtp.js"></script>
+ <script type="application/javascript" src="sdpUtils.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1416932",
+ title: "Basic audio-only peer connection and verify rtp header extensions"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ test = new PeerConnectionTest(options);
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ // pc.js uses video elements by default, we want to test audio elements here
+ test.pcLocal.audioElementsOnly = true;
+
+ let getRtpPacket = (pc) => {
+ // we only examine received packets
+ let sending = false;
+ pc.mozEnablePacketDump(0, "rtp", sending);
+ return new Promise((res, rej) =>
+ pc.mozSetPacketCallback((...args) => {
+ res([...args]);
+ pc.mozSetPacketCallback(() => {});
+ pc.mozDisablePacketDump(0, "rtp", sending);
+ })
+ );
+ }
+
+ const pc = SpecialPowers.wrap(test.pcRemote._pc);
+ const haveFirstPacket = getRtpPacket(pc);
+
+ test.chain.insertBefore('PC_REMOTE_WAIT_FOR_MEDIA_FLOW', [
+ async function PC_REMOTE_CHECK_RTP_HEADER_EXTS_AGAINST_SDP() {
+
+ const sdpExtmapIds = sdputils.findExtmapIds(test.originalAnswer.sdp);
+
+ const [level, type, sending, data] = await haveFirstPacket;
+ const extensions = ParseRtpPacket(data).header.extensions;
+
+ // make sure we got the same number of rtp header extensions in
+ // the received packet as were negotiated in the sdp. Then
+ // check to make sure each of the received extension ids were in
+ // the sdp.
+ is(sdpExtmapIds.length, extensions.length, "number of received ids match sdp ids");
+ // note, we are comparing a number (from the parsed rtp packet)
+ // and a string (from the answer sdp)
+ ok(extensions.every((ext) => sdpExtmapIds.includes(""+ext.id)), "extension id arrays equivalent");
+ }
+ ]);
+
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideo.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideo.html
new file mode 100644
index 0000000000..c2c2d43f09
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideo.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "796890",
+ title: "Basic audio/video (separate) peer connection"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ test = new PeerConnectionTest(options);
+ test.setMediaConstraints([{audio: true}, {video: true}],
+ [{audio: true}, {video: true}]);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoCombined.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoCombined.html
new file mode 100644
index 0000000000..02a561f9b8
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoCombined.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "796890",
+ title: "Basic audio/video (combined) peer connection"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ test = new PeerConnectionTest(options);
+ test.setMediaConstraints([{audio: true, video: true}],
+ [{audio: true, video: true}]);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoNoBundle.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoNoBundle.html
new file mode 100644
index 0000000000..cae7f6617f
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoNoBundle.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1016476",
+ title: "Basic audio/video peer connection with no Bundle"
+ });
+
+ runNetworkTest(options => {
+ options = options || { };
+ options.bundle = false;
+ var test = new PeerConnectionTest(options);
+ test.setMediaConstraints([{audio: true}, {video: true}],
+ [{audio: true}, {video: true}]);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoNoBundleNoRtcpMux.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoNoBundleNoRtcpMux.html
new file mode 100644
index 0000000000..49b0136752
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoNoBundleNoRtcpMux.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1167443",
+ title: "Basic audio & video call with disabled bundle and disabled RTCP-Mux"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ options = options || { };
+ options.bundle = false;
+ options.rtcpmux = false;
+ test = new PeerConnectionTest(options);
+ test.chain.replace("PC_LOCAL_VERIFY_SDP_AFTER_END_OF_TRICKLE", [
+ function PC_LOCAL_REQUIRE_SDP_AFTER_END_OF_TRICKLE(test) {
+ return test.pcLocal.endOfTrickleSdp .then(sdp =>
+ sdputils.checkSdpAfterEndOfTrickle(sdp, test.testOptions, test.pcLocal.label));
+ }
+ ]);
+ test.chain.replace("PC_REMOTE_VERIFY_SDP_AFTER_END_OF_TRICKLE", [
+ function PC_REMOTE_REQUIRE_SDP_AFTER_END_OF_TRICKLE(test) {
+ return test.pcRemote.endOfTrickleSdp .then(sdp =>
+ sdputils.checkSdpAfterEndOfTrickle(sdp, test.testOptions, test.pcRemote.label));
+ }
+ ]);
+ test.setMediaConstraints([{audio: true}, {video: true}],
+ [{audio: true}, {video: true}]);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoNoRtcpMux.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoNoRtcpMux.html
new file mode 100644
index 0000000000..48524604ba
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoNoRtcpMux.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1167443",
+ title: "Basic audio & video call with disabled RTCP-Mux"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ options = options || { };
+ options.rtcpmux = false;
+ test = new PeerConnectionTest(options);
+ test.chain.replace("PC_LOCAL_VERIFY_SDP_AFTER_END_OF_TRICKLE", [
+ function PC_LOCAL_REQUIRE_SDP_AFTER_END_OF_TRICKLE(test) {
+ return test.pcLocal.endOfTrickleSdp .then(sdp =>
+ sdputils.checkSdpAfterEndOfTrickle(sdp, test.testOptions, test.pcLocal.label));
+ }
+ ]);
+ test.chain.replace("PC_REMOTE_VERIFY_SDP_AFTER_END_OF_TRICKLE", [
+ function PC_REMOTE_REQUIRE_SDP_AFTER_END_OF_TRICKLE(test) {
+ return test.pcRemote.endOfTrickleSdp .then(sdp =>
+ sdputils.checkSdpAfterEndOfTrickle(sdp, test.testOptions, test.pcRemote.label));
+ }
+ ]);
+ test.setMediaConstraints([{audio: true}, {video: true}],
+ [{audio: true}, {video: true}]);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoTransceivers.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoTransceivers.html
new file mode 100644
index 0000000000..181d089d26
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoTransceivers.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1290948",
+ title: "Basic audio/video with addTransceiver"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ test = new PeerConnectionTest(options);
+ test.setMediaConstraints([{audio: true}, {video: true}],
+ [{audio: true}, {video: true}]);
+ test.chain.replace("PC_LOCAL_GUM",
+ [
+ function PC_LOCAL_GUM_TRANSCEIVERS(test) {
+ return test.pcLocal.getAllUserMediaAndAddTransceivers(test.pcLocal.constraints);
+ }
+ ]);
+
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoVerifyExtmap.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoVerifyExtmap.html
new file mode 100644
index 0000000000..e3da00bfa5
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoVerifyExtmap.html
@@ -0,0 +1,97 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1406529",
+ title: "Verify SDP extmap attribute for sendrecv connection"
+ });
+
+ var test;
+ runNetworkTest(async function (options) {
+ await pushPrefs(["media.navigator.video.use_transport_cc", true]);
+
+ test = new PeerConnectionTest(options);
+ test.setMediaConstraints([{audio: true}, {video: true}],
+ [{audio: true}, {video: true}]);
+
+ test.chain.insertAfter('PC_LOCAL_SET_LOCAL_DESCRIPTION', [
+ async function PC_LOCAL_CHECK_SDP_OFFER_EXTMAP() {
+ sdputils.verify_unique_extmap_ids(test.originalOffer.sdp);
+
+ const audio = sdputils.findExtmapIdsUrnsDirections(
+ sdputils.getAudioMSections(test.originalOffer.sdp));
+ const expected_audio = [
+ /* Please modify this list when you add or remove RTP header
+ extensions. */
+ ["1", "urn:ietf:params:rtp-hdrext:ssrc-audio-level", ""],
+ ["2", "urn:ietf:params:rtp-hdrext:csrc-audio-level", "recvonly"],
+ ["3", "urn:ietf:params:rtp-hdrext:sdes:mid", ""],
+ ];
+ // *Ugh* ...
+ ok(JSON.stringify(audio) ===
+ JSON.stringify(expected_audio),
+ "List of offer audio URNs meets expected values");
+
+ const video = sdputils.findExtmapIdsUrnsDirections(
+ sdputils.getVideoMSections(test.originalOffer.sdp));
+ const expected_video = [
+ /* Please modify this list when you add or remove RTP header
+ extensions. */
+ ["3", "urn:ietf:params:rtp-hdrext:sdes:mid", ""],
+ ["4", "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time", ""],
+ ["5", "urn:ietf:params:rtp-hdrext:toffset", ""],
+ ["6", "http://www.webrtc.org/experiments/rtp-hdrext/playout-delay", "recvonly"],
+ ["7", "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01", ""],
+ ];
+ // *Ugh* ...
+ ok(JSON.stringify(video) ===
+ JSON.stringify(expected_video),
+ "List of offer video URNs meets expected values");
+ }
+ ]);
+
+ test.chain.removeAfter('PC_REMOTE_SET_LOCAL_DESCRIPTION');
+ test.chain.append([
+ async function PC_REMOTE_CHECK_SDP_ANSWER_EXTMAP() {
+ sdputils.verify_unique_extmap_ids(test.originalAnswer.sdp);
+
+ const audio = sdputils.findExtmapIdsUrnsDirections(
+ sdputils.getAudioMSections(test.originalAnswer.sdp));
+ const expected_audio = [
+ /* Please modify this list when you add or remove RTP header
+ extensions. */
+ ["1", "urn:ietf:params:rtp-hdrext:ssrc-audio-level",""],
+ ["3", "urn:ietf:params:rtp-hdrext:sdes:mid",""],
+ ];
+ // *Ugh* ...
+ ok(JSON.stringify(audio) ===
+ JSON.stringify(expected_audio),
+ "List of answer audio URNs meets expected values");
+
+ const video = sdputils.findExtmapIdsUrnsDirections(
+ sdputils.getVideoMSections(test.originalAnswer.sdp));
+ const expected_video = [
+ /* Please modify this list when you add or remove RTP header
+ extensions. */
+ ["3", "urn:ietf:params:rtp-hdrext:sdes:mid",""],
+ ["4", "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time",""],
+ ["5", "urn:ietf:params:rtp-hdrext:toffset",""],
+ ["7", "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01", ""],
+ ];
+ ok(JSON.stringify(video) ===
+ JSON.stringify(expected_video),
+ "List of answer video URNs meets expected values");
+ }
+ ]);
+
+ await test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoVerifyExtmapSendonly.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoVerifyExtmapSendonly.html
new file mode 100644
index 0000000000..6cbc9e4c00
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoVerifyExtmapSendonly.html
@@ -0,0 +1,97 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1406529",
+ title: "Verify SDP extmap attribute for sendonly connection"
+ });
+
+ var test;
+ runNetworkTest(async function (options) {
+ await pushPrefs(["media.navigator.video.use_transport_cc", true]);
+
+ test = new PeerConnectionTest(options);
+ test.setMediaConstraints([{audio: true}, {video: true}],
+ []);
+
+ test.chain.insertAfter('PC_LOCAL_SET_LOCAL_DESCRIPTION', [
+ async function PC_LOCAL_CHECK_SDP_OFFER_EXTMAP() {
+ sdputils.verify_unique_extmap_ids(test.originalOffer.sdp);
+
+ const audio = sdputils.findExtmapIdsUrnsDirections(
+ sdputils.getAudioMSections(test.originalOffer.sdp));
+ const expected_audio = [
+ /* Please modify this list when you add or remove RTP header
+ extensions. */
+ ["1", "urn:ietf:params:rtp-hdrext:ssrc-audio-level", ""],
+ ["2", "urn:ietf:params:rtp-hdrext:csrc-audio-level", "recvonly"],
+ ["3", "urn:ietf:params:rtp-hdrext:sdes:mid", ""],
+ ];
+ // *Ugh* ...
+ ok(JSON.stringify(audio) ===
+ JSON.stringify(expected_audio),
+ "List of offer audio URNs meets expected values");
+
+ const video = sdputils.findExtmapIdsUrnsDirections(
+ sdputils.getVideoMSections(test.originalOffer.sdp));
+ const expected_video = [
+ /* Please modify this list when you add or remove RTP header
+ extensions. */
+ ["3", "urn:ietf:params:rtp-hdrext:sdes:mid", ""],
+ ["4", "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time", ""],
+ ["5", "urn:ietf:params:rtp-hdrext:toffset", ""],
+ ["6", "http://www.webrtc.org/experiments/rtp-hdrext/playout-delay", "recvonly"],
+ ["7", "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01", ""],
+ ];
+ // *Ugh* ...
+ ok(JSON.stringify(video) ===
+ JSON.stringify(expected_video),
+ "List of offer video URNs meets expected values");
+ }
+ ]);
+
+ test.chain.removeAfter('PC_REMOTE_SET_LOCAL_DESCRIPTION');
+ test.chain.append([
+ async function PC_REMOTE_CHECK_SDP_ANSWER_EXTMAP() {
+ sdputils.verify_unique_extmap_ids(test.originalAnswer.sdp);
+
+ const audio = sdputils.findExtmapIdsUrnsDirections(
+ sdputils.getAudioMSections(test.originalAnswer.sdp));
+ const expected_audio = [
+ /* Please modify this list when you add or remove RTP header
+ extensions. */
+ ["1", "urn:ietf:params:rtp-hdrext:ssrc-audio-level",""],
+ ["3", "urn:ietf:params:rtp-hdrext:sdes:mid",""],
+ ];
+ // *Ugh* ...
+ ok(JSON.stringify(audio) ===
+ JSON.stringify(expected_audio),
+ "List of answer audio URNs meets expected values");
+
+ const video = sdputils.findExtmapIdsUrnsDirections(
+ sdputils.getVideoMSections(test.originalAnswer.sdp));
+ const expected_video = [
+ /* Please modify this list when you add or remove RTP header
+ extensions. */
+ ["3", "urn:ietf:params:rtp-hdrext:sdes:mid",""],
+ ["4", "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time",""],
+ ["5", "urn:ietf:params:rtp-hdrext:toffset",""],
+ ["7", "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01", ""],
+ ];
+ ok(JSON.stringify(video) ===
+ JSON.stringify(expected_video),
+ "List of answer video URNs meets expected values");
+ }
+ ]);
+
+ await test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoVerifyTooLongMidFails.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoVerifyTooLongMidFails.html
new file mode 100644
index 0000000000..70d27b48c6
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioVideoVerifyTooLongMidFails.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1427009",
+ title: "Test mid longer than 16 characters fails"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ options = options || { };
+ options.bundle = false;
+ test = new PeerConnectionTest(options);
+ test.setMediaConstraints([{audio: true}, {video: true}],
+ [{audio: true}, {video: true}]);
+
+ test.chain.replaceAfter("PC_LOCAL_CREATE_OFFER",
+ [
+ function PC_LOCAL_MUNGE_OFFER_SDP(test) {
+ test.originalOffer.sdp =
+ test.originalOffer.sdp.replace(/a=mid:.*\r\n/g,
+ "a=mid:really_long_mid_over_16_chars\r\n");
+ },
+ function PC_LOCAL_EXPECT_SET_LOCAL_DESCRIPTION_FAIL(test) {
+ return test.setLocalDescription(test.pcLocal,
+ test.originalOffer,
+ HAVE_LOCAL_OFFER)
+ .then(() => ok(false, "setLocalDescription must fail"),
+ // This needs to be RTCError once we support it, and once we
+ // stop allowing any modification, InvalidModificationError
+ e => is(e.name, "OperationError",
+ "setLocalDescription must fail and did"));
+ }
+ ], 0 // first occurance
+ );
+
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudio_forced_higher_rate.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudio_forced_higher_rate.html
new file mode 100644
index 0000000000..95bfb06514
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudio_forced_higher_rate.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="peerconnection_audio_forced_sample_rate.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ bug: "1437366",
+ title: "Basic audio-only peer connection, with the MTG running at a rate not supported by the MediaPipeline (49000Hz)"
+});
+
+test_peerconnection_audio_forced_sample_rate(49000);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudio_forced_lower_rate.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudio_forced_lower_rate.html
new file mode 100644
index 0000000000..aab9778971
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudio_forced_lower_rate.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="peerconnection_audio_forced_sample_rate.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ bug: "1437366",
+ title: "Basic audio-only peer connection, with the MTG running at a rate not supported by the MediaPipeline (24000Hz)"
+});
+
+test_peerconnection_audio_forced_sample_rate(24000);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicH264Video.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicH264Video.html
new file mode 100644
index 0000000000..072c35da39
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicH264Video.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1040346",
+ title: "Basic H.264 GMP video-only peer connection"
+ });
+
+ var test;
+ runNetworkTest(async function (options) {
+ matchPlatformH264CodecPrefs();
+ options = options || { };
+ options.h264 = true;
+ test = new PeerConnectionTest(options);
+ test.setMediaConstraints([{video: true}], [{video: true}]);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicScreenshare.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicScreenshare.html
new file mode 100644
index 0000000000..93148ac5fd
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicScreenshare.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1039666",
+ title: "Basic screenshare-only peer connection"
+ });
+
+ async function supportedVideoPayloadTypes() {
+ const pc = new RTCPeerConnection();
+ const offer = await pc.createOffer({offerToReceiveVideo: true});
+ return sdputils.getPayloadTypes(offer.sdp);
+ }
+
+ async function testScreenshare(payloadType) {
+ const options = {};
+ options.h264 = payloadType == 97 || payloadType == 126;
+ const test = new PeerConnectionTest(options);
+ const constraints = {
+ video: { mediaSource: "screen" },
+ };
+ test.setMediaConstraints([constraints], []);
+ test.chain.insertAfterEach("PC_LOCAL_CREATE_OFFER", [
+ function PC_LOCAL_ISOLATE_CODEC() {
+ info(`Forcing payload type ${payloadType}. Note that other associated ` +
+ `payload types, like RTX, are removed too.`);
+ test.originalOffer.sdp =
+ sdputils.removeAllButPayloadType(test.originalOffer.sdp, payloadType);
+ },
+ ]);
+ await test.run();
+ }
+
+ runNetworkTest(async () => {
+ await matchPlatformH264CodecPrefs();
+ const pts = await supportedVideoPayloadTypes();
+ ok(pts.includes("120"), "VP8 is supported");
+ ok(pts.includes("121"), "VP9 is supported");
+ if (pts.length > 2) {
+ is(pts.length, 4, "Expected VP8, VP9 and two variants of H264");
+ ok(pts.includes("97"), "H264 with no packetization-mode is supported");
+ ok(pts.includes("126"), "H264 with packetization-mode=1 is supported");
+ }
+ for (const pt of pts) {
+ await testScreenshare(pt);
+ }
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicVideo.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicVideo.html
new file mode 100644
index 0000000000..4a0655d696
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicVideo.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "796888",
+ title: "Basic video-only peer connection"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ test = new PeerConnectionTest(options);
+ test.setMediaConstraints([{video: true}], [{video: true}]);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicVideoVerifyRtpHeaderExtensions.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicVideoVerifyRtpHeaderExtensions.html
new file mode 100644
index 0000000000..7874e52a10
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicVideoVerifyRtpHeaderExtensions.html
@@ -0,0 +1,82 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="parser_rtp.js"></script>
+ <script type="application/javascript" src="sdpUtils.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1416932",
+ title: "Basic video-only peer connection and verify rtp header extensions"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ test = new PeerConnectionTest(options);
+ test.setMediaConstraints([{video: true}], [{video: true}]);
+
+ let getRtpPacketWithExtension = (pc, extension) => {
+ // we only examine received packets
+ let sending = false;
+ pc.mozEnablePacketDump(0, "rtp", sending);
+ return new Promise((res, rej) =>
+ pc.mozSetPacketCallback((...args) => {
+ const packet = ParseRtpPacket(args[3]);
+ info(`midId = ${extension} packet = ${JSON.stringify(packet, null, 2)}`);
+ if (packet.header.extensions.find(e => e.id == extension) !== undefined) {
+ res(packet);
+ pc.mozSetPacketCallback(() => {});
+ pc.mozDisablePacketDump(0, "rtp", sending);
+ }
+ })
+ );
+ }
+
+ let havePacketWithMid;
+ let sdpExtmaps;
+
+ // MID can stop being sent when acked causing failures if packets are checked later.
+ // Starting packet sniffer before PC_LOCAL_SET_REMOTE_DESCRIPTION to be ready
+ // to inspect packets ahead of any packets arriving.
+ test.chain.insertBefore('PC_LOCAL_SET_REMOTE_DESCRIPTION', [
+ function PC_REMOTE_FIND_RTP_PACKETS_WITH_MIDID() {
+
+ sdpExtmaps = sdputils.findExtmapIdsUrnsDirections(test.originalAnswer.sdp);
+ const [midId] = sdpExtmaps.find(([, urn]) => urn == "urn:ietf:params:rtp-hdrext:sdes:mid");
+ const pc = SpecialPowers.wrap(test.pcRemote._pc);
+ havePacketWithMid = getRtpPacketWithExtension(pc, midId);
+ }
+ ]);
+
+ test.chain.insertBefore('PC_REMOTE_WAIT_FOR_MEDIA_FLOW', [
+ async function PC_REMOTE_CHECK_RTP_HEADER_EXTS_AGAINST_SDP() {
+
+ const sdpExtmapIds = sdpExtmaps.map(e => e[0]);
+ const packet = await havePacketWithMid;
+ const extIds = packet.header.extensions.map(e => `${e.id}`);
+ // make sure we got the same number of rtp header extensions in
+ // the received packet as were negotiated in the sdp. Then
+ // check to make sure each of the received extension ids were in
+ // the sdp.
+ is(sdpExtmapIds.length, extIds.length,
+ `number of sdp ids match received ids ` +
+ `${JSON.stringify(sdpExtmapIds)} == ${JSON.stringify(extIds)}\n` +
+ `sdp = ${test.originalAnswer.sdp}\n` +
+ `packet = ${JSON.stringify(packet, null, 2)}`);
+ // note, we are comparing a number (from the parsed rtp packet)
+ // and a string (from the answer sdp)
+ ok(extIds.every(id => sdpExtmapIds.includes(id)) &&
+ sdpExtmapIds.every(id => extIds.includes(id)),
+ `extension id arrays equivalent`);
+ }
+ ]);
+
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicWindowshare.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicWindowshare.html
new file mode 100644
index 0000000000..1cfb0797db
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicWindowshare.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1038926",
+ title: "Basic windowshare-only peer connection"
+ });
+
+ runNetworkTest(function (options) {
+ const test = new PeerConnectionTest(options);
+ const constraints = {
+ video: { mediaSource: "window" },
+ };
+ test.setMediaConstraints([constraints], []);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_bug1013809.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug1013809.html
new file mode 100644
index 0000000000..a8c7004793
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug1013809.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1013809",
+ title: "Audio-only peer connection with swapped setLocal and setRemote steps"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ test = new PeerConnectionTest(options);
+ var sld = test.chain.remove("PC_REMOTE_SET_LOCAL_DESCRIPTION");
+ test.chain.insertAfter("PC_LOCAL_SET_REMOTE_DESCRIPTION", sld);
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_bug1042791.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug1042791.html
new file mode 100644
index 0000000000..a84dcf9d09
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug1042791.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1040346",
+ title: "Basic H.264 GMP video-only peer connection"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ options = options || { };
+ options.h264 = true;
+ test = new PeerConnectionTest(options);
+ test.setMediaConstraints([{video: true}], [{video: true}]);
+ test.chain.removeAfter("PC_LOCAL_CREATE_OFFER");
+
+ test.chain.append([
+ function PC_LOCAL_VERIFY_H264_OFFER(test) {
+ ok(!test.pcLocal._latest_offer.sdp.toLowerCase().includes("profile-level-id=0x42e0"),
+ "H264 offer does not contain profile-level-id=0x42e0");
+ ok(test.pcLocal._latest_offer.sdp.toLowerCase().includes("profile-level-id=42e0"),
+ "H264 offer contains profile-level-id=42e0");
+ }
+ ]);
+
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_bug1227781.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug1227781.html
new file mode 100644
index 0000000000..41e4aec457
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug1227781.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1227781",
+ title: "Test with invalid TURN server"
+ });
+
+ const turnConfig = {
+ iceServers: [
+ {
+ username: "mozilla",
+ credential: "mozilla",
+ url: "turn:test@10.0.0.1",
+ },
+ ],
+ };
+ runNetworkTest(function (options) {
+ let exception = false;
+ try {
+ new RTCPeerConnection(turnConfig);
+ } catch (e) {
+ info(e);
+ exception = true;
+ }
+ is(exception, true, "Exception fired");
+ ok("Success");
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_bug1512281.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug1512281.html
new file mode 100644
index 0000000000..e6451becea
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug1512281.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1512281",
+ title: "Test that RTCP sender and receiver stats are not swapped"
+ });
+
+const ensure_missing_rtcp = async stats => {
+ const rtcp_stats = [...stats.values()].filter(
+ s => s.type.endsWith("bound-rtp") &&
+ s.isRemote == true).map(s => JSON.stringify(s))
+ is(rtcp_stats, [],
+ "There are no RTCP stats when RTCP reception is turned off");
+};
+
+const PC_LOCAL_TEST_FOR_MISSING_RTCP = async test =>
+ await ensure_missing_rtcp(await test.pcLocal.getStats());
+
+const PC_REMOTE_TEST_FOR_MISSING_RTCP = async test =>
+ await ensure_missing_rtcp(await test.pcRemote.getStats());
+
+runNetworkTest(async options => {
+ await pushPrefs(["media.webrtc.net.force_disable_rtcp_reception", true]);
+
+ const test = new PeerConnectionTest(options);
+
+ test.chain.insertAfter("PC_LOCAL_WAIT_FOR_MEDIA_FLOW",
+ [PC_LOCAL_TEST_FOR_MISSING_RTCP]);
+
+ test.chain.insertAfter("PC_REMOTE_WAIT_FOR_MEDIA_FLOW",
+ [PC_REMOTE_TEST_FOR_MISSING_RTCP]);
+
+ test.setMediaConstraints([{audio: true}, {video: true}],
+ [{audio: true}, {video: true}]);
+ await test.run();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_bug1773067.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug1773067.html
new file mode 100644
index 0000000000..9e6d79a107
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug1773067.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1773067",
+ title: "getStats on a closed peer connection should fail, not hang, " +
+ " until bug 1056433 is fixed"
+ });
+
+ // TODO: Bug 1056433 removes the need for this test
+ runNetworkTest(async function () {
+ let errorName;
+ try {
+ const pc = new RTCPeerConnection();
+ pc.close();
+ await pc.getStats();
+ } catch(e) {
+ errorName = e.name;
+ }
+ is(errorName,
+ "InvalidStateError",
+ "getStats on closed peer connection fails instead of hanging");
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_bug822674.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug822674.html
new file mode 100644
index 0000000000..fceb2c2a1d
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug822674.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "822674",
+ title: "RTCPeerConnection isn't a true javascript object as it should be"
+ });
+
+ runNetworkTest(function () {
+ var pc = new RTCPeerConnection();
+
+ pc.thereIsNeverGoingToBeAPropertyWithThisNameOnThisInterface = 1;
+ is(pc.thereIsNeverGoingToBeAPropertyWithThisNameOnThisInterface, 1,
+ "Can set expandos on an RTCPeerConnection");
+
+ pc = null;
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_bug825703.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug825703.html
new file mode 100644
index 0000000000..5cd168af8a
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug825703.html
@@ -0,0 +1,140 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "825703",
+ title: "RTCConfiguration valid/invalid permutations"
+ });
+
+// ^^^ Don't insert data above this line without adjusting line number below!
+var lineNumberAndFunction = {
+// <--- 16 is the line this must be.
+ line: 17, func: () => new RTCPeerConnection().onaddstream = () => {}
+};
+
+var makePC = (config, expected_error) => {
+ var exception;
+ try {
+ new RTCPeerConnection(config).close();
+ } catch (e) {
+ exception = e;
+ }
+ is((exception? exception.name : "success"), expected_error || "success",
+ "RTCPeerConnection(" + JSON.stringify(config) + ")");
+};
+
+// The order of properties in objects is not guaranteed in JavaScript, so this
+// transform produces json-comparable dictionaries. The resulting copy is only
+// meant to be used in comparisons (e.g. array-ness is not preserved).
+
+var toComparable = o =>
+ (typeof o != 'object' || !o)? o : Object.keys(o).sort().reduce((co, key) => {
+ co[key] = toComparable(o[key]);
+ return co;
+}, {});
+
+// This is a test of the iceServers parsing code + readable errors
+runNetworkTest(() => {
+ var exception = null;
+
+ try {
+ new RTCPeerConnection().close();
+ } catch (e) {
+ exception = e;
+ }
+ ok(!exception, "RTCPeerConnection() succeeds");
+ exception = null;
+
+ // Some overlap still with WPT RTCConfiguration-iceServers.html
+
+ makePC({ iceServers: [
+ { urls:"stun:127.0.0.1" },
+ { urls:"stun:localhost", foo:"" },
+ { urls: ["stun:127.0.0.1", "stun:localhost"] },
+ { urls:"stuns:localhost", foo:"" },
+ { urls:"turn:[::1]:3478", username:"p", credential:"p" },
+ { urls:"turn:[::1]:3478", username:"", credential:"" },
+ { urls:"turns:[::1]:3478", username:"", credential:"" },
+ { urls:"turn:localhost:3478?transport=udp", username:"p", credential:"p" },
+ { urls: ["turn:[::1]:3478", "turn:localhost"], username:"p", credential:"p" },
+ { urls:"turns:localhost:3478?transport=udp", username:"p", credential:"p" },
+ { url:"stun:localhost", foo:"" },
+ { url:"turn:localhost", username:"p", credential:"p" }
+ ]});
+
+ makePC({ iceServers: [{ urls:"http:0.0.0.0" }] }, "SyntaxError");
+
+ try {
+ new RTCPeerConnection({ iceServers: [{ url:"http:0.0.0.0" }] }).close();
+ } catch (e) {
+ ok(e.message.indexOf("http") > 0,
+ "RTCPeerConnection() constructor has readable exceptions");
+ }
+
+ // Test getConfiguration
+ const config = {
+ bundlePolicy: "max-bundle",
+ iceTransportPolicy: "relay",
+ peerIdentity: null,
+ certificates: [],
+ iceServers: [
+ { urls: ["stun:127.0.0.1", "stun:localhost"], credentialType:"password" },
+ { urls: ["turn:[::1]:3478"], username:"p", credential:"p", credentialType:"password" },
+ ],
+ };
+ // Make sure sdpSemantics is not exposed in getConfiguration
+ const configWithExtraProps = Object.assign({},
+ config,
+ {sdpSemantics: "plan-b"});
+ ok("sdpSemantics" in configWithExtraProps, "sdpSemantics control");
+
+ const pc = new RTCPeerConnection(configWithExtraProps);
+ is(JSON.stringify(toComparable(pc.getConfiguration())),
+ JSON.stringify(toComparable(config)), "getConfiguration");
+ pc.close();
+
+ var push = prefs => SpecialPowers.pushPrefEnv(prefs);
+
+ return Promise.resolve()
+ // This set of tests are setting the about:config User preferences for default
+ // ice servers and checking the outputs when RTCPeerConnection() is
+ // invoked. See Bug 1167922 for more information.
+ .then(() => push({ set: [['media.peerconnection.default_iceservers', ""]] })
+ .then(() => makePC())
+ .then(() => push({ set: [['media.peerconnection.default_iceservers', "k"]] }))
+ .then(() => makePC())
+ .then(() => push({ set: [['media.peerconnection.default_iceservers', "[{\"urls\": [\"stun:stun.services.mozilla.com\"]}]"]] }))
+ .then(() => makePC()))
+ // This set of tests check that warnings work. See Bug 1254839 for more.
+ .then(() => {
+ let promise = new Promise(resolve => {
+ SpecialPowers.registerConsoleListener(msg => {
+ if (msg.message.includes("onaddstream")) {
+ SpecialPowers.postConsoleSentinel();
+ resolve(msg.message);
+ }
+ });
+ });
+ lineNumberAndFunction.func();
+ return promise;
+ }).then(warning => {
+ is(warning.split('"')[1],
+ "WebRTC: onaddstream is deprecated! Use peerConnection.ontrack instead.",
+ "warning logged");
+ var remainder = warning.split('"').slice(2).join('"');
+ info(remainder);
+ ok(remainder.includes('file: "' + window.location + '"'),
+ "warning has this file");
+ ok(remainder.includes('line: ' + lineNumberAndFunction.line),
+ "warning has correct line number");
+ });
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_bug827843.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug827843.html
new file mode 100644
index 0000000000..06cfde9e5d
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug827843.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "827843",
+ title: "Ensure that localDescription and remoteDescription are null after close"
+ });
+
+var steps = [
+ function CHECK_SDP_ON_CLOSED_PC(test) {
+ var description;
+ var exception = null;
+
+ test.pcLocal.close();
+
+ try { description = test.pcLocal.localDescription; } catch (e) { exception = e; }
+ ok(exception, "Attempt to access localDescription of pcLocal after close throws exception");
+ exception = null;
+
+ try { description = test.pcLocal.remoteDescription; } catch (e) { exception = e; }
+ ok(exception, "Attempt to access remoteDescription of pcLocal after close throws exception");
+ exception = null;
+
+ test.pcRemote.close();
+
+ try { description = test.pcRemote.localDescription; } catch (e) { exception = e; }
+ ok(exception, "Attempt to access localDescription of pcRemote after close throws exception");
+ exception = null;
+
+ try { description = test.pcRemote.remoteDescription; } catch (e) { exception = e; }
+ ok(exception, "Attempt to access remoteDescription of pcRemote after close throws exception");
+ }
+];
+
+var test;
+runNetworkTest(() => {
+ test = new PeerConnectionTest();
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ test.chain.append(steps);
+ return test.run();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_bug834153.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug834153.html
new file mode 100644
index 0000000000..6d8ca2a7ce
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_bug834153.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "834153",
+ title: "Queue CreateAnswer in PeerConnection.js"
+ });
+
+ runNetworkTest(function () {
+ var pc1 = new RTCPeerConnection();
+ var pc2 = new RTCPeerConnection();
+
+ return pc1.createOffer({ offerToReceiveAudio: true }).then(offer => {
+ // The whole point of this test is not to wait for the
+ // setRemoteDescription call to succesfully complete, so we
+ // don't wait for it to succeed.
+ pc2.setRemoteDescription(offer);
+ return pc2.createAnswer();
+ })
+ .then(answer => is(answer.type, "answer", "CreateAnswer created an answer"))
+ .catch(reason => ok(false, reason.message))
+ .then(() => {
+ pc1.close();
+ pc2.close();
+ })
+ .catch(reason => ok(false, reason.message));
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_callbacks.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_callbacks.html
new file mode 100644
index 0000000000..4c890e4400
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_callbacks.html
@@ -0,0 +1,86 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ title: "PeerConnection using callback functions",
+ bug: "1119593",
+ visible: true
+ });
+
+// This still aggressively uses promises, but it is testing that the callback functions
+// are properly in place.
+
+// wrapper that turns a callback-based function call into a promise
+function pcall(o, f, beforeArg) {
+ return new Promise((resolve, reject) => {
+ var args = [resolve, reject];
+ if (typeof beforeArg !== 'undefined') {
+ args.unshift(beforeArg);
+ }
+ info('Calling ' + f.name);
+ f.apply(o, args);
+ });
+}
+
+var pc1 = new RTCPeerConnection();
+var pc2 = new RTCPeerConnection();
+
+var pc2_haveRemoteOffer = new Promise(resolve => {
+ pc2.onsignalingstatechange =
+ e => (e.target.signalingState == "have-remote-offer") && resolve();
+});
+var pc1_stable = new Promise(resolve => {
+ pc1.onsignalingstatechange =
+ e => (e.target.signalingState == "stable") && resolve();
+});
+
+pc1.onicecandidate = e => {
+ pc2_haveRemoteOffer
+ .then(() => !e.candidate || pcall(pc2, pc2.addIceCandidate, e.candidate))
+ .catch(generateErrorCallback());
+};
+pc2.onicecandidate = e => {
+ pc1_stable
+ .then(() => !e.candidate || pcall(pc1, pc1.addIceCandidate, e.candidate))
+ .catch(generateErrorCallback());
+};
+
+var v1, v2;
+var delivered = new Promise(resolve => {
+ pc2.onaddstream = e => {
+ v2.srcObject = e.stream;
+ resolve(e.stream);
+ };
+});
+
+runNetworkTest(function() {
+ v1 = createMediaElement('video', 'v1');
+ v2 = createMediaElement('video', 'v2');
+ var canPlayThrough = new Promise(resolve => v2.canplaythrough = resolve);
+ is(v2.currentTime, 0, "v2.currentTime is zero at outset");
+
+ // not testing legacy gUM here
+ return navigator.mediaDevices.getUserMedia({ video: true, audio: true })
+ .then(stream => pc1.addStream(v1.srcObject = stream))
+ .then(() => pcall(pc1, pc1.createOffer))
+ .then(offer => pcall(pc1, pc1.setLocalDescription, offer))
+ .then(() => pcall(pc2, pc2.setRemoteDescription, pc1.localDescription))
+ .then(() => pcall(pc2, pc2.createAnswer))
+ .then(answer => pcall(pc2, pc2.setLocalDescription, answer))
+ .then(() => pcall(pc1, pc1.setRemoteDescription, pc2.localDescription))
+ .then(() => delivered)
+ // .then(() => canPlayThrough) // why doesn't this fire?
+ .then(() => waitUntil(() => v2.currentTime > 0))
+ .then(() => ok(v2.currentTime > 0, "v2.currentTime is moving (" + v2.currentTime + ")"))
+ .then(() => ok(true, "Connected."))
+ .then(() => { v1.pause(); v2.pause(); });
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_captureStream_canvas_2d.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_captureStream_canvas_2d.html
new file mode 100644
index 0000000000..db3a735008
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_captureStream_canvas_2d.html
@@ -0,0 +1,81 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ bug: "1032848",
+ title: "Canvas(2D)::CaptureStream as video-only input to peerconnection",
+ visible: true
+});
+
+runNetworkTest(async () => {
+ // [TODO] re-enable HW decoder after bug 1526207 is fixed.
+ if (navigator.userAgent.includes("Android")) {
+ await pushPrefs(["media.navigator.mediadatadecoder_vpx_enabled", false],
+ ["media.webrtc.hw.h264.enabled", false]);
+ }
+
+ var test = new PeerConnectionTest();
+ var mediaElement;
+ var h = new CaptureStreamTestHelper2D();
+ var canvas = document.createElement('canvas');
+ var stream;
+ canvas.id = 'source_canvas';
+ canvas.width = canvas.height = 16;
+ document.getElementById('content').appendChild(canvas);
+
+ test.setMediaConstraints([{video: true}], []);
+ test.chain.replace("PC_LOCAL_GUM", [
+ function PC_LOCAL_CANVAS_CAPTURESTREAM(test) {
+ h.drawColor(canvas, h.green);
+ stream = canvas.captureStream(0);
+ test.pcLocal.attachLocalStream(stream);
+ stream.requestFrame();
+ var i = 0;
+ return setInterval(function() {
+ try {
+ info("draw " + i ? "green" : "red");
+ h.drawColor(canvas, i ? h.green : h.red);
+ i = 1 - i;
+ stream.requestFrame();
+ } catch (e) {
+ // ignore; stream might have shut down, and we don't bother clearing
+ // the setInterval.
+ }
+ }, 500);
+ }
+ ]);
+ test.chain.append([
+ function PC_REMOTE_WAIT_FOR_REMOTE_GREEN() {
+ mediaElement = test.pcRemote.remoteMediaElements[0];
+ ok(!!mediaElement, "Should have remote video element for pcRemote");
+ return h.pixelMustBecome(mediaElement, h.green, {
+ threshold: 128,
+ infoString: "pcRemote's remote should become green",
+ });
+ },
+ function PC_LOCAL_DRAW_LOCAL_RED() {
+ // After requesting a frame it will be captured at the time of next render.
+ // Next render will happen at next stable state, at the earliest,
+ // i.e., this order of `requestFrame(); draw();` should work.
+ stream.requestFrame();
+ h.drawColor(canvas, h.red);
+ },
+ function PC_REMOTE_WAIT_FOR_REMOTE_RED() {
+ return h.pixelMustBecome(mediaElement, h.red, {
+ threshold: 128,
+ infoString: "pcRemote's remote should become red",
+ });
+ }
+ ]);
+ await test.run();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_captureStream_canvas_2d_noSSRC.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_captureStream_canvas_2d_noSSRC.html
new file mode 100644
index 0000000000..e33a7e8886
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_captureStream_canvas_2d_noSSRC.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ title: "Canvas(2D)::CaptureStream as video-only input to peerconnection with no a=ssrc",
+ visible: true
+});
+
+var test;
+runNetworkTest(async (options) => {
+ // [TODO] re-enable HW decoder after bug 1526207 is fixed.
+ if (navigator.userAgent.includes("Android")) {
+ await pushPrefs(["media.navigator.mediadatadecoder_vpx_enabled", false],
+ ["media.webrtc.hw.h264.enabled", false]);
+ }
+
+ options = options || { };
+ options.ssrc = false;
+ test = new PeerConnectionTest(options);
+ var mediaElement;
+ var h = new CaptureStreamTestHelper2D();
+ var canvas = document.createElement('canvas');
+ var stream;
+ canvas.id = 'source_canvas';
+ canvas.width = canvas.height = 16;
+ document.getElementById('content').appendChild(canvas);
+
+ test.setMediaConstraints([{video: true}], []);
+ test.chain.replace("PC_LOCAL_GUM", [
+ function PC_LOCAL_CANVAS_CAPTURESTREAM(test) {
+ h.drawColor(canvas, h.green);
+ stream = canvas.captureStream(0);
+ test.pcLocal.attachLocalStream(stream);
+ stream.requestFrame();
+ var i = 0;
+ return setInterval(function() {
+ try {
+ info("draw " + i ? "green" : "red");
+ h.drawColor(canvas, i ? h.green : h.red);
+ i = 1 - i;
+ stream.requestFrame();
+ } catch (e) {
+ // ignore; stream might have shut down, and we don't bother clearing
+ // the setInterval.
+ }
+ }, 500);
+ }
+ ]);
+ test.chain.append([
+ function PC_REMOTE_WAIT_FOR_REMOTE_GREEN() {
+ mediaElement = test.pcRemote.remoteMediaElements[0];
+ ok(!!mediaElement, "Should have remote video element for pcRemote");
+ return h.pixelMustBecome(mediaElement, h.green, {
+ threshold: 128,
+ infoString: "pcRemote's remote should become green",
+ });
+ },
+ function PC_LOCAL_DRAW_LOCAL_RED() {
+ // After requesting a frame it will be captured at the time of next render.
+ // Next render will happen at next stable state, at the earliest,
+ // i.e., this order of `requestFrame(); draw();` should work.
+ stream.requestFrame();
+ h.drawColor(canvas, h.red);
+ },
+ function PC_REMOTE_WAIT_FOR_REMOTE_RED() {
+ return h.pixelMustBecome(mediaElement, h.red, {
+ threshold: 128,
+ infoString: "pcRemote's remote should become red",
+ });
+ }
+ ]);
+ await test.run();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_captureStream_canvas_webgl.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_captureStream_canvas_webgl.html
new file mode 100644
index 0000000000..167379fb37
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_captureStream_canvas_webgl.html
@@ -0,0 +1,130 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
+ <script type="application/javascript" src="/tests/dom/canvas/test/webgl-mochitest/webgl-util.js"></script>
+</head>
+<body>
+<pre id="test">
+<script id="v-shader" type="x-shader/x-vertex">
+ attribute vec2 aPosition;
+ void main() {
+ gl_Position = vec4(aPosition, 0, 1);
+}
+</script>
+<script id="f-shader" type="x-shader/x-fragment">
+ precision mediump float;
+ uniform vec4 uColor;
+ void main() { gl_FragColor = uColor; }
+</script>
+<script type="application/javascript">
+createHTML({
+ bug: "1032848",
+ title: "Canvas(WebGL)::CaptureStream as video-only input to peerconnection"
+});
+
+runNetworkTest(async () => {
+ // [TODO] re-enable HW decoder after bug 1526207 is fixed.
+ if (navigator.userAgent.includes("Android")) {
+ await pushPrefs(["media.navigator.mediadatadecoder_vpx_enabled", false],
+ ["media.webrtc.hw.h264.enabled", false]);
+ }
+
+ var test = new PeerConnectionTest();
+ var vremote;
+ var h = new CaptureStreamTestHelperWebGL();
+ var canvas = document.createElement('canvas');
+ canvas.id = 'source_canvas';
+ canvas.width = canvas.height = 16;
+ canvas.style.display = 'none';
+ document.getElementById('content').appendChild(canvas);
+
+ var gl = canvas.getContext('webgl');
+ if (!gl) {
+ todo(false, "WebGL unavailable.");
+ networkTestFinished();
+ return;
+ }
+
+ test.setMediaConstraints([{video: true}], []);
+ test.chain.replace("PC_LOCAL_GUM", [
+ function WEBGL_SETUP(test) {
+ var program = WebGLUtil.createProgramByIds(gl, 'v-shader', 'f-shader');
+
+ if (!program) {
+ ok(false, "Program should link");
+ return Promise.reject("Program should link");
+ }
+ gl.useProgram(program);
+
+ var uColorLocation = gl.getUniformLocation(program, "uColor");
+ h.setFragmentColorLocation(uColorLocation);
+
+ var squareBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, squareBuffer);
+
+ var vertices = [ 0, 0,
+ -1, 0,
+ 0, 1,
+ -1, 1 ];
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
+ squareBuffer.itemSize = 2;
+ squareBuffer.numItems = 4;
+
+ program.aPosition = gl.getAttribLocation(program, "aPosition");
+ gl.enableVertexAttribArray(program.aPosition);
+ gl.vertexAttribPointer(program.aPosition, squareBuffer.itemSize, gl.FLOAT, false, 0, 0);
+ },
+ function PC_LOCAL_CANVAS_CAPTURESTREAM(test) {
+ h.drawColor(canvas, h.green);
+ test.pcLocal.canvasStream = canvas.captureStream(0.0);
+ is(test.pcLocal.canvasStream.canvas, canvas, "Canvas attribute is correct");
+ test.pcLocal.attachLocalStream(test.pcLocal.canvasStream);
+ var i = 0;
+ return setInterval(function() {
+ try {
+ info("draw " + i ? "green" : "red");
+ h.drawColor(canvas, i ? h.green : h.red);
+ i = 1 - i;
+ test.pcLocal.canvasStream.requestFrame();
+ } catch (e) {
+ // ignore; stream might have shut down, and we don't bother clearing
+ // the setInterval.
+ }
+ }, 500);
+ }
+ ]);
+ test.chain.append([
+ function FIND_REMOTE_VIDEO() {
+ vremote = test.pcRemote.remoteMediaElements[0];
+ ok(!!vremote, "Should have remote video element for pcRemote");
+ },
+ function WAIT_FOR_REMOTE_GREEN() {
+ return h.pixelMustBecome(vremote, h.green, {
+ threshold: 128,
+ infoString: "pcRemote's remote should become green",
+ });
+ },
+ function REQUEST_FRAME(test) {
+ // After requesting a frame it will be captured at the time of next render.
+ // Next render will happen at next stable state, at the earliest,
+ // i.e., this order of `requestFrame(); draw();` should work.
+ test.pcLocal.canvasStream.requestFrame();
+ },
+ function DRAW_LOCAL_RED() {
+ h.drawColor(canvas, h.red);
+ },
+ function WAIT_FOR_REMOTE_RED() {
+ return h.pixelMustBecome(vremote, h.red, {
+ threshold: 128,
+ infoString: "pcRemote's remote should become red",
+ });
+ }
+ ]);
+ await test.run();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_capturedVideo.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_capturedVideo.html
new file mode 100644
index 0000000000..f6f48ba429
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_capturedVideo.html
@@ -0,0 +1,81 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script src="pc.js"></script>
+ <script src="../../../test/manifest.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+(async () => {
+ await createHTML({
+ bug: "1081409",
+ title: "Captured video-only over peer connection",
+ visible: true
+ });
+
+ // Run tests in sequence for log readability.
+ PARALLEL_TESTS = 1;
+ const manager = new MediaTestManager;
+
+ async function startTest(media, token) {
+ manager.started(token);
+ info(`Starting test for ${media.name}`);
+ const video = document.createElement('video');
+ video.id = "id_" + media.name;
+ video.width = 160;
+ video.height = 120;
+ video.muted = true;
+ video.controls = true;
+ video.preload = "metadata";
+ video.src = "../../../test/" + media.name;
+
+ document.getElementById("content").appendChild(video);
+
+ const onerror = new Promise(r => video.onerror = r).then(_ =>
+ new Error(`${media.name} failed in playback. code=${video.error.code}`));
+
+ await Promise.race([
+ new Promise(res => video.onloadedmetadata = res),
+ onerror,
+ ]);
+ onerror.catch(e => ok(false, e));
+ setupEnvironment();
+ await testConfigured;
+ const stream = video.mozCaptureStream();
+ const test = new PeerConnectionTest(
+ {
+ config_local: { label_suffix: media.name },
+ config_remote: { label_suffix: media.name },
+ }
+ );
+ test.setOfferOptions(
+ {
+ offerToReceiveVideo: false,
+ offerToReceiveAudio: false,
+ }
+ );
+ const hasVideo = !!stream.getVideoTracks().length;
+ const hasAudio = !!stream.getAudioTracks().length;
+ test.setMediaConstraints([{ video: hasVideo, audio: hasAudio }], []);
+ test.chain.replace("PC_LOCAL_GUM", [
+ function PC_LOCAL_CAPTUREVIDEO(test) {
+ test.pcLocal.attachLocalStream(stream);
+ },
+ ]);
+ test.chain.insertBefore("PC_LOCAL_WAIT_FOR_MEDIA_FLOW", [
+ function PC_LOCAL_START_MEDIA(test) {
+ video.play();
+ },
+ ]);
+ await test.run();
+ removeNodeAndSource(video);
+ manager.finished(token);
+ }
+
+ manager.runTests(getPlayableVideos(gLongerTests), startTest);
+})();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_certificates.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_certificates.html
new file mode 100644
index 0000000000..561f285f60
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_certificates.html
@@ -0,0 +1,185 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1172785",
+ title: "Certificate management"
+ });
+
+ function badCertificate(config, expectedError, message) {
+ return RTCPeerConnection.generateCertificate(config)
+ .then(() => ok(false, message),
+ e => is(e.name, expectedError, message));
+ }
+
+ // Checks a handful of obviously bad options to RTCCertificate.create(). Most
+ // of the checking is done by the WebCrypto code underpinning this, hence the
+ // baffling error codes, but a sanity check is still in order.
+ function checkBadParameters() {
+ return Promise.all([
+ badCertificate({
+ name: "RSASSA-PKCS1-v1_5",
+ hash: "SHA-256",
+ modulusLength: 1023,
+ publicExponent: new Uint8Array([1, 0, 1])
+ }, "NotSupportedError", "1023-bit is too small to succeed"),
+
+ badCertificate({
+ name: "RSASSA-PKCS1-v1_5",
+ hash: "SHA-384",
+ modulusLength: 2048,
+ publicExponent: new Uint8Array([1, 0, 1])
+ }, "NotSupportedError", "SHA-384 isn't supported yet"),
+
+ // A SyntaxError happens in the "generate key operation" step, but
+ // webrtc-pc does not say to reject the promise if this step fails.
+ // It does say to throw NotSupportedError if we have passed "an
+ // algorithm that the user agent cannot or will not use to generate a
+ // certificate".
+ badCertificate({
+ name: "ECDH",
+ namedCurve: "P-256"
+ }, "NotSupportedError", "ECDH is rejected because the usage is neither \"deriveKey\" or \"deriveBits\""),
+
+ badCertificate({
+ name: "not a valid algorithm"
+ }, "NotSupportedError", "not a valid algorithm"),
+
+ badCertificate("ECDSA", "NotSupportedError", "a bare name is not enough"),
+
+ badCertificate({
+ name: "ECDSA",
+ namedCurve: "not a curve"
+ }, "NotSupportedError", "ECDSA with an unknown curve")
+ ]);
+ }
+
+ function createDB() {
+ var openDB = indexedDB.open("genericstore");
+ openDB.onupgradeneeded = e => {
+ var db = e.target.result;
+ db.createObjectStore("data");
+ };
+ return new Promise(resolve => {
+ openDB.onsuccess = e => resolve(e.target.result);
+ });
+ }
+
+ function resultPromise(tx, op) {
+ return new Promise((resolve, reject) => {
+ op.onsuccess = e => resolve(e.target.result);
+ op.onerror = () => reject(op.error);
+ tx.onabort = () => reject(tx.error);
+ });
+ }
+
+ function store(db, value) {
+ var tx = db.transaction("data", "readwrite");
+ var store = tx.objectStore("data");
+ return resultPromise(tx, store.put(value, "value"));
+ }
+
+ function retrieve(db) {
+ var tx = db.transaction("data", "readonly");
+ var store = tx.objectStore("data");
+ return resultPromise(tx, store.get("value"));
+ }
+
+ // Creates a database, stores a value, retrieves it.
+ function storeAndRetrieve(value) {
+ return createDB().then(db => {
+ return store(db, value)
+ .then(() => retrieve(db))
+ .then(retrieved => {
+ db.close();
+ return retrieved;
+ });
+ });
+ }
+
+ var test;
+ runNetworkTest(function (options) {
+ var expiredCert;
+ return Promise.resolve()
+ .then(() => RTCPeerConnection.generateCertificate({
+ name: "ECDSA",
+ namedCurve: "P-256",
+ expires: 1 // smallest possible expiration window
+ }))
+ .then(cert => {
+ ok(!isNaN(cert.expires), 'cert has expiration time');
+ info('Expires at ' + new Date(cert.expires));
+ expiredCert = cert;
+ })
+
+ .then(() => checkBadParameters())
+
+ .then(() => {
+ var delay = expiredCert.expires - Date.now();
+ // Hopefully this delay is never needed.
+ if (delay > 0) {
+ return new Promise(r => setTimeout(r, delay));
+ }
+ })
+ .then(() => {
+ ok(expiredCert.expires <= Date.now(), 'Cert should be at or past expiration');
+ try {
+ new RTCPeerConnection({ certificates: [expiredCert] });
+ ok(false, 'Constructing peer connection with an expired cert is not allowed');
+ } catch(e) {
+ is(e.name, 'InvalidAccessError',
+ 'Constructing peer connection with an expired certs is not allowed');
+ }
+ })
+
+ .then(() => Promise.all([
+ RTCPeerConnection.generateCertificate({
+ name: "ECDSA",
+ namedCurve: "P-256"
+ }),
+ RTCPeerConnection.generateCertificate({
+ name: "RSASSA-PKCS1-v1_5",
+ hash: "SHA-256",
+ modulusLength: 2048,
+ publicExponent: new Uint8Array([1, 0, 1])
+ })
+ ]))
+
+ // A round trip through indexedDB should not do anything.
+ .then(storeAndRetrieve)
+ .then(certs => {
+ try {
+ new RTCPeerConnection({ certificates: certs });
+ ok(false, 'Constructing peer connection with multiple certs is not allowed');
+ } catch(e) {
+ is(e.name, 'NotSupportedError',
+ 'Constructing peer connection with multiple certs is not allowed');
+ }
+ return certs;
+ })
+ .then(certs => {
+ test = new PeerConnectionTest({
+ config_local: {
+ certificates: [certs[0]]
+ },
+ config_remote: {
+ certificates: [certs[1]]
+ }
+ });
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ return test.run();
+ })
+ .catch(e => {
+ console.log('test failure', e);
+ ok(false, 'test failed: ' + e);
+ });
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_checkPacketDumpHook.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_checkPacketDumpHook.html
new file mode 100644
index 0000000000..248e102dd2
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_checkPacketDumpHook.html
@@ -0,0 +1,107 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1377299",
+ title: "Check that packet dump hooks generate callbacks"
+ });
+
+ function waitForPacket(pc, checkFunction) {
+ return new Promise(resolve => {
+ function onPacket(level, type, sending, packet) {
+ if (checkFunction(level, type, sending, packet)) {
+ SpecialPowers.wrap(pc).mozSetPacketCallback(() => {});
+ resolve();
+ }
+ }
+
+ SpecialPowers.wrap(pc).mozSetPacketCallback(onPacket);
+ }
+ );
+ }
+
+ async function waitForSendPacket(pc, type, level) {
+ await SpecialPowers.wrap(pc).mozEnablePacketDump(level, type, true);
+ await timeout(
+ waitForPacket(pc, (obsLevel, obsType, sending) => {
+ is(obsLevel, level, "Level for packet is " + level);
+ is(obsType, type, "Type for packet is " + type);
+ ok(sending, "This is a send packet");
+ return true;
+ }),
+ 10000, "Timeout waiting for " + type + " send packet on level " + level);
+ await SpecialPowers.wrap(pc).mozDisablePacketDump(level, type, true);
+ }
+
+ async function waitForRecvPacket(pc, type, level) {
+ await SpecialPowers.wrap(pc).mozEnablePacketDump(level, type, false);
+ await timeout(
+ waitForPacket(pc, (obsLevel, obsType, sending) => {
+ is(obsLevel, level, "Level for packet is " + level);
+ is(obsType, type, "Type for packet is " + type);
+ ok(!sending, "This is a recv packet");
+ return true;
+ }),
+ 10000, "Timeout waiting for " + type + " recv packet on level " + level);
+ await SpecialPowers.wrap(pc).mozDisablePacketDump(level, type, false);
+ }
+
+ var test;
+ runNetworkTest(function (options) {
+ test = new PeerConnectionTest(options);
+ test.setMediaConstraints([{audio: true, video: true}],
+ [{audio: true, video: true}]);
+ // pc.js uses video elements by default, we want to test audio elements here
+ test.pcLocal.audioElementsOnly = true;
+
+ test.chain.insertBefore('PC_LOCAL_WAIT_FOR_MEDIA_FLOW',[
+ async function PC_LOCAL_CHECK_PACKET_DUMP_HOOKS() {
+ await waitForRecvPacket(test.pcLocal._pc, "rtp", 0);
+ await waitForRecvPacket(test.pcLocal._pc, "rtcp", 0);
+ await waitForRecvPacket(test.pcLocal._pc, "srtp", 0);
+ await waitForRecvPacket(test.pcLocal._pc, "srtcp", 0);
+ await waitForSendPacket(test.pcLocal._pc, "rtp", 0);
+ await waitForSendPacket(test.pcLocal._pc, "rtcp", 0);
+ await waitForSendPacket(test.pcLocal._pc, "srtp", 0);
+ await waitForSendPacket(test.pcLocal._pc, "srtcp", 0);
+
+ await waitForRecvPacket(test.pcLocal._pc, "rtp", 1);
+ await waitForRecvPacket(test.pcLocal._pc, "rtcp", 1);
+ await waitForRecvPacket(test.pcLocal._pc, "srtp", 1);
+ await waitForRecvPacket(test.pcLocal._pc, "srtcp", 1);
+ await waitForSendPacket(test.pcLocal._pc, "rtp", 1);
+ await waitForSendPacket(test.pcLocal._pc, "rtcp", 1);
+ await waitForSendPacket(test.pcLocal._pc, "srtp", 1);
+ await waitForSendPacket(test.pcLocal._pc, "srtcp", 1);
+ },
+ async function PC_REMOTE_CHECK_PACKET_DUMP_HOOKS() {
+ await waitForRecvPacket(test.pcRemote._pc, "rtp", 0);
+ await waitForRecvPacket(test.pcRemote._pc, "rtcp", 0);
+ await waitForRecvPacket(test.pcRemote._pc, "srtp", 0);
+ await waitForRecvPacket(test.pcRemote._pc, "srtcp", 0);
+ await waitForSendPacket(test.pcRemote._pc, "rtp", 0);
+ await waitForSendPacket(test.pcRemote._pc, "rtcp", 0);
+ await waitForSendPacket(test.pcRemote._pc, "srtp", 0);
+ await waitForSendPacket(test.pcRemote._pc, "srtcp", 0);
+
+ await waitForRecvPacket(test.pcRemote._pc, "rtp", 1);
+ await waitForRecvPacket(test.pcRemote._pc, "rtcp", 1);
+ await waitForRecvPacket(test.pcRemote._pc, "srtp", 1);
+ await waitForRecvPacket(test.pcRemote._pc, "srtcp", 1);
+ await waitForSendPacket(test.pcRemote._pc, "rtp", 1);
+ await waitForSendPacket(test.pcRemote._pc, "rtcp", 1);
+ await waitForSendPacket(test.pcRemote._pc, "srtp", 1);
+ await waitForSendPacket(test.pcRemote._pc, "srtcp", 1);
+ }
+ ]);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_close.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_close.html
new file mode 100644
index 0000000000..3edf677203
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_close.html
@@ -0,0 +1,134 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "991877",
+ title: "Basic RTCPeerConnection.close() tests"
+ });
+
+ runNetworkTest(function () {
+ var pc = new RTCPeerConnection();
+ var sender = pc.addTrack(getSilentTrack(), new MediaStream());
+ var exception = null;
+ var eTimeout = null;
+
+ // everything should be in initial state
+ is(pc.signalingState, "stable", "Initial signalingState is 'stable'");
+ is(pc.iceConnectionState, "new", "Initial iceConnectionState is 'new'");
+ is(pc.iceGatheringState, "new", "Initial iceGatheringState is 'new'");
+
+ var finish;
+ var finished = new Promise(resolve => finish = resolve);
+
+ var mustNotSettle = (p, ms, msg) => Promise.race([
+ p.then(() => ok(false, msg + " must not settle"),
+ e => ok(false, msg + " must not settle. Got " + e.name)),
+ wait(ms).then(() => ok(true, msg + " must not settle"))
+ ]);
+
+ var silence = mustNotSettle(pc.createOffer(), 1000,
+ "createOffer immediately followed by close");
+ try {
+ pc.close();
+ } catch (e) {
+ exception = e;
+ }
+ is(exception, null, "closing the connection raises no exception");
+ is(pc.signalingState, "closed", "Final signalingState is 'closed'");
+ is(pc.iceConnectionState, "closed", "Final iceConnectionState is 'closed'");
+
+ // test that pc is really closed (and doesn't crash, bug 1259728)
+ try {
+ pc.getLocalStreams();
+ } catch (e) {
+ exception = e;
+ }
+ is(exception && exception.name, "InvalidStateError",
+ "pc.getLocalStreams should throw when closed");
+ exception = null;
+
+ try {
+ pc.close();
+ } catch (e) {
+ exception = e;
+ }
+ is(exception, null, "A second close() should not raise an exception");
+ is(pc.signalingState, "closed", "Final signalingState stays at 'closed'");
+ is(pc.iceConnectionState, "closed", "Final iceConnectionState stays at 'closed'");
+
+ // Due to a limitation in our WebIDL compiler that prevents overloads with
+ // both Promise and non-Promise return types, legacy APIs with callbacks
+ // are unable to continue to throw exceptions. Luckily the spec uses
+ // exceptions solely for "programming errors" so this should not hinder
+ // working code from working, which is the point of the legacy API. All
+ // new code should use the promise API.
+ //
+ // The legacy methods that no longer throw on programming errors like
+ // "invalid-on-close" are:
+ // - createOffer
+ // - createAnswer
+ // - setLocalDescription
+ // - setRemoteDescription
+ // - addIceCandidate
+ // - getStats
+ //
+ // These legacy methods fire the error callback instead. This is not
+ // entirely to spec but is better than ignoring programming errors.
+
+ var offer = new RTCSessionDescription({ sdp: "sdp", type: "offer" });
+ var answer = new RTCSessionDescription({ sdp: "sdp", type: "answer" });
+ var candidate = new RTCIceCandidate({ candidate: "dummy",
+ sdpMid: "test",
+ sdpMLineIndex: 3 });
+
+ var doesFail = (p, msg) => p.then(generateErrorCallback(msg),
+ r => is(r.name, "InvalidStateError", msg));
+ Promise.all([
+ [pc.createOffer(), "createOffer"],
+ [pc.createOffer({offerToReceiveAudio: true}), "createOffer({offerToReceiveAudio: true})"],
+ [pc.createOffer({offerToReceiveAudio: false}), "createOffer({offerToReceiveAudio: false})"],
+ [pc.createOffer({offerToReceiveVideo: true}), "createOffer({offerToReceiveVideo: true})"],
+ [pc.createOffer({offerToReceiveVideo: false}), "createOffer({offerToReceiveVideo: false})"],
+ [pc.createAnswer(), "createAnswer"],
+ [pc.setLocalDescription(offer), "setLocalDescription"],
+ [pc.setRemoteDescription(answer), "setRemoteDescription"],
+ [pc.addIceCandidate(candidate), "addIceCandidate"],
+ [new Promise((y, n) => pc.createOffer(y, n)), "Legacy createOffer"],
+ [new Promise((y, n) => pc.createAnswer(y, n)), "Legacy createAnswer"],
+ [new Promise((y, n) => pc.setLocalDescription(offer, y, n)), "Legacy setLocalDescription"],
+ [new Promise((y, n) => pc.setRemoteDescription(answer, y, n)), "Legacy setRemoteDescription"],
+ [new Promise((y, n) => pc.addIceCandidate(candidate, y, n)), "Legacy addIceCandidate"],
+ [sender.replaceTrack(getSilentTrack()), "replaceTrack"],
+ ].map(([p, name]) => doesFail(p, name + " fails on close")))
+ .catch(reason => ok(false, "unexpected failure: " + reason))
+ .then(finish);
+
+ // Other methods are unaffected.
+
+ SimpleTest.doesThrow(function() {
+ pc.updateIce("Invalid RTC Configuration")},
+ "updateIce() on closed PC raised expected exception");
+
+ SimpleTest.doesThrow(function() {
+ pc.addStream("Invalid Media Stream")},
+ "addStream() on closed PC raised expected exception");
+
+ SimpleTest.doesThrow(function() {
+ pc.createDataChannel({})},
+ "createDataChannel() on closed PC raised expected exception");
+
+ SimpleTest.doesThrow(function() {
+ pc.setIdentityProvider("Invalid Provider")},
+ "setIdentityProvider() on closed PC raised expected exception");
+
+ return Promise.all([finished, silence]);
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_closeDuringIce.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_closeDuringIce.html
new file mode 100644
index 0000000000..db3a2922d5
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_closeDuringIce.html
@@ -0,0 +1,79 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1087629",
+ title: "Close PCs during ICE connectivity check"
+ });
+
+// Test closeDuringIce to simulate problems during peer connections
+
+
+function PC_LOCAL_SETUP_NULL_ICE_HANDLER(test) {
+ test.pcLocal.setupIceCandidateHandler(test, function() {}, function () {});
+}
+function PC_REMOTE_SETUP_NULL_ICE_HANDLER(test) {
+ test.pcRemote.setupIceCandidateHandler(test, function() {}, function () {});
+}
+function PC_REMOTE_ADD_FAKE_ICE_CANDIDATE(test) {
+ var cand = {"candidate":"candidate:0 1 UDP 2130379007 192.0.2.1 12345 typ host","sdpMid":"","sdpMLineIndex":0};
+ test.pcRemote.storeOrAddIceCandidate(cand);
+ info(test.pcRemote + " Stored fake candidate: " + JSON.stringify(cand));
+}
+function PC_LOCAL_ADD_FAKE_ICE_CANDIDATE(test) {
+ var cand = {"candidate":"candidate:0 1 UDP 2130379007 192.0.2.2 56789 typ host","sdpMid":"","sdpMLineIndex":0};
+ test.pcLocal.storeOrAddIceCandidate(cand);
+ info(test.pcLocal + " Stored fake candidate: " + JSON.stringify(cand));
+}
+function PC_LOCAL_CLOSE_DURING_ICE(test) {
+ return test.pcLocal.iceChecking.then(() => {
+ test.pcLocal.onsignalingstatechange = function () {};
+ test.pcLocal.close();
+ });
+}
+function PC_REMOTE_CLOSE_DURING_ICE(test) {
+ return test.pcRemote.iceChecking.then(() => {
+ test.pcRemote.onsignalingstatechange = function () {};
+ test.pcRemote.close();
+ });
+}
+function PC_LOCAL_WAIT_FOR_ICE_CHECKING(test) {
+ var resolveIceChecking;
+ test.pcLocal.iceChecking = new Promise(r => resolveIceChecking = r);
+ test.pcLocal.ice_connection_callbacks.checkIceStatus = () => {
+ if (test.pcLocal._pc.iceConnectionState === "checking") {
+ resolveIceChecking();
+ }
+ }
+}
+function PC_REMOTE_WAIT_FOR_ICE_CHECKING(test) {
+ var resolveIceChecking;
+ test.pcRemote.iceChecking = new Promise(r => resolveIceChecking = r);
+ test.pcRemote.ice_connection_callbacks.checkIceStatus = () => {
+ if (test.pcRemote._pc.iceConnectionState === "checking") {
+ resolveIceChecking();
+ }
+ }
+}
+
+runNetworkTest(() => {
+ var test = new PeerConnectionTest();
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ test.chain.replace("PC_LOCAL_SETUP_ICE_HANDLER", PC_LOCAL_SETUP_NULL_ICE_HANDLER);
+ test.chain.replace("PC_REMOTE_SETUP_ICE_HANDLER", PC_REMOTE_SETUP_NULL_ICE_HANDLER);
+ test.chain.insertAfter("PC_REMOTE_SETUP_NULL_ICE_HANDLER", PC_LOCAL_WAIT_FOR_ICE_CHECKING);
+ test.chain.insertAfter("PC_LOCAL_WAIT_FOR_ICE_CHECKING", PC_REMOTE_WAIT_FOR_ICE_CHECKING);
+ test.chain.removeAfter("PC_LOCAL_SET_REMOTE_DESCRIPTION");
+ test.chain.append([PC_REMOTE_ADD_FAKE_ICE_CANDIDATE, PC_LOCAL_ADD_FAKE_ICE_CANDIDATE,
+ PC_LOCAL_CLOSE_DURING_ICE, PC_REMOTE_CLOSE_DURING_ICE]);
+ return test.run();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_codecNegotiationFailure.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_codecNegotiationFailure.html
new file mode 100644
index 0000000000..819e13fe1b
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_codecNegotiationFailure.html
@@ -0,0 +1,111 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="iceTestUtils.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1683934",
+ title: "RTCPeerConnection check codec negotiation failure"
+ });
+
+ function makeWeirdCodecs(sdp) {
+ return sdp
+ .replaceAll('VP8', 'VEEEEEEEEP8')
+ .replaceAll('VP9', 'VEEEEEEEEP9')
+ .replaceAll('H264', 'HERP264');
+ }
+
+ const tests = [
+ async function offererWeirdCodecs() {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+
+ const stream = await navigator.mediaDevices.getUserMedia({video: true});
+ pc1.addTrack(stream.getTracks()[0]);
+ pc2.addTrack(stream.getTracks()[0]);
+
+ const offer = await pc1.createOffer();
+ offer.sdp = makeWeirdCodecs(offer.sdp);
+ // It is not an error to receive an offer with no codecs we support
+ await pc2.setRemoteDescription(offer);
+ await pc2.setLocalDescription();
+ await wait(2000);
+ },
+
+ async function answererWeirdCodecs() {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+
+ const stream = await navigator.mediaDevices.getUserMedia({video: true});
+ pc1.addTrack(stream.getTracks()[0]);
+ pc2.addTrack(stream.getTracks()[0]);
+
+ await pc1.setLocalDescription();
+ await pc2.setRemoteDescription(pc1.localDescription);
+ const answer = await pc2.createAnswer();
+ answer.sdp = makeWeirdCodecs(answer.sdp);
+ try {
+ await pc1.setRemoteDescription(answer);
+ ok(false, "Should have thrown");
+ } catch (e) {
+ ok(true, "Should have thrown");
+ }
+ },
+
+ async function reoffererWeirdCodecs() {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+
+ const stream = await navigator.mediaDevices.getUserMedia({video: true});
+ pc1.addTrack(stream.getTracks()[0]);
+ pc2.addTrack(stream.getTracks()[0]);
+
+ await connect(pc1, pc2, 32000, "Initial connection");
+
+ const offer = await pc1.createOffer();
+ offer.sdp = makeWeirdCodecs(offer.sdp);
+ // It is not an error to receive an offer with no codecs we support
+ await pc2.setRemoteDescription(offer);
+ await pc2.setLocalDescription();
+ await wait(2000);
+ },
+
+ async function reanswererWeirdCodecs() {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+
+ const stream = await navigator.mediaDevices.getUserMedia({video: true});
+ pc1.addTrack(stream.getTracks()[0]);
+ pc2.addTrack(stream.getTracks()[0]);
+
+ await connect(pc1, pc2, 32000, "Initial connection");
+ await pc1.setLocalDescription();
+ await pc2.setRemoteDescription(pc1.localDescription);
+ const answer = await pc2.createAnswer();
+ answer.sdp = makeWeirdCodecs(answer.sdp);
+ try {
+ await pc1.setRemoteDescription(answer);
+ ok(false, "Should have thrown");
+ } catch (e) {
+ ok(true, "Should have thrown");
+ }
+ },
+
+ ];
+
+ runNetworkTest(async () => {
+ for (const test of tests) {
+ info(`Running test: ${test.name}`);
+ await test();
+ info(`Done running test: ${test.name}`);
+ }
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_constructedStream.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_constructedStream.html
new file mode 100644
index 0000000000..8431b7534e
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_constructedStream.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ bug: "1271669",
+ title: "Test that pc.addTrack() accepts any MediaStream",
+ visible: true
+});
+
+runNetworkTest(() => {
+ var test = new PeerConnectionTest();
+ var constructedStream;
+ var dummyStream = new MediaStream();
+ var dummyStreamTracks = [];
+
+ test.setMediaConstraints([ {audio: true, video: true}
+ , {audio: true}
+ , {video: true}
+ ], []);
+ test.chain.replace("PC_LOCAL_GUM", [
+ function PC_LOCAL_GUM_CONSTRUCTED_STREAM(test) {
+ return getUserMedia(test.pcLocal.constraints[0]).then(stream => {
+ constructedStream = new MediaStream(stream.getTracks());
+ test.pcLocal.attachLocalStream(constructedStream);
+ });
+ },
+ function PC_LOCAL_GUM_DUMMY_STREAM(test) {
+ return getUserMedia(test.pcLocal.constraints[1])
+ .then(stream => dummyStreamTracks.push(...stream.getTracks()))
+ .then(() => getUserMedia(test.pcLocal.constraints[2]))
+ .then(stream => dummyStreamTracks.push(...stream.getTracks()))
+ .then(() => dummyStreamTracks.forEach(t =>
+ test.pcLocal.attachLocalTrack(t, dummyStream)));
+ },
+ ]);
+
+ let checkSentTracksReceived = (sentStreamId, sentTracks) => {
+ let receivedStream =
+ test.pcRemote._pc.getRemoteStreams().find(s => s.id == sentStreamId);
+ ok(receivedStream, "We should receive a stream with with the sent stream's id (" + sentStreamId + ")");
+ if (!receivedStream) {
+ return;
+ }
+
+ is(receivedStream.getTracks().length, sentTracks.length,
+ "Should receive same number of tracks as were sent");
+ };
+
+ test.chain.append([
+ function PC_REMOTE_CHECK_RECEIVED_CONSTRUCTED_STREAM() {
+ checkSentTracksReceived(constructedStream.id, constructedStream.getTracks());
+ },
+ function PC_REMOTE_CHECK_RECEIVED_DUMMY_STREAM() {
+ checkSentTracksReceived(dummyStream.id, dummyStreamTracks);
+ },
+ ]);
+ return test.run();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_disabledVideoPreNegotiation.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_disabledVideoPreNegotiation.html
new file mode 100644
index 0000000000..4c06de792e
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_disabledVideoPreNegotiation.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1570673",
+ title: "Sending an initially disabled video track should be playable remotely",
+ visible: true,
+ });
+
+ var test;
+ runNetworkTest(async (options) => {
+ // [TODO] re-enable HW decoder after bug 1526207 is fixed.
+ if (navigator.userAgent.includes("Android")) {
+ await pushPrefs(["media.navigator.mediadatadecoder_vpx_enabled", false],
+ ["media.webrtc.hw.h264.enabled", false]);
+ }
+
+ test = new PeerConnectionTest(options);
+ test.setMediaConstraints([{video: true}], []);
+ test.chain.insertAfter("PC_LOCAL_GUM", function PC_LOCAL_DISABLE_VIDEO() {
+ for (const {track} of test.pcLocal._pc.getSenders()) {
+ if (track.kind == "video") {
+ track.enabled = false;
+ }
+ }
+ });
+ test.chain.append(async function PC_REMOTE_RECEIVING_BLACK() {
+ const v = test.pcRemote.remoteMediaElements[0];
+ is(v.readyState, v.HAVE_ENOUGH_DATA, "video element should be playing");
+ const h = new CaptureStreamTestHelper2D();
+ await h.waitForPixel(test.pcRemote.remoteMediaElements[0],
+ px => h.isPixel(px, h.black, 128));
+ });
+ await test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_encodingsNegotiation.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_encodingsNegotiation.html
new file mode 100644
index 0000000000..f46d7eb0d2
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_encodingsNegotiation.html
@@ -0,0 +1,85 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="simulcast.js"></script>
+ <script type="application/javascript" src="helpers_from_wpt/sdp.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ bug: "1401592",
+ title: "Simulcast negotiation tests",
+ visible: true
+});
+
+// simulcast negotiation is mostly tested in wpt, but we test a few
+// implementation-specific things here.
+const tests = [
+ async function checkVideoEncodingLimit() {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+
+ const stream = await navigator.mediaDevices.getUserMedia({video: true});
+ const sender = pc1.addTrack(stream.getTracks()[0]);
+ pc2.addTrack(stream.getTracks()[0]);
+
+ await doOfferToRecvSimulcast(pc2, pc1, ["1", "2", "3", "4"]);
+
+ const {encodings} = sender.getParameters();
+ const rids = encodings.map(({rid}) => rid);
+ isDeeply(rids, ["1", "2", "3"]);
+
+ pc1.close();
+ pc2.close();
+ stream.getTracks().forEach(track => track.stop());
+ },
+
+ // wpt currently does not assume support for 3 encodings, which limits the
+ // effectiveness of its powers-of-2 test (since it can test only for 1 and 2)
+ async function checkScaleResolutionDownByAutoFillPowersOf2() {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+ const stream = await navigator.mediaDevices.getUserMedia({video: true});
+ const sender = pc1.addTrack(stream.getTracks()[0]);
+ pc2.addTrack(stream.getTracks()[0]);
+
+ await doOfferToRecvSimulcast(pc2, pc1, ["1", "2", "3"]);
+
+ const {encodings} = sender.getParameters();
+ const scaleValues = encodings.map(({scaleResolutionDownBy}) => scaleResolutionDownBy);
+ isDeeply(scaleValues, [4, 2, 1]);
+ },
+
+ async function checkLibwebrtcRidLengthLimit() {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+
+ const stream = await navigator.mediaDevices.getUserMedia({video: true});
+ const sender = pc1.addTrack(stream.getTracks()[0]);
+ pc2.addTrack(stream.getTracks()[0]);
+
+ await doOfferToRecvSimulcast(pc2, pc1, ["foo", "wibblywobblyjeremybearimy"]);
+ const {encodings} = sender.getParameters();
+ const rids = encodings.map(({rid}) => rid);
+ isDeeply(rids, ["foo"]);
+
+ pc1.close();
+ pc2.close();
+ stream.getTracks().forEach(track => track.stop());
+ },
+];
+
+runNetworkTest(async () => {
+ for (const test of tests) {
+ info(`Running test: ${test.name}`);
+ await test();
+ info(`Done running test: ${test.name}`);
+ }
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_errorCallbacks.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_errorCallbacks.html
new file mode 100644
index 0000000000..851a256509
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_errorCallbacks.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "834270",
+ title: "Align PeerConnection error handling with WebRTC specification"
+ });
+
+ function validateReason(reason) {
+ ok(reason.name.length, "Reason name = " + reason.name);
+ ok(reason.message.length, "Reason message = " + reason.message);
+ };
+
+ function testCreateAnswerError() {
+ var pc = new RTCPeerConnection();
+ info ("Testing createAnswer error");
+ return pc.createAnswer()
+ .then(generateErrorCallback("createAnswer before offer should fail"),
+ validateReason);
+ };
+
+ function testSetLocalDescriptionError() {
+ var pc = new RTCPeerConnection();
+ info ("Testing setLocalDescription error");
+ return pc.setLocalDescription({ sdp: "Picklechips!", type: "offer" })
+ .then(generateErrorCallback("setLocalDescription with nonsense SDP should fail"),
+ validateReason);
+ };
+
+ function testSetRemoteDescriptionError() {
+ var pc = new RTCPeerConnection();
+ info ("Testing setRemoteDescription error");
+ return pc.setRemoteDescription({ sdp: "Who?", type: "offer" })
+ .then(generateErrorCallback("setRemoteDescription with nonsense SDP should fail"),
+ validateReason);
+ };
+
+ // No test for createOffer errors -- there's nothing we can do at this
+ // level to evoke an error in createOffer.
+
+ runNetworkTest(function () {
+ return testCreateAnswerError()
+ .then(testSetLocalDescriptionError)
+ .then(testSetRemoteDescriptionError);
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_extmapRenegotiation.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_extmapRenegotiation.html
new file mode 100644
index 0000000000..78c6bb986c
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_extmapRenegotiation.html
@@ -0,0 +1,325 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="iceTestUtils.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1799932",
+ title: "RTCPeerConnection check renegotiation of extmap"
+ });
+
+ function setExtmap(sdp, uri, id) {
+ const regex = new RegExp(`a=extmap:[0-9]+(\/[a-z]+)? ${uri}`, 'g');
+ if (id) {
+ return sdp.replaceAll(regex, `a=extmap:${id}$1 ${uri}`);
+ } else {
+ return sdp.replaceAll(regex, `a=unknownattr`);
+ }
+ }
+
+ function getExtmap(sdp, uri) {
+ const regex = new RegExp(`a=extmap:([0-9]+)(\/[a-z]+)? ${uri}`);
+ return sdp.match(regex)[1];
+ }
+
+ function replaceExtUri(sdp, oldUri, newUri) {
+ const regex = new RegExp(`(a=extmap:[0-9]+\/[a-z]+)? ${oldUri}`, 'g');
+ return sdp.replaceAll(regex, `$1 ${newUri}`);
+ }
+
+ const tests = [
+ async function checkAudioMidChange() {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+
+ const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+ pc1.addTrack(stream.getTracks()[0]);
+ pc2.addTrack(stream.getTracks()[0]);
+
+ await connect(pc1, pc2, 32000, "Initial connection");
+
+ // Sadly, there's no way to tell the offerer to change the extmap. Other
+ // types of endpoint could conceivably do this, so we at least don't want
+ // to crash.
+ // TODO: Would be nice to be able to test this with an endpoint that
+ // actually changes the ids it uses.
+ const reoffer = await pc1.createOffer();
+ reoffer.sdp = setExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:sdes:mid", 14);
+ info(`New reoffer: ${reoffer.sdp}`);
+ await pc2.setRemoteDescription(reoffer);
+ await pc2.setLocalDescription();
+ await wait(2000);
+ },
+
+ async function checkVideoMidChange() {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+
+ const stream = await navigator.mediaDevices.getUserMedia({video: true});
+ pc1.addTrack(stream.getTracks()[0]);
+ pc2.addTrack(stream.getTracks()[0]);
+
+ await connect(pc1, pc2, 32000, "Initial connection");
+
+ // Sadly, there's no way to tell the offerer to change the extmap. Other
+ // types of endpoint could conceivably do this, so we at least don't want
+ // to crash.
+ // TODO: Would be nice to be able to test this with an endpoint that
+ // actually changes the ids it uses.
+ const reoffer = await pc1.createOffer();
+ reoffer.sdp = setExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:sdes:mid", 14);
+ info(`New reoffer: ${reoffer.sdp}`);
+ await pc2.setRemoteDescription(reoffer);
+ await pc2.setLocalDescription();
+ await wait(2000);
+ },
+
+ async function checkAudioMidSwap() {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+
+ const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+ pc1.addTrack(stream.getTracks()[0]);
+ pc2.addTrack(stream.getTracks()[0]);
+
+ await connect(pc1, pc2, 32000, "Initial connection");
+
+ // Sadly, there's no way to tell the offerer to change the extmap. Other
+ // types of endpoint could conceivably do this, so we at least don't want
+ // to crash.
+ // TODO: Would be nice to be able to test this with an endpoint that
+ // actually changes the ids it uses.
+ const reoffer = await pc1.createOffer();
+ const midId = getExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:sdes:mid");
+ const ssrcLevelId = getExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level");
+ reoffer.sdp = setExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:sdes:mid", ssrcLevelId);
+ reoffer.sdp = setExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", midId);
+ info(`New reoffer: ${reoffer.sdp}`);
+ try {
+ await pc2.setRemoteDescription(reoffer);
+ ok(false, "sRD should fail when it attempts extension id remapping");
+ } catch (e) {
+ ok(true, "sRD should fail when it attempts extension id remapping");
+ }
+ },
+
+ async function checkVideoMidSwap() {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+
+ const stream = await navigator.mediaDevices.getUserMedia({video: true});
+ pc1.addTrack(stream.getTracks()[0]);
+ pc2.addTrack(stream.getTracks()[0]);
+
+ await connect(pc1, pc2, 32000, "Initial connection");
+
+ // Sadly, there's no way to tell the offerer to change the extmap. Other
+ // types of endpoint could conceivably do this, so we at least don't want
+ // to crash.
+ // TODO: Would be nice to be able to test this with an endpoint that
+ // actually changes the ids it uses.
+ const reoffer = await pc1.createOffer();
+ const midId = getExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:sdes:mid");
+ const toffsetId = getExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:toffset");
+ reoffer.sdp = setExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:sdes:mid", toffsetId);
+ reoffer.sdp = setExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:toffset", midId);
+ info(`New reoffer: ${reoffer.sdp}`);
+ try {
+ await pc2.setRemoteDescription(reoffer);
+ ok(false, "sRD should fail when it attempts extension id remapping");
+ } catch (e) {
+ ok(true, "sRD should fail when it attempts extension id remapping");
+ }
+ },
+
+ async function checkAudioIdReuse() {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+
+ const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+ pc1.addTrack(stream.getTracks()[0]);
+ pc2.addTrack(stream.getTracks()[0]);
+
+ await connect(pc1, pc2, 32000, "Initial connection");
+
+ // Sadly, there's no way to tell the offerer to change the extmap. Other
+ // types of endpoint could conceivably do this, so we at least don't want
+ // to crash.
+ // TODO: Would be nice to be able to test this with an endpoint that
+ // actually changes the ids it uses.
+ const reoffer = await pc1.createOffer();
+ // Change uri, but not the id, so the id now refers to foo.
+ reoffer.sdp = replaceExtUri(reoffer.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", "foo");
+ info(`New reoffer: ${reoffer.sdp}`);
+ try {
+ await pc2.setRemoteDescription(reoffer);
+ ok(false, "sRD should fail when it attempts extension id remapping");
+ } catch (e) {
+ ok(true, "sRD should fail when it attempts extension id remapping");
+ }
+ },
+
+ async function checkVideoIdReuse() {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+
+ const stream = await navigator.mediaDevices.getUserMedia({video: true});
+ pc1.addTrack(stream.getTracks()[0]);
+ pc2.addTrack(stream.getTracks()[0]);
+
+ await connect(pc1, pc2, 32000, "Initial connection");
+
+ // Sadly, there's no way to tell the offerer to change the extmap. Other
+ // types of endpoint could conceivably do this, so we at least don't want
+ // to crash.
+ // TODO: Would be nice to be able to test this with an endpoint that
+ // actually changes the ids it uses.
+ const reoffer = await pc1.createOffer();
+ // Change uri, but not the id, so the id now refers to foo.
+ reoffer.sdp = replaceExtUri(reoffer.sdp, "urn:ietf:params:rtp-hdrext:toffset", "foo");
+ info(`New reoffer: ${reoffer.sdp}`);
+ try {
+ await pc2.setRemoteDescription(reoffer);
+ ok(false, "sRD should fail when it attempts extension id remapping");
+ } catch (e) {
+ ok(true, "sRD should fail when it attempts extension id remapping");
+ }
+ },
+
+ // What happens when remote answer uses an extmap id, and then a remote
+ // reoffer tries to use the same id for something else?
+ async function checkAudioIdReuseOffererThenAnswerer() {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+
+ const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+ pc1.addTrack(stream.getTracks()[0]);
+ pc2.addTrack(stream.getTracks()[0]);
+
+ await connect(pc1, pc2, 32000, "Initial connection");
+
+ const reoffer = await pc2.createOffer();
+ // Change uri, but not the id, so the id now refers to foo.
+ reoffer.sdp = replaceExtUri(reoffer.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", "foo");
+ info(`New reoffer: ${reoffer.sdp}`);
+ try {
+ await pc1.setRemoteDescription(reoffer);
+ ok(false, "sRD should fail when it attempts extension id remapping");
+ } catch (e) {
+ ok(true, "sRD should fail when it attempts extension id remapping");
+ }
+ },
+
+ // What happens when a remote offer uses a different extmap id than the
+ // default? Does the answerer remember the new id in reoffers?
+ async function checkAudioIdReuseOffererThenAnswerer() {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+
+ const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+ pc1.addTrack(stream.getTracks()[0]);
+ pc2.addTrack(stream.getTracks()[0]);
+
+ // Negotiate, but change id for ssrc-audio-level to something pc2 would
+ // not typically use.
+ await pc1.setLocalDescription();
+ const mungedOffer = setExtmap(pc1.localDescription.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", 12);
+ await pc2.setRemoteDescription({sdp: mungedOffer, type: "offer"});
+ await pc2.setLocalDescription();
+
+ const reoffer = await pc2.createOffer();
+ is(getExtmap(reoffer.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level"), "12");
+ },
+
+ async function checkAudioUnnegotiatedIdReuse1() {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+
+ const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+ pc1.addTrack(stream.getTracks()[0]);
+ pc2.addTrack(stream.getTracks()[0]);
+
+ // Negotiate, but remove ssrc-audio-level from answer
+ await pc1.setLocalDescription();
+ const levelId = getExtmap(pc1.localDescription.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level");
+ await pc2.setRemoteDescription(pc1.localDescription);
+ await pc2.setLocalDescription();
+ const answerNoExt = setExtmap(pc2.localDescription.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", undefined);
+ await pc1.setRemoteDescription({sdp: answerNoExt, type: "answer"});
+
+ // Renegotiate, and use the id that offerer used for ssrc-audio-level for
+ // something different (while making sure we don't use it twice)
+ await pc2.setLocalDescription();
+ const mungedReoffer = setExtmap(pc2.localDescription.sdp, "urn:ietf:params:rtp-hdrext:sdes:mid", levelId);
+ const twiceMungedReoffer = setExtmap(mungedReoffer, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", undefined);
+ await pc1.setRemoteDescription({sdp: twiceMungedReoffer, type: "offer"});
+ },
+
+ async function checkAudioUnnegotiatedIdReuse2() {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+
+ const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+ pc1.addTrack(stream.getTracks()[0]);
+ pc2.addTrack(stream.getTracks()[0]);
+
+ // Negotiate, but remove ssrc-audio-level from offer. pc2 has never seen
+ // |levelId| in extmap yet, but internally probably wants to use that for
+ // ssrc-audio-level
+ await pc1.setLocalDescription();
+ const levelId = getExtmap(pc1.localDescription.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level");
+ const offerNoExt = setExtmap(pc1.localDescription.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", undefined);
+ await pc2.setRemoteDescription({sdp: offerNoExt, type: "offer"});
+ await pc2.setLocalDescription();
+ await pc1.setRemoteDescription(pc2.localDescription);
+
+ // Renegotiate, but use |levelId| for something other than
+ // ssrc-audio-level. pc2 should not throw.
+ await pc1.setLocalDescription();
+ const mungedReoffer = setExtmap(pc1.localDescription.sdp, "urn:ietf:params:rtp-hdrext:sdes:mid", levelId);
+ const twiceMungedReoffer = setExtmap(mungedReoffer, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", undefined);
+ await pc2.setRemoteDescription({sdp: twiceMungedReoffer, type: "offer"});
+ },
+
+ async function checkAudioUnnegotiatedIdReuse3() {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+
+ const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+ pc1.addTrack(stream.getTracks()[0]);
+ pc2.addTrack(stream.getTracks()[0]);
+
+ // Negotiate, but replace ssrc-audio-level with something pc2 won't
+ // support in offer.
+ await pc1.setLocalDescription();
+ const levelId = getExtmap(pc1.localDescription.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level");
+ const mungedOffer = replaceExtUri(pc1.localDescription.sdp, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", "fooba");
+ await pc2.setRemoteDescription({sdp: mungedOffer, type: "offer"});
+ await pc2.setLocalDescription();
+ await pc1.setRemoteDescription(pc2.localDescription);
+
+ // Renegotiate, and use levelId for something pc2 _will_ support.
+ await pc1.setLocalDescription();
+ const mungedReoffer = setExtmap(pc1.localDescription.sdp, "urn:ietf:params:rtp-hdrext:sdes:mid", levelId);
+ const twiceMungedReoffer = setExtmap(mungedReoffer, "urn:ietf:params:rtp-hdrext:ssrc-audio-level", undefined);
+ await pc2.setRemoteDescription({sdp: twiceMungedReoffer, type: "offer"});
+ },
+
+ ];
+
+ runNetworkTest(async () => {
+ for (const test of tests) {
+ info(`Running test: ${test.name}`);
+ await test();
+ info(`Done running test: ${test.name}`);
+ }
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_forwarding_basicAudioVideoCombined.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_forwarding_basicAudioVideoCombined.html
new file mode 100644
index 0000000000..84b53a123b
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_forwarding_basicAudioVideoCombined.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "931903",
+ title: "Forwarding a stream from a combined audio/video peerconnection to another"
+ });
+
+runNetworkTest(function() {
+ var gumTest = new PeerConnectionTest();
+
+ var forwardingOptions = { config_local: { label_suffix: "forwarded" },
+ config_remote: { label_suffix: "forwarded" } };
+ var forwardingTest = new PeerConnectionTest(forwardingOptions);
+
+ gumTest.setMediaConstraints([{audio: true, video: true}], []);
+ forwardingTest.setMediaConstraints([{audio: true, video: true}], []);
+ forwardingTest.chain.replace("PC_LOCAL_GUM", [
+ function PC_FORWARDING_CAPTUREVIDEO(test) {
+ var streams = gumTest.pcRemote._pc.getRemoteStreams();
+ is(streams.length, 1, "One stream to forward");
+ is(streams[0].getTracks().length, 2, "Forwarded stream has 2 tracks");
+ forwardingTest.pcLocal.attachLocalStream(streams[0]);
+ return Promise.resolve();
+ }
+ ]);
+ gumTest.chain.removeAfter("PC_REMOTE_WAIT_FOR_MEDIA_FLOW");
+ return gumTest.chain.execute()
+ .then(() => forwardingTest.chain.execute())
+ .then(() => gumTest.close())
+ .then(() => forwardingTest.close());
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_gatherWithSetConfiguration.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_gatherWithSetConfiguration.html
new file mode 100644
index 0000000000..6710e628aa
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_gatherWithSetConfiguration.html
@@ -0,0 +1,450 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="iceTestUtils.js"></script>
+ <script type="application/javascript" src="helpers_from_wpt/sdp.js"></script></head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1253706",
+ title: "Test ICE gathering when setConfiguration is used to change the ICE config"
+ });
+
+ const tests = [
+ async function baselineV4Cases() {
+ await checkSrflx([{urls:[`stun:${turnAddressV4}`]}]);
+ await checkRelayUdp([{urls:[`turn:${turnAddressV4}`], username, credential}]);
+ await checkRelayTcp([{urls:[`turn:${turnAddressV4}?transport=tcp`], username, credential}]);
+ await checkRelayUdpTcp([{urls:[`turn:${turnAddressV4}`, `turn:${turnAddressV4}?transport=tcp`], username, credential}]);
+ await checkNoSrflx();
+ await checkNoRelay();
+ },
+
+ async function addStunServerBeforeOffer() {
+ const pc = new RTCPeerConnection();
+ try {
+ pc.setConfiguration({iceServers: [{urls:[`stun:${turnAddressV4}`]}]});
+ const candidates = await gatherWithTimeout(pc, 32000, `just a stun server`);
+ ok(candidates.some(c => c.candidate.includes("srflx")), "Should get at least one srflx candidate");
+ ok(!candidates.some(c => c.candidate.includes("relay")), "Should not get any relay candidates");
+ } finally {
+ pc.close();
+ }
+ },
+
+ async function addTurnServerBeforeOffer() {
+ const pc = new RTCPeerConnection();
+ try {
+ pc.setConfiguration({iceServers: [{urls:[`turn:${turnAddressV4}`], username, credential}]});
+ const candidates = await gatherWithTimeout(pc, 32000, `a turn (udp) server`);
+ ok(candidates.some(c => c.candidate.includes("srflx")), "Should get at least one srflx candidate");
+ ok(candidates.some(c => c.candidate.includes("relay")), "Should get at least one relay candidate");
+ } finally {
+ pc.close();
+ }
+ },
+
+ async function addTurnTcpServerBeforeOffer() {
+ const pc = new RTCPeerConnection();
+ try {
+ pc.setConfiguration({iceServers: [{urls:[`turn:${turnAddressV4}?transport=tcp`], username, credential}]});
+ const candidates = await gatherWithTimeout(pc, 32000, `a turn (tcp) server`);
+ ok(!candidates.some(c => c.candidate.includes("srflx")), "Should not get any srflx candidates");
+ ok(candidates.some(c => c.candidate.includes("relay")), "Should get at least one relay candidate");
+ } finally {
+ pc.close();
+ }
+ },
+
+ async function addStunServerAfterOffer() {
+ const pc = new RTCPeerConnection();
+ try {
+ const candidates1 = await gatherWithTimeout(pc, 32000, `no ICE servers`);
+ ok(!candidates1.some(c => c.candidate.includes("srflx")), "Should not get any srflx candidates");
+ ok(!candidates1.some(c => c.candidate.includes("relay")), "Should not get any relay candidates");
+ await pc.setLocalDescription({type: "rollback"});
+
+ pc.setConfiguration({iceServers: [{urls:[`stun:${turnAddressV4}`]}]});
+ const candidates2 = await gatherWithTimeout(pc, 32000, `just a stun server`);
+ ok(candidates2.some(c => c.candidate.includes("srflx")), "Should get at least one srflx candidate");
+ ok(!candidates2.some(c => c.candidate.includes("relay")), "Should not get any relay candidates");
+ } finally {
+ pc.close();
+ }
+ },
+
+ async function addTurnServerAfterOffer() {
+ const pc = new RTCPeerConnection();
+ try {
+ const candidates1 = await gatherWithTimeout(pc, 32000, `no ICE servers`);
+ ok(!candidates1.some(c => c.candidate.includes("srflx")), "Should not get any srflx candidates");
+ ok(!candidates1.some(c => c.candidate.includes("relay")), "Should not get any relay candidates");
+ await pc.setLocalDescription({type: "rollback"});
+
+ pc.setConfiguration({iceServers: [{urls:[`turn:${turnAddressV4}`], username, credential}]});
+ const candidates2 = await gatherWithTimeout(pc, 32000, `a turn (udp) server`);
+ ok(candidates2.some(c => c.candidate.includes("srflx")), "Should get at least one srflx candidate");
+ ok(candidates2.some(c => c.candidate.includes("relay")), "Should get at least one relay candidate");
+ } finally {
+ pc.close();
+ }
+ },
+
+ async function addTurnTcpServerAfterOffer() {
+ const pc = new RTCPeerConnection();
+ try {
+ const candidates1 = await gatherWithTimeout(pc, 32000, `no ICE servers`);
+ ok(!candidates1.some(c => c.candidate.includes("srflx")), "Should not get any srflx candidates");
+ ok(!candidates1.some(c => c.candidate.includes("relay")), "Should not get any relay candidates");
+ await pc.setLocalDescription({type: "rollback"});
+
+ pc.setConfiguration({iceServers: [{urls:[`turn:${turnAddressV4}?transport=tcp`], username, credential}]});
+ const candidates2 = await gatherWithTimeout(pc, 32000, `a turn (tcp) server`);
+ ok(!candidates2.some(c => c.candidate.includes("srflx")), "Should get no srflx candidates");
+ ok(candidates2.some(c => c.candidate.includes("relay")), "Should get at least one relay candidate");
+ } finally {
+ pc.close();
+ }
+ },
+
+ async function removeStunServerBeforeOffer() {
+ const pc = new RTCPeerConnection({iceServers: [{urls:[`stun:${turnAddressV4}`]}]});
+ try {
+ pc.setConfiguration({});
+ const candidates = await gatherWithTimeout(pc, 32000, `no ICE servers`);
+ ok(!candidates.some(c => c.candidate.includes("srflx")), "Should not get any srflx candidates");
+ ok(!candidates.some(c => c.candidate.includes("relay")), "Should not get any relay candidates");
+ } finally {
+ pc.close();
+ }
+ },
+
+ async function removeTurnServerBeforeOffer() {
+ const pc = new RTCPeerConnection({iceServers: [{urls:[`turn:${turnAddressV4}`], username, credential}]});
+ try {
+ pc.setConfiguration({});
+ const candidates = await gatherWithTimeout(pc, 32000, `no ICE servers`);
+ ok(!candidates.some(c => c.candidate.includes("srflx")), "Should not get any srflx candidates");
+ ok(!candidates.some(c => c.candidate.includes("relay")), "Should not get any relay candidates");
+ } finally {
+ pc.close();
+ }
+ },
+
+ async function removeTurnTcpServerBeforeOffer() {
+ const pc = new RTCPeerConnection({iceServers: [{urls:[`turn:${turnAddressV4}?transport=tcp`], username, credential}]});
+ try {
+ pc.setConfiguration({});
+ const candidates = await gatherWithTimeout(pc, 32000, `no ICE servers`);
+ ok(!candidates.some(c => c.candidate.includes("srflx")), "Should not get any srflx candidates");
+ ok(!candidates.some(c => c.candidate.includes("relay")), "Should not get any relay candidates");
+ } finally {
+ pc.close();
+ }
+ },
+
+ async function removeStunServerAfterOffer() {
+ const pc = new RTCPeerConnection({iceServers: [{urls:[`stun:${turnAddressV4}`]}]});
+ try {
+ const candidates1 = await gatherWithTimeout(pc, 32000, `just a stun server`);
+ ok(candidates1.some(c => c.candidate.includes("srflx")), "Should get at least one srflx candidate");
+ ok(!candidates1.some(c => c.candidate.includes("relay")), "Should not get any relay candidates");
+ await pc.setLocalDescription({type: "rollback"});
+
+ pc.setConfiguration({});
+ const candidates2 = await gatherWithTimeout(pc, 32000, `no ICE servers`);
+ ok(!candidates2.some(c => c.candidate.includes("srflx")), "Should not get any srflx candidates");
+ ok(!candidates2.some(c => c.candidate.includes("relay")), "Should not get any relay candidates");
+ } finally {
+ pc.close();
+ }
+ },
+
+ async function removeTurnServerAfterOffer() {
+ const pc = new RTCPeerConnection({iceServers: [{urls:[`turn:${turnAddressV4}`], username, credential}]});
+ try {
+ const candidates1 = await gatherWithTimeout(pc, 32000, `a turn (udp) server`);
+ ok(candidates1.some(c => c.candidate.includes("srflx")), "Should get at least one srflx candidate");
+ ok(candidates1.some(c => c.candidate.includes("relay")), "Should get at least one relay candidate");
+ await pc.setLocalDescription({type: "rollback"});
+
+ pc.setConfiguration({});
+ const candidates2 = await gatherWithTimeout(pc, 32000, `no ICE servers`);
+ ok(!candidates2.some(c => c.candidate.includes("srflx")), "Should not get any srflx candidates");
+ ok(!candidates2.some(c => c.candidate.includes("relay")), "Should not get any relay candidates");
+ } finally {
+ pc.close();
+ }
+ },
+
+ async function removeTurnTcpServerAfterOffer() {
+ const pc = new RTCPeerConnection({iceServers: [{urls:[`turn:${turnAddressV4}?transport=tcp`], username, credential}]});
+ try {
+ const candidates1 = await gatherWithTimeout(pc, 32000, `a turn (tcp) server`);
+ ok(!candidates1.some(c => c.candidate.includes("srflx")), "Should get no srflx candidates");
+ ok(candidates1.some(c => c.candidate.includes("relay")), "Should get at least one relay candidate");
+ await pc.setLocalDescription({type: "rollback"});
+
+ pc.setConfiguration({});
+ const candidates2 = await gatherWithTimeout(pc, 32000, `no ICE servers`);
+ ok(!candidates2.some(c => c.candidate.includes("srflx")), "Should not get any srflx candidates");
+ ok(!candidates2.some(c => c.candidate.includes("relay")), "Should not get any relay candidates");
+ } finally {
+ pc.close();
+ }
+ },
+
+ async function addStunServerAfterNegotiation() {
+ const offerer = new RTCPeerConnection();
+ const answerer = new RTCPeerConnection({iceServers: [{urls:[`stun:${turnAddressV4}`]}]});
+ try {
+ const candidatePromise = trickleIce(offerer);
+ await connect(offerer, answerer, 32000, `no ICE servers`);
+ const candidates = await candidatePromise;
+ const ufrags = Array.from(new Set(candidates.map(c => c.usernameFragment)));
+ is(ufrags.length, 1, "Should have one ufrag in candidate set");
+
+ offerer.setConfiguration({iceServers: [{urls:[`stun:${turnAddressV4}`]}]});
+ const candidates2 = await gatherWithTimeout(offerer, 32000, `just a stun server`);
+ ok(candidates2.some(c => c.candidate.includes("srflx")), "Should get at least one srflx candidate");
+ ok(!candidates2.some(c => c.candidate.includes("relay")), "Should not get any relay candidates");
+ const ufrags2 = Array.from(new Set(candidates2.map(c => c.usernameFragment)));
+ is(ufrags2.length, 1, "Should have one ufrag in candidate set");
+ isnot(ufrags[0], ufrags2[0], "ufrag should change, because setConfiguration should have triggered an ICE restart");
+ } finally {
+ offerer.close();
+ answerer.close();
+ }
+ },
+
+ async function addTurnServerAfterNegotiation() {
+ const offerer = new RTCPeerConnection();
+ const answerer = new RTCPeerConnection({iceServers: [{urls:[`stun:${turnAddressV4}`]}]});
+ try {
+ const candidatePromise = trickleIce(offerer);
+ await connect(offerer, answerer, 32000, `no ICE servers`);
+ const candidates = await candidatePromise;
+ const ufrags = Array.from(new Set(candidates.map(c => c.usernameFragment)));
+ is(ufrags.length, 1, "Should have one ufrag in candidate set");
+
+ offerer.setConfiguration({iceServers: [{urls:[`turn:${turnAddressV4}`], username, credential}]});
+ const candidates2 = await gatherWithTimeout(offerer, 32000, `a turn (udp) server`);
+ ok(candidates2.some(c => c.candidate.includes("srflx")), "Should get at least one srflx candidate");
+ ok(candidates2.some(c => c.candidate.includes("relay")), "Should get at least one relay candidate");
+ const ufrags2 = Array.from(new Set(candidates2.map(c => c.usernameFragment)));
+ is(ufrags2.length, 1, "Should have one ufrag in candidate set");
+ isnot(ufrags[0], ufrags2[0], "ufrag should change, because setConfiguration should have triggered an ICE restart");
+ } finally {
+ offerer.close();
+ answerer.close();
+ }
+ },
+
+ async function addTurnTcpServerAfterNegotiation() {
+ const offerer = new RTCPeerConnection();
+ const answerer = new RTCPeerConnection({iceServers: [{urls:[`stun:${turnAddressV4}`]}]});
+ try {
+ const candidatePromise = trickleIce(offerer);
+ await connect(offerer, answerer, 32000, `no ICE servers`);
+ const candidates = await candidatePromise;
+ const ufrags = Array.from(new Set(candidates.map(c => c.usernameFragment)));
+ is(ufrags.length, 1, "Should have one ufrag in candidate set");
+
+ offerer.setConfiguration({iceServers: [{urls:[`turn:${turnAddressV4}?transport=tcp`], username, credential}]});
+ const candidates2 = await gatherWithTimeout(offerer, 32000, `a turn (tcp) server`);
+ ok(!candidates2.some(c => c.candidate.includes("srflx")), "Should not get any srflx candidates");
+ ok(candidates2.some(c => c.candidate.includes("relay")), "Should get at least one relay candidate");
+ const ufrags2 = Array.from(new Set(candidates2.map(c => c.usernameFragment)));
+ is(ufrags2.length, 1, "Should have one ufrag in candidate set");
+ isnot(ufrags[0], ufrags2[0], "ufrag should change, because setConfiguration should have triggered an ICE restart");
+ } finally {
+ offerer.close();
+ answerer.close();
+ }
+ },
+
+ async function addStunServerBeforeCreateAnswer() {
+ const offerer = new RTCPeerConnection();
+ const answerer = new RTCPeerConnection();
+
+ try {
+ await answerer.setRemoteDescription(await offerer.createOffer({offerToReceiveAudio: true}));
+
+ answerer.setConfiguration({iceServers: [{urls:[`stun:${turnAddressV4}`]}]});
+ const candidates = await gatherWithTimeout(answerer, 32000, `just a stun server`);
+ ok(candidates.some(c => c.candidate.includes("srflx")), "Should get at least one srflx candidate");
+ ok(!candidates.some(c => c.candidate.includes("relay")), "Should not get any relay candidates");
+ } finally {
+ offerer.close();
+ answerer.close();
+ }
+ },
+
+ async function addTurnServerBeforeCreateAnswer() {
+ const offerer = new RTCPeerConnection();
+ const answerer = new RTCPeerConnection();
+
+ try {
+ await answerer.setRemoteDescription(await offerer.createOffer({offerToReceiveAudio: true}));
+
+ answerer.setConfiguration({iceServers: [{urls:[`turn:${turnAddressV4}`], username, credential}]});
+ const candidates = await gatherWithTimeout(answerer, 32000, `a turn (udp) server`);
+ ok(candidates.some(c => c.candidate.includes("srflx")), "Should get at least one srflx candidate");
+ ok(candidates.some(c => c.candidate.includes("relay")), "Should get at least one relay candidate");
+ } finally {
+ offerer.close();
+ answerer.close();
+ }
+ },
+
+ async function addTurnTcpServerBeforeCreateAnswer() {
+ const offerer = new RTCPeerConnection();
+ const answerer = new RTCPeerConnection();
+
+ try {
+ await answerer.setRemoteDescription(await offerer.createOffer({offerToReceiveAudio: true}));
+
+ answerer.setConfiguration({iceServers: [{urls:[`turn:${turnAddressV4}?transport=tcp`], username, credential}]});
+ const candidates = await gatherWithTimeout(answerer, 32000, `a turn (tcp) server`);
+ ok(!candidates.some(c => c.candidate.includes("srflx")), "Should not get any srflx candidates");
+ ok(candidates.some(c => c.candidate.includes("relay")), "Should get at least one relay candidate");
+ } finally {
+ offerer.close();
+ answerer.close();
+ }
+ },
+
+ async function relayPolicyPreventsSrflx() {
+ const pc = new RTCPeerConnection();
+ try {
+ pc.setConfiguration({iceServers: [{urls:[`turn:${turnAddressV4}`], username, credential}], iceTransportPolicy: "relay"});
+ const candidates = await gatherWithTimeout(pc, 32000, `a turn (udp) server`);
+ ok(!candidates.some(c => c.candidate.includes("srflx")), "Should not get a srflx candidate");
+ ok(candidates.some(c => c.candidate.includes("relay")), "Should get at least one relay candidate");
+ } finally {
+ pc.close();
+ }
+ },
+
+ async function addOffererStunServerAllowsIceToConnect() {
+ const offerer = new RTCPeerConnection();
+ const answerer = new RTCPeerConnection();
+
+ try {
+ try {
+ // Both ends are behind a simulated endpoint-independent NAT, which
+ // requires at least one side to have a srflx candidate to work.
+ await connect(offerer, answerer, 2000, `no ICE servers`);
+ ok(false, "ICE should either have failed, or timed out!");
+ } catch (e) {
+ if (!(e instanceof Error)) throw e;
+ ok(true, "ICE should either have failed, or timed out!");
+ }
+
+ offerer.setConfiguration({iceServers: [{urls:[`stun:${turnAddressV4}`]}]});
+ await connect(offerer, answerer, 32000, `just a STUN server`);
+ } finally {
+ offerer.close();
+ answerer.close();
+ }
+ },
+
+ async function addAnswererStunServerDoesNotAllowIceToConnect() {
+ const offerer = new RTCPeerConnection();
+ const answerer = new RTCPeerConnection();
+
+ try {
+ try {
+ // Both ends are behind a simulated endpoint-independent NAT, which
+ // requires at least one side to have a srflx candidate to work.
+ await connect(offerer, answerer, 2000, `no ICE servers`);
+ ok(false, "ICE should either have failed, or timed out!");
+ } catch (e) {
+ if (!(e instanceof Error)) throw e;
+ ok(true, "ICE should either have failed, or timed out!");
+ }
+
+ // This _won't_ help, because the answerer does not get to decide to
+ // trigger an ICE restart.
+ answerer.setConfiguration({iceServers: [{urls:[`stun:${turnAddressV4}`]}]});
+ try {
+ await connectNoTrickleWait(offerer, answerer, 2000, `no ICE servers`);
+ ok(false, "ICE should either have failed, or timed out!");
+ } catch (e) {
+ if (!(e instanceof Error)) throw e;
+ ok(true, "ICE should either have failed, or timed out!");
+ }
+ } finally {
+ offerer.close();
+ answerer.close();
+ }
+ },
+
+ async function addOffererTurnServerAllowsIceToConnect() {
+ await pushPrefs(
+ ['media.peerconnection.nat_simulator.filtering_type', 'PORT_DEPENDENT'],
+ ['media.peerconnection.nat_simulator.mapping_type', 'PORT_DEPENDENT']);
+
+ const offerer = new RTCPeerConnection({iceServers: [{urls:[`stun:${turnAddressV4}`]}]});
+ const answerer = new RTCPeerConnection({iceServers: [{urls:[`stun:${turnAddressV4}`]}]});
+
+ try {
+ try {
+ // Both ends are behind a simulated port-dependent NAT, which
+ // requires at least one side to have a relay candidate to work.
+ await connect(offerer, answerer, 2000, `just a STUN server`);
+ ok(false, "ICE should either have failed, or timed out!");
+ } catch (e) {
+ if (!(e instanceof Error)) throw e;
+ ok(true, "ICE should either have failed, or timed out!");
+ }
+
+ offerer.setConfiguration({iceServers: [{urls:[`turn:${turnAddressV4}`], username, credential}]});
+ await connect(offerer, answerer, 32000, `a TURN (udp) server`);
+ } finally {
+ offerer.close();
+ answerer.close();
+ await SpecialPowers.popPrefEnv();
+ }
+ },
+
+ ];
+
+ runNetworkTest(async () => {
+ const turnServer = iceServersArray.find(server => "username" in server);
+ username = turnServer.username;
+ credential = turnServer.credential;
+ // Just use the first url. It might make sense to look for TURNS first,
+ // since that will always use a hostname, but on CI we don't have TURNS
+ // support anyway (see bug 1323439).
+ const turnHostname = getTurnHostname(turnServer.urls[0]);
+ turnAddressV4 = await dnsLookupV4(turnHostname);
+
+ await pushPrefs(
+ ['media.peerconnection.ice.obfuscate_host_addresses', false],
+ ['media.peerconnection.nat_simulator.filtering_type', 'ENDPOINT_INDEPENDENT'],
+ ['media.peerconnection.nat_simulator.mapping_type', 'ENDPOINT_INDEPENDENT'],
+ ['media.peerconnection.ice.loopback', true],
+ ['media.getusermedia.insecure.enabled', true]);
+
+ for (const test of tests) {
+ info(`Running test: ${test.name}`);
+ try {
+ await test();
+ } catch (e) {
+ ok(false, `Caught ${e.name}: ${e.message} ${e.stack}`);
+ }
+ info(`Done running test: ${test.name}`);
+ // Make sure we don't build up a pile of GC work, and also get PCImpl to
+ // print their timecards.
+ await new Promise(r => SpecialPowers.exactGC(r));
+ }
+
+ await SpecialPowers.popPrefEnv();
+ }, { useIceServer: true });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_gatherWithStun300.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_gatherWithStun300.html
new file mode 100644
index 0000000000..50bc4a6553
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_gatherWithStun300.html
@@ -0,0 +1,269 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="iceTestUtils.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "857668",
+ title: "RTCPeerConnection check STUN gathering with STUN/300 responses"
+ });
+
+ /* This is pretty hairy, so some background:
+ * Spec is here: https://datatracker.ietf.org/doc/html/rfc8489#section-10
+ * STUN/300 responses allow a server to redirect STUN requests to one or
+ more other servers, as ALTERNATE-SERVER attributes.
+ * The server specifies the IP address, IP version, and port for each
+ ALTERNATE-SERVER.
+ * The spec allows multiple rounds of redirects, and requires the client to
+ remember the servers it has already tried to avoid redirect loops.
+ * For TURNS, the TURN server can also supply an ALTERNATE-DOMAIN attribute,
+ which the client MUST use for the TLS handshake on the new target. The
+ client does _not_ use this as an FQDN; it always uses the address in the
+ ALTERNATE-SERVER. ALTERNATE-DOMAIN is meaningless in the non-TLS case.
+ * STUN/300 with ALTERNATE-SERVER is only defined for the TURN Allocate
+ message type (at least in the context of ICE). Clients are supposed to
+ treat STUN/300 as an unrecoverable error in all other cases. The TURN spec
+ does _not_ spell out how a client should handle multiple ALTERNATE-SERVERs.
+ We just take the first one that we have not already tried, and that is the
+ same IP version that we started with. This is because switching the IP
+ version is problematic for ICE.
+ * The test TURN server opens extra ports that will respond with redirects to
+ the _real_ ports, but the address remains the same. This is because we
+ cannot know ahead of time whether the machine we're running on has more
+ than one IP address of each version. This means the test TURN server is not
+ useful for testing cases where the address changes. Also, the test TURN
+ server does not currently know how to respond with multiple
+ ALTERNATE-SERVERs.
+ * To test cases where the _address_ changes, we instead use a feature in the
+ NAT simulator to respond with fake redirects when the destination address
+ matches an address that we configure with a pref. This feature can add
+ multiple ALTERNATE-SERVERs.
+ * The test TURN server's STUN/300 responses have a proper MESSAGE-INTEGRITY,
+ but the NAT simulator's do _not_. For now, we want both cases to work,
+ because some servers respond with STUN/300 without including
+ MESSAGE-INTEGRITY. This is a spec violation, even though the spec
+ contradicts itself in non-normative language elsewhere.
+ * Right now, neither the NAT simulator nor the test TURN server support
+ ALTERNATE-DOMAIN.
+ */
+
+ // These are the ports the test TURN server will respond with redirects on.
+ // The test TURN server tells us what these are in the JSON it spits out when
+ // we start it.
+ let turnRedirectPort;
+ let turnsRedirectPort;
+
+ // These are the addresses that we will configure the NAT simulator to
+ // redirect to. We do DNS lookups of the host in iceServersArray (provided
+ // by the test TURN server), and put the results here. On some platforms this
+ // will be 127.0.0.1 and ::1, but on others we may use a real address.
+ let redirectTargetV4;
+
+ // Test TURN server tells us these in the JSON it spits out when we start it
+ let username;
+ let credential;
+
+ // This is the address we will configure the NAT simulator to respond with
+ // redirects for. We use an address from TEST-NET since it is really unlikely
+ // we'll see that on a real machine, and also because we do not have
+ // special-case code in nICEr for TEST-NET (like we do for link-local, for
+ // example).
+ const redirectAddressV4 = '198.51.100.1';
+
+ const tests = [
+ async function baselineV4Cases() {
+ await checkSrflx([{urls:[`stun:${redirectTargetV4}`]}]);
+ await checkRelayUdp([{urls:[`turn:${redirectTargetV4}`], username, credential}]);
+ await checkRelayTcp([{urls:[`turn:${redirectTargetV4}?transport=tcp`], username, credential}]);
+ await checkRelayUdpTcp([{urls:[`turn:${redirectTargetV4}`, `turn:${redirectTargetV4}?transport=tcp`], username, credential}]);
+ },
+
+ async function stunV4Redirect() {
+ // This test uses the test TURN server, because nICEr drops responses
+ // without MESSAGE-INTEGRITY on the floor _unless_ they are a STUN/300 to
+ // an Allocate request. If we tried to use the NAT simulator for this, we
+ // would have to wait for nICEr to time out, since the NAT simulator does
+ // not know how to do MESSAGE-INTEGRITY.
+ await checkNoSrflx(
+ [{urls:[`stun:${redirectTargetV4}:${turnRedirectPort}`]}]);
+ },
+
+ async function turnV4UdpPortRedirect() {
+ await checkRelayUdp([{urls:[`turn:${redirectTargetV4}:${turnRedirectPort}`], username, credential}]);
+ },
+
+ async function turnV4TcpPortRedirect() {
+ await checkRelayTcp([{urls:[`turn:${redirectTargetV4}:${turnRedirectPort}?transport=tcp`], username, credential}]);
+ },
+
+ async function turnV4UdpTcpPortRedirect() {
+ await checkRelayUdpTcp([{urls:[`turn:${redirectTargetV4}:${turnRedirectPort}`, `turn:${redirectTargetV4}:${turnRedirectPort}?transport=tcp`], username, credential}]);
+ },
+
+ async function turnV4UdpAddressRedirect() {
+ await pushPrefs(
+ ['media.peerconnection.nat_simulator.redirect_address', `${redirectAddressV4}`],
+ ['media.peerconnection.nat_simulator.redirect_targets', `${redirectTargetV4}`]);
+ await checkRelayUdp([{urls:[`turn:${redirectAddressV4}`], username, credential}]);
+ await SpecialPowers.popPrefEnv();
+ },
+
+ async function turnV4TcpAddressRedirect() {
+ await pushPrefs(
+ ['media.peerconnection.nat_simulator.redirect_address', `${redirectAddressV4}`],
+ ['media.peerconnection.nat_simulator.redirect_targets', `${redirectTargetV4}`]);
+ await checkRelayTcp([{urls:[`turn:${redirectAddressV4}?transport=tcp`], username, credential}]);
+ await SpecialPowers.popPrefEnv();
+ },
+
+ async function turnV4UdpTcpAddressRedirect() {
+ await pushPrefs(
+ ['media.peerconnection.nat_simulator.redirect_address', `${redirectAddressV4}`],
+ ['media.peerconnection.nat_simulator.redirect_targets', `${redirectTargetV4}`]);
+ await checkRelayUdpTcp([{urls:[`turn:${redirectAddressV4}`, `turn:${redirectAddressV4}?transport=tcp`], username, credential}]);
+ await SpecialPowers.popPrefEnv();
+ },
+
+ async function turnV4UdpEmptyRedirect() {
+ await pushPrefs(
+ ['media.peerconnection.nat_simulator.redirect_address', `${redirectAddressV4}`],
+ ['media.peerconnection.nat_simulator.redirect_targets', '']);
+ await checkNoRelay([{urls:[`turn:${redirectAddressV4}`], username, credential}]);
+ await SpecialPowers.popPrefEnv();
+ },
+
+ async function turnV4TcpEmptyRedirect() {
+ await pushPrefs(
+ ['media.peerconnection.nat_simulator.redirect_address', `${redirectAddressV4}`],
+ ['media.peerconnection.nat_simulator.redirect_targets', '']);
+ await checkNoRelay([{urls:[`turn:${redirectAddressV4}?transport=tcp`], username, credential}]);
+ await SpecialPowers.popPrefEnv();
+ },
+
+ async function turnV4UdpTcpEmptyRedirect() {
+ await pushPrefs(
+ ['media.peerconnection.nat_simulator.redirect_address', `${redirectAddressV4}`],
+ ['media.peerconnection.nat_simulator.redirect_targets', '']);
+ await checkNoRelay([{urls:[`turn:${redirectAddressV4}`, `turn:${redirectAddressV4}?transport=tcp`], username, credential}]);
+ await SpecialPowers.popPrefEnv();
+ },
+
+ async function turnV4UdpAddressAndPortRedirect() {
+ // This should result in two rounds of redirection; the first is by
+ // address, the second is by port.
+ await pushPrefs(
+ ['media.peerconnection.nat_simulator.redirect_address', `${redirectAddressV4}`],
+ ['media.peerconnection.nat_simulator.redirect_targets', `${redirectTargetV4}`]);
+ await checkRelayUdp([{urls:[`turn:${redirectAddressV4}:${turnRedirectPort}`], username, credential}]);
+ await SpecialPowers.popPrefEnv();
+ },
+
+ async function turnV4TcpAddressAndPortRedirect() {
+ // This should result in two rounds of redirection; the first is by
+ // address, the second is by port.
+ await pushPrefs(
+ ['media.peerconnection.nat_simulator.redirect_address', `${redirectAddressV4}`],
+ ['media.peerconnection.nat_simulator.redirect_targets', `${redirectTargetV4}`]);
+ await checkRelayTcp([{urls:[`turn:${redirectAddressV4}:${turnRedirectPort}?transport=tcp`], username, credential}]);
+ await SpecialPowers.popPrefEnv();
+ },
+
+ async function turnV4UdpTcpAddressAndPortRedirect() {
+ // This should result in two rounds of redirection; the first is by
+ // address, the second is by port.
+ await pushPrefs(
+ ['media.peerconnection.nat_simulator.redirect_address', `${redirectAddressV4}`],
+ ['media.peerconnection.nat_simulator.redirect_targets', `${redirectTargetV4}`]);
+ await checkRelayUdpTcp([{urls:[`turn:${redirectAddressV4}:${turnRedirectPort}`, `turn:${redirectAddressV4}:${turnRedirectPort}?transport=tcp`], username, credential}]);
+ await SpecialPowers.popPrefEnv();
+ },
+
+ async function turnV4UdpRedirectLoop() {
+ await pushPrefs(
+ ['media.peerconnection.nat_simulator.redirect_address', `${redirectAddressV4}`],
+ ['media.peerconnection.nat_simulator.redirect_targets', `${redirectAddressV4}`]);
+ // If we don't detect the loop, gathering will not finish
+ await checkNoRelay([{urls:[`turn:${redirectAddressV4}`], username, credential}]);
+ await SpecialPowers.popPrefEnv();
+ },
+
+ async function turnV4TcpRedirectLoop() {
+ await pushPrefs(
+ ['media.peerconnection.nat_simulator.redirect_address', `${redirectAddressV4}`],
+ ['media.peerconnection.nat_simulator.redirect_targets', `${redirectAddressV4}`]);
+ // If we don't detect the loop, gathering will not finish
+ await checkNoRelay([{urls:[`turn:${redirectAddressV4}?transport=tcp`], username, credential}]);
+ await SpecialPowers.popPrefEnv();
+ },
+
+ async function turnV4UdpTcpRedirectLoop() {
+ await pushPrefs(
+ ['media.peerconnection.nat_simulator.redirect_address', `${redirectAddressV4}`],
+ ['media.peerconnection.nat_simulator.redirect_targets', `${redirectAddressV4}`]);
+ // If we don't detect the loop, gathering will not finish
+ await checkNoRelay([{urls:[`turn:${redirectAddressV4}`, `turn:${redirectAddressV4}?transport=tcp`], username, credential}]);
+ await SpecialPowers.popPrefEnv();
+ },
+
+ async function turnV4UdpMultipleAddressRedirect() {
+ await pushPrefs(
+ ['media.peerconnection.nat_simulator.redirect_address', `${redirectAddressV4}`],
+ ['media.peerconnection.nat_simulator.redirect_targets', `${redirectAddressV4},${redirectTargetV4}`]);
+ await checkRelayUdp([{urls:[`turn:${redirectAddressV4}`], username, credential}]);
+ await SpecialPowers.popPrefEnv();
+ },
+
+ async function turnV4TcpMultipleAddressRedirect() {
+ await pushPrefs(
+ ['media.peerconnection.nat_simulator.redirect_address', `${redirectAddressV4}`],
+ ['media.peerconnection.nat_simulator.redirect_targets', `${redirectAddressV4},${redirectTargetV4}`]);
+ await checkRelayTcp([{urls:[`turn:${redirectAddressV4}?transport=tcp`], username, credential}]);
+ await SpecialPowers.popPrefEnv();
+ },
+
+ async function turnV4UdpTcpMultipleAddressRedirect() {
+ await pushPrefs(
+ ['media.peerconnection.nat_simulator.redirect_address', `${redirectAddressV4}`],
+ ['media.peerconnection.nat_simulator.redirect_targets', `${redirectAddressV4},${redirectTargetV4}`]);
+ await checkRelayUdpTcp([{urls:[`turn:${redirectAddressV4}`, `turn:${redirectAddressV4}?transport=tcp`], username, credential}]);
+ await SpecialPowers.popPrefEnv();
+ },
+ ];
+
+ runNetworkTest(async () => {
+ const turnServer = iceServersArray.find(server => "username" in server);
+ username = turnServer.username;
+ credential = turnServer.credential;
+ // Special props, non-standard
+ turnRedirectPort = turnServer.turn_redirect_port;
+ turnsRedirectPort = turnServer.turns_redirect_port;
+ // Just use the first url. It might make sense to look for TURNS first,
+ // since that will always use a hostname, but on CI we don't have TURNS
+ // support anyway (see bug 1323439).
+ const turnHostname = getTurnHostname(turnServer.urls[0]);
+ redirectTargetV4 = await dnsLookupV4(turnHostname);
+
+ await pushPrefs(
+ ['media.peerconnection.ice.obfuscate_host_addresses', false],
+ ['media.peerconnection.nat_simulator.filtering_type', 'ENDPOINT_INDEPENDENT'],
+ ['media.peerconnection.nat_simulator.mapping_type', 'ENDPOINT_INDEPENDENT'],
+ ['media.peerconnection.ice.loopback', true],
+ ['media.getusermedia.insecure.enabled', true]);
+
+ for (const test of tests) {
+ info(`Running test: ${test.name}`);
+ await test();
+ info(`Done running test: ${test.name}`);
+ }
+
+ await SpecialPowers.popPrefEnv();
+ }, { useIceServer: true });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_gatherWithStun300IPv6.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_gatherWithStun300IPv6.html
new file mode 100644
index 0000000000..16f8f39978
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_gatherWithStun300IPv6.html
@@ -0,0 +1,283 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="iceTestUtils.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "857668",
+ title: "RTCPeerConnection check STUN gathering with STUN/300 responses (IPv6)"
+ });
+
+ /* This is pretty hairy, so some background:
+ * Spec is here: https://datatracker.ietf.org/doc/html/rfc8489#section-10
+ * STUN/300 responses allow a server to redirect STUN requests to one or
+ more other servers, as ALTERNATE-SERVER attributes.
+ * The server specifies the IP address, IP version, and port for each
+ ALTERNATE-SERVER.
+ * The spec allows multiple rounds of redirects, and requires the client to
+ remember the servers it has already tried to avoid redirect loops.
+ * For TURNS, the TURN server can also supply an ALTERNATE-DOMAIN attribute,
+ which the client MUST use for the TLS handshake on the new target. The
+ client does _not_ use this as an FQDN; it always uses the address in the
+ ALTERNATE-SERVER. ALTERNATE-DOMAIN is meaningless in the non-TLS case.
+ * STUN/300 with ALTERNATE-SERVER is only defined for the TURN Allocate
+ message type (at least in the context of ICE). Clients are supposed to
+ treat STUN/300 as an unrecoverable error in all other cases. The TURN spec
+ does _not_ spell out how a client should handle multiple ALTERNATE-SERVERs.
+ We just take the first one that we have not already tried, and that is the
+ same IP version that we started with. This is because switching the IP
+ version is problematic for ICE.
+ * The test TURN server opens extra ports that will respond with redirects to
+ the _real_ ports, but the address remains the same. This is because we
+ cannot know ahead of time whether the machine we're running on has more
+ than one IP address of each version. This means the test TURN server is not
+ useful for testing cases where the address changes. Also, the test TURN
+ server does not currently know how to respond with multiple
+ ALTERNATE-SERVERs.
+ * To test cases where the _address_ changes, we instead use a feature in the
+ NAT simulator to respond with fake redirects when the destination address
+ matches an address that we configure with a pref. This feature can add
+ multiple ALTERNATE-SERVERs.
+ * The test TURN server's STUN/300 responses have a proper MESSAGE-INTEGRITY,
+ but the NAT simulator's do _not_. For now, we want both cases to work,
+ because some servers respond with STUN/300 without including
+ MESSAGE-INTEGRITY. This is a spec violation, even though the spec
+ contradicts itself in non-normative language elsewhere.
+ * Right now, neither the NAT simulator nor the test TURN server support
+ ALTERNATE-DOMAIN.
+ */
+
+ // These are the ports the test TURN server will respond with redirects on.
+ // The test TURN server tells us what these are in the JSON it spits out when
+ // we start it.
+ let turnRedirectPort;
+ let turnsRedirectPort;
+
+ // These are the addresses that we will configure the NAT simulator to
+ // redirect to. We do DNS lookups of the host in iceServersArray (provided
+ // by the test TURN server), and put the results here. On some platforms this
+ // will be 127.0.0.1 and ::1, but on others we may use a real address.
+ let redirectTargetV6;
+
+ // Test TURN server tells us these in the JSON it spits out when we start it
+ let username;
+ let credential;
+
+ // This is the address we will configure the NAT simulator to respond with
+ // redirects for. We use an address from TEST-NET since it is really unlikely
+ // we'll see that on a real machine, and also because we do not have
+ // special-case code in nICEr for TEST-NET (like we do for link-local, for
+ // example).
+ const redirectAddressV6 = '::ffff:198.51.100.1';
+
+ const tests = [
+ async function baselineV6Cases() {
+ await checkSrflx([{urls:[`stun:[${redirectTargetV6}]`]}]);
+ await checkRelayUdp([{urls:[`turn:[${redirectTargetV6}]`], username, credential}]);
+ await checkRelayTcp([{urls:[`turn:[${redirectTargetV6}]?transport=tcp`], username, credential}]);
+ await checkRelayUdpTcp([{urls:[`turn:[${redirectTargetV6}]`, `turn:[${redirectTargetV6}]?transport=tcp`], username, credential}]);
+ },
+
+ async function stunV6Redirect() {
+ // This test uses the test TURN server, because nICEr drops responses
+ // without MESSAGE-INTEGRITY on the floor _unless_ they are a STUN/300 to
+ // an Allocate request. If we tried to use the NAT simulator for this, we
+ // would have to wait for nICEr to time out, since the NAT simulator does
+ // not know how to do MESSAGE-INTEGRITY.
+ await checkNoSrflx(
+ [{urls:[`stun:[${redirectTargetV6}]:${turnRedirectPort}`]}]);
+ },
+
+ async function turnV6UdpPortRedirect() {
+ await checkRelayUdp([{urls:[`turn:[${redirectTargetV6}]:${turnRedirectPort}`], username, credential}]);
+ },
+
+ async function turnV6TcpPortRedirect() {
+ await checkRelayTcp([{urls:[`turn:[${redirectTargetV6}]:${turnRedirectPort}?transport=tcp`], username, credential}]);
+ },
+
+ async function turnV6UdpTcpPortRedirect() {
+ await checkRelayUdpTcp([{urls:[`turn:[${redirectTargetV6}]:${turnRedirectPort}`, `turn:[${redirectTargetV6}]:${turnRedirectPort}?transport=tcp`], username, credential}]);
+ },
+
+ async function turnV6UdpAddressRedirect() {
+ await pushPrefs(
+ ['media.peerconnection.nat_simulator.redirect_address', `${redirectAddressV6}`],
+ ['media.peerconnection.nat_simulator.redirect_targets', `${redirectTargetV6}`]);
+ await checkRelayUdp([{urls:[`turn:[${redirectAddressV6}]`], username, credential}]);
+ await SpecialPowers.popPrefEnv();
+ },
+
+ async function turnV6TcpAddressRedirect() {
+ await pushPrefs(
+ ['media.peerconnection.nat_simulator.redirect_address', `${redirectAddressV6}`],
+ ['media.peerconnection.nat_simulator.redirect_targets', `${redirectTargetV6}`]);
+ await checkRelayTcp([{urls:[`turn:[${redirectAddressV6}]?transport=tcp`], username, credential}]);
+ await SpecialPowers.popPrefEnv();
+ },
+
+ async function turnV6UdpTcpAddressRedirect() {
+ await pushPrefs(
+ ['media.peerconnection.nat_simulator.redirect_address', `${redirectAddressV6}`],
+ ['media.peerconnection.nat_simulator.redirect_targets', `${redirectTargetV6}`]);
+ await checkRelayUdpTcp([{urls:[`turn:[${redirectAddressV6}]`, `turn:[${redirectAddressV6}]?transport=tcp`], username, credential}]);
+ await SpecialPowers.popPrefEnv();
+ },
+
+ async function turnV6UdpEmptyRedirect() {
+ await pushPrefs(
+ ['media.peerconnection.nat_simulator.redirect_address', `${redirectAddressV6}`],
+ ['media.peerconnection.nat_simulator.redirect_targets', '']);
+ await checkNoRelay([{urls:[`turn:[${redirectAddressV6}]`], username, credential}]);
+ await SpecialPowers.popPrefEnv();
+ },
+
+ async function turnV6TcpEmptyRedirect() {
+ await pushPrefs(
+ ['media.peerconnection.nat_simulator.redirect_address', `${redirectAddressV6}`],
+ ['media.peerconnection.nat_simulator.redirect_targets', '']);
+ await checkNoRelay([{urls:[`turn:[${redirectAddressV6}]?transport=tcp`], username, credential}]);
+ await SpecialPowers.popPrefEnv();
+ },
+
+ async function turnV6UdpTcpEmptyRedirect() {
+ await pushPrefs(
+ ['media.peerconnection.nat_simulator.redirect_address', `${redirectAddressV6}`],
+ ['media.peerconnection.nat_simulator.redirect_targets', '']);
+ await checkNoRelay([{urls:[`turn:[${redirectAddressV6}]`, `turn:[${redirectAddressV6}]?transport=tcp`], username, credential}]);
+ await SpecialPowers.popPrefEnv();
+ },
+
+ async function turnV6UdpAddressAndPortRedirect() {
+ // This should result in two rounds of redirection; the first is by
+ // address, the second is by port.
+ await pushPrefs(
+ ['media.peerconnection.nat_simulator.redirect_address', `${redirectAddressV6}`],
+ ['media.peerconnection.nat_simulator.redirect_targets', `${redirectTargetV6}`]);
+ await checkRelayUdp([{urls:[`turn:[${redirectAddressV6}]:${turnRedirectPort}`], username, credential}]);
+ await SpecialPowers.popPrefEnv();
+ },
+
+ async function turnV6TcpAddressAndPortRedirect() {
+ // This should result in two rounds of redirection; the first is by
+ // address, the second is by port.
+ await pushPrefs(
+ ['media.peerconnection.nat_simulator.redirect_address', `${redirectAddressV6}`],
+ ['media.peerconnection.nat_simulator.redirect_targets', `${redirectTargetV6}`]);
+ await checkRelayTcp([{urls:[`turn:[${redirectAddressV6}]:${turnRedirectPort}?transport=tcp`], username, credential}]);
+ await SpecialPowers.popPrefEnv();
+ },
+
+ async function turnV6UdpTcpAddressAndPortRedirect() {
+ // This should result in two rounds of redirection; the first is by
+ // address, the second is by port.
+ await pushPrefs(
+ ['media.peerconnection.nat_simulator.redirect_address', `${redirectAddressV6}`],
+ ['media.peerconnection.nat_simulator.redirect_targets', `${redirectTargetV6}`]);
+ await checkRelayUdpTcp([{urls:[`turn:[${redirectAddressV6}]:${turnRedirectPort}`, `turn:[${redirectAddressV6}]:${turnRedirectPort}?transport=tcp`], username, credential}]);
+ await SpecialPowers.popPrefEnv();
+ },
+
+ async function turnV6UdpRedirectLoop() {
+ await pushPrefs(
+ ['media.peerconnection.nat_simulator.redirect_address', `${redirectAddressV6}`],
+ ['media.peerconnection.nat_simulator.redirect_targets', `${redirectAddressV6}`]);
+ // If we don't detect the loop, gathering will not finish
+ await checkNoRelay([{urls:[`turn:[${redirectAddressV6}]`], username, credential}]);
+ await SpecialPowers.popPrefEnv();
+ },
+
+ async function turnV6TcpRedirectLoop() {
+ await pushPrefs(
+ ['media.peerconnection.nat_simulator.redirect_address', `${redirectAddressV6}`],
+ ['media.peerconnection.nat_simulator.redirect_targets', `${redirectAddressV6}`]);
+ // If we don't detect the loop, gathering will not finish
+ await checkNoRelay([{urls:[`turn:[${redirectAddressV6}]?transport=tcp`], username, credential}]);
+ await SpecialPowers.popPrefEnv();
+ },
+
+ async function turnV6UdpTcpRedirectLoop() {
+ await pushPrefs(
+ ['media.peerconnection.nat_simulator.redirect_address', `${redirectAddressV6}`],
+ ['media.peerconnection.nat_simulator.redirect_targets', `${redirectAddressV6}`]);
+ // If we don't detect the loop, gathering will not finish
+ await checkNoRelay([{urls:[`turn:[${redirectAddressV6}]`, `turn:[${redirectAddressV6}]?transport=tcp`], username, credential}]);
+ await SpecialPowers.popPrefEnv();
+ },
+
+ async function turnV6UdpMultipleAddressRedirect() {
+ await pushPrefs(
+ ['media.peerconnection.nat_simulator.redirect_address', `${redirectAddressV6}`],
+ ['media.peerconnection.nat_simulator.redirect_targets', `${redirectAddressV6},${redirectTargetV6}`]);
+ await checkRelayUdp([{urls:[`turn:[${redirectAddressV6}]`], username, credential}]);
+ await SpecialPowers.popPrefEnv();
+ },
+
+ async function turnV6TcpMultipleAddressRedirect() {
+ await pushPrefs(
+ ['media.peerconnection.nat_simulator.redirect_address', `${redirectAddressV6}`],
+ ['media.peerconnection.nat_simulator.redirect_targets', `${redirectAddressV6},${redirectTargetV6}`]);
+ await checkRelayTcp([{urls:[`turn:[${redirectAddressV6}]?transport=tcp`], username, credential}]);
+ await SpecialPowers.popPrefEnv();
+ },
+
+ async function turnV6UdpTcpMultipleAddressRedirect() {
+ await pushPrefs(
+ ['media.peerconnection.nat_simulator.redirect_address', `${redirectAddressV6}`],
+ ['media.peerconnection.nat_simulator.redirect_targets', `${redirectAddressV6},${redirectTargetV6}`]);
+ await checkRelayUdpTcp([{urls:[`turn:[${redirectAddressV6}]`, `turn:[${redirectAddressV6}]?transport=tcp`], username, credential}]);
+ await SpecialPowers.popPrefEnv();
+ },
+ ];
+
+ runNetworkTest(async () => {
+ const turnServer = iceServersArray.find(server => "username" in server);
+ username = turnServer.username;
+ credential = turnServer.credential;
+ // Special props, non-standard
+ turnRedirectPort = turnServer.turn_redirect_port;
+ turnsRedirectPort = turnServer.turns_redirect_port;
+ // Just use the first url. It might make sense to look for TURNS first,
+ // since that will always use a hostname, but on CI we don't have TURNS
+ // support anyway (see bug 1323439).
+ const turnHostname = getTurnHostname(turnServer.urls[0]);
+
+ await pushPrefs(
+ ['media.peerconnection.ice.obfuscate_host_addresses', false],
+ ['media.peerconnection.nat_simulator.filtering_type', 'ENDPOINT_INDEPENDENT'],
+ ['media.peerconnection.nat_simulator.mapping_type', 'ENDPOINT_INDEPENDENT'],
+ ['media.peerconnection.ice.loopback', true],
+ ['media.getusermedia.insecure.enabled', true]);
+
+ if (await ipv6Supported()) {
+ redirectTargetV6 = await dnsLookupV6(turnHostname);
+ if (redirectTargetV6 == '' && turnHostname == 'localhost') {
+ // Our testers don't seem to have IPv6 DNS resolution for localhost
+ // set up...
+ redirectTargetV6 = '::1';
+ }
+
+ if (redirectTargetV6 != '') {
+ for (const test of tests) {
+ info(`Running test: ${test.name}`);
+ await test();
+ info(`Done running test: ${test.name}`);
+ }
+ } else {
+ ok(false, `This machine has an IPv6 address, but ${turnHostname} does not resolve to an IPv6 address`);
+ }
+ } else {
+ ok(false, 'This machine appears to not have an IPv6 address');
+ }
+
+ await SpecialPowers.popPrefEnv();
+ }, { useIceServer: true });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_glean.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_glean.html
new file mode 100644
index 0000000000..d5d6026ee5
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_glean.html
@@ -0,0 +1,488 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ bug: "1401592",
+ title: "Test that glean is recording stats as expected",
+ visible: true
+});
+
+const { AppConstants } = SpecialPowers.ChromeUtils.import(
+ "resource://gre/modules/AppConstants.jsm"
+);
+
+async function getAllWarningRates() {
+ return {
+ warnNoGetparameters:
+ await GleanTest.rtcrtpsenderSetparameters.warnNoGetparameters.testGetValue(),
+ warnLengthChanged:
+ await GleanTest.rtcrtpsenderSetparameters.warnLengthChanged.testGetValue(),
+ warnRidChanged:
+ await GleanTest.rtcrtpsenderSetparameters.warnRidChanged.testGetValue(),
+ warnNoTransactionid:
+ await GleanTest.rtcrtpsenderSetparameters.warnNoTransactionid.testGetValue(),
+ warnStaleTransactionid:
+ await GleanTest.rtcrtpsenderSetparameters.warnStaleTransactionid.testGetValue(),
+ };
+}
+
+const tests = [
+ async function checkRTCRtpSenderCount() {
+ const pc = new RTCPeerConnection();
+ const oldCount = await GleanTest.rtcrtpsender.count.testGetValue() ?? 0;
+ const {sender} = pc.addTransceiver('video', {
+ sendEncodings: [{rid: "0"},{rid: "1"},{rid: "2"}]
+ });
+ const countDiff = await GleanTest.rtcrtpsender.count.testGetValue() - oldCount;
+ is(countDiff, 1, "Glean should have recorded the creation of a single RTCRtpSender");
+ },
+
+ async function checkRTCRtpSenderSetParametersCompatCount() {
+ await pushPrefs(
+ ["media.peerconnection.allow_old_setParameters", true]);
+ const pc = new RTCPeerConnection();
+ const oldCount = await GleanTest.rtcrtpsender.countSetparametersCompat.testGetValue() ?? 0;
+ const {sender} = pc.addTransceiver('video', {
+ sendEncodings: [{rid: "0"},{rid: "1"},{rid: "2"}]
+ });
+ const countDiff = await GleanTest.rtcrtpsender.countSetparametersCompat.testGetValue() - oldCount;
+ is(countDiff, 1, "Glean should have recorded the creation of a single RTCRtpSender that uses the setParameters compat mode");
+ },
+
+ async function checkSendEncodings() {
+ const pc = new RTCPeerConnection();
+ const oldRate = await GleanTest.rtcrtpsender.usedSendencodings.testGetValue();
+ const {sender} = pc.addTransceiver('video', {
+ sendEncodings: [{rid: "0"},{rid: "1"},{rid: "2"}]
+ });
+ const newRate = await GleanTest.rtcrtpsender.usedSendencodings.testGetValue();
+ is(newRate.denominator, oldRate.denominator + 1, "Glean should have recorded the creation of a single RTCRtpSender");
+ is(newRate.numerator, oldRate.numerator + 1, "Glean should have recorded the use of sendEncodings");
+ },
+
+ async function checkAddTransceiverNoSendEncodings() {
+ const pc = new RTCPeerConnection();
+ const oldRate = await GleanTest.rtcrtpsender.usedSendencodings.testGetValue();
+ const {sender} = pc.addTransceiver('video');
+ const newRate = await GleanTest.rtcrtpsender.usedSendencodings.testGetValue();
+ is(newRate.denominator, oldRate.denominator + 1, "Glean should have recorded the creation of a single RTCRtpSender");
+ is(newRate.numerator, oldRate.numerator, "Glean should not have recorded a use of sendEncodings");
+ },
+
+ async function checkAddTrack() {
+ const pc = new RTCPeerConnection();
+ const oldRate = await GleanTest.rtcrtpsender.usedSendencodings.testGetValue();
+ const stream = await navigator.mediaDevices.getUserMedia({video: true});
+ const sender = pc.addTrack(stream.getTracks()[0]);
+ const newRate = await GleanTest.rtcrtpsender.usedSendencodings.testGetValue();
+ is(newRate.denominator, oldRate.denominator + 1, "Glean should have recorded the creation of a single RTCRtpSender");
+ is(newRate.numerator, oldRate.numerator, "Glean should not have recorded a use of sendEncodings");
+ },
+
+ async function checkGoodSetParametersCompatMode() {
+ await pushPrefs(
+ ["media.peerconnection.allow_old_setParameters", true]);
+ const pc = new RTCPeerConnection();
+ const {sender} = pc.addTransceiver('video', {
+ sendEncodings: [{rid: "0"},{rid: "1"},{rid: "2"}]
+ });
+ const oldWarningRates = await getAllWarningRates();
+ await sender.setParameters(sender.getParameters());
+ const newWarningRates = await getAllWarningRates();
+ isDeeply(oldWarningRates, newWarningRates);
+ },
+
+ async function checkBadSetParametersNoGetParametersWarning() {
+ await pushPrefs(
+ ["media.peerconnection.allow_old_setParameters", true]);
+ const pc = new RTCPeerConnection();
+ const {sender} = pc.addTransceiver('video', {
+ sendEncodings: [{rid: "0"},{rid: "1"},{rid: "2"}]
+ });
+
+ let oldRate = await GleanTest.rtcrtpsenderSetparameters.warnNoGetparameters.testGetValue();
+ let oldBlameCount = await GleanTest.rtcrtpsenderSetparameters.blameNoGetparameters["example.com"].testGetValue() || 0;
+
+ await sender.setParameters({encodings: [{rid: "0"},{rid: "1"},{rid: "2"}]});
+
+ let newRate = await GleanTest.rtcrtpsenderSetparameters.warnNoGetparameters.testGetValue();
+ let newBlameCount = await GleanTest.rtcrtpsenderSetparameters.blameNoGetparameters["example.com"].testGetValue() || 0;
+
+ is(newRate.numerator, oldRate.numerator + 1, "Glean should have recorded a warning in setParameters due to lack of a getParameters call");
+
+ if (AppConstants.EARLY_BETA_OR_EARLIER) {
+ is(newBlameCount, oldBlameCount + 1, "Glean should have recorded that example.com encountered this warning");
+ } else {
+ is(newBlameCount, oldBlameCount, "Glean should not have recorded that example.com encountered this warning, because we're running this test on a stable channel");
+ }
+
+ oldRate = newRate;
+ oldBlameCount = newBlameCount;
+
+ // Glean should only record the warning once per sender!
+ await sender.setParameters({encodings: [{rid: "0"},{rid: "1"},{rid: "2"}]});
+
+ newRate = await GleanTest.rtcrtpsenderSetparameters.warnNoGetparameters.testGetValue();
+ newBlameCount = await GleanTest.rtcrtpsenderSetparameters.blameNoGetparameters["example.com"].testGetValue() || 0;
+
+ is(newRate.numerator, oldRate.numerator, "Glean should not have recorded another warning in setParameters due to lack of a getParameters call");
+ is(newBlameCount, oldBlameCount, "Glean should not have recorded that example.com encountered this warning a second time, since this is the same sender");
+ },
+
+ async function checkBadSetParametersLengthChangedWarning() {
+ await pushPrefs(
+ ["media.peerconnection.allow_old_setParameters", true]);
+ const pc = new RTCPeerConnection();
+ const {sender} = pc.addTransceiver('video', {
+ sendEncodings: [{rid: "0"},{rid: "1"},{rid: "2"}]
+ });
+
+ let oldRate = await GleanTest.rtcrtpsenderSetparameters.warnLengthChanged.testGetValue();
+ let oldBlameCount = await GleanTest.rtcrtpsenderSetparameters.blameLengthChanged["example.com"].testGetValue() || 0;
+
+ let params = sender.getParameters();
+ params.encodings.pop();
+ await sender.setParameters(params);
+
+ let newRate = await GleanTest.rtcrtpsenderSetparameters.warnLengthChanged.testGetValue();
+ let newBlameCount = await GleanTest.rtcrtpsenderSetparameters.blameLengthChanged["example.com"].testGetValue() || 0;
+
+ is(newRate.numerator, oldRate.numerator + 1, "Glean should have recorded a warning due to a length change in encodings");
+
+ if (AppConstants.EARLY_BETA_OR_EARLIER) {
+ is(newBlameCount, oldBlameCount + 1, "Glean should have recorded that example.com encountered this warning");
+ } else {
+ is(newBlameCount, oldBlameCount, "Glean should not have recorded that example.com encountered this warning, because we're running this test on a stable channel");
+ }
+
+ oldRate = newRate;
+ oldBlameCount = newBlameCount;
+
+ // Glean should only record the warning once per sender!
+ params = sender.getParameters();
+ params.encodings.pop();
+ await sender.setParameters(params);
+
+ newRate = await GleanTest.rtcrtpsenderSetparameters.warnLengthChanged.testGetValue();
+ newBlameCount = await GleanTest.rtcrtpsenderSetparameters.blameLengthChanged["example.com"].testGetValue() || 0;
+
+ is(newRate.numerator, oldRate.numerator, "Glean should not have recorded another warning due to a length change in encodings");
+ is(newBlameCount, oldBlameCount, "Glean should not have recorded that example.com encountered this warning a second time, since this is the same sender");
+ },
+
+ async function checkBadSetParametersRidChangedWarning() {
+ // This pref does not let rid change errors slide anymore
+ await pushPrefs(
+ ["media.peerconnection.allow_old_setParameters", true]);
+ const pc = new RTCPeerConnection();
+ const {sender} = pc.addTransceiver('video', {
+ sendEncodings: [{rid: "0"},{rid: "1"},{rid: "2"}]
+ });
+
+ let oldRate = await GleanTest.rtcrtpsenderSetparameters.failRidChanged.testGetValue();
+ let oldWarnRate = await GleanTest.rtcrtpsenderSetparameters.warnRidChanged.testGetValue();
+
+ let params = sender.getParameters();
+ params.encodings[1].rid = "foo";
+ try {
+ await sender.setParameters(params);
+ } catch (e) {
+ }
+ let newRate = await GleanTest.rtcrtpsenderSetparameters.failRidChanged.testGetValue();
+ let newWarnRate = await GleanTest.rtcrtpsenderSetparameters.warnRidChanged.testGetValue();
+ is(newRate.numerator, oldRate.numerator + 1, "Glean should have recorded an error due to a rid change in encodings");
+ is(newWarnRate.numerator, oldWarnRate.numerator, "Glean should not have recorded a warning due to a rid change in encodings");
+
+ // Glean should only record the error once per sender!
+ params = sender.getParameters();
+ params.encodings[1].rid = "bar";
+ oldRate = newRate;
+ try {
+ await sender.setParameters(params);
+ } catch (e) {
+ }
+ newRate = await GleanTest.rtcrtpsenderSetparameters.failRidChanged.testGetValue();
+ newWarnRate = await GleanTest.rtcrtpsenderSetparameters.warnRidChanged.testGetValue();
+ is(newRate.numerator, oldRate.numerator, "Glean should not have recorded another error due to a rid change in encodings");
+ is(newWarnRate.numerator, oldWarnRate.numerator, "Glean should not have recorded a warning due to a rid change in encodings");
+ },
+
+ async function checkBadSetParametersNoTransactionIdWarning() {
+ await pushPrefs(
+ ["media.peerconnection.allow_old_setParameters", true]);
+ const pc = new RTCPeerConnection();
+ const {sender} = pc.addTransceiver('video', {
+ sendEncodings: [{rid: "0"},{rid: "1"},{rid: "2"}]
+ });
+
+ let oldRate = await GleanTest.rtcrtpsenderSetparameters.warnNoTransactionid.testGetValue();
+ let oldBlameCount = await GleanTest.rtcrtpsenderSetparameters.blameNoTransactionid["example.com"].testGetValue() || 0;
+
+ await sender.setParameters({encodings: [{rid: "0"},{rid: "1"},{rid: "2"}]});
+
+ let newRate = await GleanTest.rtcrtpsenderSetparameters.warnNoTransactionid.testGetValue();
+ let newBlameCount = await GleanTest.rtcrtpsenderSetparameters.blameNoTransactionid["example.com"].testGetValue() || 0;
+
+ is(newRate.numerator, oldRate.numerator + 1, "Glean should have recorded a warning due to missing transactionId in setParameters");
+
+ if (AppConstants.EARLY_BETA_OR_EARLIER) {
+ is(newBlameCount, oldBlameCount + 1, "Glean should have recorded that example.com encountered this warning");
+ } else {
+ is(newBlameCount, oldBlameCount, "Glean should not have recorded that example.com encountered this warning, because we're running this test on a stable channel");
+ }
+
+ oldRate = newRate;
+ oldBlameCount = newBlameCount;
+
+ // Glean should only record the warning once per sender!
+ await sender.setParameters({encodings: [{rid: "0"},{rid: "1"},{rid: "2"}]});
+
+ newRate = await GleanTest.rtcrtpsenderSetparameters.warnNoTransactionid.testGetValue();
+ newBlameCount = await GleanTest.rtcrtpsenderSetparameters.blameNoTransactionid["example.com"].testGetValue() || 0;
+
+ is(newRate.numerator, oldRate.numerator, "Glean should not have recorded another warning due to missing transactionId in setParameters");
+ is(newBlameCount, oldBlameCount, "Glean should not have recorded that example.com encountered this warning a second time, since this is the same sender");
+ },
+
+ async function checkBadSetParametersStaleTransactionIdWarning() {
+ await pushPrefs(
+ ["media.peerconnection.allow_old_setParameters", true]);
+ const pc = new RTCPeerConnection();
+ const {sender} = pc.addTransceiver('video', {
+ sendEncodings: [{rid: "0"},{rid: "1"},{rid: "2"}]
+ });
+
+ let oldRate = await GleanTest.rtcrtpsenderSetparameters.warnStaleTransactionid.testGetValue();
+ let oldBlameCount = await GleanTest.rtcrtpsenderSetparameters.blameStaleTransactionid["example.com"].testGetValue() || 0;
+
+ let params = sender.getParameters();
+ // Cause transactionId to be stale
+ await pc.createOffer();
+ // ...but make sure there is a recent getParameters call
+ sender.getParameters();
+ await sender.setParameters(params);
+
+ let newRate = await GleanTest.rtcrtpsenderSetparameters.warnStaleTransactionid.testGetValue();
+ let newBlameCount = await GleanTest.rtcrtpsenderSetparameters.blameStaleTransactionid["example.com"].testGetValue() || 0;
+
+ is(newRate.numerator, oldRate.numerator + 1, "Glean should have recorded a warning due to stale transactionId in setParameters");
+
+ if (AppConstants.EARLY_BETA_OR_EARLIER) {
+ is(newBlameCount, oldBlameCount + 1, "Glean should have recorded that example.com encountered this warning");
+ } else {
+ is(newBlameCount, oldBlameCount, "Glean should not have recorded that example.com encountered this warning, because we're running this test on a stable channel");
+ }
+
+ oldRate = newRate;
+ oldBlameCount = newBlameCount;
+
+ // Glean should only record the warning once per sender!
+ params = sender.getParameters();
+ // Cause transactionId to be stale
+ await pc.createOffer();
+ // ...but make sure there is a recent getParameters call
+ sender.getParameters();
+ await sender.setParameters(params);
+
+ newRate = await GleanTest.rtcrtpsenderSetparameters.warnStaleTransactionid.testGetValue();
+ newBlameCount = await GleanTest.rtcrtpsenderSetparameters.blameStaleTransactionid["example.com"].testGetValue() || 0;
+
+ is(newRate.numerator, oldRate.numerator, "Glean should not have recorded another warning due to stale transactionId in setParameters");
+ is(newBlameCount, oldBlameCount, "Glean should not have recorded that example.com encountered this warning a second time, since this is the same sender");
+ },
+
+ async function checkBadSetParametersLengthChangedError() {
+ await pushPrefs(
+ ["media.peerconnection.allow_old_setParameters", false]);
+ const pc = new RTCPeerConnection();
+ const {sender} = pc.addTransceiver('video', {
+ sendEncodings: [{rid: "0"},{rid: "1"},{rid: "2"}]
+ });
+ let oldRate = await GleanTest.rtcrtpsenderSetparameters.failLengthChanged.testGetValue();
+ let params = sender.getParameters();
+ params.encodings.pop();
+ try {
+ await sender.setParameters(params);
+ } catch(e) {
+ }
+ let newRate = await GleanTest.rtcrtpsenderSetparameters.failLengthChanged.testGetValue();
+ is(newRate.numerator, oldRate.numerator + 1, "Glean should have recorded an error due to a length change in encodings");
+
+ // Glean should only record the error once per sender!
+ params = sender.getParameters();
+ params.encodings.pop();
+ oldRate = newRate;
+ try {
+ await sender.setParameters(params);
+ } catch (e) {
+ }
+ newRate = await GleanTest.rtcrtpsenderSetparameters.failLengthChanged.testGetValue();
+ is(newRate.numerator, oldRate.numerator, "Glean should not have recorded another error due to a length change in encodings");
+ },
+
+ async function checkBadSetParametersRidChangedError() {
+ await pushPrefs(
+ ["media.peerconnection.allow_old_setParameters", false]);
+ const pc = new RTCPeerConnection();
+ const {sender} = pc.addTransceiver('video', {
+ sendEncodings: [{rid: "0"},{rid: "1"},{rid: "2"}]
+ });
+ let oldRate = await GleanTest.rtcrtpsenderSetparameters.failRidChanged.testGetValue();
+ let params = sender.getParameters();
+ params.encodings[1].rid = "foo";
+ try {
+ await sender.setParameters(params);
+ } catch (e) {
+ }
+ let newRate = await GleanTest.rtcrtpsenderSetparameters.failRidChanged.testGetValue();
+ is(newRate.numerator, oldRate.numerator + 1, "Glean should have recorded an error due to a rid change in encodings");
+
+ // Glean should only record the error once per sender!
+ params = sender.getParameters();
+ params.encodings[1].rid = "bar";
+ oldRate = newRate;
+ try {
+ await sender.setParameters(params);
+ } catch (e) {
+ }
+ newRate = await GleanTest.rtcrtpsenderSetparameters.failRidChanged.testGetValue();
+ is(newRate.numerator, oldRate.numerator, "Glean should not have recorded another error due to a rid change in encodings");
+ },
+
+ async function checkBadSetParametersNoGetParametersError() {
+ await pushPrefs(
+ ["media.peerconnection.allow_old_setParameters", false]);
+ const pc = new RTCPeerConnection();
+ const {sender} = pc.addTransceiver('video', {
+ sendEncodings: [{rid: "0"},{rid: "1"},{rid: "2"}]
+ });
+ let oldRate = await GleanTest.rtcrtpsenderSetparameters.failNoGetparameters.testGetValue();
+ try {
+ await sender.setParameters({encodings: [{rid: "0"},{rid: "1"},{rid: "2"}]});
+ } catch (e) {
+ }
+ let newRate = await GleanTest.rtcrtpsenderSetparameters.failNoGetparameters.testGetValue();
+ is(newRate.numerator, oldRate.numerator + 1, "Glean should have recorded an error in setParameters due to lack of a getParameters call");
+
+ // Glean should only record the error once per sender!
+ oldRate = newRate;
+ try {
+ await sender.setParameters({encodings: [{rid: "0"},{rid: "1"},{rid: "2"}]});
+ } catch (e) {
+ }
+ newRate = await GleanTest.rtcrtpsenderSetparameters.failNoGetparameters.testGetValue();
+ is(newRate.numerator, oldRate.numerator, "Glean should not have recorded another error in setParameters due to lack of a getParameters call");
+ },
+
+ async function checkBadSetParametersStaleTransactionIdError() {
+ await pushPrefs(
+ ["media.peerconnection.allow_old_setParameters", false]);
+ const pc = new RTCPeerConnection();
+ const {sender} = pc.addTransceiver('video', {
+ sendEncodings: [{rid: "0"},{rid: "1"},{rid: "2"}]
+ });
+ let oldRate = await GleanTest.rtcrtpsenderSetparameters.failStaleTransactionid.testGetValue();
+ let params = sender.getParameters();
+ // Cause transactionId to be stale
+ await pc.createOffer();
+ // ...but make sure there is a recent getParameters call
+ sender.getParameters();
+ try {
+ await sender.setParameters(params);
+ } catch (e) {
+ }
+ let newRate = await GleanTest.rtcrtpsenderSetparameters.failStaleTransactionid.testGetValue();
+ is(newRate.numerator, oldRate.numerator + 1, "Glean should have recorded an error due to stale transactionId in setParameters");
+
+ // Glean should only record the error once per sender!
+ oldRate = newRate;
+ params = sender.getParameters();
+ // Cause transactionId to be stale
+ await pc.createOffer();
+ // ...but make sure there is a recent getParameters call
+ sender.getParameters();
+ try {
+ await sender.setParameters(params);
+ } catch (e) {
+ }
+ newRate = await GleanTest.rtcrtpsenderSetparameters.failStaleTransactionid.testGetValue();
+ is(newRate.numerator, oldRate.numerator, "Glean should not have recorded another error due to stale transactionId in setParameters");
+ },
+
+ async function checkBadSetParametersNoEncodingsError() {
+ // If we do not allow the old setParameters, this will fail the length check
+ // instead.
+ await pushPrefs(
+ ["media.peerconnection.allow_old_setParameters", true]);
+ const pc = new RTCPeerConnection();
+ const {sender} = pc.addTransceiver('video', {
+ sendEncodings: [{rid: "0"},{rid: "1"},{rid: "2"}]
+ });
+ let oldRate = await GleanTest.rtcrtpsenderSetparameters.failNoEncodings.testGetValue();
+ let params = sender.getParameters();
+ params.encodings = [];
+ try {
+ await sender.setParameters(params);
+ } catch (e) {
+ }
+ let newRate = await GleanTest.rtcrtpsenderSetparameters.failNoEncodings.testGetValue();
+ is(newRate.numerator, oldRate.numerator, "Glean should not have recorded an error due to empty encodings in setParameters");
+
+ // Glean should only record the error once per sender!
+ oldRate = newRate;
+ params = sender.getParameters();
+ params.encodings = [];
+ try {
+ await sender.setParameters(params);
+ } catch (e) {
+ }
+ newRate = await GleanTest.rtcrtpsenderSetparameters.failNoEncodings.testGetValue();
+ is(newRate.numerator, oldRate.numerator, "Glean should not have recorded an error due empty encodings in setParameters");
+ },
+
+ async function checkBadSetParametersOtherError() {
+ const pc = new RTCPeerConnection();
+ const {sender} = pc.addTransceiver('video', {
+ sendEncodings: [{rid: "0"},{rid: "1"},{rid: "2"}]
+ });
+ let oldRate = await GleanTest.rtcrtpsenderSetparameters.failOther.testGetValue();
+ let params = sender.getParameters();
+ params.encodings[0].scaleResolutionDownBy = 0.5;
+ try {
+ await sender.setParameters(params);
+ } catch (e) {
+ }
+ let newRate = await GleanTest.rtcrtpsenderSetparameters.failOther.testGetValue();
+ is(newRate.numerator, oldRate.numerator + 1, "Glean should have recorded an error due to some other failure");
+
+ // Glean should only record the error once per sender!
+ oldRate = newRate;
+ params = sender.getParameters();
+ params.encodings[0].scaleResolutionDownBy = 0.5;
+ try {
+ await sender.setParameters(params);
+ } catch (e) {
+ }
+ newRate = await GleanTest.rtcrtpsenderSetparameters.failOther.testGetValue();
+ is(newRate.numerator, oldRate.numerator, "Glean should not have recorded another error due to some other failure");
+ },
+
+];
+
+runNetworkTest(async () => {
+ for (const test of tests) {
+ info(`Running test: ${test.name}`);
+ await test();
+ info(`Done running test: ${test.name}`);
+ }
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_iceFailure.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_iceFailure.html
new file mode 100644
index 0000000000..1b82473997
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_iceFailure.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1087629",
+ title: "Wait for ICE failure"
+ });
+
+// Test iceFailure
+
+function PC_LOCAL_SETUP_NULL_ICE_HANDLER(test) {
+ test.pcLocal.setupIceCandidateHandler(test, function() {}, function () {});
+}
+function PC_REMOTE_SETUP_NULL_ICE_HANDLER(test) {
+ test.pcRemote.setupIceCandidateHandler(test, function() {}, function () {});
+}
+function PC_REMOTE_ADD_FAKE_ICE_CANDIDATE(test) {
+ var cand = {"candidate":"candidate:0 1 UDP 2130379007 192.0.2.1 12345 typ host","sdpMid":"","sdpMLineIndex":0};
+ test.pcRemote.storeOrAddIceCandidate(cand);
+ info(test.pcRemote + " Stored fake candidate: " + JSON.stringify(cand));
+}
+function PC_LOCAL_ADD_FAKE_ICE_CANDIDATE(test) {
+ var cand = {"candidate":"candidate:0 1 UDP 2130379007 192.0.2.2 56789 typ host","sdpMid":"","sdpMLineIndex":0};
+ test.pcLocal.storeOrAddIceCandidate(cand);
+ info(test.pcLocal + " Stored fake candidate: " + JSON.stringify(cand));
+}
+function PC_LOCAL_WAIT_FOR_ICE_FAILURE(test) {
+ return test.pcLocal.iceFailed.then(() => {
+ ok(true, this.pcLocal + " Ice Failure Reached.");
+ });
+}
+function PC_REMOTE_WAIT_FOR_ICE_FAILURE(test) {
+ return test.pcRemote.iceFailed.then(() => {
+ ok(true, this.pcRemote + " Ice Failure Reached.");
+ });
+}
+function PC_LOCAL_WAIT_FOR_ICE_FAILED(test) {
+ var resolveIceFailed;
+ test.pcLocal.iceFailed = new Promise(r => resolveIceFailed = r);
+ test.pcLocal.ice_connection_callbacks.checkIceStatus = () => {
+ if (test.pcLocal._pc.iceConnectionState === "failed") {
+ resolveIceFailed();
+ }
+ }
+}
+function PC_REMOTE_WAIT_FOR_ICE_FAILED(test) {
+ var resolveIceFailed;
+ test.pcRemote.iceFailed = new Promise(r => resolveIceFailed = r);
+ test.pcRemote.ice_connection_callbacks.checkIceStatus = () => {
+ if (test.pcRemote._pc.iceConnectionState === "failed") {
+ resolveIceFailed();
+ }
+ }
+}
+
+runNetworkTest(async () => {
+ await pushPrefs(
+ ['media.peerconnection.ice.stun_client_maximum_transmits', 3],
+ ['media.peerconnection.ice.trickle_grace_period', 3000],
+ );
+ var test = new PeerConnectionTest();
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ test.chain.replace("PC_LOCAL_SETUP_ICE_HANDLER", PC_LOCAL_SETUP_NULL_ICE_HANDLER);
+ test.chain.replace("PC_REMOTE_SETUP_ICE_HANDLER", PC_REMOTE_SETUP_NULL_ICE_HANDLER);
+ test.chain.insertAfter("PC_REMOTE_SETUP_NULL_ICE_HANDLER", PC_LOCAL_WAIT_FOR_ICE_FAILED);
+ test.chain.insertAfter("PC_LOCAL_WAIT_FOR_ICE_FAILED", PC_REMOTE_WAIT_FOR_ICE_FAILED);
+ test.chain.removeAfter("PC_LOCAL_SET_REMOTE_DESCRIPTION");
+ test.chain.append([
+ PC_REMOTE_ADD_FAKE_ICE_CANDIDATE,
+ PC_LOCAL_ADD_FAKE_ICE_CANDIDATE,
+ PC_LOCAL_WAIT_FOR_ICE_FAILURE,
+ PC_REMOTE_WAIT_FOR_ICE_FAILURE
+ ]);
+ await test.run();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_insertDTMF.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_insertDTMF.html
new file mode 100644
index 0000000000..ca8b866a6d
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_insertDTMF.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ bug: "1291715",
+ title: "Test insertDTMF on sender",
+ visible: true
+});
+
+function insertdtmftest(pc) {
+ ok(pc.getSenders().length, "have senders");
+ var sender = pc.getSenders()[0];
+ ok(sender.dtmf, "sender has dtmf object");
+
+ ok(sender.dtmf.toneBuffer === "", "sender should start with empty tonebuffer");
+
+ // These will trigger assertions on debug builds if we do not enforce the
+ // specified minimums and maximums for duration and interToneGap.
+ sender.dtmf.insertDTMF("A", 10);
+ sender.dtmf.insertDTMF("A", 10000);
+ sender.dtmf.insertDTMF("A", 70, 10);
+
+ var threw = false;
+ try {
+ sender.dtmf.insertDTMF("bad tones");
+ } catch (ex) {
+ threw = true;
+ is(ex.code, DOMException.INVALID_CHARACTER_ERR, "Expected InvalidCharacterError");
+ }
+ ok(threw, "Expected exception");
+
+ sender.dtmf.insertDTMF("A");
+ sender.dtmf.insertDTMF("B");
+ ok(!sender.dtmf.toneBuffer.includes("A"), "calling insertDTMF should replace current characters");
+
+ sender.dtmf.insertDTMF("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
+ ok(sender.dtmf.toneBuffer.includes("A"), "lowercase characters should be normalized");
+
+ pc.removeTrack(sender);
+ threw = false;
+ try {
+ sender.dtmf.insertDTMF("AAA");
+ } catch (ex) {
+ threw = true;
+ is(ex.code, DOMException.INVALID_STATE_ERR, "Expected InvalidStateError");
+ }
+ ok(threw, "Expected exception");
+}
+
+runNetworkTest(() => {
+ test = new PeerConnectionTest();
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ test.chain.removeAfter("PC_REMOTE_WAIT_FOR_MEDIA_FLOW");
+
+ // Test sender dtmf.
+ test.chain.append([
+ function PC_LOCAL_INSERT_DTMF(test) {
+ // We want to call removeTrack
+ test.pcLocal.expectNegotiationNeeded();
+ return insertdtmftest(test.pcLocal._pc);
+ }
+ ]);
+
+ return pushPrefs(['media.peerconnection.dtmf.enabled', true])
+ .then(() => test.run());
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_localReofferRollback.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_localReofferRollback.html
new file mode 100644
index 0000000000..16406ece6e
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_localReofferRollback.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "952145",
+ title: "Rollback local reoffer"
+ });
+
+ runNetworkTest(function (options) {
+ const test = new PeerConnectionTest(options);
+ addRenegotiation(test.chain, [
+ function PC_LOCAL_ADD_SECOND_STREAM(test) {
+ test.setMediaConstraints([{audio: true}, {audio: true}],
+ [{audio: true}]);
+ return test.pcLocal.getAllUserMediaAndAddStreams([{audio: true}]);
+ },
+
+ function PC_LOCAL_CREATE_AND_SET_OFFER(test) {
+ return test.createOffer(test.pcLocal).then(offer => {
+ return test.setLocalDescription(test.pcLocal, offer, HAVE_LOCAL_OFFER);
+ });
+ },
+
+ function PC_LOCAL_ROLLBACK(test) {
+ // the negotiationNeeded slot should have been true both before and
+ // after this SLD, so the event should fire again.
+ test.pcLocal.expectNegotiationNeeded();
+ return test.setLocalDescription(test.pcLocal,
+ { type: "rollback", sdp: "" },
+ STABLE);
+ },
+ ]);
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_localRollback.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_localRollback.html
new file mode 100644
index 0000000000..5bdc8cc029
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_localRollback.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "952145",
+ title: "Rollback local offer"
+ });
+
+ runNetworkTest(function (options) {
+ const test = new PeerConnectionTest(options);
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ test.chain.insertBefore('PC_LOCAL_CREATE_OFFER', [
+ function PC_REMOTE_CREATE_AND_SET_OFFER(test) {
+ return test.createOffer(test.pcRemote).then(offer => {
+ return test.setLocalDescription(test.pcRemote, offer, HAVE_LOCAL_OFFER);
+ });
+ },
+
+ function PC_REMOTE_ROLLBACK(test) {
+ // the negotiationNeeded slot should have been true both before and
+ // after this SLD, so the event should fire again.
+ test.pcRemote.expectNegotiationNeeded();
+ return test.setLocalDescription(test.pcRemote,
+ { type: "rollback", sdp: "" },
+ STABLE);
+ },
+
+ // Rolling back should shut down gathering
+ function PC_REMOTE_WAIT_FOR_END_OF_TRICKLE(test) {
+ return test.pcRemote.endOfTrickleIce;
+ },
+
+ function PC_REMOTE_SETUP_ICE_HANDLER(test) {
+ test.pcRemote.setupIceCandidateHandler(test);
+ },
+ ]);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_maxFsConstraint.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_maxFsConstraint.html
new file mode 100644
index 0000000000..a2f2555020
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_maxFsConstraint.html
@@ -0,0 +1,112 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1393687",
+ title: "Enforce max-fs constraint on a PeerConnection",
+ visible: true
+ });
+
+ var mustRejectWith = (msg, reason, f) =>
+ f().then(() => ok(false, msg),
+ e => is(e.name, reason, msg));
+
+ var removeAllButCodec = (d, codec) =>
+ (d.sdp = d.sdp.replace(/m=video (\w) UDP\/TLS\/RTP\/SAVPF \w.*\r\n/,
+ "m=video $1 UDP/TLS/RTP/SAVPF " + codec + "\r\n"), d);
+
+ var mungeSDP = (d, forceH264) => {
+ if (forceH264) {
+ removeAllButCodec(d, 126);
+ d.sdp = d.sdp.replace(/a=fmtp:126 (.*);packetization-mode=1/, "a=fmtp:126 $1;packetization-mode=1;max-fs=100");
+ } else {
+ d.sdp = d.sdp.replace(/max-fs=\d+/, "max-fs=100");
+ }
+ return d;
+ };
+
+ const checkForH264Support = async () => {
+ const pc = new RTCPeerConnection();
+ const offer = await pc.createOffer({offerToReceiveVideo: true});
+ return offer.sdp.match(/a=rtpmap:[1-9][0-9]* H264/);
+ };
+
+ let resolutionAlignment = 1;
+
+ function testScale(codec) {
+ var v1 = createMediaElement('video', 'v1');
+ var v2 = createMediaElement('video', 'v2');
+
+ var pc1 = new RTCPeerConnection();
+ var pc2 = new RTCPeerConnection();
+
+ var add = (pc, can, failed) => can && pc.addIceCandidate(can).catch(failed);
+ pc1.onicecandidate = e => add(pc2, e.candidate, generateErrorCallback());
+ pc2.onicecandidate = e => add(pc1, e.candidate, generateErrorCallback());
+
+ info("testing max-fs with" + codec);
+
+ pc1.onnegotiationneeded = e =>
+ pc1.createOffer()
+ .then(d => pc1.setLocalDescription(mungeSDP(d, codec == "H264")))
+ .then(() => pc2.setRemoteDescription(pc1.localDescription))
+ .then(() => pc2.createAnswer()).then(d => pc2.setLocalDescription(mungeSDP(d, codec =="H264")))
+ .then(() => pc1.setRemoteDescription(pc2.localDescription))
+ .catch(generateErrorCallback());
+
+ pc2.ontrack = e => {
+ v2.srcObject = e.streams[0];
+ };
+
+ var stream;
+
+ return navigator.mediaDevices.getUserMedia({ video: true })
+ .then(s => {
+ stream = s;
+ v1.srcObject = stream;
+ let track = stream.getVideoTracks()[0];
+ let sender = pc1.addTrack(track, stream);
+ is(v2.currentTime, 0, "v2.currentTime is zero at outset");
+ })
+ .then(() => wait(5000))
+ .then(() => {
+ if (v2.videoWidth == 0 && v2.videoHeight == 0) {
+ info("Skipping test, insufficient time for video to start.");
+ } else {
+ const expectedWidth = 184 - 184 % resolutionAlignment;
+ const expectedHeight = 138 - 138 % resolutionAlignment;
+ is(v2.videoWidth, expectedWidth,
+ `sink width should be ${expectedWidth} for ${codec}`);
+ is(v2.videoHeight, expectedHeight,
+ `sink height should be ${expectedHeight} for ${codec}`);
+ }})
+ .then(() => {
+ stream.getTracks().forEach(track => track.stop());
+ v1.srcObject = v2.srcObject = null;
+ }).catch(generateErrorCallback());
+ }
+
+ runNetworkTest(async () => {
+ await pushPrefs(['media.peerconnection.video.lock_scaling', true]);
+ if (await checkForH264Support()) {
+ if (navigator.userAgent.includes("Android")) {
+ // Android only has a hw encoder for h264
+ resolutionAlignment = 16;
+ }
+ await matchPlatformH264CodecPrefs();
+ await testScale("H264");
+ }
+
+ // Disable h264 hardware support, to ensure it is not prioritized over VP8
+ await pushPrefs(["media.webrtc.hw.h264.enabled", false]);
+ await testScale("VP8");
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_multiple_captureStream_canvas_2d.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_multiple_captureStream_canvas_2d.html
new file mode 100644
index 0000000000..9ad25e7852
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_multiple_captureStream_canvas_2d.html
@@ -0,0 +1,115 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ bug: "1166832",
+ title: "Canvas(2D)::Multiple CaptureStream as video-only input to peerconnection",
+ visible: true
+});
+
+/**
+ * Test to verify using multiple capture streams concurrently.
+ */
+runNetworkTest(async () => {
+ // [TODO] re-enable HW decoder after bug 1526207 is fixed.
+ if (navigator.userAgent.includes("Android")) {
+ await pushPrefs(["media.navigator.mediadatadecoder_vpx_enabled", false]);
+ await pushPrefs(["media.webrtc.hw.h264.enabled", false]);
+ }
+
+ var test = new PeerConnectionTest();
+ var h = new CaptureStreamTestHelper2D(50, 50);
+
+ var vremote1;
+ var stream1;
+ var canvas1 = h.createAndAppendElement('canvas', 'source_canvas1');
+
+ var vremote2;
+ var stream2;
+ var canvas2 = h.createAndAppendElement('canvas', 'source_canvas2');
+
+ const threshold = 128;
+
+ test.setMediaConstraints([{video: true}, {video: true}], []);
+ test.chain.replace("PC_LOCAL_GUM", [
+ function PC_LOCAL_CANVAS_CAPTURESTREAM(test) {
+ h.drawColor(canvas1, h.green);
+ h.drawColor(canvas2, h.blue);
+ stream1 = canvas1.captureStream(0); // fps = 0 to capture single frame
+ test.pcLocal.attachLocalStream(stream1);
+ stream2 = canvas2.captureStream(0); // fps = 0 to capture single frame
+ test.pcLocal.attachLocalStream(stream2);
+ var i = 0;
+ return setInterval(function() {
+ try {
+ info("draw " + i ? "green" : "red/blue");
+ h.drawColor(canvas1, i ? h.green : h.red);
+ h.drawColor(canvas2, i ? h.green : h.blue);
+ i = 1 - i;
+ stream1.requestFrame();
+ stream2.requestFrame();
+ } catch (e) {
+ // ignore; stream might have shut down, and we don't bother clearing
+ // the setInterval.
+ }
+ }, 500);
+ }
+ ]);
+
+ test.chain.append([
+ function CHECK_REMOTE_VIDEO() {
+ is(test.pcRemote.remoteMediaElements.length, 2, "pcRemote Should have 2 remote media elements");
+ vremote1 = test.pcRemote.remoteMediaElements[0];
+ vremote2 = test.pcRemote.remoteMediaElements[1];
+
+ // since we don't know which remote video is created first, we don't know
+ // which should be blue or red, but this will make sure that one is
+ // green and one is blue
+ return Promise.race([
+ Promise.all([
+ h.pixelMustBecome(vremote1, h.red, {
+ threshold,
+ infoString: "pcRemote's remote1 should become red",
+ }),
+ h.pixelMustBecome(vremote2, h.blue, {
+ threshold,
+ infoString: "pcRemote's remote2 should become blue",
+ }),
+ ]),
+ Promise.all([
+ h.pixelMustBecome(vremote2, h.red, {
+ threshold,
+ infoString: "pcRemote's remote2 should become red",
+ }),
+ h.pixelMustBecome(vremote1, h.blue, {
+ threshold,
+ infoString: "pcRemote's remote1 should become blue",
+ }),
+ ])
+ ]);
+ },
+ function WAIT_FOR_REMOTE_BOTH_GREEN() {
+ return Promise.all([
+ h.pixelMustBecome(vremote1, h.green, {
+ threshold,
+ infoString: "pcRemote's remote1 should become green",
+ }),
+ h.pixelMustBecome(vremote2, h.green, {
+ threshold,
+ infoString: "pcRemote's remote2 should become green",
+ }),
+ ])
+ },
+ ]);
+ await test.run();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_noTrickleAnswer.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_noTrickleAnswer.html
new file mode 100644
index 0000000000..7e3fd78430
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_noTrickleAnswer.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="nonTrickleIce.js"></script>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1060102",
+ title: "Basic audio only SDP answer without trickle ICE"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ test = new PeerConnectionTest(options);
+ makeAnswererNonTrickle(test.chain);
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_noTrickleOffer.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_noTrickleOffer.html
new file mode 100644
index 0000000000..12b2a95596
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_noTrickleOffer.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="nonTrickleIce.js"></script>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1060102",
+ title: "Basic audio only SDP offer without trickle ICE"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ test = new PeerConnectionTest(options);
+ makeOffererNonTrickle(test.chain);
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_noTrickleOfferAnswer.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_noTrickleOfferAnswer.html
new file mode 100644
index 0000000000..554750e975
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_noTrickleOfferAnswer.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="nonTrickleIce.js"></script>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1060102",
+ title: "Basic audio only SDP offer and answer without trickle ICE"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ test = new PeerConnectionTest(options);
+ makeOffererNonTrickle(test.chain);
+ makeAnswererNonTrickle(test.chain);
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_nonDefaultRate.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_nonDefaultRate.html
new file mode 100644
index 0000000000..ad9414cef2
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_nonDefaultRate.html
@@ -0,0 +1,200 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({ title: "PeerConnection feed to a graph with non default rate", bug: "1387454" });
+ /**
+ * Run a test to verify that when we use the streem with nonDefault rate to/from a PC
+ * the connection fails. (PC is always on default rate).
+ */
+
+ let pc1;
+ let pc2;
+
+ const offerOptions = {
+ offerToReceiveAudio: 1,
+ };
+
+ function getName(pc) {
+ return (pc === pc1) ? 'pc1' : 'pc2';
+ }
+
+ function getOtherPc(pc) {
+ return (pc === pc1) ? pc2 : pc1;
+ }
+
+ function onAddIceCandidateSuccess(pc) {
+ ok(true, getName(pc) + ' addIceCandidate success');
+ }
+
+ function onAddIceCandidateError(pc, error) {
+ ok(false, getName(pc) + ' failed to add ICE Candidate: ' + error.toString());
+ }
+
+ function onIceCandidate(pc, event, done) {
+ if (!event.candidate) {
+ ok(pc.iceGatheringState === 'complete', getName(pc) + " ICE gathering state has reached complete");
+ done();
+ return;
+ }
+ getOtherPc(pc).addIceCandidate(event.candidate)
+ .then(() => {
+ onAddIceCandidateSuccess(pc);
+ },
+ (err) => {
+ onAddIceCandidateError(pc, err);
+ });
+ info(getName(pc) + ' ICE candidate: ' + event.candidate.candidate);
+ }
+
+ function onIceStateChange(pc, event) {
+ if (pc) {
+ info(getName(pc) + ' ICE state: ' + pc.iceConnectionState);
+ info('ICE state change event: ', event);
+ }
+ }
+
+ function onCreateOfferSuccess(desc) {
+ info('Offer from pc1\n' + desc.sdp);
+ info('pc1 setLocalDescription start');
+
+ pc1.setLocalDescription(desc)
+ .then(() => {
+ onSetLocalSuccess(pc1);
+ },
+ onSetSessionDescriptionError);
+
+ info('pc2 setRemoteDescription start');
+ pc2.setRemoteDescription(desc).then(() => {
+ onSetRemoteSuccess(pc2);
+ },
+ onSetSessionDescriptionError);
+
+ info('pc2 createAnswer start');
+
+ // Since the 'remote' side has no media stream we need
+ // to pass in the right constraints in order for it to
+ // accept the incoming offer of audio and video.
+ pc2.createAnswer()
+ .then(onCreateAnswerSuccess, onCreateSessionDescriptionError);
+ }
+
+ function onSetSessionDescriptionError(error) {
+ ok(false, 'Failed to set session description: ' + error.toString());
+ }
+
+ function onSetLocalSuccess(pc) {
+ ok(true, getName(pc) + ' setLocalDescription complete');
+ }
+
+ function onCreateSessionDescriptionError(error) {
+ ok(false, 'Failed to create session description: ' + error.toString());
+ }
+
+ function onSetRemoteSuccess(pc) {
+ ok(true, getName(pc) + ' setRemoteDescription complete');
+ }
+
+ function onCreateAnswerSuccess(desc) {
+ info('Answer from pc2:\n' + desc.sdp);
+ info('pc2 setLocalDescription start');
+ pc2.setLocalDescription(desc).then(() => {
+ onSetLocalSuccess(pc2);
+ },
+ onSetSessionDescriptionError);
+ info('pc1 setRemoteDescription start');
+ pc1.setRemoteDescription(desc).then(() => {
+ onSetRemoteSuccess(pc1);
+ },
+ onSetSessionDescriptionError);
+ }
+
+ async function getRemoteStream(localStream) {
+ info("got local stream")
+ const audioTracks = localStream.getAudioTracks();
+
+ const servers = null;
+
+ pc1 = new RTCPeerConnection(servers);
+ info('Created local peer connection object pc1');
+ const iceComplete1 = new Promise((resolve, reject) => {
+ pc1.onicecandidate = (e) => {
+ onIceCandidate(pc1, e, resolve);
+ };
+ });
+
+ pc2 = new RTCPeerConnection(servers);
+ info('Created remote peer connection object pc2');
+ const iceComplete2 = new Promise((resolve, reject) => {
+ pc2.onicecandidate = (e) => {
+ onIceCandidate(pc2, e, resolve);
+ };
+ });
+
+ pc1.oniceconnectionstatechange = (e) => {
+ onIceStateChange(pc1, e);
+ };
+ pc2.oniceconnectionstatechange = (e) => {
+ onIceStateChange(pc2, e);
+ };
+
+ const remoteStreamPromise = new Promise((resolve, reject) => {
+ pc2.ontrack = (e) => {
+ info('pc2 received remote stream ' + e.streams[0]);
+ resolve(e.streams[0]);
+ };
+ });
+
+ localStream.getTracks().forEach((track) => {
+ pc1.addTrack(track, localStream);
+ });
+ info('Added local stream to pc1');
+
+ info('pc1 createOffer start');
+ pc1.createOffer(offerOptions)
+ .then(onCreateOfferSuccess,onCreateSessionDescriptionError);
+
+ let promise_arr = await Promise.all([remoteStreamPromise, iceComplete1, iceComplete2]);
+ return promise_arr[0];
+ }
+
+ runTest(async () => {
+ // Local stream operates at non default rate (32000)
+ const nonDefaultRate = 32000;
+ const nonDefault_ctx = new AudioContext({sampleRate: nonDefaultRate});
+ oscillator = nonDefault_ctx.createOscillator();
+ const dest = nonDefault_ctx.createMediaStreamDestination();
+ oscillator.connect(dest);
+ oscillator.start();
+
+ // Wait for remote stream
+ const remoteStream = await getRemoteStream(dest.stream)
+ ok(true, 'Got remote stream ' + remoteStream);
+
+ // remoteStream now comes from PC so operates at default
+ // rates. Verify that by adding to a default context
+ const ac = new AudioContext;
+ const source_default_rate = ac.createMediaStreamSource(remoteStream);
+
+ // Now try to add the remoteStream on a non default context
+ mustThrowWith(
+ "Connect stream with graph of different sample rate",
+ "NotSupportedError", () => {
+ nonDefault_ctx.createMediaStreamSource(remoteStream);
+ }
+ );
+
+ // Close peer connections to make sure we don't get error:
+ // "logged result after SimpleTest.finish(): pc1 addIceCandidate success"
+ // See Bug 1626814.
+ pc1.close();
+ pc2.close();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_offerRequiresReceiveAudio.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_offerRequiresReceiveAudio.html
new file mode 100644
index 0000000000..1f936714f1
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_offerRequiresReceiveAudio.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "850275",
+ title: "Simple offer media constraint test with audio"
+ });
+
+ runNetworkTest(function() {
+ var test = new PeerConnectionTest();
+ test.setMediaConstraints([], [{audio: true}]);
+ test.setOfferOptions({ offerToReceiveAudio: true });
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_offerRequiresReceiveVideo.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_offerRequiresReceiveVideo.html
new file mode 100644
index 0000000000..c5afbb5c1f
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_offerRequiresReceiveVideo.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "850275",
+ title: "Simple offer media constraint test with video"
+ });
+
+ runNetworkTest(function() {
+ var test = new PeerConnectionTest();
+ test.setMediaConstraints([], [{video: true}]);
+ test.setOfferOptions({ offerToReceiveVideo: true });
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_offerRequiresReceiveVideoAudio.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_offerRequiresReceiveVideoAudio.html
new file mode 100644
index 0000000000..d7bc29c6d3
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_offerRequiresReceiveVideoAudio.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "850275",
+ title: "Simple offer media constraint test with video/audio"
+ });
+
+ runNetworkTest(function() {
+ var test = new PeerConnectionTest();
+ test.setMediaConstraints([], [{audio: true, video: true}]);
+ test.setOfferOptions({ offerToReceiveVideo: true, offerToReceiveAudio: true });
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_portRestrictions.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_portRestrictions.html
new file mode 100644
index 0000000000..7cd695ff54
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_portRestrictions.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1677046",
+ title: "RTCPeerConnection check restricted ports"
+ });
+
+var makePC = (config, expected_error) => {
+ var exception;
+ try {
+ new RTCPeerConnection(config).close();
+ } catch (e) {
+ exception = e;
+ }
+ is((exception? exception.name : "success"), expected_error || "success",
+ "RTCPeerConnection(" + JSON.stringify(config) + ")");
+};
+
+// This is a test of the iceServers parsing code + readable errors
+runNetworkTest(() => {
+ var exception = null;
+
+ // check various ports on the blocklist
+ makePC({ iceServers: [
+ { urls:"turn:[::1]:6666", username:"p", credential:"p" }] }, "NS_ERROR_UNEXPECTED");
+ makePC({ iceServers: [
+ { urls:"turns:localhost:6667?transport=udp", username:"p", credential:"p" }] },
+ "NS_ERROR_UNEXPECTED");
+ makePC({ iceServers: [
+ { urls:"stun:localhost:21", foo:"" }] }, "NS_ERROR_UNEXPECTED");
+ makePC({ iceServers: [
+ { urls:"stun:[::1]:22", foo:"" }] }, "NS_ERROR_UNEXPECTED");
+ makePC({ iceServers: [
+ { urls:"turn:localhost:5060", username:"p", credential:"p" }] },
+ "NS_ERROR_UNEXPECTED");
+
+ // check various ports on the good list for webrtc (or default port)
+ makePC({ iceServers: [
+ { urls:"turn:[::1]:53", username:"p", credential:"p" },
+ { urls:"turn:[::1]:5349", username:"p", credential:"p" },
+ { urls:"turn:[::1]:3478", username:"p", credential:"p" },
+ { urls:"turn:[::1]", username:"p", credential:"p" },
+ { urls:"turn:localhost:53?transport=udp", username:"p", credential:"p" },
+ { urls:"turn:localhost:3478?transport=udp", username:"p", credential:"p" },
+ { urls:"turn:localhost:53?transport=tcp", username:"p", credential:"p" },
+ { urls:"turn:localhost:3478?transport=tcp", username:"p", credential:"p" },
+ { urls:"turns:localhost:3478?transport=udp", username:"p", credential:"p" },
+ { urls:"stun:localhost", foo:"" }
+ ]});
+
+ // not in the known good ports and not on the generic block list
+ makePC({ iceServers: [{ urls:"turn:localhost:6664", username:"p", credential:"p" }] });
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_promiseSendOnly.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_promiseSendOnly.html
new file mode 100644
index 0000000000..a3fbb5753c
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_promiseSendOnly.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1091898",
+ title: "PeerConnection with promises (sendonly)",
+ visible: true
+ });
+
+ var pc1 = new RTCPeerConnection();
+ var pc2 = new RTCPeerConnection();
+
+ var add = (pc, can, failed) => can && pc.addIceCandidate(can).catch(failed);
+ pc1.onicecandidate = e => add(pc2, e.candidate, generateErrorCallback());
+ pc2.onicecandidate = e => add(pc1, e.candidate, generateErrorCallback());
+
+ var v1, v2;
+ var delivered = new Promise(resolve => pc2.ontrack = e => {
+ // Test RTCTrackEvent here.
+ ok(e.streams.length, "has streams");
+ ok(e.streams[0].getTrackById(e.track.id), "has track");
+ ok(pc2.getReceivers().some(receiver => receiver == e.receiver), "has receiver");
+ if (e.streams[0].getTracks().length == 2) {
+ // Test RTCTrackEvent required args here.
+ mustThrowWith("RTCTrackEvent wo/required args",
+ "TypeError", () => new RTCTrackEvent("track", {}));
+ v2.srcObject = e.streams[0];
+ resolve();
+ }
+ });
+
+ runNetworkTest(function() {
+ v1 = createMediaElement('video', 'v1');
+ v2 = createMediaElement('video', 'v2');
+ var canPlayThrough = new Promise(resolve => v2.canplaythrough = e => resolve());
+
+ is(v2.currentTime, 0, "v2.currentTime is zero at outset");
+
+ return navigator.mediaDevices.getUserMedia({ video: true, audio: true })
+ .then(stream => (v1.srcObject = stream).getTracks().forEach(t => pc1.addTrack(t, stream)))
+ .then(() => pc1.createOffer({})) // check that createOffer accepts arg.
+ .then(offer => pc1.setLocalDescription(offer))
+ .then(() => pc2.setRemoteDescription(pc1.localDescription))
+ .then(() => pc2.createAnswer({})) // check that createAnswer accepts arg.
+ .then(answer => pc2.setLocalDescription(answer))
+ .then(() => pc1.setRemoteDescription(pc2.localDescription))
+ .then(() => delivered)
+// .then(() => canPlayThrough) // why doesn't this fire?
+ .then(() => waitUntil(() => v2.currentTime > 0))
+ .then(() => ok(v2.currentTime > 0, "v2.currentTime is moving (" + v2.currentTime + ")"))
+ .then(() => ok(true, "Connected."));
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_recordReceiveTrack.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_recordReceiveTrack.html
new file mode 100644
index 0000000000..d5cb91b048
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_recordReceiveTrack.html
@@ -0,0 +1,101 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script>
+createHTML({
+ bug: "1212237",
+ title: "Recording a fresh receive track should not throw",
+ visible: true,
+});
+
+/**
+ * Called when a fresh track is available, and tests that the track can be
+ * recorded until it ends without any thrown errors or fired error events.
+ */
+let generation = 0;
+async function testTrackAccessible(track) {
+ const id = ++generation;
+ info(`Testing accessibility for ${track.kind} track ${id}`);
+ const recorder = new MediaRecorder(new MediaStream([track]));
+ recorder.start();
+ let haveError = new Promise((_, rej) => recorder.onerror = e => rej(e.error));
+ await Promise.race([
+ new Promise(r => recorder.onstart = r),
+ haveError,
+ ]);
+ info(`Recording of ${track.kind} track ${id} started`);
+
+ const {data} = await Promise.race([
+ new Promise(r => recorder.ondataavailable = r),
+ haveError,
+ ]);
+ info(`Recording of ${track.kind} track ${id} finished at size ${data.size}`);
+
+ await Promise.race([
+ new Promise(r => recorder.onstop = r),
+ haveError,
+ ]);
+ info(`Recording of ${track.kind} track ${id} stopped`);
+
+ const element = createMediaElement(track.kind, `recording_${track.id}`);
+ const url = URL.createObjectURL(data);
+ try {
+ element.src = url;
+ element.preload = "metadata";
+ haveError = new Promise(
+ (_, rej) => element.onerror = e => rej(element.error));
+ await Promise.race([
+ new Promise(r => element.onloadeddata = r),
+ haveError,
+ ]);
+ info(`Playback of recording of ${track.kind} track ${id} loaded data`);
+
+ element.play();
+ await Promise.race([
+ new Promise(r => element.onended = r),
+ haveError,
+ ]);
+ info(`Playback of recording of ${track.kind} track ${id} ended`);
+ } finally {
+ URL.revokeObjectURL(data);
+ }
+}
+
+runNetworkTest(async options => {
+ // [TODO] re-enable HW decoder after bug 1526207 is fixed.
+ if (navigator.userAgent.includes("Android")) {
+ await pushPrefs(["media.navigator.mediadatadecoder_vpx_enabled", false],
+ ["media.webrtc.hw.h264.enabled", false]);
+ }
+ const test = new PeerConnectionTest(options);
+ test.setMediaConstraints([{video: true}], [{audio: true}]);
+ test.setOfferOptions({offerToReceiveAudio: true});
+ const freshVideoTrackIsAccessible = new Promise(
+ r => test.pcRemote._pc.addEventListener("track", r, {once: true})
+ ).then(({track}) => testTrackAccessible(track));
+ const freshAudioTrackIsAccessible = new Promise(
+ r => test.pcLocal._pc.addEventListener("track", r, {once: true})
+ ).then(({track}) => testTrackAccessible(track));
+ test.chain.append([
+ function PC_CLOSE_TO_END_TRACKS() {
+ return test.close();
+ },
+ async function FRESH_VIDEO_TRACK_IS_ACCESSIBLE() {
+ await freshVideoTrackIsAccessible;
+ ok(true, "A freshly received video track is accessible by MediaRecorder");
+ },
+ async function FRESH_AUDIO_TRACK_IS_ACCESSIBLE() {
+ await freshAudioTrackIsAccessible;
+ ok(true, "A freshly received audio track is accessible by MediaRecorder");
+ },
+ ]);
+ await test.run();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_relayOnly.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_relayOnly.html
new file mode 100644
index 0000000000..3b07783c04
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_relayOnly.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ bug: "1187775",
+ title: "peer connection ICE fails on relay-only without TURN"
+});
+
+function PC_LOCAL_NO_CANDIDATES(test) {
+ var isnt = can => is(can, null, "No candidates: " + JSON.stringify(can));
+ test.pcLocal._pc.addEventListener("icecandidate", e => isnt(e.candidate));
+}
+
+function PC_BOTH_WAIT_FOR_ICE_FAILED(test) {
+ var isFail = (f, reason, msg) =>
+ f().then(() => { throw new Error(msg + " must fail"); },
+ e => is(e.message, reason, msg + " must fail with: " + e.message));
+
+ return Promise.all([
+ isFail(() => test.pcLocal.waitForIceConnected(), "ICE failed", "Local ICE"),
+ isFail(() => test.pcRemote.waitForIceConnected(), "ICE failed", "Remote ICE")
+ ])
+ .then(() => ok(true, "ICE on both sides must fail."));
+}
+
+runNetworkTest(async options => {
+ await pushPrefs(
+ ['media.peerconnection.ice.stun_client_maximum_transmits', 3],
+ ['media.peerconnection.ice.trickle_grace_period', 5000]
+ );
+ options = options || {};
+ options.config_local = options.config_local || {};
+ const servers = options.config_local.iceServers || [];
+ // remove any turn servers
+ options.config_local.iceServers = servers.filter(server =>
+ server.urls.every(u => !u.toLowerCase().startsWith('turn')));
+
+ // Here's the setting we're testing. Comment out and this test should fail:
+ options.config_local.iceTransportPolicy = "relay";
+
+ const test = new PeerConnectionTest(options);
+ test.setMediaConstraints([{audio: true}, {video: true}],
+ [{audio: true}, {video: true}]);
+ test.chain.remove("PC_LOCAL_SETUP_ICE_LOGGER"); // Needed to suppress failing
+ test.chain.remove("PC_REMOTE_SETUP_ICE_LOGGER"); // on ICE-failure.
+ test.chain.insertAfter("PC_LOCAL_SETUP_ICE_HANDLER", PC_LOCAL_NO_CANDIDATES);
+ test.chain.replace("PC_LOCAL_WAIT_FOR_ICE_CONNECTED", PC_BOTH_WAIT_FOR_ICE_FAILED);
+ test.chain.removeAfter("PC_BOTH_WAIT_FOR_ICE_FAILED");
+ await test.run();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_remoteReofferRollback.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_remoteReofferRollback.html
new file mode 100644
index 0000000000..80aa30beaa
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_remoteReofferRollback.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "952145",
+ title: "Rollback remote reoffer"
+ });
+
+ runNetworkTest(function (options) {
+ const test = new PeerConnectionTest(options);
+ addRenegotiation(test.chain,
+ [
+ function PC_LOCAL_ADD_SECOND_STREAM(test) {
+ test.setMediaConstraints([{audio: true}, {audio: true}],
+ [{audio: true}]);
+ return test.pcLocal.getAllUserMediaAndAddStreams([{audio: true}]);
+ },
+ ]
+ );
+ test.chain.replaceAfter('PC_REMOTE_SET_REMOTE_DESCRIPTION',
+ [
+ function PC_REMOTE_ROLLBACK(test) {
+ return test.setRemoteDescription(test.pcRemote, { type: "rollback" },
+ STABLE);
+ },
+
+ function PC_LOCAL_ROLLBACK(test) {
+ // We haven't negotiated the new stream yet.
+ test.pcLocal.expectNegotiationNeeded();
+ return test.setLocalDescription(
+ test.pcLocal,
+ new RTCSessionDescription({ type: "rollback", sdp: ""}),
+ STABLE);
+ },
+ ],
+ 1 // Second PC_REMOTE_SET_REMOTE_DESCRIPTION
+ );
+ test.chain.append(commandsPeerConnectionOfferAnswer);
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_remoteRollback.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_remoteRollback.html
new file mode 100644
index 0000000000..827646b0de
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_remoteRollback.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "952145",
+ title: "Rollback remote offer"
+ });
+
+ runNetworkTest(function (options) {
+ const test = new PeerConnectionTest(options);
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ test.chain.removeAfter('PC_REMOTE_CHECK_CAN_TRICKLE_SYNC');
+ test.chain.append([
+ function PC_REMOTE_ROLLBACK(test) {
+ // We still haven't negotiated the tracks
+ test.pcRemote.expectNegotiationNeeded();
+ return test.setRemoteDescription(test.pcRemote, { type: "rollback" },
+ STABLE);
+ },
+
+ function PC_REMOTE_CHECK_CAN_TRICKLE_REVERT_SYNC(test) {
+ is(test.pcRemote._pc.canTrickleIceCandidates, null,
+ "Remote canTrickleIceCandidates is reverted to null");
+ },
+
+ function PC_LOCAL_ROLLBACK(test) {
+ // We still haven't negotiated the tracks
+ test.pcLocal.expectNegotiationNeeded();
+ return test.setLocalDescription(
+ test.pcLocal,
+ new RTCSessionDescription({ type: "rollback", sdp: ""}),
+ STABLE);
+ },
+
+ // Rolling back should shut down gathering
+ function PC_LOCAL_WAIT_FOR_END_OF_TRICKLE(test) {
+ return test.pcLocal.endOfTrickleIce;
+ },
+ ]);
+ test.chain.append(commandsPeerConnectionOfferAnswer);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_removeAudioTrack.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_removeAudioTrack.html
new file mode 100644
index 0000000000..e1e99b38c9
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_removeAudioTrack.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1017888",
+ title: "Renegotiation: remove audio track"
+ });
+
+ runNetworkTest(function (options) {
+ const test = new PeerConnectionTest(options);
+ let receivedTrack, analyser, freq;
+ addRenegotiation(test.chain,
+ [
+ function PC_REMOTE_SETUP_ANALYSER(test) {
+ is(test.pcRemote._pc.getReceivers().length, 1,
+ "pcRemote should have one receiver before renegotiation");
+
+ receivedTrack = test.pcRemote._pc.getReceivers()[0].track;
+ is(receivedTrack.readyState, "live",
+ "The received track should be live");
+
+ analyser = new AudioStreamAnalyser(
+ new AudioContext(), new MediaStream([receivedTrack]));
+ freq = analyser.binIndexForFrequency(TEST_AUDIO_FREQ);
+
+ return analyser.waitForAnalysisSuccess(arr => arr[freq] > 200);
+ },
+ function PC_LOCAL_REMOVE_AUDIO_TRACK(test) {
+ test.setOfferOptions({ offerToReceiveAudio: true });
+ return test.pcLocal.removeSender(0);
+ },
+ ],
+ [
+ function PC_REMOTE_CHECK_FLOW_STOPPED(test) {
+ // Simply removing a track is not enough to cause it to be
+ // signaled as ended. Spec may change though.
+ // TODO: One last check of the spec is in order
+ is(receivedTrack.readyState, "live",
+ "The received track should not have ended");
+
+ return analyser.waitForAnalysisSuccess(arr => arr[freq] < 50);
+ },
+ ]
+ );
+
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_removeThenAddAudioTrack.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_removeThenAddAudioTrack.html
new file mode 100644
index 0000000000..28b76e3b43
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_removeThenAddAudioTrack.html
@@ -0,0 +1,87 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1017888",
+ title: "Renegotiation: remove then add audio track"
+ });
+
+ runNetworkTest(function (options) {
+ const test = new PeerConnectionTest(options);
+ let originalTrack;
+ let haveMuteEvent = new Promise(() => {});
+ let haveUnmuteEvent = new Promise(() => {});
+ addRenegotiation(test.chain,
+ [
+ function PC_REMOTE_FIND_RECEIVER(test) {
+ is(test.pcRemote._pc.getReceivers().length, 1,
+ "pcRemote should have one receiver");
+ originalTrack = test.pcRemote._pc.getReceivers()[0].track;
+ },
+ function PC_LOCAL_REMOVE_AUDIO_TRACK(test) {
+ return test.pcLocal.removeSender(0);
+ },
+ function PC_LOCAL_ADD_AUDIO_TRACK(test) {
+ // The new track's pipeline will start with a packet count of
+ // 0, but the remote side will keep its old pipeline and packet
+ // count.
+ test.pcLocal.disableRtpCountChecking = true;
+ return test.pcLocal.getAllUserMediaAndAddStreams([{audio: true}]);
+ },
+ ],
+ [
+ function PC_REMOTE_WAIT_FOR_UNMUTE() {
+ return haveUnmuteEvent;
+ },
+ function PC_REMOTE_CHECK_ADDED_TRACK(test) {
+ is(test.pcRemote._pc.getTransceivers().length, 2,
+ "pcRemote should have two transceivers");
+ const track = test.pcRemote._pc.getTransceivers()[1].receiver.track;
+
+ const analyser = new AudioStreamAnalyser(
+ new AudioContext(), new MediaStream([track]));
+ const freq = analyser.binIndexForFrequency(TEST_AUDIO_FREQ);
+ return analyser.waitForAnalysisSuccess(arr => arr[freq] > 200);
+ },
+ function PC_REMOTE_WAIT_FOR_MUTE() {
+ return haveMuteEvent;
+ },
+ function PC_REMOTE_CHECK_REMOVED_TRACK(test) {
+ is(test.pcRemote._pc.getTransceivers().length, 2,
+ "pcRemote should have two transceivers");
+ const track = test.pcRemote._pc.getTransceivers()[0].receiver.track;
+
+ const analyser = new AudioStreamAnalyser(
+ new AudioContext(), new MediaStream([track]));
+ const freq = analyser.binIndexForFrequency(TEST_AUDIO_FREQ);
+ return analyser.waitForAnalysisSuccess(arr => arr[freq] < 50);
+ }
+ ]
+ );
+
+ // The first track should mute when the connection is closed.
+ test.chain.insertBefore("PC_REMOTE_SET_REMOTE_DESCRIPTION", [
+ function PC_REMOTE_SETUP_ONMUTE(test) {
+ haveMuteEvent = haveEvent(test.pcRemote._pc.getReceivers()[0].track, "mute");
+ }
+ ]);
+
+ // Second negotiation should cause the second track to unmute.
+ test.chain.insertAfter("PC_REMOTE_SET_REMOTE_DESCRIPTION", [
+ function PC_REMOTE_SETUP_ONUNMUTE(test) {
+ haveUnmuteEvent = haveEvent(test.pcRemote._pc.getReceivers()[1].track, "unmute");
+ }
+ ], false, 1);
+
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_removeThenAddAudioTrackNoBundle.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_removeThenAddAudioTrackNoBundle.html
new file mode 100644
index 0000000000..cff424e12c
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_removeThenAddAudioTrackNoBundle.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1017888",
+ title: "Renegotiation: remove then add audio track"
+ });
+
+ runNetworkTest(function (options) {
+ options = options || { };
+ options.bundle = false;
+ const test = new PeerConnectionTest(options);
+ let originalTrack;
+ addRenegotiation(test.chain,
+ [
+ function PC_REMOTE_FIND_RECEIVER(test) {
+ is(test.pcRemote._pc.getReceivers().length, 1,
+ "pcRemote should have one receiver");
+ originalTrack = test.pcRemote._pc.getReceivers()[0].track;
+ },
+ function PC_LOCAL_REMOVE_AUDIO_TRACK(test) {
+ // The new track's pipeline will start with a packet count of
+ // 0, but the remote side will keep its old pipeline and packet
+ // count.
+ test.pcLocal.disableRtpCountChecking = true;
+ return test.pcLocal.removeSender(0);
+ },
+ function PC_LOCAL_ADD_AUDIO_TRACK(test) {
+ return test.pcLocal.getAllUserMediaAndAddStreams([{audio: true}]);
+ },
+ function PC_LOCAL_EXPECT_ICE_CHECKING(test) {
+ test.pcLocal.expectIceChecking();
+ },
+ function PC_REMOTE_EXPECT_ICE_CHECKING(test) {
+ test.pcRemote.expectIceChecking();
+ },
+ ],
+ [
+ function PC_REMOTE_CHECK_ADDED_TRACK(test) {
+ is(test.pcRemote._pc.getTransceivers().length, 2,
+ "pcRemote should have two transceivers");
+ const track = test.pcRemote._pc.getTransceivers()[1].receiver.track;
+
+ const analyser = new AudioStreamAnalyser(
+ new AudioContext(), new MediaStream([track]));
+ const freq = analyser.binIndexForFrequency(TEST_AUDIO_FREQ);
+ return analyser.waitForAnalysisSuccess(arr => arr[freq] > 200);
+ },
+ function PC_REMOTE_CHECK_REMOVED_TRACK(test) {
+ is(test.pcRemote._pc.getTransceivers().length, 2,
+ "pcRemote should have two transceivers");
+ const track = test.pcRemote._pc.getTransceivers()[0].receiver.track;
+
+ const analyser = new AudioStreamAnalyser(
+ new AudioContext(), new MediaStream([track]));
+ const freq = analyser.binIndexForFrequency(TEST_AUDIO_FREQ);
+ return analyser.waitForAnalysisSuccess(arr => arr[freq] < 50);
+ }
+ ]
+ );
+
+ test.chain.insertAfterEach('PC_LOCAL_CREATE_OFFER',
+ PC_LOCAL_REMOVE_BUNDLE_FROM_OFFER);
+
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_removeThenAddVideoTrack.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_removeThenAddVideoTrack.html
new file mode 100644
index 0000000000..b1be690e5b
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_removeThenAddVideoTrack.html
@@ -0,0 +1,98 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1017888",
+ title: "Renegotiation: remove then add video track"
+ });
+
+ runNetworkTest(async function (options) {
+ // Use fake video here since the native fake device on linux doesn't
+ // change color as needed by checkVideoPlaying() below.
+ await pushPrefs(
+ ['media.video_loopback_dev', ''],
+ ['media.navigator.streams.fake', true]);
+ // [TODO] re-enable HW decoder after bug 1526207 is fixed.
+ if (navigator.userAgent.includes("Android")) {
+ await pushPrefs(["media.navigator.mediadatadecoder_vpx_enabled", false],
+ ["media.webrtc.hw.h264.enabled", false]);
+ }
+
+ const test = new PeerConnectionTest(options);
+ const helper = new VideoStreamHelper();
+ var originalTrack;
+ let haveMuteEvent = new Promise(() => {});
+ let haveUnmuteEvent = new Promise(() => {});
+ addRenegotiation(test.chain,
+ [
+ function PC_REMOTE_FIND_RECEIVER(test) {
+ is(test.pcRemote._pc.getReceivers().length, 1,
+ "pcRemote should have one receiver");
+ originalTrack = test.pcRemote._pc.getReceivers()[0].track;
+ },
+ function PC_LOCAL_REMOVE_VIDEO_TRACK(test) {
+ // The new track's pipeline will start with a packet count of
+ // 0, but the remote side will keep its old pipeline and packet
+ // count.
+ test.pcLocal.disableRtpCountChecking = true;
+ return test.pcLocal.removeSender(0);
+ },
+ function PC_LOCAL_ADD_VIDEO_TRACK(test) {
+ return test.pcLocal.getAllUserMediaAndAddStreams([{video: true}]);
+ },
+ ],
+ [
+ function PC_REMOTE_WAIT_FOR_UNMUTE() {
+ return haveUnmuteEvent;
+ },
+ function PC_REMOTE_CHECK_ADDED_TRACK(test) {
+ is(test.pcRemote._pc.getTransceivers().length, 2,
+ "pcRemote should have two transceivers");
+ const track = test.pcRemote._pc.getTransceivers()[1].receiver.track;
+
+ const vAdded = test.pcRemote.remoteMediaElements.find(
+ elem => elem.id.includes(track.id));
+ return helper.checkVideoPlaying(vAdded);
+ },
+ function PC_REMOTE_WAIT_FOR_MUTE() {
+ return haveMuteEvent;
+ },
+ function PC_REMOTE_CHECK_REMOVED_TRACK(test) {
+ is(test.pcRemote._pc.getTransceivers().length, 2,
+ "pcRemote should have two transceivers");
+ const track = test.pcRemote._pc.getTransceivers()[0].receiver.track;
+
+ const vAdded = test.pcRemote.remoteMediaElements.find(
+ elem => elem.id.includes(track.id));
+ return helper.checkVideoPaused(vAdded, 10, 10, 16, 5000);
+ }
+ ]
+ );
+
+ // The first track should mute when the connection is closed.
+ test.chain.insertBefore("PC_REMOTE_SET_REMOTE_DESCRIPTION", [
+ function PC_REMOTE_SETUP_ONMUTE(test) {
+ haveMuteEvent = haveEvent(test.pcRemote._pc.getReceivers()[0].track, "mute");
+ }
+ ]);
+
+ // Second negotiation should cause the second track to unmute.
+ test.chain.insertAfter("PC_REMOTE_SET_REMOTE_DESCRIPTION", [
+ function PC_REMOTE_SETUP_ONUNMUTE(test) {
+ haveUnmuteEvent = haveEvent(test.pcRemote._pc.getReceivers()[1].track, "unmute");
+ }
+ ], false, 1);
+
+ test.setMediaConstraints([{video: true}], [{video: true}]);
+ await test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_removeThenAddVideoTrackNoBundle.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_removeThenAddVideoTrackNoBundle.html
new file mode 100644
index 0000000000..dcaf7943e2
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_removeThenAddVideoTrackNoBundle.html
@@ -0,0 +1,89 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1017888",
+ title: "Renegotiation: remove then add video track, no bundle"
+ });
+
+ runNetworkTest(async function (options) {
+ // Use fake video here since the native fake device on linux doesn't
+ // change color as needed by checkVideoPlaying() below.
+ await pushPrefs(
+ ['media.video_loopback_dev', ''],
+ ['media.navigator.streams.fake', true]);
+ // [TODO] re-enable HW decoder after bug 1526207 is fixed.
+ if (navigator.userAgent.includes("Android")) {
+ await pushPrefs(["media.navigator.mediadatadecoder_vpx_enabled", false],
+ ["media.webrtc.hw.h264.enabled", false]);
+ }
+
+ options = options || { };
+ options.bundle = false;
+ const test = new PeerConnectionTest(options);
+ const helper = new VideoStreamHelper();
+ var originalTrack;
+ addRenegotiation(test.chain,
+ [
+ function PC_REMOTE_FIND_RECEIVER(test) {
+ is(test.pcRemote._pc.getReceivers().length, 1,
+ "pcRemote should have one receiver");
+ originalTrack = test.pcRemote._pc.getReceivers()[0].track;
+ },
+ function PC_LOCAL_REMOVE_VIDEO_TRACK(test) {
+ // The new track's pipeline will start with a packet count of
+ // 0, but the remote side will keep its old pipeline and packet
+ // count.
+ test.pcLocal.disableRtpCountChecking = true;
+ return test.pcLocal.removeSender(0);
+ },
+ function PC_LOCAL_ADD_VIDEO_TRACK(test) {
+ // Use fake:true here since the native fake device on linux doesn't
+ // change color as needed by checkVideoPlaying() below.
+ return test.pcLocal.getAllUserMediaAndAddStreams([{video: true}]);
+ },
+ function PC_LOCAL_EXPECT_ICE_CHECKING(test) {
+ test.pcLocal.expectIceChecking();
+ },
+ function PC_REMOTE_EXPECT_ICE_CHECKING(test) {
+ test.pcRemote.expectIceChecking();
+ },
+ ],
+ [
+ function PC_REMOTE_CHECK_ADDED_TRACK(test) {
+ is(test.pcRemote._pc.getTransceivers().length, 2,
+ "pcRemote should have two transceivers");
+ const track = test.pcRemote._pc.getTransceivers()[1].receiver.track;
+
+ const vAdded = test.pcRemote.remoteMediaElements.find(
+ elem => elem.id.includes(track.id));
+ return helper.checkVideoPlaying(vAdded);
+ },
+ function PC_REMOTE_CHECK_REMOVED_TRACK(test) {
+ is(test.pcRemote._pc.getTransceivers().length, 2,
+ "pcRemote should have two transceivers");
+ const track = test.pcRemote._pc.getTransceivers()[0].receiver.track;
+
+ const vAdded = test.pcRemote.remoteMediaElements.find(
+ elem => elem.id.includes(track.id));
+ return helper.checkVideoPaused(vAdded, 10, 10, 16, 5000);
+ },
+ ]
+ );
+
+ test.chain.insertAfterEach('PC_LOCAL_CREATE_OFFER',
+ PC_LOCAL_REMOVE_BUNDLE_FROM_OFFER);
+
+ test.setMediaConstraints([{video: true}], [{video: true}]);
+ await test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_removeVideoTrack.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_removeVideoTrack.html
new file mode 100644
index 0000000000..4c4e7905e1
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_removeVideoTrack.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1017888",
+ title: "Renegotiation: remove video track"
+ });
+
+ runNetworkTest(async (options) => {
+ // [TODO] re-enable HW decoder after bug 1526207 is fixed.
+ if (navigator.userAgent.includes("Android")) {
+ await pushPrefs(["media.navigator.mediadatadecoder_vpx_enabled", false],
+ ["media.webrtc.hw.h264.enabled", false]);
+ }
+
+ const test = new PeerConnectionTest(options);
+ let receivedTrack, element;
+ addRenegotiation(test.chain,
+ [
+ function PC_REMOTE_SETUP_HELPER(test) {
+ is(test.pcRemote._pc.getReceivers().length, 1,
+ "pcRemote should have one receiver before renegotiation");
+
+ receivedTrack = test.pcRemote._pc.getReceivers()[0].track;
+ is(receivedTrack.readyState, "live",
+ "The received track should be live");
+
+ element = createMediaElement("video", "pcRemoteReceivedVideo");
+ element.srcObject = new MediaStream([receivedTrack]);
+ return haveEvent(element, "loadeddata");
+ },
+ function PC_LOCAL_REMOVE_VIDEO_TRACK(test) {
+ test.setOfferOptions({ offerToReceiveVideo: true });
+ test.setMediaConstraints([], [{video: true}]);
+ return test.pcLocal.removeSender(0);
+ },
+ ],
+ [
+ function PC_REMOTE_CHECK_FLOW_STOPPED(test) {
+ is(test.pcRemote._pc.getTransceivers().length, 1,
+ "pcRemote should have one transceiver");
+ const track = test.pcRemote._pc.getTransceivers()[0].receiver.track;
+
+ const vAdded = test.pcRemote.remoteMediaElements.find(
+ elem => elem.id.includes(track.id));
+ const helper = new VideoStreamHelper();
+ return helper.checkVideoPaused(vAdded, 10, 10, 16, 5000);
+ },
+ ]
+ );
+
+ test.setMediaConstraints([{video: true}], [{video: true}]);
+ await test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_renderAfterRenegotiation.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_renderAfterRenegotiation.html
new file mode 100644
index 0000000000..c8091d7a9e
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_renderAfterRenegotiation.html
@@ -0,0 +1,89 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1273652",
+ title: "Video receiver still renders after renegotiation",
+ visible: true
+ });
+
+ var pc1 = new RTCPeerConnection();
+ var pc2 = new RTCPeerConnection();
+
+ var add = (pc, can, failed) => can && pc.addIceCandidate(can).catch(failed);
+ pc1.onicecandidate = e => add(pc2, e.candidate, generateErrorCallback());
+ pc2.onicecandidate = e => add(pc1, e.candidate, generateErrorCallback());
+
+ var v1, v2;
+ var delivered = new Promise(resolve => pc2.ontrack = e => {
+ // Test RTCTrackEvent here.
+ ok(e.streams.length, "has streams");
+ ok(e.streams[0].getTrackById(e.track.id), "has track");
+ ok(pc2.getReceivers().some(receiver => receiver == e.receiver), "has receiver");
+ if (e.streams[0].getTracks().length == 1) {
+ // Test RTCTrackEvent required args here.
+ mustThrowWith("RTCTrackEvent wo/required args",
+ "TypeError", () => new RTCTrackEvent("track", {}));
+ v2.srcObject = e.streams[0];
+ resolve();
+ }
+ });
+
+ runNetworkTest(async () => {
+ // [TODO] re-enable HW decoder after bug 1526207 is fixed.
+ if (navigator.userAgent.includes("Android")) {
+ await pushPrefs(["media.navigator.mediadatadecoder_vpx_enabled", false]);
+ await pushPrefs(["media.webrtc.hw.h264.enabled", false]);
+ }
+
+ v2 = createMediaElement('video', 'v2');
+ is(v2.currentTime, 0, "v2.currentTime is zero at outset");
+
+ const emitter = new VideoFrameEmitter(CaptureStreamTestHelper.prototype.blue,
+ CaptureStreamTestHelper.prototype.green,
+ 16, 16);
+ emitter.start();
+ emitter.stream().getTracks().forEach(t => pc1.addTrack(t, emitter.stream()));
+ let h = emitter.helper();
+
+ let offer = await pc1.createOffer({});
+ await pc1.setLocalDescription(offer);
+ await pc2.setRemoteDescription(pc1.localDescription);
+ // check that createAnswer accepts arg.
+ let answer = await pc2.createAnswer({});
+ await pc2.setLocalDescription(answer);
+ await pc1.setRemoteDescription(pc2.localDescription);
+
+ // re-negotiate to trigger the race condition in the jitter buffer
+ offer = await pc1.createOffer({}); // check that createOffer accepts arg.
+ await pc1.setLocalDescription(offer);
+ await pc2.setRemoteDescription(pc1.localDescription);
+ answer = await pc2.createAnswer({});
+ await pc2.setLocalDescription(answer);
+ await pc1.setRemoteDescription(pc2.localDescription);
+ await delivered;
+
+ // now verify that actually something gets rendered into the remote video
+ // element.
+ await h.pixelMustBecome(v2, h.blue, {
+ threshold: 128,
+ infoString: "pcRemote's video should become blue",
+ });
+ // This will verify that new changes to the canvas propagate through
+ // the peerconnection
+ emitter.colors(h.red, h.green)
+ await h.pixelMustBecome(v2, h.red, {
+ threshold: 128,
+ infoString: "pcRemote's video should become red",
+ });
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceNullTrackThenRenegotiateAudio.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceNullTrackThenRenegotiateAudio.html
new file mode 100644
index 0000000000..2253b87672
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceNullTrackThenRenegotiateAudio.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1763832",
+ title: "Renegotiation (audio): Start with no track and recvonly, then replace and set direction to sendrecv, then renegotiate"
+ });
+
+ runNetworkTest(async () => {
+ await pushPrefs(
+ ['media.audio_loopback_dev', ''],
+ ['media.navigator.streams.fake', true]);
+ const offerer = new RTCPeerConnection();
+ const answerer = new RTCPeerConnection();
+ const transceiverSend = offerer.addTransceiver('audio', {direction: 'recvonly'});
+
+ const add = (pc, can, failed) => can && pc.addIceCandidate(can).catch(failed);
+ offerer.onicecandidate = e => add(answerer, e.candidate, generateErrorCallback());
+ answerer.onicecandidate = e => add(offerer, e.candidate, generateErrorCallback());
+
+ await offerer.setLocalDescription();
+ await answerer.setRemoteDescription(offerer.localDescription);
+ await answerer.setLocalDescription();
+ await offerer.setRemoteDescription(answerer.localDescription);
+
+ // add audio with replaceTrack, set send bit, and renegotiate
+ const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+ const [track] = stream.getAudioTracks();
+ transceiverSend.sender.replaceTrack(track);
+ transceiverSend.direction = "sendrecv";
+ const remoteStreamAvailable = new Promise(r => {
+ answerer.ontrack = ({track}) => r(new MediaStream([track]));
+ });
+
+ await offerer.setLocalDescription();
+ await answerer.setRemoteDescription(offerer.localDescription);
+ await answerer.setLocalDescription();
+ await offerer.setRemoteDescription(answerer.localDescription);
+
+ const remoteStream = await remoteStreamAvailable;
+ const h = new AudioStreamHelper();
+ await h.checkAudioFlowing(remoteStream);
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceNullTrackThenRenegotiateVideo.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceNullTrackThenRenegotiateVideo.html
new file mode 100644
index 0000000000..d7bd6d8a37
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceNullTrackThenRenegotiateVideo.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
+ <script type="application/javascript" src="simulcast.js"></script></head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1763832",
+ title: "Renegotiation (video): Start with no track and recvonly, then replace and set direction to sendrecv, then renegotiate"
+ });
+
+ runNetworkTest(async () => {
+ await pushPrefs(
+ ['media.video_loopback_dev', ''],
+ ['media.navigator.streams.fake', true]);
+ // [TODO] re-enable HW decoder after bug 1526207 is fixed.
+ if (navigator.userAgent.includes("Android")) {
+ await pushPrefs(["media.navigator.mediadatadecoder_vpx_enabled", false],
+ ["media.webrtc.hw.h264.enabled", false]);
+ }
+
+ const offerer = new RTCPeerConnection();
+ const answerer = new RTCPeerConnection();
+ const transceiverSend = offerer.addTransceiver('video', {direction: 'recvonly'});
+
+ const add = (pc, can, failed) => can && pc.addIceCandidate(can).catch(failed);
+ offerer.onicecandidate = e => add(answerer, e.candidate, generateErrorCallback());
+ answerer.onicecandidate = e => add(offerer, e.candidate, generateErrorCallback());
+
+ await offerer.setLocalDescription();
+ await answerer.setRemoteDescription(offerer.localDescription);
+ await answerer.setLocalDescription();
+ await offerer.setRemoteDescription(answerer.localDescription);
+
+ // add video with replaceTrack, set send bit, and renegotiate
+ const stream = await navigator.mediaDevices.getUserMedia({video: true});
+ const [track] = stream.getVideoTracks();
+ transceiverSend.sender.replaceTrack(track);
+ transceiverSend.direction = "sendrecv";
+ const metadataToBeLoaded = [];
+ answerer.ontrack = (e) => {
+ metadataToBeLoaded.push(getPlaybackWithLoadedMetadata(e.track));
+ };
+
+ await offerer.setLocalDescription();
+ await answerer.setRemoteDescription(offerer.localDescription);
+ await answerer.setLocalDescription();
+ await offerer.setRemoteDescription(answerer.localDescription);
+
+ const elems = await Promise.all(metadataToBeLoaded);
+ is(elems.length, 1, "Should have one video element");
+
+ const helper = new VideoStreamHelper();
+ await helper.checkVideoPlaying(elems[0]);
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceTrack.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceTrack.html
new file mode 100644
index 0000000000..9befc5c564
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceTrack.html
@@ -0,0 +1,187 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1032839",
+ title: "Replace video and audio (with WebAudio) tracks",
+ visible: true
+ });
+
+ function allLocalStreamsHaveSender(pc) {
+ return pc.getLocalStreams()
+ .every(s => s.getTracks() // Every local stream,
+ .some(t => pc.getSenders() // should have some track,
+ .some(sn => sn.track == t))) // that's being sent over |pc|.
+ }
+
+ function allRemoteStreamsHaveReceiver(pc) {
+ return pc.getRemoteStreams()
+ .every(s => s.getTracks() // Every remote stream,
+ .some(t => pc.getReceivers() // should have some track,
+ .some(sn => sn.track == t))) // that's being received over |pc|.
+ }
+
+ function replacetest(wrapper) {
+ var pc = wrapper._pc;
+ var oldSenderCount = pc.getSenders().length;
+ var sender = pc.getSenders().find(sn => sn.track.kind == "video");
+ var oldTrack = sender.track;
+ ok(sender, "We have a sender for video");
+ ok(allLocalStreamsHaveSender(pc),
+ "Shouldn't have any local streams without a corresponding sender");
+ ok(allRemoteStreamsHaveReceiver(pc),
+ "Shouldn't have any remote streams without a corresponding receiver");
+
+ var newTrack;
+ var audiotrack;
+ return getUserMedia({video:true, audio:true})
+ .then(newStream => {
+ window.grip = newStream;
+ newTrack = newStream.getVideoTracks()[0];
+ audiotrack = newStream.getAudioTracks()[0];
+ isnot(newTrack, sender.track, "replacing with a different track");
+ ok(!pc.getLocalStreams().some(s => s == newStream),
+ "from a different stream");
+ // Use wrapper function, since it updates expected tracks
+ return wrapper.senderReplaceTrack(sender, newTrack, newStream);
+ })
+ .then(() => {
+ is(pc.getSenders().length, oldSenderCount, "same sender count");
+ is(sender.track, newTrack, "sender.track has been replaced");
+ ok(!pc.getSenders().map(sn => sn.track).some(t => t == oldTrack),
+ "old track not among senders");
+ // Spec does not say we add this new track to any stream
+ ok(!pc.getLocalStreams().some(s => s.getTracks()
+ .some(t => t == sender.track)),
+ "track does not exist among pc's local streams");
+ return sender.replaceTrack(audiotrack)
+ .then(() => ok(false, "replacing with different kind should fail"),
+ e => is(e.name, "TypeError",
+ "replacing with different kind should fail"));
+ });
+ }
+
+ runNetworkTest(function () {
+ test = new PeerConnectionTest();
+ test.audioCtx = new AudioContext();
+ test.setMediaConstraints([{video: true, audio: true}], [{video: true}]);
+ test.chain.removeAfter("PC_REMOTE_WAIT_FOR_MEDIA_FLOW");
+
+ // Test replaceTrack on pcRemote separately since it's video only.
+ test.chain.append([
+ function PC_REMOTE_VIDEOONLY_REPLACE_VIDEOTRACK(test) {
+ return replacetest(test.pcRemote);
+ },
+ function PC_LOCAL_NEW_VIDEOTRACK_WAIT_FOR_MEDIA_FLOW(test) {
+ return test.pcLocal.waitForMediaFlow();
+ }
+ ]);
+
+ // Replace video twice on pcLocal to make sure it still works
+ // (does audio twice too, but hey)
+ test.chain.append([
+ function PC_LOCAL_AUDIOVIDEO_REPLACE_VIDEOTRACK_1(test) {
+ return replacetest(test.pcLocal);
+ },
+ function PC_REMOTE_NEW_VIDEOTRACK_WAIT_FOR_MEDIA_FLOW_1(test) {
+ return test.pcRemote.waitForMediaFlow();
+ },
+ function PC_LOCAL_AUDIOVIDEO_REPLACE_VIDEOTRACK_2(test) {
+ return replacetest(test.pcLocal);
+ },
+ function PC_REMOTE_NEW_VIDEOTRACK_WAIT_FOR_MEDIA_FLOW_2(test) {
+ return test.pcRemote.waitForMediaFlow();
+ }
+ ]);
+
+ test.chain.append([
+ function PC_LOCAL_AUDIOVIDEO_REPLACE_VIDEOTRACK_WITHSAME(test) {
+ var pc = test.pcLocal._pc;
+ var sender = pc.getSenders().find(sn => sn.track.kind == "video");
+ ok(sender, "should still have a sender of video");
+ return sender.replaceTrack(sender.track)
+ .then(() => ok(true, "replacing with itself should succeed"));
+ },
+ function PC_REMOTE_NEW_SAME_VIDEOTRACK_WAIT_FOR_MEDIA_FLOW(test) {
+ return test.pcRemote.waitForMediaFlow();
+ }
+ ]);
+
+ // Replace the gUM audio track on pcLocal with a WebAudio track.
+ test.chain.append([
+ function PC_LOCAL_AUDIOVIDEO_REPLACE_AUDIOTRACK_WEBAUDIO(test) {
+ var pc = test.pcLocal._pc;
+ var sender = pc.getSenders().find(sn => sn.track.kind == "audio");
+ ok(sender, "track has a sender");
+ var oldSenderCount = pc.getSenders().length;
+ var oldTrack = sender.track;
+
+ var sourceNode = test.audioCtx.createOscillator();
+ sourceNode.type = 'sine';
+ // We need a frequency not too close to the fake audio track
+ // (440Hz for loopback devices, 1kHz for fake tracks).
+ sourceNode.frequency.value = 2000;
+ sourceNode.start();
+
+ var destNode = test.audioCtx.createMediaStreamDestination();
+ sourceNode.connect(destNode);
+ var newTrack = destNode.stream.getAudioTracks()[0];
+
+ return test.pcLocal.senderReplaceTrack(
+ sender, newTrack, destNode.stream)
+ .then(() => {
+ is(pc.getSenders().length, oldSenderCount, "same sender count");
+ ok(!pc.getSenders().some(sn => sn.track == oldTrack),
+ "Replaced track should be removed from senders");
+ // TODO: Should PC remove local streams when there are no senders
+ // associated with it? getLocalStreams() isn't in the spec anymore,
+ // so I guess it is pretty arbitrary?
+ is(sender.track, newTrack, "sender.track has been replaced");
+ // Spec does not say we add this new track to any stream
+ ok(!pc.getLocalStreams().some(s => s.getTracks()
+ .some(t => t == sender.track)),
+ "track exists among pc's local streams");
+ });
+ }
+ ]);
+ test.chain.append([
+ function PC_LOCAL_CHECK_WEBAUDIO_FLOW_PRESENT(test) {
+ return test.pcRemote.checkReceivingToneFrom(test.audioCtx, test.pcLocal);
+ }
+ ]);
+ test.chain.append([
+ function PC_LOCAL_INVALID_ADD_VIDEOTRACKS(test) {
+ let videoTransceivers = test.pcLocal._pc.getTransceivers()
+ .filter(transceiver => {
+ return !transceiver.stopped &&
+ transceiver.receiver.track.kind == "video" &&
+ transceiver.sender.track;
+ });
+
+ ok(videoTransceivers.length,
+ "There is at least one non-stopped video transceiver with a track.");
+
+ videoTransceivers.forEach(transceiver => {
+ var stream = test.pcLocal._pc.getLocalStreams()[0];;
+ var track = transceiver.sender.track;
+ try {
+ test.pcLocal._pc.addTrack(track, stream);
+ ok(false, "addTrack existing track should fail");
+ } catch (e) {
+ is(e.name, "InvalidAccessError",
+ "addTrack existing track should fail");
+ }
+ });
+ }
+ ]);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceTrack_camera.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceTrack_camera.html
new file mode 100644
index 0000000000..356517e79f
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceTrack_camera.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
+</head>
+<body>
+<pre id="test">
+
+<video id="video" width="160" height="120" autoplay></video>
+
+<script type="application/javascript">
+ createHTML({
+ bug: "1709481",
+ title: "replaceTrack (null -> camera) test",
+ visible: true
+ });
+
+ runNetworkTest(async () => {
+ // Make sure we use the fake video device, and not loopback
+ await pushPrefs(
+ ['media.video_loopback_dev', ''],
+ ['media.navigator.streams.fake', true]);
+ const pc1 = new RTCPeerConnection(), pc2 = new RTCPeerConnection();
+ pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate);
+ pc2.onicecandidate = e => pc1.addIceCandidate(e.candidate);
+ pc2.ontrack = ({track}) => video.srcObject = new MediaStream([track]);
+ pc1.addTransceiver("audio");
+ const tc1 = pc1.addTransceiver("video");
+ const stream = await navigator.mediaDevices.getUserMedia({video: true});
+ const [track] = stream.getVideoTracks();
+ await pc1.setLocalDescription();
+ await pc2.setRemoteDescription(pc1.localDescription);
+ await pc2.setLocalDescription();
+ await pc1.setRemoteDescription(pc2.localDescription);
+ await wait(100);
+ await tc1.sender.replaceTrack(track);
+ const h = new VideoStreamHelper();
+ await h.checkVideoPlaying(video);
+ pc1.close();
+ pc2.close();
+ await SpecialPowers.popPrefEnv();
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceTrack_disabled.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceTrack_disabled.html
new file mode 100644
index 0000000000..11b2762d96
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceTrack_disabled.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script src="pc.js"></script>
+<script src="/tests/dom/canvas/test/captureStream_common.js"></script>
+</head>
+<body>
+<pre id="test">
+<script>
+createHTML({
+ bug: "1576771",
+ title: "Replace a disabled video track with an enabled one",
+ visible: true,
+});
+
+runNetworkTest(() => {
+ const helper = new CaptureStreamTestHelper2D(240, 160);
+ const emitter = new VideoFrameEmitter(helper.green, helper.green, 240, 160);
+ const test = new PeerConnectionTest();
+ test.setMediaConstraints([{video: true}], []);
+ test.chain.insertAfter("PC_REMOTE_WAIT_FOR_MEDIA_FLOW", [
+ function PC_LOCAL_DISABLE_SENDTRACK(test) {
+ test.pcLocal._pc.getSenders()[0].track.enabled = false;
+ },
+ function PC_REMOTE_WAIT_FOR_BLACK(test) {
+ return helper.pixelMustBecome(
+ test.pcRemote.remoteMediaElements[0], helper.black, {
+ threshold: 128,
+ infoString: "Remote disabled track becomes black",
+ cancel: wait(10000).then(
+ () => new Error("Timeout waiting for black"))});
+ },
+ function PC_LOCAL_REPLACETRACK_WITH_ENABLED_TRACK(test) {
+ emitter.start();
+ test.pcLocal._pc.getSenders()[0].replaceTrack(
+ emitter.stream().getTracks()[0]);
+ },
+ ]);
+ test.chain.append([
+ function PC_REMOTE_WAIT_FOR_GREEN(test) {
+ return helper.pixelMustBecome(
+ test.pcRemote.remoteMediaElements[0], helper.green, {
+ threshold: 128,
+ infoString: "Remote disabled track becomes green",
+ cancel: wait(10000).then(
+ () => new Error("Timeout waiting for green"))});
+ },
+ function CLEANUP(test) {
+ emitter.stop();
+ for (const track of emitter.stream().getTracks()) {
+ track.stop();
+ }
+ },
+ ]);
+ return test.run();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceTrack_microphone.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceTrack_microphone.html
new file mode 100644
index 0000000000..5886caf6a4
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceTrack_microphone.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
+</head>
+<body>
+<pre id="test">
+
+<script type="application/javascript">
+ createHTML({
+ bug: "1709481",
+ title: "replaceTrack (null -> microphone) test",
+ visible: true
+ });
+
+ runNetworkTest(async () => {
+ // Make sure we use the fake audio device, and not loopback
+ await pushPrefs(
+ ['media.audio_loopback_dev', ''],
+ ['media.navigator.streams.fake', true]);
+ const pc1 = new RTCPeerConnection(), pc2 = new RTCPeerConnection();
+ pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate);
+ pc2.onicecandidate = e => pc1.addIceCandidate(e.candidate);
+ let remoteStream;
+ pc2.ontrack = ({track}) => remoteStream = new MediaStream([track]);
+ const tc1 = pc1.addTransceiver("audio");
+ const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+ const [track] = stream.getAudioTracks();
+ await pc1.setLocalDescription();
+ await pc2.setRemoteDescription(pc1.localDescription);
+ await pc2.setLocalDescription();
+ await pc1.setRemoteDescription(pc2.localDescription);
+ await wait(100);
+ await tc1.sender.replaceTrack(track);
+ const h = new AudioStreamHelper();
+ await h.checkAudioFlowing(remoteStream);
+ pc1.close();
+ pc2.close();
+ await SpecialPowers.popPrefEnv();
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceVideoThenRenegotiate.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceVideoThenRenegotiate.html
new file mode 100644
index 0000000000..070cb42fcb
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_replaceVideoThenRenegotiate.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1017888",
+ title: "Renegotiation: replaceTrack followed by adding a second video stream"
+ });
+
+ runNetworkTest(async (options) => {
+ await pushPrefs(['media.peerconnection.video.min_bitrate_estimate', 180*1000]);
+ // [TODO] re-enable HW decoder after bug 1526207 is fixed.
+ if (navigator.userAgent.includes("Android")) {
+ await pushPrefs(["media.navigator.mediadatadecoder_vpx_enabled", false],
+ ["media.webrtc.hw.h264.enabled", false]);
+ }
+
+ const test = new PeerConnectionTest(options);
+ test.setMediaConstraints([{video:true}], [{video:true}]);
+ const helper = new VideoStreamHelper();
+ const emitter1 = new VideoFrameEmitter(CaptureStreamTestHelper.prototype.red,
+ CaptureStreamTestHelper.prototype.green);
+ const emitter2 = new VideoFrameEmitter(CaptureStreamTestHelper.prototype.blue,
+ CaptureStreamTestHelper.prototype.grey);
+ test.chain.replace("PC_LOCAL_GUM", [
+ function PC_LOCAL_ADDTRACK(test) {
+ test.pcLocal.attachLocalStream(emitter1.stream());
+ emitter1.start();
+ },
+ ]);
+ addRenegotiation(test.chain,
+ [
+ function PC_LOCAL_REPLACE_VIDEO_TRACK_THEN_ADD_SECOND_STREAM(test) {
+ emitter1.stop();
+ emitter2.start();
+ const newstream = emitter2.stream();
+ const newtrack = newstream.getVideoTracks()[0];
+ var sender = test.pcLocal._pc.getSenders()[0];
+ return test.pcLocal.senderReplaceTrack(sender, newtrack, newstream)
+ .then(() => {
+ test.setMediaConstraints([{video: true}, {video: true}],
+ [{video: true}]);
+ });
+ },
+ ],
+ [
+ function PC_REMOTE_CHECK_ORIGINAL_TRACK_NOT_ENDED(test) {
+ is(test.pcRemote._pc.getTransceivers().length, 1,
+ "pcRemote should have one transceiver");
+ const track = test.pcRemote._pc.getTransceivers()[0].receiver.track;
+
+ const vremote = test.pcRemote.remoteMediaElements.find(
+ elem => elem.id.includes(track.id));
+ if (!vremote) {
+ return Promise.reject(new Error("Couldn't find video element"));
+ }
+ ok(!vremote.ended, "Original track should not have ended after renegotiation (replaceTrack is not signalled!)");
+ return helper.checkVideoPlaying(vremote);
+ }
+ ]
+ );
+
+ await test.run();
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIce.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIce.html
new file mode 100644
index 0000000000..d94bb084b7
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIce.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "906986",
+ title: "Renegotiation: restart ice"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ test = new PeerConnectionTest(options);
+
+ addRenegotiation(test.chain,
+ [
+ // causes a full, normal ice restart
+ function PC_LOCAL_SET_OFFER_OPTION(test) {
+ test.setOfferOptions({ iceRestart: true });
+ },
+ function PC_LOCAL_EXPECT_ICE_CHECKING(test) {
+ test.pcLocal.expectIceChecking();
+ },
+ function PC_REMOTE_EXPECT_ICE_CHECKING(test) {
+ test.pcRemote.expectIceChecking();
+ }
+ ]
+ );
+
+ test.setMediaConstraints([{audio: true}, {video: true}],
+ [{audio: true}, {video: true}]);
+ return test.run();
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceBadAnswer.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceBadAnswer.html
new file mode 100644
index 0000000000..b71001b0db
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceBadAnswer.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1413709",
+ title: "Renegotiation: bad answer ICE credentials"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ test = new PeerConnectionTest(options);
+
+ addRenegotiation(test.chain,
+ [
+ function PC_LOCAL_ADD_SECOND_STREAM(test) {
+ test.setMediaConstraints([{audio: true}],
+ []);
+ return test.pcLocal.getAllUserMedia([{audio: true}]);
+ },
+ ]
+ );
+
+ // If the offerer hasn't indicated ICE restart, then an answer
+ // arriving during renegotiation that has modified ICE credentials
+ // should cause an error
+ test.chain.replaceAfter("PC_LOCAL_GET_ANSWER",
+ [
+ function PC_LOCAL_REWRITE_REMOTE_SDP_ICE_CREDS(test) {
+ test._remote_answer.sdp =
+ test._remote_answer.sdp.replace(/a=ice-pwd:.*\r\n/g,
+ "a=ice-pwd:bad-pwd\r\n")
+ .replace(/a=ice-ufrag:.*\r\n/g,
+ "a=ice-ufrag:bad-ufrag\r\n");
+ },
+
+ function PC_LOCAL_EXPECT_SET_REMOTE_DESCRIPTION_FAIL(test) {
+ return test.setRemoteDescription(test.pcLocal,
+ test._remote_answer,
+ STABLE)
+ .then(() => ok(false, "setRemoteDescription must fail"),
+ e => is(e.name, "InvalidAccessError",
+ "setRemoteDescription must fail and did"));
+ }
+ ], 1 // replace after the second PC_LOCAL_GET_ANSWER
+ );
+
+ test.setMediaConstraints([{audio: true}], []);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalAndRemoteRollback.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalAndRemoteRollback.html
new file mode 100644
index 0000000000..6bbf9440fc
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalAndRemoteRollback.html
@@ -0,0 +1,82 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "906986",
+ title: "Renegotiation: restart ice, local and remote rollback"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ test = new PeerConnectionTest(options);
+
+ addRenegotiation(test.chain,
+ [
+ async function PC_LOCAL_SETUP_ICE_HANDLER(test) {
+ await test.pcLocal.endOfTrickleIce;
+ test.pcLocal.setupIceCandidateHandler(test);
+ },
+
+ // causes a full, normal ice restart
+ function PC_LOCAL_SET_OFFER_OPTION(test) {
+ test.setOfferOptions({ iceRestart: true });
+ }
+ ]
+ );
+
+ test.chain.replaceAfter('PC_REMOTE_CREATE_ANSWER',
+ [
+ function PC_LOCAL_EXPECT_ICE_CONNECTED(test) {
+ test.pcLocal.iceCheckingIceRollbackExpected = true;
+ },
+
+ function PC_REMOTE_ROLLBACK(test) {
+ return test.setRemoteDescription(test.pcRemote, { type: "rollback" },
+ STABLE);
+ },
+
+ async function PC_LOCAL_ROLLBACK(test) {
+ await test.pcLocal.endOfTrickleIce;
+ // We haven't negotiated the new stream yet.
+ test.pcLocal.expectNegotiationNeeded();
+ return test.setLocalDescription(
+ test.pcLocal,
+ new RTCSessionDescription({ type: "rollback", sdp: ""}),
+ STABLE);
+ },
+
+ // Rolling back should shut down gathering for the offerer,
+ // but because the answerer never set a local description, no ICE
+ // gathering has happened yet, so there's no changes to ICE gathering
+ // state
+ function PC_LOCAL_WAIT_FOR_END_OF_TRICKLE(test) {
+ return test.pcLocal.endOfTrickleIce;
+ },
+
+ function PC_LOCAL_EXPECT_ICE_CHECKING(test) {
+ test.pcLocal.expectIceChecking();
+ },
+ function PC_REMOTE_EXPECT_ICE_CHECKING(test) {
+ test.pcRemote.expectIceChecking();
+ }
+ ],
+ 1 // Replaces after second PC_REMOTE_CREATE_ANSWER
+ );
+ test.chain.append(commandsPeerConnectionOfferAnswer);
+
+ // for now, only use one stream, because rollback doesn't seem to
+ // like multiple streams. See bug 1259465.
+ test.setMediaConstraints([{audio: true}],
+ [{audio: true}]);
+ return test.run();
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalAndRemoteRollbackNoSubsequentRestart.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalAndRemoteRollbackNoSubsequentRestart.html
new file mode 100644
index 0000000000..37b0fc68fc
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalAndRemoteRollbackNoSubsequentRestart.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "906986",
+ title: "Renegotiation: restart ice, local and remote rollback, without a subsequent ICE restart"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ test = new PeerConnectionTest(options);
+
+ addRenegotiation(test.chain,
+ [
+ function PC_LOCAL_SETUP_ICE_HANDLER(test) {
+ test.pcLocal.setupIceCandidateHandler(test);
+ },
+ function PC_REMOTE_SETUP_ICE_HANDLER(test) {
+ test.pcRemote.setupIceCandidateHandler(test);
+ },
+
+ // causes a full, normal ice restart
+ function PC_LOCAL_SET_OFFER_OPTION(test) {
+ test.setOfferOptions({ iceRestart: true });
+ }
+ ]
+ );
+
+ test.chain.replaceAfter('PC_REMOTE_CREATE_ANSWER',
+ [
+ function PC_REMOTE_ROLLBACK(test) {
+ return test.setRemoteDescription(test.pcRemote, { type: "rollback" },
+ STABLE);
+ },
+
+ async function PC_LOCAL_ROLLBACK(test) {
+ await test.pcLocal.endOfTrickleIce;
+ // We haven't negotiated the new stream yet.
+ test.pcLocal.expectNegotiationNeeded();
+ return test.setLocalDescription(
+ test.pcLocal,
+ new RTCSessionDescription({ type: "rollback", sdp: ""}),
+ STABLE);
+ },
+
+ // Rolling back should shut down gathering for the offerer,
+ // but because the answerer never set a local description, no ICE
+ // gathering has happened yet, so there's no changes to ICE gathering
+ // state
+ function PC_LOCAL_WAIT_FOR_END_OF_TRICKLE(test) {
+ return test.pcLocal.endOfTrickleIce;
+ },
+
+ function PC_LOCAL_SET_OFFER_OPTION(test) {
+ test.setOfferOptions({ iceRestart: false });
+ }
+ ],
+ 1 // Replaces after second PC_REMOTE_CREATE_ANSWER
+ );
+ test.chain.append(commandsPeerConnectionOfferAnswer);
+
+ // for now, only use one stream, because rollback doesn't seem to
+ // like multiple streams. See bug 1259465.
+ test.setMediaConstraints([{audio: true}],
+ [{audio: true}]);
+ return test.run();
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalRollback.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalRollback.html
new file mode 100644
index 0000000000..f5f9a1f220
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalRollback.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "906986",
+ title: "Renegotiation: restart ice, local rollback"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ test = new PeerConnectionTest(options);
+
+ addRenegotiation(test.chain,
+ [
+ // causes a full, normal ice restart
+ function PC_LOCAL_SET_OFFER_OPTION(test) {
+ test.setOfferOptions({ iceRestart: true });
+ },
+ // causes an ice restart and then rolls it back
+ // (does not result in sending an offer)
+ function PC_LOCAL_SETUP_ICE_HANDLER(test) {
+ test.pcLocal.setupIceCandidateHandler(test, () => {});
+ },
+ function PC_LOCAL_CREATE_AND_SET_OFFER(test) {
+ return test.createOffer(test.pcLocal).then(offer => {
+ return test.setLocalDescription(test.pcLocal,
+ offer,
+ HAVE_LOCAL_OFFER);
+ });
+ },
+ function PC_LOCAL_EXPECT_ICE_CONNECTED(test) {
+ test.pcLocal.iceCheckingIceRollbackExpected = true;
+ },
+ function PC_LOCAL_WAIT_FOR_GATHERING(test) {
+ return new Promise(r => {
+ test.pcLocal._pc.addEventListener("icegatheringstatechange", () => {
+ if (test.pcLocal._pc.iceGatheringState == "gathering") {
+ r();
+ }
+ });
+ });
+ },
+ function PC_LOCAL_ROLLBACK(test) {
+ return test.setLocalDescription(test.pcLocal,
+ { type: "rollback", sdp: ""},
+ STABLE);
+ },
+ // Rolling back should shut down gathering
+ function PC_LOCAL_WAIT_FOR_END_OF_TRICKLE(test) {
+ return test.pcLocal.endOfTrickleIce;
+ },
+ function PC_LOCAL_EXPECT_ICE_CHECKING(test) {
+ test.pcLocal.expectIceChecking();
+ },
+ function PC_REMOTE_EXPECT_ICE_CHECKING(test) {
+ test.pcRemote.expectIceChecking();
+ }
+ ]
+ );
+
+ // for now, only use one stream, because rollback doesn't seem to
+ // like multiple streams. See bug 1259465.
+ test.setMediaConstraints([{audio: true}],
+ [{audio: true}]);
+ return test.run();
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalRollbackNoSubsequentRestart.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalRollbackNoSubsequentRestart.html
new file mode 100644
index 0000000000..8e27864aae
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceLocalRollbackNoSubsequentRestart.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "906986",
+ title: "Renegotiation: restart ice, local rollback, then renegotiation without ICE restart"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ test = new PeerConnectionTest(options);
+
+ addRenegotiation(test.chain,
+ [
+ // causes a full, normal ice restart
+ function PC_LOCAL_SET_OFFER_OPTION(test) {
+ test.setOfferOptions({ iceRestart: true });
+ },
+ // causes an ice restart and then rolls it back
+ // (does not result in sending an offer)
+ function PC_LOCAL_SETUP_ICE_HANDLER(test) {
+ test.pcLocal.setupIceCandidateHandler(test, () => {});
+ },
+ function PC_LOCAL_CREATE_AND_SET_OFFER(test) {
+ return test.createOffer(test.pcLocal).then(offer => {
+ return test.setLocalDescription(test.pcLocal,
+ offer,
+ HAVE_LOCAL_OFFER);
+ });
+ },
+ function PC_LOCAL_WAIT_FOR_END_OF_TRICKLE(test) {
+ return test.pcLocal.endOfTrickleIce;
+ },
+ function PC_LOCAL_ROLLBACK(test) {
+ return test.setLocalDescription(test.pcLocal,
+ { type: "rollback", sdp: ""},
+ STABLE);
+ },
+ function PC_LOCAL_SET_OFFER_OPTION(test) {
+ test.setOfferOptions({ iceRestart: false });
+ }
+ ]
+ );
+
+ // for now, only use one stream, because rollback doesn't seem to
+ // like multiple streams. See bug 1259465.
+ test.setMediaConstraints([{audio: true}],
+ [{audio: true}]);
+ return test.run();
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoBundle.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoBundle.html
new file mode 100644
index 0000000000..134fa97cc0
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoBundle.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "906986",
+ title: "Renegotiation: restart ice, no bundle"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ options = options || { };
+ options.bundle = false;
+ test = new PeerConnectionTest(options);
+
+ addRenegotiation(test.chain,
+ [
+ // causes a full, normal ice restart
+ function PC_LOCAL_SET_OFFER_OPTION(test) {
+ test.setOfferOptions({ iceRestart: true });
+ },
+ function PC_LOCAL_EXPECT_ICE_CHECKING(test) {
+ test.pcLocal.expectIceChecking();
+ },
+ function PC_REMOTE_EXPECT_ICE_CHECKING(test) {
+ test.pcRemote.expectIceChecking();
+ }
+ ]
+ );
+
+ test.setMediaConstraints([{audio: true}, {video: true}],
+ [{audio: true}, {video: true}]);
+ return test.run();
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoBundleNoRtcpMux.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoBundleNoRtcpMux.html
new file mode 100644
index 0000000000..06a3a3c980
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoBundleNoRtcpMux.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "906986",
+ title: "Renegotiation: restart ice, no bundle and disabled RTCP-Mux"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ options = options || { };
+ options.bundle = false;
+ options.rtcpmux = false;
+ test = new PeerConnectionTest(options);
+
+ addRenegotiation(test.chain,
+ [
+ // causes a full, normal ice restart
+ function PC_LOCAL_SET_OFFER_OPTION(test) {
+ test.setOfferOptions({ iceRestart: true });
+ },
+ function PC_LOCAL_EXPECT_ICE_CHECKING(test) {
+ test.pcLocal.expectIceChecking();
+ },
+ function PC_REMOTE_EXPECT_ICE_CHECKING(test) {
+ test.pcRemote.expectIceChecking();
+ }
+ ]
+ );
+
+ test.setMediaConstraints([{audio: true}, {video: true}],
+ [{audio: true}, {video: true}]);
+ return test.run();
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoRtcpMux.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoRtcpMux.html
new file mode 100644
index 0000000000..5d4780211a
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_restartIceNoRtcpMux.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "906986",
+ title: "Renegotiation: restart ice, with disabled RTCP-Mux"
+ });
+
+ var test;
+ runNetworkTest(function (options) {
+ options = options || { };
+ options.rtcpmux = false;
+ test = new PeerConnectionTest(options);
+
+ addRenegotiation(test.chain,
+ [
+ // causes a full, normal ice restart
+ function PC_LOCAL_SET_OFFER_OPTION(test) {
+ test.setOfferOptions({ iceRestart: true });
+ },
+ function PC_LOCAL_EXPECT_ICE_CHECKING(test) {
+ test.pcLocal.expectIceChecking();
+ },
+ function PC_REMOTE_EXPECT_ICE_CHECKING(test) {
+ test.pcRemote.expectIceChecking();
+ }
+ ]
+ );
+
+ test.setMediaConstraints([{audio: true}, {video: true}],
+ [{audio: true}, {video: true}]);
+ return test.run();
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_restrictBandwidthTargetBitrate.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_restrictBandwidthTargetBitrate.html
new file mode 100644
index 0000000000..ff9fb1fc22
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_restrictBandwidthTargetBitrate.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "Bug 1404250",
+ title: "Extremely bitrate restricted video-only peer connection"
+ });
+
+ runNetworkTest(function (options) {
+ const test = new PeerConnectionTest(options);
+ test.setMediaConstraints([{video: true}], [{video: true}]);
+ test.chain.insertAfter('PC_REMOTE_GET_OFFER', [
+ function PC_REMOTE_ADD_TIAS(test) {
+ test._local_offer.sdp = sdputils.addTiasBps(
+ test._local_offer.sdp, 25000);
+ info("Offer with TIAS: " + JSON.stringify(test._local_offer));
+ }
+ ]);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_restrictBandwidthWithTias.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_restrictBandwidthWithTias.html
new file mode 100644
index 0000000000..85b831e9a8
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_restrictBandwidthWithTias.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1359854",
+ title: "500Kb restricted video-only peer connection"
+ });
+
+ runNetworkTest(function (options) {
+ const test = new PeerConnectionTest(options);
+ test.setMediaConstraints([{video: true}], [{video: true}]);
+ test.chain.insertAfter('PC_REMOTE_GET_OFFER', [
+ function PC_REMOTE_ADD_TIAS(test) {
+ test._local_offer.sdp = sdputils.addTiasBps(
+ test._local_offer.sdp, 250000);
+ info("Offer with TIAS: " + JSON.stringify(test._local_offer));
+ }
+ ]);
+ // TODO it would be nice to verify the used bandwidth
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_rtcp_rsize.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_rtcp_rsize.html
new file mode 100644
index 0000000000..25270984ea
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_rtcp_rsize.html
@@ -0,0 +1,81 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="stats.js"></script>
+ <script type="application/javascript" src="sdpUtils.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1279153",
+ title: "rtcp-rsize",
+ visible: true
+ });
+
+ // 0) Use webrtc-sdp
+ // 1) ADD RTCP-RISZE to all video m-sections
+ // 2) Check for RTCP-RSIZE in ANSWER
+ // 3) Wait for media to flow
+ // 4) Wait for RTCP stats
+
+ runNetworkTest(async function (options) {
+ const test = new PeerConnectionTest(options);
+
+ let mSectionsAltered = 0;
+
+ test.chain.insertAfter("PC_LOCAL_CREATE_OFFER", [
+ function PC_LOCAL_ADD_RTCP_RSIZE(test) {
+ const lines = test.originalOffer.sdp.split("\r\n");
+ info(`SDP before rtcp-rsize: ${lines.join('\n')}`);
+ // Insert an rtcp-rsize for each m section
+ const rsizeAdded = lines.flatMap(line => {
+ if (line.startsWith("m=video")) {
+ mSectionsAltered = mSectionsAltered + 1;
+ return [line, "a=rtcp-rsize"];
+ }
+ return [line];
+ });
+ test.originalOffer.sdp = rsizeAdded.join("\r\n");
+ info(`SDP with rtcp-rsize: ${rsizeAdded.join("\n")}`);
+ is(mSectionsAltered, 1, "We only altered 1 msection")
+ }]);
+
+ // Check that the rtcp-rsize makes into the answer
+ test.chain.insertAfter("PC_LOCAL_SET_REMOTE_DESCRIPTION", [
+ function PC_LOCAL_CHECK_RTCP_RSIZE(test) {
+ const msections = sdputils.getMSections(test.pcLocal._pc.currentRemoteDescription.sdp);
+ var alteredMSectionsFound = 0;
+ for (msection of msections) {
+ if (msection.startsWith("m=video")) {
+ ok(msection.includes("\r\na=rtcp-rsize\r\n"), "video m-section includes RTCP-RSIZE");
+ alteredMSectionsFound = alteredMSectionsFound + 1;
+ } else {
+ ok(!msection.includes("\r\na=rtcp-rsize\r\n"), "audio m-section does not include RTCP-RSIZE");
+ }
+ }
+ is(alteredMSectionsFound, mSectionsAltered, "correct number of msections found");
+ }
+ ]);
+
+ // Make sure that we are still getting RTCP stats
+ test.chain.insertAfter("PC_REMOTE_WAIT_FOR_MEDIA_FLOW",
+ async function PC_LOCAL_AND_REMOTE_CHECK_FOR_RTCP_STATS(test) {
+ await Promise.all([
+ waitForSyncedRtcp(test.pcLocal._pc),
+ waitForSyncedRtcp(test.pcRemote._pc),
+ ]);
+ // The work is done by waitForSyncedRtcp which will throw if
+ // RTCP stats are not received.
+ info("RTCP stats received!");
+ },
+ );
+ test.setMediaConstraints([{audio: true}, {video: true}], []);
+ await test.run();
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_scaleResolution.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_scaleResolution.html
new file mode 100644
index 0000000000..4be6873fa6
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_scaleResolution.html
@@ -0,0 +1,119 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1244913",
+ title: "Scale resolution down on a PeerConnection",
+ visible: true
+ });
+
+ async function checkForH264Support() {
+ const pc = new RTCPeerConnection();
+ const offer = await pc.createOffer({offerToReceiveVideo: true});
+ return offer.sdp.match(/a=rtpmap:[1-9][0-9]* H264/);
+ }
+
+ let resolutionAlignment = 1;
+
+ var mustRejectWith = (msg, reason, f) =>
+ f().then(() => ok(false, msg),
+ e => is(e.name, reason, msg));
+
+ async function testScale(codec) {
+ var pc1 = new RTCPeerConnection();
+ var pc2 = new RTCPeerConnection();
+
+ var add = (pc, can, failed) => can && pc.addIceCandidate(can).catch(failed);
+ pc1.onicecandidate = e => add(pc2, e.candidate, generateErrorCallback());
+ pc2.onicecandidate = e => add(pc1, e.candidate, generateErrorCallback());
+
+ info("testing scaling with " + codec);
+
+ let stream = await navigator.mediaDevices.getUserMedia({ video: true });
+
+ var v1 = createMediaElement('video', 'v1');
+ var v2 = createMediaElement('video', 'v2');
+
+ var ontrackfired = new Promise(resolve => pc2.ontrack = e => resolve(e));
+ var v2loadedmetadata = new Promise(resolve => v2.onloadedmetadata = resolve);
+
+ is(v2.currentTime, 0, "v2.currentTime is zero at outset");
+
+ v1.srcObject = stream;
+ var sender = pc1.addTrack(stream.getVideoTracks()[0], stream);
+ let parameters = sender.getParameters();
+ is(parameters.encodings.length, 1, "Default number of encodings should be 1");
+ parameters.encodings[0].scaleResolutionDownBy = 0.5;
+
+ await mustRejectWith(
+ "Invalid scaleResolutionDownBy must reject", "RangeError",
+ () => sender.setParameters(parameters)
+ );
+
+ parameters = sender.getParameters();
+ parameters.encodings[0].scaleResolutionDownBy = 2;
+ parameters.encodings[0].maxBitrate = 60000;
+
+ await sender.setParameters(parameters);
+
+ parameters = sender.getParameters();
+ is(parameters.encodings[0].scaleResolutionDownBy, 2, "Should be able to set scaleResolutionDownBy");
+ is(parameters.encodings[0].maxBitrate, 60000, "Should be able to set maxBitrate");
+
+ let offer = await pc1.createOffer();
+ if (codec == "VP8") {
+ offer.sdp = sdputils.removeAllButPayloadType(offer.sdp, 126);
+ }
+ await pc1.setLocalDescription(offer);
+ await pc2.setRemoteDescription(pc1.localDescription);
+
+ let answer = await pc2.createAnswer();
+ await pc2.setLocalDescription(answer);
+ await pc1.setRemoteDescription(pc2.localDescription);
+ let trackevent = await ontrackfired;
+
+ v2.srcObject = trackevent.streams[0];
+
+ await v2loadedmetadata;
+
+ await waitUntil(() => v2.currentTime > 0);
+ ok(v2.currentTime > 0, "v2.currentTime is moving (" + v2.currentTime + ")");
+
+ ok(v1.videoWidth > 0, "source width is positive");
+ ok(v1.videoHeight > 0, "source height is positive");
+ const expectedWidth =
+ v1.videoWidth / 2 - (v1.videoWidth / 2 % resolutionAlignment);
+ const expectedHeight =
+ v1.videoHeight / 2 - (v1.videoHeight / 2 % resolutionAlignment);
+ is(v2.videoWidth, expectedWidth,
+ "sink is half the width of the source");
+ is(v2.videoHeight, expectedHeight,
+ "sink is half the height of the source");
+ stream.getTracks().forEach(track => track.stop());
+ v1.srcObject = v2.srcObject = null;
+ pc1.close()
+ pc2.close()
+ }
+
+ runNetworkTest(async () => {
+ await matchPlatformH264CodecPrefs();
+ const hasH264 = await checkForH264Support();
+ if (hasH264 && navigator.userAgent.includes("Android")) {
+ // Android only has a hw encoder for h264
+ resolutionAlignment = 16;
+ }
+ await pushPrefs(['media.peerconnection.video.lock_scaling', true]);
+ await testScale("VP8");
+ if (hasH264) {
+ await testScale("H264");
+ }
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_scaleResolution_oldSetParameters.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_scaleResolution_oldSetParameters.html
new file mode 100644
index 0000000000..85a989ba32
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_scaleResolution_oldSetParameters.html
@@ -0,0 +1,122 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1244913",
+ title: "Scale resolution down on a PeerConnection",
+ visible: true
+ });
+
+ async function checkForH264Support() {
+ const pc = new RTCPeerConnection();
+ const offer = await pc.createOffer({offerToReceiveVideo: true});
+ return offer.sdp.match(/a=rtpmap:[1-9][0-9]* H264/);
+ }
+
+ let resolutionAlignment = 1;
+
+ var mustRejectWith = (msg, reason, f) =>
+ f().then(() => ok(false, msg),
+ e => is(e.name, reason, msg));
+
+ async function testScale(codec) {
+ var pc1 = new RTCPeerConnection();
+ var pc2 = new RTCPeerConnection();
+
+ var add = (pc, can, failed) => can && pc.addIceCandidate(can).catch(failed);
+ pc1.onicecandidate = e => add(pc2, e.candidate, generateErrorCallback());
+ pc2.onicecandidate = e => add(pc1, e.candidate, generateErrorCallback());
+
+ info("testing scaling with " + codec);
+
+ let stream = await navigator.mediaDevices.getUserMedia({ video: true });
+
+ var v1 = createMediaElement('video', 'v1');
+ var v2 = createMediaElement('video', 'v2');
+
+ var ontrackfired = new Promise(resolve => pc2.ontrack = e => resolve(e));
+ var v2loadedmetadata = new Promise(resolve => v2.onloadedmetadata = resolve);
+
+ is(v2.currentTime, 0, "v2.currentTime is zero at outset");
+
+ v1.srcObject = stream;
+ var sender = pc1.addTrack(stream.getVideoTracks()[0], stream);
+
+ const otherErrorStart = await GleanTest.rtcrtpsenderSetparameters.failOther.testGetValue();
+ const noTransactionIdWarningStart = await GleanTest.rtcrtpsenderSetparameters.warnNoTransactionid.testGetValue();
+
+ await mustRejectWith(
+ "Invalid scaleResolutionDownBy must reject", "RangeError",
+ () => sender.setParameters(
+ { encodings:[{ scaleResolutionDownBy: 0.5 } ] })
+ );
+
+ const otherErrorEnd = await GleanTest.rtcrtpsenderSetparameters.failOther.testGetValue();
+ const noTransactionIdWarningEnd = await GleanTest.rtcrtpsenderSetparameters.warnNoTransactionid.testGetValue();
+
+ // Make sure Glean is recording these statistics
+ is(otherErrorEnd.denominator, otherErrorStart.denominator, "No new RTCRtpSenders were created during this time");
+ is(otherErrorEnd.numerator, otherErrorStart.numerator + 1, "RTCRtpSender.setParameters reported a failure via Glean");
+ is(noTransactionIdWarningEnd.denominator, noTransactionIdWarningStart.denominator, "No new RTCRtpSenders were created during this time");
+ is(noTransactionIdWarningEnd.numerator, noTransactionIdWarningStart.numerator + 1, "Glean should have recorded a warning due to missing transactionId");
+
+ await sender.setParameters({ encodings: [{ maxBitrate: 60000,
+ scaleResolutionDownBy: 2 }] });
+
+ let offer = await pc1.createOffer();
+ if (codec == "VP8") {
+ offer.sdp = sdputils.removeAllButPayloadType(offer.sdp, 126);
+ }
+ await pc1.setLocalDescription(offer);
+ await pc2.setRemoteDescription(pc1.localDescription);
+
+ let answer = await pc2.createAnswer();
+ await pc2.setLocalDescription(answer);
+ await pc1.setRemoteDescription(pc2.localDescription);
+ let trackevent = await ontrackfired;
+
+ v2.srcObject = trackevent.streams[0];
+
+ await v2loadedmetadata;
+
+ await waitUntil(() => v2.currentTime > 0);
+ ok(v2.currentTime > 0, "v2.currentTime is moving (" + v2.currentTime + ")");
+
+ ok(v1.videoWidth > 0, "source width is positive");
+ ok(v1.videoHeight > 0, "source height is positive");
+ const expectedWidth =
+ v1.videoWidth / 2 - (v1.videoWidth / 2 % resolutionAlignment);
+ const expectedHeight =
+ v1.videoHeight / 2 - (v1.videoHeight / 2 % resolutionAlignment);
+ is(v2.videoWidth, expectedWidth,
+ "sink is half the width of the source");
+ is(v2.videoHeight, expectedHeight,
+ "sink is half the height of the source");
+ stream.getTracks().forEach(track => track.stop());
+ v1.srcObject = v2.srcObject = null;
+ pc1.close()
+ pc2.close()
+ }
+
+ runNetworkTest(async () => {
+ await matchPlatformH264CodecPrefs();
+ const hasH264 = await checkForH264Support();
+ if (hasH264 && navigator.userAgent.includes("Android")) {
+ // Android only has a hw encoder for h264
+ resolutionAlignment = 16;
+ }
+ await pushPrefs(['media.peerconnection.video.lock_scaling', true]);
+ await testScale("VP8");
+ if (hasH264) {
+ await testScale("H264");
+ }
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_sender_and_receiver_stats.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_sender_and_receiver_stats.html
new file mode 100644
index 0000000000..72749e8c50
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_sender_and_receiver_stats.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="stats.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1355220",
+ title: "RTCRtpSender.getStats() and RTCRtpReceiver.getStats()",
+ visible: true
+ });
+
+ const checkStats = (sndReport, rcvReport, kind) => {
+ ok(sndReport instanceof window.RTCStatsReport, "sender stats are a RTCStatsReport");
+ ok(rcvReport instanceof window.RTCStatsReport, "receiver stats are a RTCStatsReport");
+ // Returns SSRCs and performs some checks
+ let getSsrcs = (report, kind) => {
+ return [...report.values()]
+ .filter(stat => stat.type.endsWith("bound-rtp")).map(stat =>{
+ isnot(parseInt(stat.id, 16), NaN,
+ `id ${stat.id} is an opaque (hex) number`);
+ is(stat.kind, kind, "kind of " + stat.id
+ + " is expected type " + kind);
+ return stat.ssrc;
+ }).sort().join("|");
+ };
+ let sndSsrcs = getSsrcs(sndReport, kind);
+ let rcvSsrcs = getSsrcs(rcvReport, kind);
+ ok(sndSsrcs, "sender SSRCs is not empty");
+ ok(rcvSsrcs, "receiver SSRCs is not empty");
+ is(sndSsrcs, rcvSsrcs, "sender SSRCs match receiver SSRCs");
+ };
+
+ // This MUST be run after PC_*_WAIT_FOR_MEDIA_FLOW to ensure that we have RTP
+ // before checking for RTCP.
+ // It will throw UnsyncedRtcpError if it times out waiting for sync.
+
+ runNetworkTest(function (options) {
+ test = new PeerConnectionTest(options);
+ test.chain.insertAfter("PC_REMOTE_WAIT_FOR_MEDIA_FLOW",
+ async function PC_LOCAL_AND_REMOTE_CHECK_SENDER_RECEIVER_STATS(test) {
+ await Promise.all([
+ waitForSyncedRtcp(test.pcLocal._pc),
+ waitForSyncedRtcp(test.pcRemote._pc),
+ ]);
+ let senders = test.pcLocal.getSenders();
+ let receivers = test.pcRemote.getReceivers();
+ is(senders.length, 2, "Have exactly two senders.");
+ is(receivers.length, 2, "Have exactly two receivers.");
+ for(let kind of ["audio", "video"]) {
+ let senderStats =
+ await senders.find(s => s.track.kind == kind).getStats();
+ is(senders.filter(s => s.track.kind == kind).length, 1,
+ "Exactly 1 sender of kind " + kind);
+ let receiverStats =
+ await receivers.find(r => r.track.kind == kind).getStats();
+ is(receivers.filter(r => r.track.kind == kind).length, 1,
+ "Exactly 1 receiver of kind " + kind);
+
+ checkStats(senderStats, receiverStats, kind);
+ }
+ }
+ );
+ test.setMediaConstraints([{audio: true}, {video: true}], []);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_setLocalAnswerInHaveLocalOffer.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_setLocalAnswerInHaveLocalOffer.html
new file mode 100644
index 0000000000..07cdd7d6bd
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_setLocalAnswerInHaveLocalOffer.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "784519",
+ title: "setLocalDescription (answer) in 'have-local-offer'"
+ });
+
+runNetworkTest(function () {
+ const test = new PeerConnectionTest();
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ test.chain.removeAfter("PC_LOCAL_SET_LOCAL_DESCRIPTION");
+
+ test.chain.append([
+ function PC_LOCAL_SET_LOCAL_ANSWER(test) {
+ test.pcLocal._latest_offer.type = "answer";
+ return test.pcLocal.setLocalDescriptionAndFail(test.pcLocal._latest_offer)
+ .then(err => {
+ is(err.name, "InvalidModificationError", "Error is InvalidModificationError");
+ });
+ }
+ ]);
+
+ return test.run();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_setLocalAnswerInStable.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_setLocalAnswerInStable.html
new file mode 100644
index 0000000000..e57c0640f4
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_setLocalAnswerInStable.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "784519",
+ title: "setLocalDescription (answer) in 'stable'"
+ });
+
+runNetworkTest(function () {
+ const test = new PeerConnectionTest();
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ test.chain.removeAfter("PC_LOCAL_CREATE_OFFER");
+
+ test.chain.append([
+ function PC_LOCAL_SET_LOCAL_ANSWER(test) {
+ test.pcLocal._latest_offer.type = "answer";
+ return test.pcLocal.setLocalDescriptionAndFail(test.pcLocal._latest_offer)
+ .then(err => {
+ is(err.name, "InvalidModificationError", "Error is InvalidModificationError");
+ });
+ }
+ ]);
+
+ return test.run();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_setLocalOfferInHaveRemoteOffer.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_setLocalOfferInHaveRemoteOffer.html
new file mode 100644
index 0000000000..bd98a83635
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_setLocalOfferInHaveRemoteOffer.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "784519",
+ title: "setLocalDescription (offer) in 'have-remote-offer'"
+ });
+
+runNetworkTest(function () {
+ const test = new PeerConnectionTest();
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ test.chain.removeAfter("PC_REMOTE_SET_REMOTE_DESCRIPTION");
+
+ test.chain.append([
+ async function PC_REMOTE_SET_LOCAL_OFFER(test) {
+ const err = await test.pcRemote.setLocalDescriptionAndFail(test.pcLocal._latest_offer);
+ is(err.name, "InvalidModificationError", "Error is InvalidModificationError");
+ }
+ ]);
+
+ return test.run();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters.html
new file mode 100644
index 0000000000..5df97e39f5
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters.html
@@ -0,0 +1,470 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ bug: "1230184",
+ title: "Set parameters on sender",
+ visible: true
+});
+
+const simulcastOffer = `v=0
+o=- 3840232462471583827 0 IN IP4 127.0.0.1
+s=-
+t=0 0
+a=group:BUNDLE 0
+a=msid-semantic: WMS
+m=video 9 UDP/TLS/RTP/SAVPF 96
+c=IN IP4 0.0.0.0
+a=rtcp:9 IN IP4 0.0.0.0
+a=ice-ufrag:Li6+
+a=ice-pwd:3C05CTZBRQVmGCAq7hVasHlT
+a=ice-options:trickle
+a=fingerprint:sha-256 5B:D3:8E:66:0E:7D:D3:F3:8E:E6:80:28:19:FC:55:AD:58:5D:B9:3D:A8:DE:45:4A:E7:87:02:F8:3C:0B:3B:B3
+a=setup:actpass
+a=mid:0
+a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
+a=recvonly
+a=rtcp-mux
+a=rtpmap:96 VP8/90000
+a=rtcp-fb:96 goog-remb
+a=rtcp-fb:96 transport-cc
+a=rtcp-fb:96 ccm fir
+a=rid:foo recv
+a=rid:bar recv
+a=simulcast:recv foo;bar
+`;
+
+function buildMaximumSendEncodings() {
+ const sendEncodings = [];
+ while (true) {
+ // isDeeply does not see identical string primitives and String objects
+ // as the same, so we make this a string primitive.
+ sendEncodings.push({rid: `${sendEncodings.length}`});
+ const pc = new RTCPeerConnection();
+ const {sender} = pc.addTransceiver('video', {sendEncodings});
+ const {encodings} = sender.getParameters();
+ if (encodings.length < sendEncodings.length) {
+ sendEncodings.pop();
+ return sendEncodings;
+ }
+ }
+}
+
+async function queueAWebrtcTask() {
+ const pc = new RTCPeerConnection();
+ pc.addTransceiver('audio');
+ await new Promise(r => pc.onnegotiationneeded = r);
+ pc.close();
+}
+
+// setParameters is mostly tested in wpt, but we test a few
+// implementation-specific things here. Other mochitests check whether the
+// set parameters actually have the desired effect on the media streams.
+const tests = [
+
+ // wpt currently does not assume support for 3 encodings, which limits the
+ // effectiveness of its powers-of-2 test (since it can test only for 1 and 2)
+ async function checkScaleResolutionDownByAutoFillPowersOf2() {
+ const pc = new RTCPeerConnection();
+ const {sender} = pc.addTransceiver('video', {
+ sendEncodings: [{rid: "0"},{rid: "1"},{rid: "2"}]
+ });
+ const {encodings} = sender.getParameters();
+ const scaleValues = encodings.map(({scaleResolutionDownBy}) => scaleResolutionDownBy);
+ isDeeply(scaleValues, [4, 2, 1]);
+ },
+
+ // wpt currently does not assume support for 3 encodings, which limits the
+ // effectiveness of its fill-with-1 test
+ async function checkScaleResolutionDownByAutoFillWith1() {
+ const pc = new RTCPeerConnection();
+ const {sender} = pc.addTransceiver('video', {
+ sendEncodings: [
+ {rid: "0"},{rid: "1", scaleResolutionDownBy: 3},{rid: "2"}
+ ]
+ });
+ const {encodings} = sender.getParameters();
+ const scaleValues = encodings.map(({scaleResolutionDownBy}) => scaleResolutionDownBy);
+ isDeeply(scaleValues, [1, 3, 1]);
+ },
+
+ async function checkVideoEncodingLimit() {
+ const pc = new RTCPeerConnection();
+ const maxSendEncodings = buildMaximumSendEncodings();
+ const sendEncodings = maxSendEncodings.concat({rid: "a"});
+ const {sender} = pc.addTransceiver('video', {sendEncodings});
+ const {encodings} = sender.getParameters();
+
+ const rids = encodings.map(({rid}) => rid);
+ const expectedRids = maxSendEncodings.map(({rid}) => rid);
+ isDeeply(rids, expectedRids);
+
+ const scaleValues = encodings.map(({scaleResolutionDownBy}) => scaleResolutionDownBy);
+ const expectedScaleValues = [];
+ let scale = 1;
+ while (expectedScaleValues.length < maxSendEncodings.length) {
+ expectedScaleValues.push(scale);
+ scale *= 2;
+ }
+ isDeeply(scaleValues, expectedScaleValues.reverse());
+ },
+
+ async function checkScaleDownByInTrimmedEncoding() {
+ const pc = new RTCPeerConnection();
+ const maxSendEncodings = buildMaximumSendEncodings();
+ const sendEncodings = maxSendEncodings.concat({rid: "a", scaleResolutionDownBy: 3});
+ const {sender} = pc.addTransceiver('video', {sendEncodings});
+ const {encodings} = sender.getParameters();
+ const rids = encodings.map(({rid}) => rid);
+ const expectedRids = maxSendEncodings.map(({rid}) => rid);
+ isDeeply(rids, expectedRids);
+ const scaleValues = encodings.map(({scaleResolutionDownBy}) => scaleResolutionDownBy);
+ const expectedScaleValues = maxSendEncodings.map(() => 1);
+ isDeeply(scaleValues, expectedScaleValues);
+ },
+
+ async function checkLibwebrtcRidLengthLimit() {
+ const pc = new RTCPeerConnection();
+ try {
+ pc.addTransceiver('video', {
+ sendEncodings: [{rid: "wibblywobblyjeremybearimy"}]}
+ );
+ ok(false, "Rid should be too long for libwebrtc!");
+ } catch (e) {
+ is(e.name, "TypeError",
+ "Rid that is too long for libwebrtc should result in a TypeError");
+ }
+ },
+
+ async function checkErrorsInTrimmedEncodings() {
+ const pc = new RTCPeerConnection();
+ const maxSendEncodings = buildMaximumSendEncodings();
+ try {
+ const sendEncodings = maxSendEncodings.concat({rid: "foo-bar"});
+ pc.addTransceiver('video', { sendEncodings });
+ ok(false, "Should throw due to invalid rid characters");
+ } catch (e) {
+ is(e.name, "TypeError")
+ }
+ try {
+ const sendEncodings = maxSendEncodings.concat({rid: "wibblywobblyjeremybearimy"});
+ pc.addTransceiver('video', { sendEncodings });
+ ok(false, "Should throw because rid too long");
+ } catch (e) {
+ is(e.name, "TypeError")
+ }
+ try {
+ const sendEncodings = maxSendEncodings.concat({scaleResolutionDownBy: 2});
+ pc.addTransceiver('video', { sendEncodings });
+ ok(false, "Should throw due to missing rid");
+ } catch (e) {
+ is(e.name, "TypeError")
+ }
+ try {
+ const sendEncodings = maxSendEncodings.concat(maxSendEncodings[0]);
+ pc.addTransceiver('video', { sendEncodings });
+ ok(false, "Should throw due to duplicate rid");
+ } catch (e) {
+ is(e.name, "TypeError")
+ }
+ try {
+ const sendEncodings = maxSendEncodings.concat({rid: maxSendEncodings.length, scaleResolutionDownBy: 0});
+ pc.addTransceiver('video', { sendEncodings });
+ ok(false, "Should throw due to invalid scaleResolutionDownBy");
+ } catch (e) {
+ is(e.name, "RangeError")
+ }
+ try {
+ const sendEncodings = maxSendEncodings.concat({rid: maxSendEncodings.length, maxFramerate: -1});
+ pc.addTransceiver('video', { sendEncodings });
+ ok(false, "Should throw due to invalid maxFramerate");
+ } catch (e) {
+ is(e.name, "RangeError")
+ }
+ },
+
+ async function checkCompatModeUnicastSetParametersAllowsSimulcastOffer() {
+ await pushPrefs(
+ ["media.peerconnection.allow_old_setParameters", true]);
+ const pc1 = new RTCPeerConnection();
+ const stream = await navigator.mediaDevices.getUserMedia({video: true});
+ const sender = pc1.addTrack(stream.getTracks()[0]);
+ const parameters = sender.getParameters();
+ parameters.encodings[0].scaleResolutionDownBy = 3.0;
+ await sender.setParameters(parameters);
+
+ await pc1.setRemoteDescription({type: "offer", sdp: simulcastOffer});
+
+ const {encodings} = sender.getParameters();
+ const rids = encodings.map(({rid}) => rid);
+ isDeeply(rids, ["foo", "bar"]);
+ is(encodings[0].scaleResolutionDownBy, 2.0);
+ is(encodings[1].scaleResolutionDownBy, 1.0);
+ },
+
+ async function checkCompatModeUnicastSetParametersInterruptAllowsSimulcastOffer() {
+ await pushPrefs(
+ ["media.peerconnection.allow_old_setParameters", true]);
+ const pc1 = new RTCPeerConnection();
+ const stream = await navigator.mediaDevices.getUserMedia({video: true});
+ const sender = pc1.addTrack(stream.getTracks()[0]);
+ const parameters = sender.getParameters();
+ parameters.encodings[0].scaleResolutionDownBy = 3.0;
+
+ const offerDone = pc1.setRemoteDescription({type: "offer", sdp: simulcastOffer});
+ await sender.setParameters(parameters);
+ await offerDone;
+
+ const {encodings} = sender.getParameters();
+ const rids = encodings.map(({rid}) => rid);
+ isDeeply(rids, ["foo", "bar"]);
+ is(encodings[0].scaleResolutionDownBy, 2.0);
+ is(encodings[1].scaleResolutionDownBy, 1.0);
+ },
+
+ async function checkCompatModeSimulcastSetParametersSetsSimulcastEnvelope() {
+ await pushPrefs(
+ ["media.peerconnection.allow_old_setParameters", true]);
+ const pc1 = new RTCPeerConnection();
+ const stream = await navigator.mediaDevices.getUserMedia({video: true});
+ const sender = pc1.addTrack(stream.getTracks()[0]);
+ const parameters = sender.getParameters();
+ parameters.encodings[0].rid = "1";
+ parameters.encodings.push({rid: "2"});
+ await sender.setParameters(parameters);
+
+ await pc1.setRemoteDescription({type: "offer", sdp: simulcastOffer});
+
+ const {encodings} = sender.getParameters();
+ const rids = encodings.map(({rid}) => rid);
+ // No overlap in rids -> unicast
+ isDeeply(rids, [undefined]);
+ },
+
+ async function checkCompatModeSimulcastSetParametersRacesLocalUnicastOffer() {
+ await pushPrefs(
+ ["media.peerconnection.allow_old_setParameters", true]);
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+ const stream = await navigator.mediaDevices.getUserMedia({video: true});
+ const sender = pc1.addTrack(stream.getTracks()[0]);
+ // unicast offer
+ const offer = await pc1.createOffer();
+ const aTask = queueAWebrtcTask();
+ const sldPromise = pc1.setLocalDescription(offer);
+
+ // Right now, we have aTask queued. The success task for sLD is not queued
+ // yet, because Firefox performs the initial steps on the microtask queue,
+ // which we have not allowed to run yet. Awaiting aTask will first clear
+ // the microtask queue, then run the task queue until aTask is finished.
+ // That _should_ result in the success task for sLD(offer) being queued.
+ await aTask;
+
+ const parameters = sender.getParameters();
+ parameters.encodings[0].rid = "foo";
+ parameters.encodings.push({rid: "bar"});
+ // simulcast setParameters; the task to update [[SendEncodings]] should be
+ // queued after the success task for sLD(offer)
+ await sender.setParameters(parameters);
+ await sldPromise;
+
+ const {encodings} = sender.getParameters();
+ const rids = encodings.map(({rid}) => rid);
+ // Compat mode lets this slide, but won't try to negotiate it since we've
+ // already applied a unicast local offer.
+ isDeeply(rids, ["foo", "bar"]);
+
+ // Let negotiation finish, so we can generate a new offer
+ await pc2.setRemoteDescription(pc1.localDescription);
+ await pc2.setLocalDescription();
+ await pc1.setRemoteDescription(pc2.localDescription);
+
+ const reoffer = await pc1.createOffer();
+ ok(!reoffer.sdp.includes("a=simulcast"), "reoffer should be unicast");
+ },
+
+ async function checkCompatModeSimulcastSetParametersRacesRemoteOffer() {
+ await pushPrefs(
+ ["media.peerconnection.allow_old_setParameters", true]);
+ const pc1 = new RTCPeerConnection();
+ const stream = await navigator.mediaDevices.getUserMedia({video: true});
+ const sender = pc1.addTrack(stream.getTracks()[0]);
+ const parameters = sender.getParameters();
+ parameters.encodings[0].rid = "foo";
+ parameters.encodings.push({rid: "bar"});
+ const p = sender.setParameters(parameters);
+ await pc1.setRemoteDescription({type: "offer", sdp: simulcastOffer});
+ await p;
+ const answer = await pc1.createAnswer();
+
+ const {encodings} = sender.getParameters();
+ const rids = encodings.map(({rid}) => rid);
+ isDeeply(rids, ["foo", "bar"]);
+ ok(answer.sdp.includes("a=simulcast:send foo;bar"), "answer should be simulcast");
+ },
+
+ async function checkCompatModeSimulcastSetParametersRacesLocalAnswer() {
+ await pushPrefs(
+ ["media.peerconnection.allow_old_setParameters", true]);
+ // We do an initial negotiation, and while the local answer is pending,
+ // perform a setParameters on a not-yet-negotiated video sender. The intent
+ // here is to have the success task for sLD(answer) run while the
+ // setParameters is pending.
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+
+ const audioStream = await navigator.mediaDevices.getUserMedia({audio: true});
+ // We use this later on, but set it up now so we don't inadvertently
+ // crank the event loop more than we intend below.
+ const videoStream = await navigator.mediaDevices.getUserMedia({video: true});
+ pc2.addTrack(audioStream.getTracks()[0]);
+ await pc2.setLocalDescription();
+ await pc1.setRemoteDescription(pc2.localDescription);
+ const answer = await pc1.createAnswer();
+ const aTask = queueAWebrtcTask();
+ const sldPromise = pc1.setLocalDescription(answer);
+
+ // Right now, we have aTask queued. The success task for sLD is not queued
+ // yet, because Firefox performs the initial steps on the microtask queue,
+ // which we have not allowed to run yet. Awaiting aTask will first clear
+ // the microtask queue, then run the task queue until aTask is finished.
+ // That _should_ result in the success task for sLD(answer) being queued.
+ await aTask;
+
+ // The success task for sLD(answer) should be queued now. Don't relinquish
+ // the event loop!
+
+ // New sender that has nothing to do with the negotiation in progress.
+ const sender = pc1.addTrack(videoStream.getTracks()[0]);
+ const parameters = sender.getParameters();
+ parameters.encodings[0].rid = "foo";
+ parameters.encodings.push({rid: "bar"});
+
+ // We have not relinquished the event loop, so the sLD(answer) should still
+ // be queued. The task that updates [[SendEncodings]] (from setParameters)
+ // should be queued behind it. Let them both run.
+ await sender.setParameters(parameters);
+ await sldPromise;
+
+ const offer = await pc1.createOffer();
+
+ const {encodings} = sender.getParameters();
+ const rids = encodings.map(({rid}) => rid);
+ isDeeply(rids, ["foo", "bar"]);
+ ok(offer.sdp.includes("a=simulcast:send foo;bar"), "offer should be simulcast");
+ },
+
+ async function checkCompatModeSimulcastSetParametersRacesRemoteAnswer() {
+ await pushPrefs(
+ ["media.peerconnection.allow_old_setParameters", true]);
+ // We do an initial negotiation, and while the remote answer is pending,
+ // perform a setParameters on a not-yet-negotiated video sender. The intent
+ // here is to have the success task for sRD(answer) run while the
+ // setParameters is pending.
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+
+ const audioStream = await navigator.mediaDevices.getUserMedia({audio: true});
+ // We use this later on, but set it up now so we don't inadvertently
+ // crank the event loop more than we intend below.
+ const videoStream = await navigator.mediaDevices.getUserMedia({video: true});
+ pc1.addTrack(audioStream.getTracks()[0]);
+ await pc1.setLocalDescription();
+ await pc2.setRemoteDescription(pc1.localDescription);
+ await pc2.setLocalDescription();
+ const aTask = queueAWebrtcTask();
+ const srdPromise = pc1.setRemoteDescription(pc2.localDescription);
+
+ // Right now, we have aTask queued. The success task for sRD is not queued
+ // yet, because Firefox performs the initial steps on the microtask queue,
+ // which we have not allowed to run yet. Awaiting aTask will first clear
+ // the microtask queue, then run the task queue until aTask is finished.
+ // That _should_ result in the success task for sRD(answer) being queued.
+ await aTask;
+
+ // The success task for sRD(answer) should be queued now. Don't relinquish
+ // the event loop!
+
+ const sender = pc1.addTrack(videoStream.getTracks()[0]);
+ const parameters = sender.getParameters();
+ parameters.encodings[0].rid = "foo";
+ parameters.encodings.push({rid: "bar"});
+
+ // We have not relinquished the event loop, so the sRD(answer) should still
+ // be queued. The task that updates [[SendEncodings]] (from setParameters)
+ // should be queued behind it. Let them both run.
+ await sender.setParameters(parameters);
+ await srdPromise;
+
+ const offer = await pc1.createOffer();
+
+ const {encodings} = sender.getParameters();
+ const rids = encodings.map(({rid}) => rid);
+ isDeeply(rids, ["foo", "bar"]);
+ ok(offer.sdp.includes("a=simulcast:send foo;bar"), "offer should be simulcast");
+ },
+
+ async function checkCompatModeSimulcastRidlessSetParametersRacesLocalOffer() {
+ await pushPrefs(
+ ["media.peerconnection.allow_old_setParameters", true]);
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+ const stream = await navigator.mediaDevices.getUserMedia({video: true});
+ const sender = pc1.addTrack(stream.getTracks()[0]);
+ // unicast offer
+ const aTask = queueAWebrtcTask();
+ const sldPromise = pc1.setLocalDescription();
+
+ // Right now, we have aTask queued. The success task for sLD is not queued
+ // yet, because Firefox performs the initial steps on the microtask queue,
+ // which we have not allowed to run yet. Awaiting aTask will first clear
+ // the microtask queue, then run the task queue until aTask is finished.
+ // That _should_ result in the success task for sLD(offer) being queued.
+ await aTask;
+
+ // simulcast setParameters; the task to update [[SendEncodings]] should be
+ // queued after the success task for sLD(offer)
+ try {
+ await sender.setParameters({"encodings": [{}, {}]});
+ ok(false, "setParameters with two ridless encodings should fail");
+ } catch (e) {
+ ok(true, "setParameters with two ridless encodings should fail");
+ }
+ await sldPromise;
+
+ const {encodings} = sender.getParameters();
+ const rids = encodings.map(({rid}) => rid);
+ // Compat mode lets this slide, but won't try to negotiate it since we've
+ // already applied a unicast local offer.
+ isDeeply(rids, [undefined]);
+
+ // Let negotiation finish, so we can generate a new offer
+ await pc2.setRemoteDescription(pc1.localDescription);
+ await pc2.setLocalDescription();
+ await pc1.setRemoteDescription(pc2.localDescription);
+
+ const reoffer = await pc1.createOffer();
+ ok(!reoffer.sdp.includes("a=simulcast"), "reoffer should be unicast");
+ },
+
+];
+
+runNetworkTest(async () => {
+ await pushPrefs(
+ ["media.peerconnection.allow_old_setParameters", false]);
+ for (const test of tests) {
+ info(`Running test: ${test.name}`);
+ await test();
+ info(`Done running test: ${test.name}`);
+ }
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_maxFramerate.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_maxFramerate.html
new file mode 100644
index 0000000000..8047719775
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_maxFramerate.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ bug: "1611957",
+ title: "Live-updating maxFramerate"
+});
+
+let sender, receiver;
+
+async function checkMaxFrameRate(rate) {
+ const parameters = sender.getParameters();
+ parameters.encodings[0].maxFramerate = rate;
+ await sender.setParameters(parameters);
+ await wait(2000);
+ const stats = Array.from((await receiver.getStats()).values());
+ const inboundRtp = stats.find(stat => stat.type == "inbound-rtp");
+ info(`inbound-rtp stats: ${JSON.stringify(inboundRtp)}`);
+ const fps = inboundRtp.framesPerSecond;
+ ok(fps <= (rate * 1.1) + 1,
+ `fps is an appropriate value (${fps}) for rate (${rate})`);
+}
+
+runNetworkTest(async function (options) {
+ let test = new PeerConnectionTest(options);
+ test.setMediaConstraints([{video: true}], []);
+ test.chain.append([
+ function CHECK_PRECONDITIONS() {
+ is(test.pcLocal._pc.getSenders().length, 1,
+ "Should have 1 local sender");
+ is(test.pcRemote._pc.getReceivers().length, 1,
+ "Should have 1 remote receiver");
+
+ sender = test.pcLocal._pc.getSenders()[0];
+ receiver = test.pcRemote._pc.getReceivers()[0];
+ },
+ function PC_LOCAL_SET_MAX_FRAMERATE_2() {
+ return checkMaxFrameRate(2);
+ },
+ function PC_LOCAL_SET_MAX_FRAMERATE_4() {
+ return checkMaxFrameRate(4);
+ },
+ function PC_LOCAL_SET_MAX_FRAMERATE_15() {
+ return checkMaxFrameRate(15);
+ },
+ function PC_LOCAL_SET_MAX_FRAMERATE_8() {
+ return checkMaxFrameRate(8);
+ },
+ function PC_LOCAL_SET_MAX_FRAMERATE_1() {
+ return checkMaxFrameRate(1);
+ },
+ ]);
+ await test.run();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_maxFramerate_oldSetParameters.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_maxFramerate_oldSetParameters.html
new file mode 100644
index 0000000000..9c68a31c0a
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_maxFramerate_oldSetParameters.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ bug: "1611957",
+ title: "Live-updating maxFramerate"
+});
+
+let sender, receiver;
+
+async function checkMaxFrameRate(rate) {
+ sender.setParameters({ encodings: [{ maxFramerate: rate }] });
+ await wait(2000);
+ const stats = Array.from((await receiver.getStats()).values());
+ const inboundRtp = stats.find(stat => stat.type == "inbound-rtp");
+ info(`inbound-rtp stats: ${JSON.stringify(inboundRtp)}`);
+ const fps = inboundRtp.framesPerSecond;
+ ok(fps <= (rate * 1.1) + 1, `fps is an appropriate value (${fps}) for rate (${rate})`);
+}
+
+runNetworkTest(async function (options) {
+ let test = new PeerConnectionTest(options);
+ test.setMediaConstraints([{video: true}], []);
+ test.chain.append([
+ function CHECK_PRECONDITIONS() {
+ is(test.pcLocal._pc.getSenders().length, 1,
+ "Should have 1 local sender");
+ is(test.pcRemote._pc.getReceivers().length, 1,
+ "Should have 1 remote receiver");
+
+ sender = test.pcLocal._pc.getSenders()[0];
+ receiver = test.pcRemote._pc.getReceivers()[0];
+ },
+ function PC_LOCAL_SET_MAX_FRAMERATE_2() {
+ return checkMaxFrameRate(2);
+ },
+ function PC_LOCAL_SET_MAX_FRAMERATE_4() {
+ return checkMaxFrameRate(4);
+ },
+ function PC_LOCAL_SET_MAX_FRAMERATE_15() {
+ return checkMaxFrameRate(15);
+ },
+ function PC_LOCAL_SET_MAX_FRAMERATE_8() {
+ return checkMaxFrameRate(8);
+ },
+ function PC_LOCAL_SET_MAX_FRAMERATE_1() {
+ return checkMaxFrameRate(1);
+ },
+ ]);
+ await test.run();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_oldSetParameters.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_oldSetParameters.html
new file mode 100644
index 0000000000..2b55ec46e6
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_oldSetParameters.html
@@ -0,0 +1,86 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ bug: "1230184",
+ title: "Set parameters on sender",
+ visible: true
+});
+
+function parameterstest(pc) {
+ ok(pc.getSenders().length, "have senders");
+ var sender = pc.getSenders()[0];
+
+ var testParameters = (params, errorName, errorMsg) => {
+ info("Trying to set " + JSON.stringify(params));
+
+ var validateParameters = (a, b) => {
+ var validateEncoding = (a, b) => {
+ is(a.rid, b.rid, "same rid");
+ is(a.maxBitrate, b.maxBitrate, "same maxBitrate");
+ is(a.maxFramerate, b.maxFramerate, "same maxFramerate");
+ is(a.scaleResolutionDownBy, b.scaleResolutionDownBy,
+ "same scaleResolutionDownBy");
+ };
+ is(a.encodings.length, (b.encodings || []).length, "same encodings");
+ a.encodings.forEach((en, i) => validateEncoding(en, b.encodings[i]));
+ };
+
+ var before = JSON.stringify(sender.getParameters());
+ isnot(JSON.stringify(params), before, "starting condition");
+
+ var p = sender.setParameters(params)
+ .then(() => {
+ isnot(JSON.stringify(sender.getParameters()), before, "parameters changed");
+ validateParameters(sender.getParameters(), params);
+ is(null, errorName || null, "is success expected");
+ }, e => {
+ is(e.name, errorName, "correct error name");
+ is(e.message, errorMsg, "correct error message");
+ });
+ is(JSON.stringify(sender.getParameters()), before, "parameters not set yet");
+ return p;
+ };
+
+ return [
+ [{ encodings: [ { rid: "foo", maxBitrate: 40000, scaleResolutionDownBy: 2 },
+ { rid: "bar", maxBitrate: 10000, scaleResolutionDownBy: 4 }]
+ }],
+ [{ encodings: [{ maxBitrate: 10000, scaleResolutionDownBy: 4 }]}],
+ [{ encodings: [{ maxFramerate: 0.0, scaleResolutionDownBy: 1 }]}],
+ [{ encodings: [{ maxFramerate: 30.5, scaleResolutionDownBy: 1 }]}],
+ [{ encodings: [{ maxFramerate: -1, scaleResolutionDownBy: 1 }]}, "RangeError", "maxFramerate must be non-negative"],
+ [{ encodings: [{ maxBitrate: 40000 },
+ { rid: "bar", maxBitrate: 10000 }] }, "TypeError", "Missing rid"],
+ [{ encodings: [{ rid: "foo", maxBitrate: 40000 },
+ { rid: "bar", maxBitrate: 10000 },
+ { rid: "bar", maxBitrate: 20000 }] }, "TypeError", "Duplicate rid"],
+ [{}, "TypeError", `RTCRtpSender.setParameters: Missing required 'encodings' member of RTCRtpSendParameters.`]
+ ].reduce((p, args) => p.then(() => testParameters.apply(this, args)),
+ Promise.resolve());
+}
+
+runNetworkTest(() => {
+ const test = new PeerConnectionTest();
+ test.setMediaConstraints([{video: true}], [{video: true}]);
+ test.chain.removeAfter("PC_REMOTE_WAIT_FOR_MEDIA_FLOW");
+
+ // Test sender parameters.
+ test.chain.append([
+ function PC_LOCAL_SET_PARAMETERS(test) {
+ return parameterstest(test.pcLocal._pc);
+ }
+ ]);
+
+ return test.run();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_scaleResolutionDownBy.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_scaleResolutionDownBy.html
new file mode 100644
index 0000000000..d1275d6523
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_scaleResolutionDownBy.html
@@ -0,0 +1,98 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ bug: "1253499",
+ title: "Live-updating scaleResolutionDownBy"
+});
+
+async function checkForH264Support() {
+ const pc = new RTCPeerConnection();
+ const offer = await pc.createOffer({offerToReceiveVideo: true});
+ return offer.sdp.match(/a=rtpmap:[1-9][0-9]* H264/);
+}
+
+let sender, localElem, remoteElem;
+let originalWidth, originalHeight;
+let resolutionAlignment = 1;
+
+async function checkScaleDownBy(scale) {
+ const parameters = sender.getParameters();
+ parameters.encodings[0].scaleResolutionDownBy = scale;
+ await sender.setParameters(parameters);
+ await haveEvent(remoteElem, "resize", wait(5000, new Error("Timeout")));
+
+ // Find the expected resolution. Internally we floor the exact scaling, then
+ // shrink each dimension to the alignment requested by the encoder.
+ let expectedWidth =
+ originalWidth / scale - (originalWidth / scale % resolutionAlignment);
+ let expectedHeight =
+ originalHeight / scale - (originalHeight / scale % resolutionAlignment);
+
+ is(remoteElem.videoWidth, expectedWidth,
+ `Width should have scaled down by ${scale}`);
+ is(remoteElem.videoHeight, expectedHeight,
+ `Height should have scaled down by ${scale}`);
+}
+
+runNetworkTest(async function (options) {
+ await pushPrefs(['media.peerconnection.video.lock_scaling', true]);
+ // [TODO] re-enable HW decoder after bug 1526207 is fixed.
+ if (navigator.userAgent.includes("Android")) {
+ if (await checkForH264Support()) {
+ // Android only has h264 in hw, so now we know it will use vp8 in hw too.
+ resolutionAlignment = 16;
+ }
+ await pushPrefs(["media.navigator.mediadatadecoder_vpx_enabled", false],
+ ["media.webrtc.hw.h264.enabled", false]);
+ }
+
+ let test = new PeerConnectionTest(options);
+ test.setMediaConstraints([{video: true}], []);
+ test.chain.append([
+ function CHECK_PRECONDITIONS() {
+ is(test.pcLocal._pc.getSenders().length, 1,
+ "Should have 1 local sender");
+ is(test.pcLocal.localMediaElements.length, 1,
+ "Should have 1 local sending media element");
+ is(test.pcRemote.remoteMediaElements.length, 1,
+ "Should have 1 remote media element");
+
+ sender = test.pcLocal._pc.getSenders()[0];
+ localElem = test.pcLocal.localMediaElements[0];
+ remoteElem = test.pcRemote.remoteMediaElements[0];
+
+ remoteElem.addEventListener("resize", () =>
+ info(`Video resized to ${remoteElem.videoWidth}x${remoteElem.videoHeight}`));
+
+ originalWidth = localElem.videoWidth;
+ originalHeight = localElem.videoHeight;
+ info(`Original width is ${originalWidth}`);
+ },
+ function PC_LOCAL_SCALEDOWNBY_2() {
+ return checkScaleDownBy(2);
+ },
+ function PC_LOCAL_SCALEDOWNBY_4() {
+ return checkScaleDownBy(4);
+ },
+ function PC_LOCAL_SCALEDOWNBY_15() {
+ return checkScaleDownBy(15);
+ },
+ function PC_LOCAL_SCALEDOWNBY_8() {
+ return checkScaleDownBy(8);
+ },
+ function PC_LOCAL_SCALEDOWNBY_1() {
+ return checkScaleDownBy(1);
+ },
+ ]);
+ await test.run();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_scaleResolutionDownBy_oldSetParameters.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_scaleResolutionDownBy_oldSetParameters.html
new file mode 100644
index 0000000000..4d515bd5c1
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_setParameters_scaleResolutionDownBy_oldSetParameters.html
@@ -0,0 +1,96 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ bug: "1253499",
+ title: "Live-updating scaleResolutionDownBy"
+});
+
+async function checkForH264Support() {
+ const pc = new RTCPeerConnection();
+ const offer = await pc.createOffer({offerToReceiveVideo: true});
+ return offer.sdp.match(/a=rtpmap:[1-9][0-9]* H264/);
+}
+
+let sender, localElem, remoteElem;
+let originalWidth, originalHeight;
+let resolutionAlignment = 1;
+
+async function checkScaleDownBy(scale) {
+ sender.setParameters({ encodings: [{ scaleResolutionDownBy: scale }] });
+ await haveEvent(remoteElem, "resize", wait(5000, new Error("Timeout")));
+
+ // Find the expected resolution. Internally we floor the exact scaling, then
+ // shrink each dimension to the alignment requested by the encoder.
+ let expectedWidth =
+ originalWidth / scale - (originalWidth / scale % resolutionAlignment);
+ let expectedHeight =
+ originalHeight / scale - (originalHeight / scale % resolutionAlignment);
+
+ is(remoteElem.videoWidth, expectedWidth,
+ `Width should have scaled down by ${scale}`);
+ is(remoteElem.videoHeight, expectedHeight,
+ `Height should have scaled down by ${scale}`);
+}
+
+runNetworkTest(async function (options) {
+ await pushPrefs(['media.peerconnection.video.lock_scaling', true]);
+ // [TODO] re-enable HW decoder after bug 1526207 is fixed.
+ if (navigator.userAgent.includes("Android")) {
+ if (await checkForH264Support()) {
+ // Android only has h264 in hw, so now we know it will use vp8 in hw too.
+ resolutionAlignment = 16;
+ }
+ await pushPrefs(["media.navigator.mediadatadecoder_vpx_enabled", false],
+ ["media.webrtc.hw.h264.enabled", false]);
+ }
+
+ let test = new PeerConnectionTest(options);
+ test.setMediaConstraints([{video: true}], []);
+ test.chain.append([
+ function CHECK_PRECONDITIONS() {
+ is(test.pcLocal._pc.getSenders().length, 1,
+ "Should have 1 local sender");
+ is(test.pcLocal.localMediaElements.length, 1,
+ "Should have 1 local sending media element");
+ is(test.pcRemote.remoteMediaElements.length, 1,
+ "Should have 1 remote media element");
+
+ sender = test.pcLocal._pc.getSenders()[0];
+ localElem = test.pcLocal.localMediaElements[0];
+ remoteElem = test.pcRemote.remoteMediaElements[0];
+
+ remoteElem.addEventListener("resize", () =>
+ info(`Video resized to ${remoteElem.videoWidth}x${remoteElem.videoHeight}`));
+
+ originalWidth = localElem.videoWidth;
+ originalHeight = localElem.videoHeight;
+ info(`Original width is ${originalWidth}`);
+ },
+ function PC_LOCAL_SCALEDOWNBY_2() {
+ return checkScaleDownBy(2);
+ },
+ function PC_LOCAL_SCALEDOWNBY_4() {
+ return checkScaleDownBy(4);
+ },
+ function PC_LOCAL_SCALEDOWNBY_15() {
+ return checkScaleDownBy(15);
+ },
+ function PC_LOCAL_SCALEDOWNBY_8() {
+ return checkScaleDownBy(8);
+ },
+ function PC_LOCAL_SCALEDOWNBY_1() {
+ return checkScaleDownBy(1);
+ },
+ ]);
+ await test.run();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_setRemoteAnswerInHaveRemoteOffer.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_setRemoteAnswerInHaveRemoteOffer.html
new file mode 100644
index 0000000000..1912835160
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_setRemoteAnswerInHaveRemoteOffer.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "784519",
+ title: "setRemoteDescription (answer) in 'have-remote-offer'"
+ });
+
+runNetworkTest(function () {
+ const test = new PeerConnectionTest();
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ test.chain.removeAfter("PC_REMOTE_SET_REMOTE_DESCRIPTION");
+
+ test.chain.append([
+ function PC_REMOTE_SET_REMOTE_ANSWER(test) {
+ test.pcLocal._latest_offer.type = "answer";
+ test.pcRemote._pc.setRemoteDescription(test.pcLocal._latest_offer)
+ .then(generateErrorCallback('setRemoteDescription should fail'),
+ err =>
+ is(err.name, "InvalidStateError", "Error is InvalidStateError"));
+ }
+ ]);
+
+ return test.run();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_setRemoteAnswerInStable.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_setRemoteAnswerInStable.html
new file mode 100644
index 0000000000..6208fdea3e
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_setRemoteAnswerInStable.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "784519",
+ title: "setRemoteDescription (answer) in 'stable'"
+ });
+
+runNetworkTest(function () {
+ const test = new PeerConnectionTest();
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ test.chain.removeAfter("PC_LOCAL_CREATE_OFFER");
+
+ test.chain.append([
+ function PC_LOCAL_SET_REMOTE_ANSWER(test) {
+ test.pcLocal._latest_offer.type = "answer";
+ test.pcLocal._pc.setRemoteDescription(test.pcLocal._latest_offer)
+ .then(generateErrorCallback('setRemoteDescription should fail'),
+ err =>
+ is(err.name, "InvalidStateError", "Error is InvalidStateError"));
+ }
+ ]);
+
+ return test.run();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_setRemoteOfferInHaveLocalOffer.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_setRemoteOfferInHaveLocalOffer.html
new file mode 100644
index 0000000000..20236f442c
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_setRemoteOfferInHaveLocalOffer.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "784519",
+ title: "setRemoteDescription (offer) in 'have-local-offer'"
+ });
+
+runNetworkTest(function () {
+ const test = new PeerConnectionTest();
+ test.setMediaConstraints([{audio: true}], [{audio: true}]);
+ test.chain.removeAfter("PC_LOCAL_SET_LOCAL_DESCRIPTION");
+
+ test.chain.append([
+ async function PC_LOCAL_SET_REMOTE_OFFER(test) {
+ const p = test.pcLocal._pc.setRemoteDescription(test.pcLocal._latest_offer);
+ await new Promise(r => test.pcLocal.onsignalingstatechange = r);
+ is(test.pcLocal._pc.signalingState, 'stable', 'should fire stable');
+ await new Promise(r => test.pcLocal.onsignalingstatechange = r);
+ is(test.pcLocal._pc.signalingState, 'have-remote-offer',
+ 'should fire have-remote-offer');
+ await p;
+ ok(true, 'setRemoteDescription should succeed');
+ }
+ ]);
+
+ return test.run();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastAnswer.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastAnswer.html
new file mode 100644
index 0000000000..ba75c72022
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastAnswer.html
@@ -0,0 +1,121 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
+ <script type="application/javascript" src="helpers_from_wpt/sdp.js"></script>
+ <script type="application/javascript" src="simulcast.js"></script>
+ <script type="application/javascript" src="stats.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1231507",
+ title: "Basic video-only peer connection with Simulcast answer",
+ visible: true
+ });
+
+ runNetworkTest(async () => {
+ await pushPrefs(
+ // 180Kbps was determined empirically, set well-higher than
+ // the 80Kbps+overhead needed for the two simulcast streams.
+ // 100Kbps was apparently too low.
+ ['media.peerconnection.video.min_bitrate_estimate', 180*1000]);
+
+
+ const offerer = new RTCPeerConnection();
+ const answerer = new RTCPeerConnection();
+
+ const add = (pc, can, failed) => can && pc.addIceCandidate(can).catch(failed);
+ offerer.onicecandidate = e => add(answerer, e.candidate, generateErrorCallback());
+ answerer.onicecandidate = e => add(offerer, e.candidate, generateErrorCallback());
+
+ const metadataToBeLoaded = [];
+ offerer.ontrack = (e) => {
+ metadataToBeLoaded.push(getPlaybackWithLoadedMetadata(e.track));
+ };
+
+ // Two recv transceivers, one for each simulcast stream
+ offerer.addTransceiver('video', { direction: 'recvonly' });
+ offerer.addTransceiver('video', { direction: 'recvonly' });
+
+ const offer = await offerer.createOffer();
+
+ const mungedOffer = midToRid(offer);
+ info(`Transformed recv offer to simulcast: ${offer.sdp} to ${mungedOffer}`);
+
+ await answerer.setRemoteDescription({type: 'offer', sdp: mungedOffer});
+
+ // One send transceiver, that will be used to send both simulcast streams
+ const emitter = new VideoFrameEmitter();
+ const videoStream = emitter.stream();
+ const sender = answerer.addTrack(videoStream.getVideoTracks()[0], videoStream);
+ let parameters = sender.getParameters();
+ is(parameters.encodings.length, 2);
+ is(answerer.getSenders().length, 1);
+ emitter.start();
+
+ await offerer.setLocalDescription(offer);
+
+ const rids = offerer.getTransceivers().map(t => t.mid);
+ is(rids.length, 2, 'Should have 2 mids in offer');
+ ok(rids[0] != '', 'First mid should be non-empty');
+ ok(rids[1] != '', 'Second mid should be non-empty');
+ info(`rids: ${JSON.stringify(rids)}`);
+
+ parameters = sender.getParameters();
+ info(`parameters: ${JSON.stringify(parameters)}`);
+ const observedRids = parameters.encodings.map(({rid}) => rid);
+ isDeeply(observedRids, rids);
+ parameters.encodings[0].maxBitrate = 40000;
+ parameters.encodings[0].scaleResolutionDownBy = 1;
+ parameters.encodings[1].maxBitrate = 40000;
+ parameters.encodings[1].scaleResolutionDownBy = 2;
+ await sender.setParameters(parameters);
+
+ const answer = await answerer.createAnswer();
+
+ const mungedAnswer = ridToMid(answer);
+ info(`Transformed send simulcast answer to multiple m-sections: ${answer.sdp} to ${mungedAnswer}`);
+ await offerer.setRemoteDescription({type: 'answer', sdp: mungedAnswer});
+ await answerer.setLocalDescription(answer);
+
+ is(metadataToBeLoaded.length, 2, 'Offerer should have gotten 2 ontrack events');
+ info('Waiting for 2 loadedmetadata events');
+ const videoElems = await Promise.all(metadataToBeLoaded);
+
+ const statsReady =
+ Promise.all([waitForSyncedRtcp(offerer), waitForSyncedRtcp(answerer)]);
+
+ const helper = new VideoStreamHelper();
+ info('Waiting for first video element to start playing');
+ await helper.checkVideoPlaying(videoElems[0]);
+ info('Waiting for second video element to start playing');
+ await helper.checkVideoPlaying(videoElems[1]);
+
+ is(videoElems[0].videoWidth, 50,
+ "sink is same width as source, modulo our cropping algorithm");
+ is(videoElems[0].videoHeight, 50,
+ "sink is same height as source, modulo our cropping algorithm");
+ is(videoElems[1].videoWidth, 25,
+ "sink is 1/2 width of source, modulo our cropping algorithm");
+ is(videoElems[1].videoHeight, 25,
+ "sink is 1/2 height of source, modulo our cropping algorithm");
+
+ await statsReady;
+ info("Stats ready");
+ const senderStats = await sender.getStats();
+ checkSenderStats(senderStats, 2);
+ checkExpectedFields(senderStats);
+ pedanticChecks(senderStats);
+
+ emitter.stop();
+ videoStream.getVideoTracks()[0].stop();
+ offerer.close();
+ answerer.close();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastAnswer_lowResFirst.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastAnswer_lowResFirst.html
new file mode 100644
index 0000000000..00c6e4ad3a
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastAnswer_lowResFirst.html
@@ -0,0 +1,113 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
+ <script type="application/javascript" src="helpers_from_wpt/sdp.js"></script>
+ <script type="application/javascript" src="simulcast.js"></script>
+ <script type="application/javascript" src="stats.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1231507",
+ title: "Basic video-only peer connection with Simulcast answer, first rid has lowest resolution",
+ visible: true
+ });
+
+ runNetworkTest(async () => {
+ await pushPrefs(
+ // 180Kbps was determined empirically, set well-higher than
+ // the 80Kbps+overhead needed for the two simulcast streams.
+ // 100Kbps was apparently too low.
+ ['media.peerconnection.video.min_bitrate_estimate', 180*1000]);
+
+
+ const offerer = new RTCPeerConnection();
+ const answerer = new RTCPeerConnection();
+
+ const add = (pc, can, failed) => can && pc.addIceCandidate(can).catch(failed);
+ offerer.onicecandidate = e => add(answerer, e.candidate, generateErrorCallback());
+ answerer.onicecandidate = e => add(offerer, e.candidate, generateErrorCallback());
+
+ const metadataToBeLoaded = [];
+ offerer.ontrack = (e) => {
+ metadataToBeLoaded.push(getPlaybackWithLoadedMetadata(e.track));
+ };
+
+ // Two recv transceivers, one for each simulcast stream
+ offerer.addTransceiver('video', { direction: 'recvonly' });
+ offerer.addTransceiver('video', { direction: 'recvonly' });
+
+ // One send transceiver, that will be used to send both simulcast streams
+ const emitter = new VideoFrameEmitter();
+ const videoStream = emitter.stream();
+ answerer.addTrack(videoStream.getVideoTracks()[0], videoStream);
+ emitter.start();
+
+ const offer = await offerer.createOffer();
+
+ const mungedOffer = midToRid(offer);
+ info(`Transformed recv offer to simulcast: ${offer.sdp} to ${mungedOffer}`);
+
+ await answerer.setRemoteDescription({type: 'offer', sdp: mungedOffer});
+ await offerer.setLocalDescription(offer);
+
+ const rids = offerer.getTransceivers().map(t => t.mid);
+ is(rids.length, 2, 'Should have 2 mids in offer');
+ ok(rids[0] != '', 'First mid should be non-empty');
+ ok(rids[1] != '', 'Second mid should be non-empty');
+ info(`rids: ${JSON.stringify(rids)}`);
+
+ const sender = answerer.getSenders()[0];
+ const parameters = sender.getParameters();
+ parameters.encodings[0].maxBitrate = 40000;
+ parameters.encodings[0].scaleResolutionDownBy = 2;
+ parameters.encodings[1].maxBitrate = 40000;
+ await sender.setParameters(parameters);
+
+ const answer = await answerer.createAnswer();
+
+ const mungedAnswer = ridToMid(answer);
+ info(`Transformed send simulcast answer to multiple m-sections: ${answer.sdp} to ${mungedAnswer}`);
+ await offerer.setRemoteDescription({type: 'answer', sdp: mungedAnswer});
+ await answerer.setLocalDescription(answer);
+
+ is(metadataToBeLoaded.length, 2, 'Offerer should have gotten 2 ontrack events');
+ info('Waiting for 2 loadedmetadata events');
+ const videoElems = await Promise.all(metadataToBeLoaded);
+
+ const statsReady =
+ Promise.all([waitForSyncedRtcp(offerer), waitForSyncedRtcp(answerer)]);
+
+ const helper = new VideoStreamHelper();
+ info('Waiting for first video element to start playing');
+ await helper.checkVideoPlaying(videoElems[0]);
+ info('Waiting for second video element to start playing');
+ await helper.checkVideoPlaying(videoElems[1]);
+
+ is(videoElems[1].videoWidth, 50,
+ "sink is same width as source, modulo our cropping algorithm");
+ is(videoElems[1].videoHeight, 50,
+ "sink is same height as source, modulo our cropping algorithm");
+ is(videoElems[0].videoWidth, 25,
+ "sink is 1/2 width of source, modulo our cropping algorithm");
+ is(videoElems[0].videoHeight, 25,
+ "sink is 1/2 height of source, modulo our cropping algorithm");
+
+ await statsReady;
+ const senderStats = await sender.getStats();
+ checkSenderStats(senderStats, 2);
+ checkExpectedFields(senderStats);
+ pedanticChecks(senderStats);
+
+ emitter.stop();
+ videoStream.getVideoTracks()[0].stop();
+ offerer.close();
+ answerer.close();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastAnswer_lowResFirst_oldSetParameters.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastAnswer_lowResFirst_oldSetParameters.html
new file mode 100644
index 0000000000..c2aafc4575
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastAnswer_lowResFirst_oldSetParameters.html
@@ -0,0 +1,115 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
+ <script type="application/javascript" src="helpers_from_wpt/sdp.js"></script>
+ <script type="application/javascript" src="simulcast.js"></script>
+ <script type="application/javascript" src="stats.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1231507",
+ title: "Basic video-only peer connection with Simulcast answer, first rid has lowest resolution",
+ visible: true
+ });
+
+ runNetworkTest(async () => {
+ await pushPrefs(
+ ['media.peerconnection.simulcast', true],
+ // 180Kbps was determined empirically, set well-higher than
+ // the 80Kbps+overhead needed for the two simulcast streams.
+ // 100Kbps was apparently too low.
+ ['media.peerconnection.video.min_bitrate_estimate', 180*1000]);
+
+
+ const offerer = new RTCPeerConnection();
+ const answerer = new RTCPeerConnection();
+
+ const add = (pc, can, failed) => can && pc.addIceCandidate(can).catch(failed);
+ offerer.onicecandidate = e => add(answerer, e.candidate, generateErrorCallback());
+ answerer.onicecandidate = e => add(offerer, e.candidate, generateErrorCallback());
+
+ const metadataToBeLoaded = [];
+ offerer.ontrack = (e) => {
+ metadataToBeLoaded.push(getPlaybackWithLoadedMetadata(e.track));
+ };
+
+ // Two recv transceivers, one for each simulcast stream
+ offerer.addTransceiver('video', { direction: 'recvonly' });
+ offerer.addTransceiver('video', { direction: 'recvonly' });
+
+ // One send transceiver, that will be used to send both simulcast streams
+ const emitter = new VideoFrameEmitter();
+ const videoStream = emitter.stream();
+ answerer.addTrack(videoStream.getVideoTracks()[0], videoStream);
+ emitter.start();
+
+ const offer = await offerer.createOffer();
+
+ const mungedOffer = midToRid(offer);
+ info(`Transformed recv offer to simulcast: ${offer.sdp} to ${mungedOffer}`);
+
+ await answerer.setRemoteDescription({type: "offer", sdp: mungedOffer});
+ await offerer.setLocalDescription(offer);
+
+ const rids = offerer.getTransceivers().map(t => t.mid);
+ is(rids.length, 2, 'Should have 2 mids in offer');
+ ok(rids[0] != '', 'First mid should be non-empty');
+ ok(rids[1] != '', 'Second mid should be non-empty');
+ info(`rids: ${JSON.stringify(rids)}`);
+
+ const sender = answerer.getSenders()[0];
+ sender.setParameters({
+ encodings: [
+ { rid: rids[0], maxBitrate: 40000, scaleResolutionDownBy: 2 },
+ { rid: rids[1], maxBitrate: 40000 }
+ ]
+ });
+
+ const answer = await answerer.createAnswer();
+
+ const mungedAnswer = ridToMid(answer);
+ info(`Transformed send simulcast answer to multiple m-sections: ${answer.sdp} to ${mungedAnswer}`);
+ await offerer.setRemoteDescription({type: "answer", sdp: mungedAnswer});
+ await answerer.setLocalDescription(answer);
+
+ is(metadataToBeLoaded.length, 2, 'Offerer should have gotten 2 ontrack events');
+ info('Waiting for 2 loadedmetadata events');
+ const videoElems = await Promise.all(metadataToBeLoaded);
+
+ const statsReady =
+ Promise.all([waitForSyncedRtcp(offerer), waitForSyncedRtcp(answerer)]);
+
+ const helper = new VideoStreamHelper();
+ info('Waiting for first video element to start playing');
+ await helper.checkVideoPlaying(videoElems[0]);
+ info('Waiting for second video element to start playing');
+ await helper.checkVideoPlaying(videoElems[1]);
+
+ is(videoElems[1].videoWidth, 50,
+ "sink is same width as source, modulo our cropping algorithm");
+ is(videoElems[1].videoHeight, 50,
+ "sink is same height as source, modulo our cropping algorithm");
+ is(videoElems[0].videoWidth, 25,
+ "sink is 1/2 width of source, modulo our cropping algorithm");
+ is(videoElems[0].videoHeight, 25,
+ "sink is 1/2 height of source, modulo our cropping algorithm");
+
+ await statsReady;
+ const senderStats = await sender.getStats();
+ checkSenderStats(senderStats, 2);
+ checkExpectedFields(senderStats);
+ pedanticChecks(senderStats);
+
+ emitter.stop();
+ videoStream.getVideoTracks()[0].stop();
+ offerer.close();
+ answerer.close();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastAnswer_oldSetParameters.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastAnswer_oldSetParameters.html
new file mode 100644
index 0000000000..bc0b9f71cc
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastAnswer_oldSetParameters.html
@@ -0,0 +1,115 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
+ <script type="application/javascript" src="helpers_from_wpt/sdp.js"></script>
+ <script type="application/javascript" src="simulcast.js"></script>
+ <script type="application/javascript" src="stats.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1231507",
+ title: "Basic video-only peer connection with Simulcast answer",
+ visible: true
+ });
+
+ runNetworkTest(async () => {
+ await pushPrefs(
+ ['media.peerconnection.simulcast', true],
+ // 180Kbps was determined empirically, set well-higher than
+ // the 80Kbps+overhead needed for the two simulcast streams.
+ // 100Kbps was apparently too low.
+ ['media.peerconnection.video.min_bitrate_estimate', 180*1000]);
+
+
+ const offerer = new RTCPeerConnection();
+ const answerer = new RTCPeerConnection();
+
+ const add = (pc, can, failed) => can && pc.addIceCandidate(can).catch(failed);
+ offerer.onicecandidate = e => add(answerer, e.candidate, generateErrorCallback());
+ answerer.onicecandidate = e => add(offerer, e.candidate, generateErrorCallback());
+
+ const metadataToBeLoaded = [];
+ offerer.ontrack = (e) => {
+ metadataToBeLoaded.push(getPlaybackWithLoadedMetadata(e.track));
+ };
+
+ // Two recv transceivers, one for each simulcast stream
+ offerer.addTransceiver('video', { direction: 'recvonly' });
+ offerer.addTransceiver('video', { direction: 'recvonly' });
+
+ // One send transceiver, that will be used to send both simulcast streams
+ const emitter = new VideoFrameEmitter();
+ const videoStream = emitter.stream();
+ answerer.addTrack(videoStream.getVideoTracks()[0], videoStream);
+ emitter.start();
+
+ const offer = await offerer.createOffer();
+
+ const mungedOffer = midToRid(offer);
+ info(`Transformed recv offer to simulcast: ${offer.sdp} to ${mungedOffer}`);
+
+ await answerer.setRemoteDescription({type: "offer", sdp: mungedOffer});
+ await offerer.setLocalDescription(offer);
+
+ const rids = offerer.getTransceivers().map(t => t.mid);
+ is(rids.length, 2, 'Should have 2 mids in offer');
+ ok(rids[0] != '', 'First mid should be non-empty');
+ ok(rids[1] != '', 'Second mid should be non-empty');
+ info(`rids: ${JSON.stringify(rids)}`);
+
+ const sender = answerer.getSenders()[0];
+ await sender.setParameters({
+ encodings: [
+ { rid: rids[0], maxBitrate: 40000 },
+ { rid: rids[1], maxBitrate: 40000, scaleResolutionDownBy: 2 }
+ ]
+ });
+
+ const answer = await answerer.createAnswer();
+
+ const mungedAnswer = ridToMid(answer);
+ info(`Transformed send simulcast answer to multiple m-sections: ${answer.sdp} to ${mungedAnswer}`);
+ await offerer.setRemoteDescription({type: "answer", sdp: mungedAnswer});
+ await answerer.setLocalDescription(answer);
+
+ is(metadataToBeLoaded.length, 2, 'Offerer should have gotten 2 ontrack events');
+ info('Waiting for 2 loadedmetadata events');
+ const videoElems = await Promise.all(metadataToBeLoaded);
+
+ const statsReady =
+ Promise.all([waitForSyncedRtcp(offerer), waitForSyncedRtcp(answerer)]);
+
+ const helper = new VideoStreamHelper();
+ info('Waiting for first video element to start playing');
+ await helper.checkVideoPlaying(videoElems[0]);
+ info('Waiting for second video element to start playing');
+ await helper.checkVideoPlaying(videoElems[1]);
+
+ is(videoElems[0].videoWidth, 50,
+ "sink is same width as source, modulo our cropping algorithm");
+ is(videoElems[0].videoHeight, 50,
+ "sink is same height as source, modulo our cropping algorithm");
+ is(videoElems[1].videoWidth, 25,
+ "sink is 1/2 width of source, modulo our cropping algorithm");
+ is(videoElems[1].videoHeight, 25,
+ "sink is 1/2 height of source, modulo our cropping algorithm");
+
+ await statsReady;
+ const senderStats = await sender.getStats();
+ checkSenderStats(senderStats, 2);
+ checkExpectedFields(senderStats);
+ pedanticChecks(senderStats);
+
+ emitter.stop();
+ videoStream.getVideoTracks()[0].stop();
+ offerer.close();
+ answerer.close();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOddResolution.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOddResolution.html
new file mode 100644
index 0000000000..c380b34f1a
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOddResolution.html
@@ -0,0 +1,183 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
+ <script type="application/javascript" src="helpers_from_wpt/sdp.js"></script>
+ <script type="application/javascript" src="simulcast.js"></script>
+ <script type="application/javascript" src="stats.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1432793",
+ title: "Simulcast with odd resolution",
+ visible: true
+ });
+
+ runNetworkTest(async () => {
+ const helper = new VideoStreamHelper();
+ const emitter = new VideoFrameEmitter(helper.green, helper.red, 705, 528);
+
+ async function checkVideoElement(senderElement, receiverElement, encoding) {
+ info(`Waiting for receiver video element ${encoding.rid} to start playing`);
+ await helper.checkVideoPlaying(receiverElement);
+ const srcWidth = senderElement.videoWidth;
+ const srcHeight = senderElement.videoHeight;
+ info(`Source resolution is ${srcWidth}x${srcHeight}`);
+
+ const scaleDownBy = encoding.scaleResolutionDownBy;
+ const expectedWidth = srcWidth / scaleDownBy;
+ const expectedHeight = srcHeight / scaleDownBy;
+ const margin = srcWidth * 0.1;
+ const width = receiverElement.videoWidth;
+ const height = receiverElement.videoHeight;
+ const rid = encoding.rid;
+ ok(width >= expectedWidth - margin && width <= expectedWidth + margin,
+ `Width ${width} should be within 10% of ${expectedWidth} for rid '${rid}'`);
+ ok(height >= expectedHeight - margin && height <= expectedHeight + margin,
+ `Height ${height} should be within 10% of ${expectedHeight} for rid '${rid}'`);
+ }
+
+ async function checkVideoElements(senderElement, receiverElements, encodings) {
+ is(receiverElements.length, encodings.length, 'Number of video elements should match number of encodings');
+ info('Waiting for sender video element to start playing');
+ await helper.checkVideoPlaying(senderElement);
+ for (let i = 0; i < encodings.length; i++) {
+ await checkVideoElement(senderElement, receiverElements[i], encodings[i]);
+ }
+ }
+
+ const sendEncodings = [{ rid: "0", maxBitrate: 40000, scaleResolutionDownBy: 1.9 },
+ { rid: "1", maxBitrate: 40000, scaleResolutionDownBy: 3.5 },
+ { rid: "2", maxBitrate: 40000, scaleResolutionDownBy: 17.8 }];
+
+ async function checkSenderStats(sender) {
+ const senderStats = await sender.getStats();
+ checkSenderStats(senderStats, sendEncodings.length);
+ checkExpectedFields(senderStats);
+ pedanticChecks(senderStats);
+ }
+
+ async function waitForResizeEvents(elements) {
+ return Promise.all(elements.map(elem => haveEvent(elem, 'resize')));
+ }
+
+ await pushPrefs(
+ // 180Kbps was determined empirically, set well-higher than
+ // the 80Kbps+overhead needed for the two simulcast streams.
+ // 100Kbps was apparently too low.
+ ['media.peerconnection.video.min_bitrate_estimate', 180*1000]);
+
+
+ const offerer = new RTCPeerConnection();
+ const answerer = new RTCPeerConnection();
+
+ const add = (pc, can, failed) => can && pc.addIceCandidate(can).catch(failed);
+ offerer.onicecandidate = e => add(answerer, e.candidate, generateErrorCallback());
+ answerer.onicecandidate = e => add(offerer, e.candidate, generateErrorCallback());
+
+ const metadataToBeLoaded = [];
+ answerer.ontrack = (e) => {
+ metadataToBeLoaded.push(getPlaybackWithLoadedMetadata(e.track));
+ };
+
+ // One send transceiver, that will be used to send both simulcast streams
+ const videoStream = emitter.stream();
+ offerer.addTransceiver(videoStream.getVideoTracks()[0], {sendEncodings});
+ const senderElement = document.createElement('video');
+ senderElement.autoplay = true;
+ senderElement.srcObject = videoStream;
+ senderElement.id = videoStream.id
+
+ const sender = offerer.getSenders()[0];
+ let parameters = sender.getParameters();
+ is(parameters.encodings[0].maxBitrate, sendEncodings[0].maxBitrate);
+ isfuzzy(parameters.encodings[0].scaleResolutionDownBy,
+ sendEncodings[0].scaleResolutionDownBy, 0.01);
+ is(parameters.encodings[1].maxBitrate, sendEncodings[1].maxBitrate);
+ isfuzzy(parameters.encodings[1].scaleResolutionDownBy,
+ sendEncodings[1].scaleResolutionDownBy, 0.01);
+ is(parameters.encodings[2].maxBitrate, sendEncodings[2].maxBitrate);
+ isfuzzy(parameters.encodings[2].scaleResolutionDownBy,
+ sendEncodings[2].scaleResolutionDownBy, 0.01);
+
+ const offer = await offerer.createOffer();
+
+ const mungedOffer = ridToMid(offer);
+ info(`Transformed send simulcast offer to multiple m-sections: ${offer.sdp} to ${mungedOffer}`);
+
+ await answerer.setRemoteDescription({type: 'offer', sdp: mungedOffer});
+ await offerer.setLocalDescription(offer);
+
+ const rids = answerer.getTransceivers().map(t => t.mid);
+ is(rids.length, 3, 'Should have 3 mids in offer');
+ ok(rids[0], 'First mid should be non-empty');
+ ok(rids[1], 'Second mid should be non-empty');
+ ok(rids[2], 'Third mid should be non-empty');
+ info(`rids: ${JSON.stringify(rids)}`);
+
+ const answer = await answerer.createAnswer();
+
+ const mungedAnswer = midToRid(answer);
+ info(`Transformed recv answer to simulcast: ${answer.sdp} to ${mungedAnswer}`);
+ await offerer.setRemoteDescription({type: 'answer', sdp: mungedAnswer});
+ await answerer.setLocalDescription(answer);
+
+ is(metadataToBeLoaded.length, 3, 'Offerer should have gotten 3 ontrack events');
+ emitter.start();
+ info('Waiting for 3 loadedmetadata events');
+ const videoElems = await Promise.all(metadataToBeLoaded);
+ await checkVideoElements(senderElement, videoElems, parameters.encodings);
+ emitter.stop();
+
+ await Promise.all([waitForSyncedRtcp(offerer), waitForSyncedRtcp(answerer)]);
+
+ info(`Changing source resolution to 1280x720`);
+ emitter.size(1280, 720);
+ emitter.start();
+ await waitForResizeEvents([senderElement, ...videoElems]);
+ await checkVideoElements(senderElement, videoElems, parameters.encodings);
+ await checkSenderStats(sender);
+
+ parameters = sender.getParameters();
+ parameters.encodings[0].scaleResolutionDownBy = 1;
+ parameters.encodings[1].scaleResolutionDownBy = 2;
+ parameters.encodings[2].scaleResolutionDownBy = 3;
+ info(`Changing encodings to ${JSON.stringify(parameters.encodings)}`);
+ await sender.setParameters(parameters);
+ await waitForResizeEvents(videoElems);
+ await checkVideoElements(senderElement, videoElems, parameters.encodings);
+ await checkSenderStats(sender);
+
+ parameters = sender.getParameters();
+ parameters.encodings[0].scaleResolutionDownBy = 6;
+ parameters.encodings[1].scaleResolutionDownBy = 5;
+ parameters.encodings[2].scaleResolutionDownBy = 4;
+ info(`Changing encodings to ${JSON.stringify(parameters.encodings)}`);
+ await sender.setParameters(parameters);
+ await waitForResizeEvents(videoElems);
+ await checkVideoElements(senderElement, videoElems, parameters.encodings);
+ await checkSenderStats(sender);
+
+ parameters = sender.getParameters();
+ parameters.encodings[0].scaleResolutionDownBy = 4;
+ parameters.encodings[1].scaleResolutionDownBy = 1;
+ parameters.encodings[2].scaleResolutionDownBy = 2;
+ info(`Changing encodings to ${JSON.stringify(parameters.encodings)}`);
+ await sender.setParameters(parameters);
+ await waitForResizeEvents(videoElems);
+ await checkVideoElements(senderElement, videoElems, parameters.encodings);
+ await checkSenderStats(sender);
+
+ emitter.stop();
+ videoStream.getVideoTracks()[0].stop();
+ offerer.close();
+ answerer.close();
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOddResolution_oldSetParameters.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOddResolution_oldSetParameters.html
new file mode 100644
index 0000000000..0f6d3c8520
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOddResolution_oldSetParameters.html
@@ -0,0 +1,172 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
+ <script type="application/javascript" src="helpers_from_wpt/sdp.js"></script>
+ <script type="application/javascript" src="simulcast.js"></script>
+ <script type="application/javascript" src="stats.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1432793",
+ title: "Simulcast with odd resolution",
+ visible: true
+ });
+
+ runNetworkTest(async () => {
+ const helper = new VideoStreamHelper();
+ const emitter = new VideoFrameEmitter(helper.green, helper.red, 705, 528);
+
+ async function checkVideoElement(senderElement, receiverElement, encoding) {
+ info(`Waiting for receiver video element ${encoding.rid} to start playing`);
+ await helper.checkVideoPlaying(receiverElement);
+ const srcWidth = senderElement.videoWidth;
+ const srcHeight = senderElement.videoHeight;
+ info(`Source resolution is ${srcWidth}x${srcHeight}`);
+
+ const scaleDownBy = encoding.scaleResolutionDownBy;
+ const expectedWidth = srcWidth / scaleDownBy;
+ const expectedHeight = srcHeight / scaleDownBy;
+ const margin = srcWidth * 0.1;
+ const width = receiverElement.videoWidth;
+ const height = receiverElement.videoHeight;
+ const rid = encoding.rid;
+ ok(width >= expectedWidth - margin && width <= expectedWidth + margin,
+ `Width ${width} should be within 10% of ${expectedWidth} for rid '${rid}'`);
+ ok(height >= expectedHeight - margin && height <= expectedHeight + margin,
+ `Height ${height} should be within 10% of ${expectedHeight} for rid '${rid}'`);
+ }
+
+ async function checkVideoElements(senderElement, receiverElements, encodings) {
+ is(receiverElements.length, encodings.length, 'Number of video elements should match number of encodings');
+ info('Waiting for sender video element to start playing');
+ await helper.checkVideoPlaying(senderElement);
+ for (let i = 0; i < encodings.length; i++) {
+ await checkVideoElement(senderElement, receiverElements[i], encodings[i]);
+ }
+ }
+
+ async function checkSenderStats(sender) {
+ const senderStats = await sender.getStats();
+ checkSenderStats(senderStats, encodings.length);
+ checkExpectedFields(senderStats);
+ pedanticChecks(senderStats);
+ }
+
+ async function waitForResizeEvents(elements) {
+ return Promise.all(elements.map(elem => haveEvent(elem, 'resize')));
+ }
+
+ const encodings = [{ rid: "0", maxBitrate: 40000, scaleResolutionDownBy: 1.9 },
+ { rid: "1", maxBitrate: 40000, scaleResolutionDownBy: 3.5 },
+ { rid: "2", maxBitrate: 40000, scaleResolutionDownBy: 17.8 }];
+
+ await pushPrefs(
+ ['media.peerconnection.simulcast', true],
+ // 180Kbps was determined empirically, set well-higher than
+ // the 80Kbps+overhead needed for the two simulcast streams.
+ // 100Kbps was apparently too low.
+ ['media.peerconnection.video.min_bitrate_estimate', 180*1000]);
+
+
+ const offerer = new RTCPeerConnection();
+ const answerer = new RTCPeerConnection();
+
+ const add = (pc, can, failed) => can && pc.addIceCandidate(can).catch(failed);
+ offerer.onicecandidate = e => add(answerer, e.candidate, generateErrorCallback());
+ answerer.onicecandidate = e => add(offerer, e.candidate, generateErrorCallback());
+
+ const metadataToBeLoaded = [];
+ answerer.ontrack = (e) => {
+ metadataToBeLoaded.push(getPlaybackWithLoadedMetadata(e.track));
+ };
+
+ // One send transceiver, that will be used to send both simulcast streams
+ const videoStream = emitter.stream();
+ offerer.addTrack(videoStream.getVideoTracks()[0], videoStream);
+ const senderElement = document.createElement('video');
+ senderElement.autoplay = true;
+ senderElement.srcObject = videoStream;
+ senderElement.id = videoStream.id
+
+ const sender = offerer.getSenders()[0];
+ sender.setParameters({encodings});
+
+ const offer = await offerer.createOffer();
+
+ const mungedOffer = ridToMid(offer);
+ info(`Transformed send simulcast offer to multiple m-sections: ${offer.sdp} to ${mungedOffer}`);
+
+ await answerer.setRemoteDescription({type: "offer", sdp: mungedOffer});
+ await offerer.setLocalDescription(offer);
+
+ const rids = answerer.getTransceivers().map(t => t.mid);
+ is(rids.length, 3, 'Should have 3 mids in offer');
+ ok(rids[0], 'First mid should be non-empty');
+ ok(rids[1], 'Second mid should be non-empty');
+ ok(rids[2], 'Third mid should be non-empty');
+ info(`rids: ${JSON.stringify(rids)}`);
+
+ const answer = await answerer.createAnswer();
+
+ const mungedAnswer = midToRid(answer);
+ info(`Transformed recv answer to simulcast: ${answer.sdp} to ${mungedAnswer}`);
+ await offerer.setRemoteDescription({type: "answer", sdp: mungedAnswer});
+ await answerer.setLocalDescription(answer);
+
+ is(metadataToBeLoaded.length, 3, 'Offerer should have gotten 3 ontrack events');
+ emitter.start();
+ info('Waiting for 3 loadedmetadata events');
+ const videoElems = await Promise.all(metadataToBeLoaded);
+ await checkVideoElements(senderElement, videoElems, encodings);
+ emitter.stop();
+
+ await Promise.all([waitForSyncedRtcp(offerer), waitForSyncedRtcp(answerer)]);
+
+ info(`Changing source resolution to 1280x720`);
+ emitter.size(1280, 720);
+ emitter.start();
+ await waitForResizeEvents([senderElement, ...videoElems]);
+ await checkVideoElements(senderElement, videoElems, encodings);
+ await checkSenderStats(sender);
+
+ encodings[0].scaleResolutionDownBy = 1;
+ encodings[1].scaleResolutionDownBy = 2;
+ encodings[2].scaleResolutionDownBy = 3;
+ info(`Changing encodings to ${JSON.stringify(encodings)}`);
+ await sender.setParameters({encodings});
+ await waitForResizeEvents(videoElems);
+ await checkVideoElements(senderElement, videoElems, encodings);
+ await checkSenderStats(sender);
+
+ encodings[0].scaleResolutionDownBy = 6;
+ encodings[1].scaleResolutionDownBy = 5;
+ encodings[2].scaleResolutionDownBy = 4;
+ info(`Changing encodings to ${JSON.stringify(encodings)}`);
+ await sender.setParameters({encodings});
+ await waitForResizeEvents(videoElems);
+ await checkVideoElements(senderElement, videoElems, encodings);
+ await checkSenderStats(sender);
+
+ encodings[0].scaleResolutionDownBy = 4;
+ encodings[1].scaleResolutionDownBy = 1;
+ encodings[2].scaleResolutionDownBy = 2;
+ info(`Changing encodings to ${JSON.stringify(encodings)}`);
+ await sender.setParameters({encodings});
+ await waitForResizeEvents(videoElems);
+ await checkVideoElements(senderElement, videoElems, encodings);
+ await checkSenderStats(sender);
+
+ emitter.stop();
+ videoStream.getVideoTracks()[0].stop();
+ offerer.close();
+ answerer.close();
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOffer.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOffer.html
new file mode 100644
index 0000000000..cb7c13a0d1
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOffer.html
@@ -0,0 +1,109 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="parser_rtp.js"></script>
+ <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
+ <script type="application/javascript" src="helpers_from_wpt/sdp.js"></script>
+ <script type="application/javascript" src="simulcast.js"></script>
+ <script type="application/javascript" src="stats.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1231507",
+ title: "Basic video-only peer connection with Simulcast offer",
+ visible: true
+ });
+
+ runNetworkTest(async () => {
+ await pushPrefs(
+ // 180Kbps was determined empirically, set well-higher than
+ // the 80Kbps+overhead needed for the two simulcast streams.
+ // 100Kbps was apparently too low.
+ ['media.peerconnection.video.min_bitrate_estimate', 180*1000]);
+
+
+ const offerer = new RTCPeerConnection();
+ const answerer = new RTCPeerConnection();
+
+ const add = (pc, can, failed) => can && pc.addIceCandidate(can).catch(failed);
+ offerer.onicecandidate = e => add(answerer, e.candidate, generateErrorCallback());
+ answerer.onicecandidate = e => add(offerer, e.candidate, generateErrorCallback());
+
+ const metadataToBeLoaded = [];
+ answerer.ontrack = (e) => {
+ metadataToBeLoaded.push(getPlaybackWithLoadedMetadata(e.track));
+ };
+
+ // One send transceiver, that will be used to send both simulcast streams
+ const emitter = new VideoFrameEmitter();
+ const videoStream = emitter.stream();
+ const sendEncodings = [
+ { rid: '0', maxBitrate: 40000 },
+ { rid: '1', maxBitrate: 40000, scaleResolutionDownBy: 2 }
+ ];
+ offerer.addTransceiver(videoStream.getVideoTracks()[0], {sendEncodings});
+ emitter.start();
+
+ const sender = offerer.getSenders()[0];
+
+ const offer = await offerer.createOffer();
+
+ const mungedOffer = ridToMid(offer);
+ info(`Transformed send simulcast offer to multiple m-sections: ${offer.sdp} to ${mungedOffer}`);
+
+ await answerer.setRemoteDescription({type: 'offer', sdp: mungedOffer});
+ await offerer.setLocalDescription(offer);
+
+ const rids = answerer.getTransceivers().map(t => t.mid);
+ is(rids.length, 2, 'Should have 2 mids in offer');
+ ok(rids[0] != '', 'First mid should be non-empty');
+ ok(rids[1] != '', 'Second mid should be non-empty');
+ info(`rids: ${JSON.stringify(rids)}`);
+
+ const answer = await answerer.createAnswer();
+
+ const mungedAnswer = midToRid(answer);
+ info(`Transformed recv answer to simulcast: ${answer.sdp} to ${mungedAnswer}`);
+ await offerer.setRemoteDescription({type: 'answer', sdp: mungedAnswer});
+ await answerer.setLocalDescription(answer);
+
+ is(metadataToBeLoaded.length, 2, 'Offerer should have gotten 2 ontrack events');
+ info('Waiting for 2 loadedmetadata events');
+ const videoElems = await Promise.all(metadataToBeLoaded);
+
+ const statsReady =
+ Promise.all([waitForSyncedRtcp(offerer), waitForSyncedRtcp(answerer)]);
+
+ const helper = new VideoStreamHelper();
+ info('Waiting for first video element to start playing');
+ await helper.checkVideoPlaying(videoElems[0]);
+ info('Waiting for second video element to start playing');
+ await helper.checkVideoPlaying(videoElems[1]);
+
+ is(videoElems[0].videoWidth, 50,
+ "sink is same width as source, modulo our cropping algorithm");
+ is(videoElems[0].videoHeight, 50,
+ "sink is same height as source, modulo our cropping algorithm");
+ is(videoElems[1].videoWidth, 25,
+ "sink is 1/2 width of source, modulo our cropping algorithm");
+ is(videoElems[1].videoHeight, 25,
+ "sink is 1/2 height of source, modulo our cropping algorithm");
+
+ await statsReady;
+ const senderStats = await sender.getStats();
+ checkSenderStats(senderStats, 2);
+ checkExpectedFields(senderStats);
+ pedanticChecks(senderStats);
+
+ emitter.stop();
+ videoStream.getVideoTracks()[0].stop();
+ offerer.close();
+ answerer.close();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOffer_lowResFirst.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOffer_lowResFirst.html
new file mode 100644
index 0000000000..93141311f1
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOffer_lowResFirst.html
@@ -0,0 +1,109 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="parser_rtp.js"></script>
+ <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
+ <script type="application/javascript" src="helpers_from_wpt/sdp.js"></script>
+ <script type="application/javascript" src="simulcast.js"></script>
+ <script type="application/javascript" src="stats.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1231507",
+ title: "Basic video-only peer connection with Simulcast offer, first rid has lowest resolution",
+ visible: true
+ });
+
+ runNetworkTest(async () => {
+ await pushPrefs(
+ // 180Kbps was determined empirically, set well-higher than
+ // the 80Kbps+overhead needed for the two simulcast streams.
+ // 100Kbps was apparently too low.
+ ['media.peerconnection.video.min_bitrate_estimate', 180*1000]);
+
+
+ const offerer = new RTCPeerConnection();
+ const answerer = new RTCPeerConnection();
+
+ const add = (pc, can, failed) => can && pc.addIceCandidate(can).catch(failed);
+ offerer.onicecandidate = e => add(answerer, e.candidate, generateErrorCallback());
+ answerer.onicecandidate = e => add(offerer, e.candidate, generateErrorCallback());
+
+ const metadataToBeLoaded = [];
+ answerer.ontrack = (e) => {
+ metadataToBeLoaded.push(getPlaybackWithLoadedMetadata(e.track));
+ };
+
+ // One send transceiver, that will be used to send both simulcast streams
+ const emitter = new VideoFrameEmitter();
+ const videoStream = emitter.stream();
+ const sendEncodings = [
+ { rid: '0', maxBitrate: 40000, scaleResolutionDownBy: 2 },
+ { rid: '1', maxBitrate: 40000 }
+ ];
+ offerer.addTransceiver(videoStream.getVideoTracks()[0], {sendEncodings});
+ emitter.start();
+
+ const sender = offerer.getSenders()[0];
+
+ const offer = await offerer.createOffer();
+
+ const mungedOffer = ridToMid(offer);
+ info(`Transformed send simulcast offer to multiple m-sections: ${offer.sdp} to ${mungedOffer}`);
+
+ await answerer.setRemoteDescription({type: 'offer', sdp: mungedOffer});
+ await offerer.setLocalDescription(offer);
+
+ const rids = answerer.getTransceivers().map(t => t.mid);
+ is(rids.length, 2, 'Should have 2 mids in offer');
+ ok(rids[0] != '', 'First mid should be non-empty');
+ ok(rids[1] != '', 'Second mid should be non-empty');
+ info(`rids: ${JSON.stringify(rids)}`);
+
+ const answer = await answerer.createAnswer();
+
+ const mungedAnswer = midToRid(answer);
+ info(`Transformed recv answer to simulcast: ${answer.sdp} to ${mungedAnswer}`);
+ await offerer.setRemoteDescription({type: 'answer', sdp: mungedAnswer});
+ await answerer.setLocalDescription(answer);
+
+ is(metadataToBeLoaded.length, 2, 'Offerer should have gotten 2 ontrack events');
+ info('Waiting for 2 loadedmetadata events');
+ const videoElems = await Promise.all(metadataToBeLoaded);
+
+ const statsReady =
+ Promise.all([waitForSyncedRtcp(offerer), waitForSyncedRtcp(answerer)]);
+
+ const helper = new VideoStreamHelper();
+ info('Waiting for first video element to start playing');
+ await helper.checkVideoPlaying(videoElems[0]);
+ info('Waiting for second video element to start playing');
+ await helper.checkVideoPlaying(videoElems[1]);
+
+ is(videoElems[1].videoWidth, 50,
+ "sink is same width as source, modulo our cropping algorithm");
+ is(videoElems[1].videoHeight, 50,
+ "sink is same height as source, modulo our cropping algorithm");
+ is(videoElems[0].videoWidth, 25,
+ "sink is 1/2 width of source, modulo our cropping algorithm");
+ is(videoElems[0].videoHeight, 25,
+ "sink is 1/2 height of source, modulo our cropping algorithm");
+
+ await statsReady;
+ const senderStats = await sender.getStats();
+ checkSenderStats(senderStats, 2);
+ checkExpectedFields(senderStats);
+ pedanticChecks(senderStats);
+
+ emitter.stop();
+ videoStream.getVideoTracks()[0].stop();
+ offerer.close();
+ answerer.close();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOffer_lowResFirst_oldSetParameters.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOffer_lowResFirst_oldSetParameters.html
new file mode 100644
index 0000000000..73e2d38eb2
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOffer_lowResFirst_oldSetParameters.html
@@ -0,0 +1,112 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="parser_rtp.js"></script>
+ <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
+ <script type="application/javascript" src="helpers_from_wpt/sdp.js"></script>
+ <script type="application/javascript" src="simulcast.js"></script>
+ <script type="application/javascript" src="stats.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1231507",
+ title: "Basic video-only peer connection with Simulcast offer, first rid has lowest resolution",
+ visible: true
+ });
+
+ runNetworkTest(async () => {
+ await pushPrefs(
+ ['media.peerconnection.simulcast', true],
+ // 180Kbps was determined empirically, set well-higher than
+ // the 80Kbps+overhead needed for the two simulcast streams.
+ // 100Kbps was apparently too low.
+ ['media.peerconnection.video.min_bitrate_estimate', 180*1000]);
+
+
+ const offerer = new RTCPeerConnection();
+ const answerer = new RTCPeerConnection();
+
+ const add = (pc, can, failed) => can && pc.addIceCandidate(can).catch(failed);
+ offerer.onicecandidate = e => add(answerer, e.candidate, generateErrorCallback());
+ answerer.onicecandidate = e => add(offerer, e.candidate, generateErrorCallback());
+
+ const metadataToBeLoaded = [];
+ answerer.ontrack = (e) => {
+ metadataToBeLoaded.push(getPlaybackWithLoadedMetadata(e.track));
+ };
+
+ // One send transceiver, that will be used to send both simulcast streams
+ const emitter = new VideoFrameEmitter();
+ const videoStream = emitter.stream();
+ offerer.addTrack(videoStream.getVideoTracks()[0], videoStream);
+ emitter.start();
+
+ const sender = offerer.getSenders()[0];
+ sender.setParameters({
+ encodings: [
+ { rid: '0', maxBitrate: 40000, scaleResolutionDownBy: 2 },
+ { rid: '1', maxBitrate: 40000 }
+ ]
+ });
+
+ const offer = await offerer.createOffer();
+
+ const mungedOffer = ridToMid(offer);
+ info(`Transformed send simulcast offer to multiple m-sections: ${offer.sdp} to ${mungedOffer}`);
+
+ await answerer.setRemoteDescription({type: 'offer', sdp: mungedOffer});
+ await offerer.setLocalDescription(offer);
+
+ const rids = answerer.getTransceivers().map(t => t.mid);
+ is(rids.length, 2, 'Should have 2 mids in offer');
+ ok(rids[0] != '', 'First mid should be non-empty');
+ ok(rids[1] != '', 'Second mid should be non-empty');
+ info(`rids: ${JSON.stringify(rids)}`);
+
+ const answer = await answerer.createAnswer();
+
+ const mungedAnswer = midToRid(answer);
+ info(`Transformed recv answer to simulcast: ${answer.sdp} to ${mungedAnswer}`);
+ await offerer.setRemoteDescription({type: 'answer', sdp: mungedAnswer});
+ await answerer.setLocalDescription(answer);
+
+ is(metadataToBeLoaded.length, 2, 'Offerer should have gotten 2 ontrack events');
+ info('Waiting for 2 loadedmetadata events');
+ const videoElems = await Promise.all(metadataToBeLoaded);
+
+ const statsReady =
+ Promise.all([waitForSyncedRtcp(offerer), waitForSyncedRtcp(answerer)]);
+
+ const helper = new VideoStreamHelper();
+ info('Waiting for first video element to start playing');
+ await helper.checkVideoPlaying(videoElems[0]);
+ info('Waiting for second video element to start playing');
+ await helper.checkVideoPlaying(videoElems[1]);
+
+ is(videoElems[1].videoWidth, 50,
+ "sink is same width as source, modulo our cropping algorithm");
+ is(videoElems[1].videoHeight, 50,
+ "sink is same height as source, modulo our cropping algorithm");
+ is(videoElems[0].videoWidth, 25,
+ "sink is 1/2 width of source, modulo our cropping algorithm");
+ is(videoElems[0].videoHeight, 25,
+ "sink is 1/2 height of source, modulo our cropping algorithm");
+
+ await statsReady;
+ const senderStats = await sender.getStats();
+ checkSenderStats(senderStats, 2);
+ checkExpectedFields(senderStats);
+ pedanticChecks(senderStats);
+
+ emitter.stop();
+ videoStream.getVideoTracks()[0].stop();
+ offerer.close();
+ answerer.close();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOffer_oldSetParameters.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOffer_oldSetParameters.html
new file mode 100644
index 0000000000..551273af5e
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_simulcastOffer_oldSetParameters.html
@@ -0,0 +1,112 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="parser_rtp.js"></script>
+ <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
+ <script type="application/javascript" src="helpers_from_wpt/sdp.js"></script>
+ <script type="application/javascript" src="simulcast.js"></script>
+ <script type="application/javascript" src="stats.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1231507",
+ title: "Basic video-only peer connection with Simulcast offer",
+ visible: true
+ });
+
+ runNetworkTest(async () => {
+ await pushPrefs(
+ ['media.peerconnection.simulcast', true],
+ // 180Kbps was determined empirically, set well-higher than
+ // the 80Kbps+overhead needed for the two simulcast streams.
+ // 100Kbps was apparently too low.
+ ['media.peerconnection.video.min_bitrate_estimate', 180*1000]);
+
+
+ const offerer = new RTCPeerConnection();
+ const answerer = new RTCPeerConnection();
+
+ const add = (pc, can, failed) => can && pc.addIceCandidate(can).catch(failed);
+ offerer.onicecandidate = e => add(answerer, e.candidate, generateErrorCallback());
+ answerer.onicecandidate = e => add(offerer, e.candidate, generateErrorCallback());
+
+ const metadataToBeLoaded = [];
+ answerer.ontrack = (e) => {
+ metadataToBeLoaded.push(getPlaybackWithLoadedMetadata(e.track));
+ };
+
+ // One send transceiver, that will be used to send both simulcast streams
+ const emitter = new VideoFrameEmitter();
+ const videoStream = emitter.stream();
+ offerer.addTrack(videoStream.getVideoTracks()[0], videoStream);
+ emitter.start();
+
+ const sender = offerer.getSenders()[0];
+ sender.setParameters({
+ encodings: [
+ { rid: '0', maxBitrate: 40000 },
+ { rid: '1', maxBitrate: 40000, scaleResolutionDownBy: 2 }
+ ]
+ });
+
+ const offer = await offerer.createOffer();
+
+ const mungedOffer = ridToMid(offer);
+ info(`Transformed send simulcast offer to multiple m-sections: ${offer.sdp} to ${mungedOffer}`);
+
+ await answerer.setRemoteDescription({type: "offer", sdp: mungedOffer});
+ await offerer.setLocalDescription(offer);
+
+ const rids = answerer.getTransceivers().map(t => t.mid);
+ is(rids.length, 2, 'Should have 2 mids in offer');
+ ok(rids[0] != '', 'First mid should be non-empty');
+ ok(rids[1] != '', 'Second mid should be non-empty');
+ info(`rids: ${JSON.stringify(rids)}`);
+
+ const answer = await answerer.createAnswer();
+
+ const mungedAnswer = midToRid(answer);
+ info(`Transformed recv answer to simulcast: ${answer.sdp} to ${mungedAnswer}`);
+ await offerer.setRemoteDescription({type: "answer", sdp: mungedAnswer});
+ await answerer.setLocalDescription(answer);
+
+ is(metadataToBeLoaded.length, 2, 'Offerer should have gotten 2 ontrack events');
+ info('Waiting for 2 loadedmetadata events');
+ const videoElems = await Promise.all(metadataToBeLoaded);
+
+ const statsReady =
+ Promise.all([waitForSyncedRtcp(offerer), waitForSyncedRtcp(answerer)]);
+
+ const helper = new VideoStreamHelper();
+ info('Waiting for first video element to start playing');
+ await helper.checkVideoPlaying(videoElems[0]);
+ info('Waiting for second video element to start playing');
+ await helper.checkVideoPlaying(videoElems[1]);
+
+ is(videoElems[0].videoWidth, 50,
+ "sink is same width as source, modulo our cropping algorithm");
+ is(videoElems[0].videoHeight, 50,
+ "sink is same height as source, modulo our cropping algorithm");
+ is(videoElems[1].videoWidth, 25,
+ "sink is 1/2 width of source, modulo our cropping algorithm");
+ is(videoElems[1].videoHeight, 25,
+ "sink is 1/2 height of source, modulo our cropping algorithm");
+
+ await statsReady;
+ const senderStats = await sender.getStats();
+ checkSenderStats(senderStats, 2);
+ checkExpectedFields(senderStats);
+ pedanticChecks(senderStats);
+
+ emitter.stop();
+ videoStream.getVideoTracks()[0].stop();
+ offerer.close();
+ answerer.close();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_stats.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_stats.html
new file mode 100644
index 0000000000..2ef98dc9c8
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_stats.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="stats.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ bug: "1337525",
+ title: "webRtc Stats composition and sanity"
+});
+
+runNetworkTest(async function (options) {
+ // We don't know how to get QP value when using Android system codecs.
+ if (navigator.userAgent.includes("Android")) {
+ await pushPrefs(["media.navigator.mediadatadecoder_vpx_enabled", false],
+ ["media.webrtc.hw.h264.enabled", false]);
+ }
+
+ // For accurate comparisons of `remoteTimestamp` (not using reduced precision)
+ // to `timestamp` (using reduced precision).
+ await pushPrefs(["privacy.resistFingerprinting.reduceTimerPrecision.jitter",
+ false]);
+
+ const test = new PeerConnectionTest(options);
+
+ test.chain.insertAfter("PC_LOCAL_WAIT_FOR_MEDIA_FLOW",
+ [PC_LOCAL_TEST_LOCAL_STATS]);
+
+ test.chain.insertAfter("PC_REMOTE_WAIT_FOR_MEDIA_FLOW",
+ [PC_REMOTE_TEST_REMOTE_STATS]);
+
+ test.setMediaConstraints([{audio: true}, {video: true}],
+ [{audio: true}, {video: true}]);
+ await test.run();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_stats_jitter.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_stats_jitter.html
new file mode 100644
index 0000000000..6e1ef698b4
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_stats_jitter.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="stats.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1672590",
+ title: "Jitter sanity check"
+ });
+
+const checkJitter = stats => {
+ stats.forEach((stat, mapKey) => {
+ if (stat.type == "remote-inbound-rtp") {
+ // This should be much lower for audio, TODO: Bug 1330575
+ const expectedJitter = stat.kind == "video" ? 0.5 : 1;
+
+ ok(stat.jitter < expectedJitter,
+ stat.type + ".jitter is sane number for a local only test. value="
+ + stat.jitter);
+ }
+ });
+};
+
+const PC_LOCAL_TEST_LOCAL_JITTER = async test => {
+ checkJitter(await waitForSyncedRtcp(test.pcLocal._pc));
+}
+
+const PC_REMOTE_TEST_REMOTE_JITTER = async test => {
+ checkJitter(await waitForSyncedRtcp(test.pcRemote._pc));
+}
+
+runNetworkTest(async function (options) {
+ // We don't know how to get QP value when using Android system codecs.
+ if (navigator.userAgent.includes("Android")) {
+ await pushPrefs(["media.navigator.mediadatadecoder_vpx_enabled", false],
+ ["media.webrtc.hw.h264.enabled", false]);
+ }
+
+ const test = new PeerConnectionTest(options);
+
+ test.chain.insertAfter("PC_LOCAL_WAIT_FOR_MEDIA_FLOW",
+ [PC_LOCAL_TEST_LOCAL_JITTER]);
+
+ test.chain.insertAfter("PC_REMOTE_WAIT_FOR_MEDIA_FLOW",
+ [PC_REMOTE_TEST_REMOTE_JITTER]);
+
+ test.setMediaConstraints([{audio: true}, {video: true}],
+ [{audio: true}, {video: true}]);
+ await test.run();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_stats_oneway.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_stats_oneway.html
new file mode 100644
index 0000000000..02ace530a9
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_stats_oneway.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="stats.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ bug: "1225722",
+ title: "WebRTC Stats composition and sanity for a one-way peer connection"
+});
+
+runNetworkTest(async function (options) {
+ // We don't know how to get QP value when using Android system codecs.
+ if (navigator.userAgent.includes("Android")) {
+ await pushPrefs(["media.navigator.mediadatadecoder_vpx_enabled", false],
+ ["media.webrtc.hw.h264.enabled", false]);
+ }
+
+ // For accurate comparisons of `remoteTimestamp` (not using reduced precision)
+ // to `timestamp` (using reduced precision).
+ await pushPrefs(["privacy.resistFingerprinting.reduceTimerPrecision.jitter",
+ false]);
+
+ const test = new PeerConnectionTest(options);
+
+ test.chain.insertAfter("PC_LOCAL_WAIT_FOR_MEDIA_FLOW",
+ [PC_LOCAL_TEST_LOCAL_STATS]);
+
+ test.chain.insertAfter("PC_REMOTE_WAIT_FOR_MEDIA_FLOW",
+ [PC_REMOTE_TEST_REMOTE_STATS]);
+
+ const testOneWayStats = (stats, codecType) => {
+ const codecs = [];
+ stats.forEach(stat => {
+ if (stat.type == "codec") {
+ codecs.push(stat);
+ is(stat.codecType, codecType, "One-way codec has specific codecType");
+ }
+ });
+ is(codecs.length, 2, "One audio and one video codec");
+ if (codecs.length == 2) {
+ isnot(codecs[0].mimeType.slice(0, 5), codecs[1].mimeType.slice(0, 5),
+ "Different media type for audio vs video mime types");
+ }
+ };
+
+ test.chain.append([
+ async function PC_LOCAL_TEST_CODECTYPE_ENCODE(test) {
+ testOneWayStats(await test.pcLocal._pc.getStats(), "encode");
+ },
+ async function PC_REMOTE_TEST_CODECTYPE_DECODE(test) {
+ testOneWayStats(await test.pcRemote._pc.getStats(), "decode");
+ },
+ ]);
+
+ test.setMediaConstraints([{audio: true}, {video: true}], []);
+ await test.run();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_stats_relayProtocol.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_stats_relayProtocol.html
new file mode 100644
index 0000000000..cdc328fd2b
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_stats_relayProtocol.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="nonTrickleIce.js"></script>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ bug: "1435789",
+ title: "WebRTC local-candidate relayProtocol stats attribute"
+});
+
+// This test uses the NAT simulator in order to get srflx candidates.
+// It doesn't work in https, so we turn on getUserMedia in http, which requires
+// a reload.
+if (!("mediaDevices" in navigator)) {
+ SpecialPowers.pushPrefEnv({set: [['media.devices.insecure.enabled', true]]},
+ () => location.reload());
+} else {
+ runNetworkTest(async (options = {}) => {
+ await pushPrefs(
+ ['media.peerconnection.nat_simulator.filtering_type', 'ENDPOINT_INDEPENDENT'],
+ ['media.peerconnection.nat_simulator.mapping_type', 'ENDPOINT_INDEPENDENT'],
+ ['media.getusermedia.insecure.enabled', true]);
+ const test = new PeerConnectionTest(options);
+ makeOffererNonTrickle(test.chain);
+ makeAnswererNonTrickle(test.chain);
+
+ test.chain.removeAfter("PC_LOCAL_WAIT_FOR_MEDIA_FLOW");
+ test.chain.append([PC_LOCAL_TEST_LOCAL_STATS_RELAYCANDIDATE]);
+
+ test.setMediaConstraints([{ audio: true }], [{ audio: true }]);
+ await test.run();
+ }, { useIceServer: true });
+}
+
+const PC_LOCAL_TEST_LOCAL_STATS_RELAYCANDIDATE = test => {
+ return test.pcLocal.getStats().then(stats => {
+ let haveRelayProtocol = {};
+ for (let [k, v] of stats) {
+ if (v.type == "local-candidate") {
+ haveRelayProtocol[v.candidateType + "-" + v.relayProtocol] = v.relayProtocol;
+ }
+ }
+ is(haveRelayProtocol["host-undefined"], undefined, "relayProtocol not set for host candidates");
+ is(haveRelayProtocol["srflx-undefined"], undefined, "relayProtocol not set for server reflexive candidates");
+ ok(haveRelayProtocol["relay-udp"], "Has UDP relay candidate");
+ ok(haveRelayProtocol["relay-tcp"], "Has TCP relay candidate");
+ ok(haveRelayProtocol["relay-tls"], "Has TLS relay candidate");
+ is(Object.keys(haveRelayProtocol).length, 5, "All candidate types are accounted for");
+ });
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_stereoFmtpPref.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_stereoFmtpPref.html
new file mode 100644
index 0000000000..ab7811fe82
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_stereoFmtpPref.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1793776",
+ title: "Test that media.peerconnection.sdp.disable_stereo_fmtp works"
+ });
+
+ const tests = [
+ async function testStereo() {
+ const offerer = new RTCPeerConnection();
+ offerer.addTransceiver('audio');
+ const answerer = new RTCPeerConnection();
+ await offerer.setLocalDescription();
+ ok(offerer.localDescription.sdp.includes('stereo=1'),
+ 'Offer uses stereo=1 when media.peerconnection.sdp.disable_stereo_fmtp is not set');
+ await answerer.setRemoteDescription(offerer.localDescription);
+ const {sdp} = await answerer.createAnswer();
+ ok(sdp.includes('stereo=1'), 'Answer uses stereo=1 when media.peerconnection.sdp.disable_stereo_fmtp is not set');
+ },
+
+ async function testNoStereo() {
+ await pushPrefs(
+ ['media.peerconnection.sdp.disable_stereo_fmtp', true]);
+
+ const offerer = new RTCPeerConnection();
+ offerer.addTransceiver('audio');
+ const answerer = new RTCPeerConnection();
+ await offerer.setLocalDescription();
+ ok(offerer.localDescription.sdp.includes('stereo=0'),
+ 'Offer uses stereo=0 when media.peerconnection.sdp.disable_stereo_fmtp is set');
+ await answerer.setRemoteDescription(offerer.localDescription);
+ const {sdp} = await answerer.createAnswer();
+ ok(sdp.includes('stereo=0'), 'Answer uses stereo=0 when media.peerconnection.sdp.disable_stereo_fmtp is set');
+ },
+ ];
+
+ runNetworkTest(async () => {
+ for (const test of tests) {
+ info(`Running test: ${test.name}`);
+ try {
+ await test();
+ } catch (e) {
+ ok(false, `Caught ${e.name}: ${e.message} ${e.stack}`);
+ }
+ info(`Done running test: ${test.name}`);
+ // Make sure we don't build up a pile of GC work, and also get PCImpl to
+ // print their timecards.
+ await new Promise(r => SpecialPowers.exactGC(r));
+ }
+
+ await SpecialPowers.popPrefEnv();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_syncSetDescription.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_syncSetDescription.html
new file mode 100644
index 0000000000..98f0de1b4a
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_syncSetDescription.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1063971",
+ title: "Legacy sync setDescription calls",
+ visible: true
+ });
+
+// Test setDescription without callbacks, which many webrtc examples still do
+
+function PC_LOCAL_SET_LOCAL_DESCRIPTION_SYNC(test) {
+ test.pcLocal.onsignalingstatechange = function() {};
+ test.pcLocal._pc.setLocalDescription(test.originalOffer);
+}
+
+function PC_REMOTE_SET_REMOTE_DESCRIPTION_SYNC(test) {
+ test.pcRemote.onsignalingstatechange = function() {};
+ test.pcRemote._pc.setRemoteDescription(test._local_offer,
+ test.pcRemote.releaseIceCandidates,
+ generateErrorCallback("pcRemote._pc.setRemoteDescription() sync failed"));
+}
+function PC_REMOTE_SET_LOCAL_DESCRIPTION_SYNC(test) {
+ test.pcRemote.onsignalingstatechange = function() {};
+ test.pcRemote._pc.setLocalDescription(test.originalAnswer);
+}
+function PC_LOCAL_SET_REMOTE_DESCRIPTION_SYNC(test) {
+ test.pcLocal.onsignalingstatechange = function() {};
+ test.pcLocal._pc.setRemoteDescription(test._remote_answer,
+ test.pcLocal.releaseIceCandidates,
+ generateErrorCallback("pcLocal._pc.setRemoteDescription() sync failed"));
+}
+
+runNetworkTest(() => {
+ const test = new PeerConnectionTest();
+ test.setMediaConstraints([{video: true}], [{video: true}]);
+ test.chain.replace("PC_LOCAL_SET_LOCAL_DESCRIPTION", PC_LOCAL_SET_LOCAL_DESCRIPTION_SYNC);
+ test.chain.replace("PC_REMOTE_SET_REMOTE_DESCRIPTION", PC_REMOTE_SET_REMOTE_DESCRIPTION_SYNC);
+ test.chain.remove("PC_REMOTE_CHECK_CAN_TRICKLE_SYNC");
+ test.chain.replace("PC_REMOTE_SET_LOCAL_DESCRIPTION", PC_REMOTE_SET_LOCAL_DESCRIPTION_SYNC);
+ test.chain.replace("PC_LOCAL_SET_REMOTE_DESCRIPTION", PC_LOCAL_SET_REMOTE_DESCRIPTION_SYNC);
+ test.chain.remove("PC_LOCAL_CHECK_CAN_TRICKLE_SYNC");
+ return test.run();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_telephoneEventFirst.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_telephoneEventFirst.html
new file mode 100644
index 0000000000..bde51c1fd0
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_telephoneEventFirst.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ title: "RTCPeerConnection with telephone-event codec first in SDP",
+ bug: "1581898",
+ visible: true
+});
+
+const test = async () => {
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+ const stream = await navigator.mediaDevices.getUserMedia({audio:true});
+ pc1.addTrack(stream.getAudioTracks()[0], stream);
+ pc2.addTrack(stream.getAudioTracks()[0], stream);
+
+ const offer = await pc1.createOffer();
+ await pc1.setLocalDescription(offer);
+
+ const regex = /^(m=audio \d+ [^ ]+) (.*) 101(.*)$/m;
+
+ // Rewrite offer so payload type 101 comes first
+ offer.sdp = offer.sdp.replace(regex, '$1 101 $2 $3');
+
+ ok(offer.sdp.match(/^m=audio \d+ [^ ]+ 101 /m),
+ "Payload type 101 should be first on the m-line");
+
+ await pc2.setRemoteDescription(offer);
+ const answer = await pc2.createAnswer();
+
+ pc1.onicecandidate = e => { pc2.addIceCandidate(e.candidate); }
+ pc2.onicecandidate = e => { pc1.addIceCandidate(e.candidate); }
+
+ await pc1.setRemoteDescription(answer);
+ await pc2.setLocalDescription(answer);
+ await new Promise(resolve => {
+ pc1.oniceconnectionstatechange = e => {
+ if (pc1.iceConnectionState == "connected") {
+ resolve();
+ }
+ };
+ });
+ await wait(1000);
+};
+
+runNetworkTest(test);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_threeUnbundledConnections.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_threeUnbundledConnections.html
new file mode 100644
index 0000000000..75f0d12463
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_threeUnbundledConnections.html
@@ -0,0 +1,134 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1342579",
+ title: "Unbundled PC connects to two different PCs",
+ visible: true
+ });
+
+ const fakeFingerPrint = "a=fingerprint:sha-256 11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11:11";
+
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+ const pc3 = new RTCPeerConnection();
+
+ const add = (pc, can, failed) => can && pc.addIceCandidate(can).catch(failed);
+ pc1.onicecandidate = e => {
+ if (e.candidate) {
+ if (e.candidate.sdpMid === "1") {
+ add(pc2, e.candidate, generateErrorCallback())
+ } else {
+ add(pc3, e.candidate, generateErrorCallback())
+ }
+ }
+ };
+ pc2.onicecandidate = e => add(pc1, e.candidate, generateErrorCallback());
+ pc3.onicecandidate = e => add(pc1, e.candidate, generateErrorCallback());
+
+ let ice1Finished, ice2Finished, ice3Finished;
+ const ice1Done = new Promise(r => ice1Finished = r);
+ const ice2Done = new Promise(r => ice2Finished = r);
+ const ice3Done = new Promise(r => ice3Finished = r);
+
+ const icsc = (pc, str, resolve) => {
+ const state = pc.iceConnectionState;
+ info(str + " ICE connection state is: " + state);
+ if (state == "connected") {
+ ok(true, str + " ICE connected");
+ resolve();
+ } else if (state == "failed") {
+ ok(false, str + " ICE failed")
+ resolve();
+ }
+ };
+
+ pc1.oniceconnectionstatechange = e => icsc(pc1, "PC1", ice1Finished);
+ pc2.oniceconnectionstatechange = e => icsc(pc2, "PC2", ice2Finished);
+ pc3.oniceconnectionstatechange = e => icsc(pc3, "PC3", ice3Finished);
+
+
+ function combineAnswer(origAnswer, answer) {
+ const sdplines = origAnswer.sdp.split('\r\n');
+ const fpIndex = sdplines.findIndex(l => l.match('^a=fingerprint'));
+ const FP = sdplines[fpIndex];
+ const audioIndex = sdplines.findIndex(l => l.match(/^m=audio [1-9]/));
+ const videoIndex = sdplines.findIndex(l => l.match(/^m=video [1-9]/));
+ if (audioIndex > -1) {
+ var ss = sdplines.slice(0, audioIndex);
+ ss.splice(fpIndex, 1);
+ answer.sessionSection = ss;
+ const rejectedVideoIndex = sdplines.findIndex(l => l.match('m=video 0'));
+ var ams = sdplines.slice(audioIndex, rejectedVideoIndex);
+ ams.push(FP);
+ ams.push(fakeFingerPrint);
+ answer.audioMsection = ams;
+ }
+ if (videoIndex > -1) {
+ var vms = sdplines.slice(videoIndex, sdplines.length -1);
+ vms.push(fakeFingerPrint);
+ vms.push(FP);
+ answer.videoMsection = vms;
+ }
+ return answer;
+ }
+
+runNetworkTest(async () => {
+ const v1 = createMediaElement('video', 'v1');
+ const v2 = createMediaElement('video', 'v2');
+ const v3 = createMediaElement('video', 'v3');
+
+ const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
+ (v1.srcObject = stream).getTracks().forEach(t => pc1.addTrack(t, stream));
+
+ const stream2 = await navigator.mediaDevices.getUserMedia({ video: true });
+ (v2.srcObject = stream2).getTracks().forEach(t => pc2.addTrack(t, stream2));
+
+ const stream3 = await navigator.mediaDevices.getUserMedia({ audio: true });
+ (v3.srcObject = stream3).getTracks().forEach(t => pc3.addTrack(t, stream3));
+
+ const offer = await pc1.createOffer();
+ await pc1.setLocalDescription(offer);
+
+ //info("Original OFFER: " + JSON.stringify(offer));
+ offer.sdp = sdputils.removeBundle(offer.sdp);
+ //info("OFFER w/o BUNDLE: " + JSON.stringify(offer));
+ const offerAudio = new RTCSessionDescription(JSON.parse(JSON.stringify(offer)));
+ offerAudio.sdp = offerAudio.sdp.replace('m=video 9', 'm=video 0');
+ //info("offerAudio: " + JSON.stringify(offerAudio));
+ const offerVideo = new RTCSessionDescription(JSON.parse(JSON.stringify(offer)));
+ offerVideo.sdp = offerVideo.sdp.replace('m=audio 9', 'm=audio 0');
+ //info("offerVideo: " + JSON.stringify(offerVideo));
+
+ // We need to do these in parallel, otherwise pc1 will start firing
+ // icecandidate events before pc3 is ready.
+ await Promise.all([pc2.setRemoteDescription(offerVideo), pc3.setRemoteDescription(offerAudio)]);
+
+ const answerVideo = await pc2.createAnswer();
+ const answerAudio = await pc3.createAnswer();
+
+ const answer = combineAnswer(answerAudio, combineAnswer(answerVideo, {}));
+ const fakeAnswer = answer.sessionSection.concat(answer.audioMsection, answer.videoMsection).join('\r\n');
+ info("ANSWER: " + fakeAnswer);
+
+ // We want to do these in parallel, because if we do them seqentially, by the
+ // time pc3.sLD completes pc2 could have fired icecandidate events, when we
+ // haven't called pc1.sRD yet.
+ await Promise.all(
+ [pc2.setLocalDescription(answerVideo),
+ pc3.setLocalDescription(answerAudio),
+ pc1.setRemoteDescription({type: 'answer', sdp: fakeAnswer})]);
+
+ await Promise.all([ice1Done, ice2Done, ice3Done]);
+
+ ok(true, "Connected.");
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_throwInCallbacks.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_throwInCallbacks.html
new file mode 100644
index 0000000000..5a3872c120
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_throwInCallbacks.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "857765",
+ title: "Throw in PeerConnection callbacks"
+ });
+
+runNetworkTest(function () {
+ let finish;
+ const onfinished = new Promise(r => finish = async () => {
+ window.onerror = oldOnError;
+ is(error_count, 7, "Seven expected errors verified.");
+ r();
+ });
+
+ function getFail() {
+ return err => {
+ window.onerror = oldOnError;
+ generateErrorCallback()(err);
+ };
+ }
+
+ let error_count = 0;
+ let oldOnError = window.onerror;
+ window.onerror = (errorMsg, url, lineNumber) => {
+ if (!errorMsg.includes("Expected")) {
+ getFail()(errorMsg);
+ }
+ error_count += 1;
+ info("onerror " + error_count + ": " + errorMsg);
+ if (error_count == 7) {
+ finish();
+ }
+ throw new Error("window.onerror may throw");
+ return false;
+ }
+
+ let pc0, pc1, pc2;
+ // Test failure callbacks (limited to 1 for now)
+ pc0 = new RTCPeerConnection();
+ pc0.close();
+ pc0.createOffer(getFail(), function(err) {
+ pc1 = new RTCPeerConnection();
+ pc2 = new RTCPeerConnection();
+
+ // Test success callbacks (happy path)
+ navigator.mozGetUserMedia({video:true}, function(video1) {
+ pc1.addStream(video1);
+ pc1.createOffer(function(offer) {
+ pc1.setLocalDescription(offer, function() {
+ pc2.setRemoteDescription(offer, function() {
+ pc2.createAnswer(function(answer) {
+ pc2.setLocalDescription(answer, function() {
+ pc1.setRemoteDescription(answer, function() {
+ throw new Error("Expected");
+ }, getFail());
+ throw new Error("Expected");
+ }, getFail());
+ throw new Error("Expected");
+ }, getFail());
+ throw new Error("Expected");
+ }, getFail());
+ throw new Error("Expected");
+ }, getFail());
+ throw new Error("Expected");
+ }, getFail());
+ }, getFail());
+ throw new Error("Expected");
+ });
+
+ return onfinished;
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_toJSON.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_toJSON.html
new file mode 100644
index 0000000000..96c2c42b78
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_toJSON.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "928304",
+ title: "test toJSON() on RTCSessionDescription and RTCIceCandidate"
+ });
+
+ runNetworkTest(function () {
+ /** Test for Bug 872377 **/
+
+ var rtcSession = new RTCSessionDescription({ sdp: "Picklechips!",
+ type: "offer" });
+ var jsonCopy = JSON.parse(JSON.stringify(rtcSession));
+ for (key in rtcSession) {
+ if (typeof(rtcSession[key]) == "function") continue;
+ is(rtcSession[key], jsonCopy[key], "key " + key + " should match.");
+ }
+
+ /** Test for Bug 928304 **/
+
+ var rtcIceCandidate = new RTCIceCandidate({ candidate: "dummy",
+ sdpMid: "test",
+ sdpMLineIndex: 3 });
+ jsonCopy = JSON.parse(JSON.stringify(rtcIceCandidate));
+ for (key in rtcIceCandidate) {
+ if (typeof(rtcIceCandidate[key]) == "function") continue;
+ is(rtcIceCandidate[key], jsonCopy[key], "key " + key + " should match.");
+ }
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_trackDisabling.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_trackDisabling.html
new file mode 100644
index 0000000000..73323cf007
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_trackDisabling.html
@@ -0,0 +1,108 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ bug: "1219711",
+ title: "Disabling locally should be reflected remotely"
+});
+
+runNetworkTest(async () => {
+ const test = new PeerConnectionTest();
+
+ await pushPrefs(
+ ["media.getusermedia.camera.stop_on_disable.enabled", true],
+ ["media.getusermedia.camera.stop_on_disable.delay_ms", 0],
+ ["media.getusermedia.microphone.stop_on_disable.enabled", true],
+ ["media.getusermedia.microphone.stop_on_disable.delay_ms", 0],
+ // Always use fake tracks since we depend on video to be somewhat green and
+ // audio to have a large 1000Hz component (or 440Hz if using fake devices).
+ ['media.audio_loopback_dev', ''],
+ ['media.video_loopback_dev', ''],
+ ['media.navigator.streams.fake', true]);
+
+ test.setMediaConstraints([{audio: true, video: true}], []);
+ test.chain.append([
+ function CHECK_ASSUMPTIONS() {
+ is(test.pcLocal.localMediaElements.length, 2,
+ "pcLocal should have one media element");
+ is(test.pcRemote.remoteMediaElements.length, 2,
+ "pcRemote should have one media element");
+ is(test.pcLocal._pc.getLocalStreams().length, 1,
+ "pcLocal should have one stream");
+ is(test.pcRemote._pc.getRemoteStreams().length, 1,
+ "pcRemote should have one stream");
+ },
+ async function CHECK_VIDEO() {
+ const h = new CaptureStreamTestHelper2D();
+ const localVideo = test.pcLocal.localMediaElements
+ .find(e => e instanceof HTMLVideoElement);
+ const remoteVideo = test.pcRemote.remoteMediaElements
+ .find(e => e instanceof HTMLVideoElement);
+ // We check a pixel somewhere away from the top left corner since
+ // MediaEngineFake puts semi-transparent time indicators there.
+ const offsetX = 50;
+ const offsetY = 50;
+ const threshold = 128;
+
+ // We're regarding black as disabled here, and we're setting the alpha
+ // channel of the pixel to 255 to disregard alpha when testing.
+ const checkVideoEnabled = video => h.waitForPixel(video,
+ px => (px[3] = 255, h.isPixelNot(px, h.black, threshold)),
+ { offsetX, offsetY }
+ );
+ const checkVideoDisabled = video => h.waitForPixel(video,
+ px => (px[3] = 255, h.isPixel(px, h.black, threshold)),
+ { offsetX, offsetY }
+ );
+
+ info("Checking local video enabled");
+ await checkVideoEnabled(localVideo);
+ info("Checking remote video enabled");
+ await checkVideoEnabled(remoteVideo);
+
+ info("Disabling original");
+ test.pcLocal._pc.getLocalStreams()[0].getVideoTracks()[0].enabled = false;
+
+ info("Checking local video disabled");
+ await checkVideoDisabled(localVideo);
+ info("Checking remote video disabled");
+ await checkVideoDisabled(remoteVideo);
+ },
+ async function CHECK_AUDIO() {
+ const ac = new AudioContext();
+ const localAnalyser = new AudioStreamAnalyser(ac, test.pcLocal._pc.getLocalStreams()[0]);
+ const remoteAnalyser = new AudioStreamAnalyser(ac, test.pcRemote._pc.getRemoteStreams()[0]);
+
+ const checkAudio = (analyser, fun) => analyser.waitForAnalysisSuccess(fun);
+
+ const freq = localAnalyser.binIndexForFrequency(TEST_AUDIO_FREQ);
+ const checkAudioEnabled = analyser =>
+ checkAudio(analyser, array => array[freq] > 200);
+ const checkAudioDisabled = analyser =>
+ checkAudio(analyser, array => array[freq] < 50);
+
+ info("Checking local audio enabled");
+ await checkAudioEnabled(localAnalyser);
+ info("Checking remote audio enabled");
+ await checkAudioEnabled(remoteAnalyser);
+
+ test.pcLocal._pc.getLocalStreams()[0].getAudioTracks()[0].enabled = false;
+
+ info("Checking local audio disabled");
+ await checkAudioDisabled(localAnalyser);
+ info("Checking remote audio disabled");
+ await checkAudioDisabled(remoteAnalyser);
+ },
+ ]);
+ await test.run();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_trackDisabling_clones.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_trackDisabling_clones.html
new file mode 100644
index 0000000000..ae7647fa1a
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_trackDisabling_clones.html
@@ -0,0 +1,162 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ bug: "1219711",
+ title: "Disabling locally should be reflected remotely, individually for clones"
+});
+
+runNetworkTest(async () => {
+ var test = new PeerConnectionTest();
+
+ await pushPrefs(
+ ["media.getusermedia.camera.stop_on_disable.enabled", true],
+ ["media.getusermedia.camera.stop_on_disable.delay_ms", 0],
+ ["media.getusermedia.microphone.stop_on_disable.enabled", true],
+ ["media.getusermedia.microphone.stop_on_disable.delay_ms", 0],
+ // Always use fake tracks since we depend on audio to have a large 1000Hz
+ // component.
+ ['media.audio_loopback_dev', ''],
+ ['media.navigator.streams.fake', true]);
+ // [TODO] re-enable HW decoder after bug 1526207 is fixed.
+ if (navigator.userAgent.includes("Android")) {
+ await pushPrefs(["media.navigator.mediadatadecoder_vpx_enabled", false],
+ ["media.webrtc.hw.h264.enabled", false]);
+ }
+
+ var originalStream;
+ var localVideoOriginal;
+
+ test.setMediaConstraints([{audio: true, video: true}], []);
+ test.chain.replace("PC_LOCAL_GUM", [
+ function PC_LOCAL_GUM_CLONE() {
+ return getUserMedia(test.pcLocal.constraints[0]).then(stream => {
+ originalStream = stream;
+ localVideoOriginal =
+ createMediaElement("video", "local-original");
+ localVideoOriginal.srcObject = stream;
+ test.pcLocal.attachLocalStream(originalStream.clone());
+ });
+ }
+ ]);
+ test.chain.append([
+ function CHECK_ASSUMPTIONS() {
+ is(test.pcLocal.localMediaElements.length, 2,
+ "pcLocal should have one media element");
+ is(test.pcRemote.remoteMediaElements.length, 2,
+ "pcRemote should have one media element");
+ is(test.pcLocal._pc.getLocalStreams().length, 1,
+ "pcLocal should have one stream");
+ is(test.pcRemote._pc.getRemoteStreams().length, 1,
+ "pcRemote should have one stream");
+ },
+ async function CHECK_VIDEO() {
+ info("Checking video");
+ var h = new CaptureStreamTestHelper2D();
+ var localVideoClone = test.pcLocal.localMediaElements
+ .find(e => e instanceof HTMLVideoElement);
+ var remoteVideoClone = test.pcRemote.remoteMediaElements
+ .find(e => e instanceof HTMLVideoElement);
+
+ // We check a pixel somewhere away from the top left corner since
+ // MediaEngineFake puts semi-transparent time indicators there.
+ const offsetX = 50;
+ const offsetY = 50;
+ const threshold = 128;
+ const remoteDisabledColor = h.black;
+
+ // We're regarding black as disabled here, and we're setting the alpha
+ // channel of the pixel to 255 to disregard alpha when testing.
+ var checkVideoEnabled = video => h.waitForPixel(video,
+ px => (px[3] = 255, h.isPixelNot(px, h.black, threshold)),
+ { offsetX, offsetY }
+ );
+ var checkVideoDisabled = video => h.waitForPixel(video,
+ px => (px[3] = 255, h.isPixel(px, h.black, threshold)),
+ { offsetX, offsetY }
+ );
+
+ info("Checking local original enabled");
+ await checkVideoEnabled(localVideoOriginal);
+ info("Checking local clone enabled");
+ await checkVideoEnabled(localVideoClone);
+ info("Checking remote clone enabled");
+ await checkVideoEnabled(remoteVideoClone);
+
+ info("Disabling original");
+ originalStream.getVideoTracks()[0].enabled = false;
+
+ info("Checking local original disabled");
+ await checkVideoDisabled(localVideoOriginal);
+ info("Checking local clone enabled");
+ await checkVideoEnabled(localVideoClone);
+ info("Checking remote clone enabled");
+ await checkVideoEnabled(remoteVideoClone);
+
+ info("Re-enabling original; disabling clone");
+ originalStream.getVideoTracks()[0].enabled = true;
+ test.pcLocal._pc.getLocalStreams()[0].getVideoTracks()[0].enabled = false;
+
+ info("Checking local original enabled");
+ await checkVideoEnabled(localVideoOriginal);
+ info("Checking local clone disabled");
+ await checkVideoDisabled(localVideoClone);
+ info("Checking remote clone disabled");
+ await checkVideoDisabled(remoteVideoClone);
+ },
+ async function CHECK_AUDIO() {
+ info("Checking audio");
+ var ac = new AudioContext();
+ var localAnalyserOriginal = new AudioStreamAnalyser(ac, originalStream);
+ var localAnalyserClone =
+ new AudioStreamAnalyser(ac, test.pcLocal._pc.getLocalStreams()[0]);
+ var remoteAnalyserClone =
+ new AudioStreamAnalyser(ac, test.pcRemote._pc.getRemoteStreams()[0]);
+
+ var freq = localAnalyserOriginal.binIndexForFrequency(TEST_AUDIO_FREQ);
+ var checkAudioEnabled = analyser =>
+ analyser.waitForAnalysisSuccess(array => array[freq] > 200);
+ var checkAudioDisabled = analyser =>
+ analyser.waitForAnalysisSuccess(array => array[freq] < 50);
+
+ info("Checking local original enabled");
+ await checkAudioEnabled(localAnalyserOriginal);
+ info("Checking local clone enabled");
+ await checkAudioEnabled(localAnalyserClone);
+ info("Checking remote clone enabled");
+ await checkAudioEnabled(remoteAnalyserClone);
+
+ info("Disabling original");
+ originalStream.getAudioTracks()[0].enabled = false;
+
+ info("Checking local original disabled");
+ await checkAudioDisabled(localAnalyserOriginal);
+ info("Checking local clone enabled");
+ await checkAudioEnabled(localAnalyserClone);
+ info("Checking remote clone enabled");
+ await checkAudioEnabled(remoteAnalyserClone);
+
+ info("Re-enabling original; disabling clone");
+ originalStream.getAudioTracks()[0].enabled = true;
+ test.pcLocal._pc.getLocalStreams()[0].getAudioTracks()[0].enabled = false;
+
+ info("Checking local original enabled");
+ await checkAudioEnabled(localAnalyserOriginal);
+ info("Checking local clone disabled");
+ await checkAudioDisabled(localAnalyserClone);
+ info("Checking remote clone disabled");
+ await checkAudioDisabled(remoteAnalyserClone);
+ },
+ ]);
+ await test.run();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_trackless_sender_stats.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_trackless_sender_stats.html
new file mode 100644
index 0000000000..f0356f5655
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_trackless_sender_stats.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="stats.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1452673",
+ title: "Trackless RTCRtpSender.getStats()",
+ visible: true
+ });
+
+ // Calling getstats() on a trackless RTCRtpSender should yield an empty
+ // stats report. When track stats are added in the future, the stats
+ // for the removed tracks should continue to appear.
+
+ runNetworkTest(function (options) {
+ const test = new PeerConnectionTest(options);
+ test.chain.removeAfter("PC_REMOTE_WAIT_FOR_MEDIA_FLOW");
+ test.chain.append(
+ async function PC_LOCAL_AND_REMOTE_TRACKLESS_SENDER_STATS(test) {
+ await Promise.all([
+ waitForSyncedRtcp(test.pcLocal._pc),
+ waitForSyncedRtcp(test.pcRemote._pc),
+ ]);
+ let senders = test.pcLocal.getSenders();
+ let receivers = test.pcRemote.getReceivers();
+ is(senders.length, 2, "Have exactly two senders.");
+ is(receivers.length, 2, "Have exactly two receivers.");
+ for(let kind of ["audio", "video"]) {
+ is(senders.filter(s => s.track.kind == kind).length, 1,
+ "Exactly 1 sender of kind " + kind);
+ is(receivers.filter(r => r.track.kind == kind).length, 1,
+ "Exactly 1 receiver of kind " + kind);
+ }
+ // Remove tracks from senders
+ for (const sender of senders) {
+ await sender.replaceTrack(null);
+ is(sender.track, null, "Sender track removed");
+ let stats = await sender.getStats();
+ ok(stats instanceof window.RTCStatsReport, "Stats is instance of RTCStatsReport");
+ // Number of stats in the report. This should be 0.
+ is(stats.size, 0, "Trackless sender stats report is empty");
+ }
+ }
+ );
+ test.setMediaConstraints([{audio: true}, {video: true}], []);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioStreams.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioStreams.html
new file mode 100644
index 0000000000..7ea18ab3dd
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioStreams.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1091242",
+ title: "Multistream: Two audio streams"
+ });
+
+ runNetworkTest(function (options) {
+ const test = new PeerConnectionTest(options);
+ test.setMediaConstraints([{audio: true}, {audio: true}],
+ [{audio: true}, {audio: true}]);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioTracksInOneStream.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioTracksInOneStream.html
new file mode 100644
index 0000000000..99d4ad625a
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioTracksInOneStream.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1145407",
+ title: "Multistream: Two audio tracks in one stream"
+ });
+
+ runNetworkTest(function (options) {
+ const test = new PeerConnectionTest(options);
+ test.chain.insertAfter("PC_REMOTE_GET_OFFER", [
+ function PC_REMOTE_OVERRIDE_STREAM_IDS_IN_OFFER(test) {
+ test._local_offer.sdp = test._local_offer.sdp.replace(
+ /a=msid:[^\s]*/g,
+ "a=msid:foo");
+ }
+ ]);
+ test.chain.insertAfter("PC_LOCAL_GET_ANSWER", [
+ function PC_LOCAL_OVERRIDE_STREAM_IDS_IN_ANSWER(test) {
+ test._remote_answer.sdp = test._remote_answer.sdp.replace(
+ /a=msid:[^\s]*/g,
+ "a=msid:foo");
+ }
+ ]);
+ test.setMediaConstraints([{audio: true}, {audio: true}],
+ [{audio: true}, {audio: true}]);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioVideoStreams.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioVideoStreams.html
new file mode 100644
index 0000000000..5f4bd463d4
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioVideoStreams.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+
+ createHTML({
+ bug: "1091242",
+ title: "Multistream: Two audio streams, two video streams"
+ });
+
+ runNetworkTest(function (options) {
+ const test = new PeerConnectionTest(options);
+ test.setMediaConstraints([{audio: true}, {video: true}, {audio: true},
+ {video: true}],
+ [{audio: true}, {video: true}, {audio: true},
+ {video: true}]);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioVideoStreamsCombined.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioVideoStreamsCombined.html
new file mode 100644
index 0000000000..fcc9c6c8fa
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioVideoStreamsCombined.html
@@ -0,0 +1,70 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="stats.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+
+ createHTML({
+ bug: "1091242",
+ title: "Multistream: Two audio/video streams"
+ });
+
+ runNetworkTest(async (options) => {
+ // Disable platform encodre for SW MFT encoder causes some stats
+ // exceeding the test thresholds.
+ // E.g. inbound-rtp.packetsDiscarded value=118 >= 100.
+ await matchPlatformH264CodecPrefs();
+
+ const test = new PeerConnectionTest(options);
+ test.setMediaConstraints([{audio: true, video: true},
+ {audio: true, video: true}],
+ [{audio: true, video: true},
+ {audio: true, video: true}]);
+
+ // Test stats, including coalescing of codec stats.
+ test.chain.insertAfter("PC_LOCAL_WAIT_FOR_MEDIA_FLOW",
+ [PC_LOCAL_TEST_LOCAL_STATS]);
+
+ test.chain.insertAfter("PC_REMOTE_WAIT_FOR_MEDIA_FLOW",
+ [PC_REMOTE_TEST_REMOTE_STATS]);
+
+ const testCoalescedCodecStats = stats => {
+ is([...stats.values()].filter(({type}) => type.endsWith("rtp")).length,
+ 16,
+ "Expected: 4 outbound, 4 remote-inbound, 4 inbound, 4 remote-inbound");
+ const codecs = [...stats.values()]
+ .filter(({type}) => type == "codec")
+ .sort((a, b) => a.mimeType > b.mimeType);
+ is(codecs.length, 2, "Should have registered two codecs (coalesced)");
+ is(new Set(codecs.map(({transportId}) => transportId)).size, 1,
+ "Should have registered only one transport with BUNDLE");
+ const codecTypes = new Set(codecs.map(({codecType}) => codecType));
+ is(codecTypes.size, 1,
+ "Should have identical encode and decode configurations (and stats)");
+ is(codecTypes[0], undefined,
+ "Should have identical encode and decode configurations (and stats)");
+ is(codecs[0].mimeType.slice(0, 5), "audio",
+ "Should have registered an audio codec");
+ is(codecs[1].mimeType.slice(0, 5), "video",
+ "Should have registered a video codec");
+ };
+
+ test.chain.append([
+ async function PC_LOCAL_TEST_COALESCED_CODEC_STATS() {
+ testCoalescedCodecStats(await test.pcLocal._pc.getStats());
+ },
+ async function PC_REMOTE_TEST_COALESCED_CODEC_STATS() {
+ testCoalescedCodecStats(await test.pcRemote._pc.getStats());
+ },
+ ]);
+
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioVideoStreamsCombinedNoBundle.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioVideoStreamsCombinedNoBundle.html
new file mode 100644
index 0000000000..8b825db617
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_twoAudioVideoStreamsCombinedNoBundle.html
@@ -0,0 +1,107 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="stats.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ bug: "1225722",
+ title: "Multistream: Two audio/video streams without BUNDLE"
+});
+
+runNetworkTest(async (options = {}) => {
+ // Disable platform encodre for SW MFT encoder causes some stats
+ // exceeding the test thresholds.
+ // E.g. inbound-rtp.packetsDiscarded value=118 >= 100.
+ await matchPlatformH264CodecPrefs();
+
+ options.bundle = false;
+ const test = new PeerConnectionTest(options);
+ test.setMediaConstraints(
+ [{audio: true, video: true}, {audio: true, video: true}],
+ [{audio: true, video: true}, {audio: true, video: true}]
+ );
+
+ // Test stats, including that codec stats do not coalesce without BUNDLE.
+ const testNonBundledStats = async pc => {
+ // This is basically PC_*_TEST_*_STATS fleshed out, but uses
+ // sender/receiver.getStats instead of pc.getStats, since the codec stats
+ // code assumes at most one sender and at most one receiver.
+ await waitForSyncedRtcp(pc);
+ const senderPromises = pc.getSenders().map(obj => obj.getStats());
+ const receiverPromises = pc.getReceivers().map(obj => obj.getStats());
+ const senderStats = await Promise.all(senderPromises);
+ const receiverStats = await Promise.all(receiverPromises);
+ for (const stats of [...senderStats, ...receiverStats]) {
+ checkExpectedFields(stats);
+ pedanticChecks(stats);
+ }
+ for (const stats of senderStats) {
+ checkSenderStats(stats, 1);
+ }
+ };
+
+ test.chain.insertAfter("PC_LOCAL_WAIT_FOR_MEDIA_FLOW", [
+ async function PC_LOCAL_TEST_LOCAL_NONBUNDLED_STATS(test) {
+ await testNonBundledStats(test.pcLocal._pc);
+ },
+ ]);
+
+ test.chain.insertAfter("PC_REMOTE_WAIT_FOR_MEDIA_FLOW", [
+ async function PC_REMOTE_TEST_LOCAL_NONBUNDLED_STATS(test) {
+ await testNonBundledStats(test.pcRemote._pc);
+ },
+ ]);
+
+ const testNonCoalescedCodecStats = stats => {
+ const codecs = [...stats.values()]
+ .filter(({type}) => type == "codec");
+ is([...stats.values()].filter(({type}) => type.endsWith("rtp")).length, 16,
+ "Expected: 4 outbound, 4 remote-inbound, 4 inbound, 4 remote-inbound");
+ const codecTypes = new Set(codecs.map(({codecType}) => codecType));
+ is(codecTypes.size, 1,
+ "Should have identical encode and decode configurations (and stats)");
+ is(codecTypes[0], undefined,
+ "Should have identical encode and decode configurations (and stats)");
+ const transportIds = new Set(codecs.map(({transportId}) => transportId));
+ is(transportIds.size, 4,
+ "Should have registered four transports for two sendrecv streams");
+ for (const transportId of transportIds) {
+ is(codecs.filter(c => c.transportId == transportId).length, 1,
+ "Should have registered one codec per transport without BUNDLE");
+ }
+ for (const prefix of ["audio", "video"]) {
+ const prefixed = codecs.filter(c => c.mimeType.startsWith(prefix));
+ is(prefixed.length, 2, `Should have registered two ${prefix} codecs`);
+ if (prefixed.length == 2) {
+ is(prefixed[0].payloadType, prefixed[1].payloadType,
+ "same payloadType");
+ isnot(prefixed[0].transportId, prefixed[1].transportId,
+ "different transportIds");
+ is(prefixed[0].mimeType, prefixed[1].mimeType, "same mimeType");
+ is(prefixed[0].clockRate, prefixed[1].clockRate, "same clockRate");
+ is(prefixed[0].channels, prefixed[1].channels, "same channels");
+ is(prefixed[0].sdpFmtpLine, prefixed[1].sdpFmtpLine,
+ "same sdpFmtpLine");
+ }
+ }
+ };
+
+ test.chain.append([
+ async function PC_LOCAL_TEST_NON_COALESCED_CODEC_STATS() {
+ testNonCoalescedCodecStats(await test.pcLocal._pc.getStats());
+ },
+ async function PC_REMOTE_TEST_NON_COALESCED_CODEC_STATS() {
+ testNonCoalescedCodecStats(await test.pcRemote._pc.getStats());
+ },
+ ]);
+
+ return test.run();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_twoVideoStreams.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_twoVideoStreams.html
new file mode 100644
index 0000000000..0ab180cc55
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_twoVideoStreams.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1091242",
+ title: "Multistream: Two video streams"
+ });
+
+ runNetworkTest(function (options) {
+ const test = new PeerConnectionTest(options);
+ test.setMediaConstraints([{video: true}, {video: true}],
+ [{video: true}, {video: true}]);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_twoVideoTracksInOneStream.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_twoVideoTracksInOneStream.html
new file mode 100644
index 0000000000..4eaf8b3f48
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_twoVideoTracksInOneStream.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1145407",
+ title: "Multistream: Two video tracks in offerer stream"
+ });
+
+ runNetworkTest(function (options) {
+ const test = new PeerConnectionTest(options);
+ test.chain.insertAfter("PC_REMOTE_GET_OFFER", [
+ function PC_REMOTE_OVERRIDE_STREAM_IDS_IN_OFFER(test) {
+ test._local_offer.sdp = test._local_offer.sdp.replace(
+ /a=msid:[^\s]*/g,
+ "a=msid:foo");
+ }
+ ]);
+ test.chain.insertAfter("PC_LOCAL_GET_ANSWER", [
+ function PC_LOCAL_OVERRIDE_STREAM_IDS_IN_ANSWER(test) {
+ test._remote_answer.sdp = test._remote_answer.sdp.replace(
+ /a=msid:[^\s]*/g,
+ "a=msid:foo");
+ }
+ ]);
+ test.setMediaConstraints([{video: true}, {video: true}],
+ [{video: true}, {video: true}]);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_verifyAudioAfterRenegotiation.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_verifyAudioAfterRenegotiation.html
new file mode 100644
index 0000000000..86ef6d4678
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_verifyAudioAfterRenegotiation.html
@@ -0,0 +1,99 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1166832",
+ title: "Renegotiation: verify audio after renegotiation"
+ });
+
+ runNetworkTest(function (options) {
+ const test = new PeerConnectionTest(options);
+ const helper = new AudioStreamHelper();
+
+ test.chain.append([
+ function CHECK_ASSUMPTIONS() {
+ is(test.pcLocal.localMediaElements.length, 1,
+ "pcLocal should have one media element");
+ is(test.pcRemote.remoteMediaElements.length, 1,
+ "pcRemote should have one media element");
+ is(test.pcLocal._pc.getLocalStreams().length, 1,
+ "pcLocal should have one stream");
+ is(test.pcRemote._pc.getRemoteStreams().length, 1,
+ "pcRemote should have one stream");
+ },
+ function CHECK_AUDIO() {
+ return Promise.resolve()
+ .then(() => info("Checking local audio enabled"))
+ .then(() => helper.checkAudioFlowing(test.pcLocal._pc.getLocalStreams()[0]))
+ .then(() => info("Checking remote audio enabled"))
+ .then(() => helper.checkAudioFlowing(test.pcRemote._pc.getRemoteStreams()[0]))
+
+ .then(() => test.pcLocal._pc.getLocalStreams()[0].getAudioTracks()[0].enabled = false)
+
+ .then(() => info("Checking local audio disabled"))
+ .then(() => helper.checkAudioNotFlowing(test.pcLocal._pc.getLocalStreams()[0]))
+ .then(() => info("Checking remote audio disabled"))
+ .then(() => helper.checkAudioNotFlowing(test.pcRemote._pc.getRemoteStreams()[0]))
+ }
+ ]);
+
+ addRenegotiation(test.chain,
+ [
+ function PC_LOCAL_ADD_SECOND_STREAM(test) {
+ test.setMediaConstraints([{audio: true}],
+ []);
+ return test.pcLocal.getAllUserMediaAndAddStreams([{audio: true}]);
+ },
+ ]
+ );
+
+ test.chain.append([
+ function CHECK_ASSUMPTIONS2() {
+ is(test.pcLocal.localMediaElements.length, 2,
+ "pcLocal should have two media elements");
+ is(test.pcRemote.remoteMediaElements.length, 2,
+ "pcRemote should have two media elements");
+ is(test.pcLocal._pc.getLocalStreams().length, 2,
+ "pcLocal should have two streams");
+ is(test.pcRemote._pc.getRemoteStreams().length, 2,
+ "pcRemote should have two streams");
+ },
+ function RE_CHECK_AUDIO() {
+ return Promise.resolve()
+ .then(() => info("Checking local audio enabled"))
+ .then(() => helper.checkAudioNotFlowing(test.pcLocal._pc.getLocalStreams()[0]))
+ .then(() => info("Checking remote audio enabled"))
+ .then(() => helper.checkAudioNotFlowing(test.pcRemote._pc.getRemoteStreams()[0]))
+
+ .then(() => info("Checking local2 audio enabled"))
+ .then(() => helper.checkAudioFlowing(test.pcLocal._pc.getLocalStreams()[1]))
+ .then(() => info("Checking remote2 audio enabled"))
+ .then(() => helper.checkAudioFlowing(test.pcRemote._pc.getRemoteStreams()[1]))
+
+ .then(() => test.pcLocal._pc.getLocalStreams()[1].getAudioTracks()[0].enabled = false)
+ .then(() => test.pcLocal._pc.getLocalStreams()[0].getAudioTracks()[0].enabled = true)
+
+ .then(() => info("Checking local2 audio disabled"))
+ .then(() => helper.checkAudioNotFlowing(test.pcLocal._pc.getLocalStreams()[1]))
+ .then(() => info("Checking remote2 audio disabled"))
+ .then(() => helper.checkAudioNotFlowing(test.pcRemote._pc.getRemoteStreams()[1]))
+
+ .then(() => info("Checking local audio enabled"))
+ .then(() => helper.checkAudioFlowing(test.pcLocal._pc.getLocalStreams()[0]))
+ .then(() => info("Checking remote audio enabled"))
+ .then(() => helper.checkAudioFlowing(test.pcRemote._pc.getRemoteStreams()[0]))
+ }
+ ]);
+
+ test.setMediaConstraints([{audio: true}], []);
+ return test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_verifyDescriptions.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_verifyDescriptions.html
new file mode 100644
index 0000000000..f685f7c99a
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_verifyDescriptions.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1264479",
+ title: "PeerConnection verify current and pending descriptions"
+ });
+
+ const pc1 = new RTCPeerConnection();
+ const pc2 = new RTCPeerConnection();
+
+ var add = (pc, can, failed) => can && pc.addIceCandidate(can).catch(failed);
+ pc1.onicecandidate = e => add(pc2, e.candidate, generateErrorCallback());
+ pc2.onicecandidate = e => add(pc1, e.candidate, generateErrorCallback());
+
+
+ runNetworkTest(function() {
+ const v1 = createMediaElement('video', 'v1');
+ const v2 = createMediaElement('video', 'v2');
+
+ return navigator.mediaDevices.getUserMedia({ video: true, audio: true })
+ .then(stream => (v1.srcObject = stream).getTracks().forEach(t => pc1.addTrack(t, stream)))
+ .then(() => pc1.createOffer({})) // check that createOffer accepts arg.
+ .then(offer => pc1.setLocalDescription(offer))
+ .then(() => {
+ ok(!pc1.currentLocalDescription, "pc1 currentLocalDescription is empty");
+ ok(pc1.pendingLocalDescription, "pc1 pendingLocalDescription is set");
+ ok(pc1.localDescription, "pc1 localDescription is set");
+ })
+ .then(() => pc2.setRemoteDescription(pc1.localDescription))
+ .then(() => {
+ ok(!pc2.currentRemoteDescription, "pc2 currentRemoteDescription is empty");
+ ok(pc2.pendingRemoteDescription, "pc2 pendingRemoteDescription is set");
+ ok(pc2.remoteDescription, "pc2 remoteDescription is set");
+ })
+ .then(() => pc2.createAnswer({})) // check that createAnswer accepts arg.
+ .then(answer => pc2.setLocalDescription(answer))
+ .then(() => {
+ ok(pc2.currentLocalDescription, "pc2 currentLocalDescription is set");
+ ok(!pc2.pendingLocalDescription, "pc2 pendingLocalDescription is empty");
+ ok(pc2.localDescription, "pc2 localDescription is set");
+ })
+ .then(() => pc1.setRemoteDescription(pc2.localDescription))
+ .then(() => {
+ ok(pc1.currentRemoteDescription, "pc1 currentRemoteDescription is set");
+ ok(!pc1.pendingRemoteDescription, "pc1 pendingRemoteDescription is empty");
+ ok(pc1.remoteDescription, "pc1 remoteDescription is set");
+ });
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_verifyVideoAfterRenegotiation.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_verifyVideoAfterRenegotiation.html
new file mode 100644
index 0000000000..8d4155ddff
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_verifyVideoAfterRenegotiation.html
@@ -0,0 +1,123 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1166832",
+ title: "Renegotiation: verify video after renegotiation"
+ });
+
+runNetworkTest(async () => {
+ // [TODO] re-enable HW decoder after bug 1526207 is fixed.
+ if (navigator.userAgent.includes("Android")) {
+ await pushPrefs(["media.navigator.mediadatadecoder_vpx_enabled", false],
+ ["media.webrtc.hw.h264.enabled", false]);
+ }
+
+ const test = new PeerConnectionTest();
+
+ const h1 = new CaptureStreamTestHelper2D(50, 50);
+ const canvas1 = h1.createAndAppendElement('canvas', 'source_canvas1');
+ let stream1;
+ let vremote1;
+
+ const h2 = new CaptureStreamTestHelper2D(50, 50);
+ let canvas2;
+ let stream2;
+ let vremote2;
+
+ test.setMediaConstraints([{video: true}], []);
+ test.chain.replace("PC_LOCAL_GUM", [
+ function DRAW_INITIAL_LOCAL_GREEN(test) {
+ h1.drawColor(canvas1, h1.green);
+ },
+ function PC_LOCAL_CANVAS_CAPTURESTREAM(test) {
+ stream1 = canvas1.captureStream(0);
+ test.pcLocal.attachLocalStream(stream1);
+ let i = 0;
+ return setInterval(function() {
+ try {
+ info("draw " + i ? "green" : "red");
+ h1.drawColor(canvas1, i ? h1.green : h1.red);
+ i = 1 - i;
+ stream1.requestFrame();
+ if (stream2 != null) {
+ h2.drawColor(canvas2, i ? h2.green : h2.blue);
+ stream2.requestFrame();
+ }
+ } catch (e) {
+ // ignore; stream might have shut down, and we don't bother clearing
+ // the setInterval.
+ }
+ }, 500);
+ }
+ ]);
+
+ test.chain.append([
+ function FIND_REMOTE_VIDEO() {
+ vremote1 = test.pcRemote.remoteMediaElements[0];
+ ok(!!vremote1, "Should have remote video element for pcRemote");
+ },
+ function WAIT_FOR_REMOTE_GREEN() {
+ return h1.pixelMustBecome(vremote1, h1.green, {
+ threshold: 128,
+ infoString: "pcRemote's remote should become green",
+ });
+ },
+ function WAIT_FOR_REMOTE_RED() {
+ return h1.pixelMustBecome(vremote1, h1.red, {
+ threshold: 128,
+ infoString: "pcRemote's remote should become red",
+ });
+ }
+ ]);
+
+ addRenegotiation(test.chain,
+ [
+ function PC_LOCAL_ADD_SECOND_STREAM(test) {
+ canvas2 = h2.createAndAppendElement('canvas', 'source_canvas2');
+ h2.drawColor(canvas2, h2.blue);
+ stream2 = canvas2.captureStream(0);
+
+ // can't use test.pcLocal.getAllUserMediaAndAddStreams([{video: true}]);
+ // because it doesn't let us substitute the capture stream
+ test.pcLocal.attachLocalStream(stream2);
+ }
+ ]
+ );
+
+ test.chain.append([
+ function FIND_REMOTE2_VIDEO() {
+ vremote2 = test.pcRemote.remoteMediaElements[1];
+ ok(!!vremote2, "Should have remote2 video element for pcRemote");
+ },
+ function WAIT_FOR_REMOTE2_BLUE() {
+ return h2.pixelMustBecome(vremote2, h2.blue, {
+ threshold: 128,
+ infoString: "pcRemote's remote2 should become blue",
+ });
+ },
+ function DRAW_NEW_LOCAL_GREEN(test) {
+ stream1.requestFrame();
+ h1.drawColor(canvas1, h1.green);
+ },
+ function WAIT_FOR_REMOTE1_GREEN() {
+ return h1.pixelMustBecome(vremote1, h1.green, {
+ threshold: 128,
+ infoString: "pcRemote's remote1 should become green",
+ });
+ }
+ ]);
+
+ await test.run();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_videoCodecs.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_videoCodecs.html
new file mode 100644
index 0000000000..7a245b5d8c
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_videoCodecs.html
@@ -0,0 +1,142 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="stats.js"></script>
+ <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1395853",
+ title: "Verify video content over WebRTC for every video codec",
+ });
+
+ async function testVideoCodec(options = {}, codec) {
+ const test = new PeerConnectionTest(options);
+ test.setMediaConstraints([{video: true}], []);
+
+ let payloadType;
+ test.chain.insertBefore("PC_LOCAL_SET_LOCAL_DESCRIPTION", [
+ function PC_LOCAL_FILTER_OUT_CODECS() {
+ const otherCodec = codecs.find(c => c != codec);
+ const otherId = sdputils.findCodecId(test.originalOffer.sdp, otherCodec.name, otherCodec.offset);
+ const otherRtpmapMatcher = new RegExp(`a=rtpmap:${otherId}.*\\r\\n`, "gi");
+
+ const id = sdputils.findCodecId(test.originalOffer.sdp, codec.name, codec.offset);
+ payloadType = Number(id);
+ if (codec.offset) {
+ isnot(id, sdputils.findCodecId(test.originalOffer.sdp, codec.name, 0),
+ "Different offsets should return different payload types");
+ }
+ test.originalOffer.sdp =
+ sdputils.removeAllButPayloadType(test.originalOffer.sdp, id);
+
+ ok(!test.originalOffer.sdp.match(new RegExp(`m=.*UDP/TLS/RTP/SAVPF.* ${otherId}[^0-9]`, "gi")),
+ `Other codec ${otherId} should be removed after filtering`);
+ ok(test.originalOffer.sdp.match(new RegExp(`m=.*UDP/TLS/RTP/SAVPF.* ${id}[^0-9]`, "gi")),
+ `Tested codec ${id} should remain after filtering`);
+
+ // We only set it now, or the framework would remove non-H264 codecs
+ // for us.
+ options.h264 = codec.name == "H264";
+ },
+ ]);
+
+ test.chain.insertAfter("PC_LOCAL_WAIT_FOR_MEDIA_FLOW",
+ [PC_LOCAL_TEST_LOCAL_STATS]);
+
+ test.chain.insertAfter("PC_REMOTE_WAIT_FOR_MEDIA_FLOW",
+ [PC_REMOTE_TEST_REMOTE_STATS]);
+
+ test.chain.append([
+ async function PC_LOCAL_TEST_CODEC() {
+ const stats = await test.pcLocal._pc.getStats();
+ let codecCount = 0;
+ stats.forEach(stat => {
+ if (stat.type == "codec") {
+ is(codecCount++, 0, "expected only one encode codec stat");
+ is(stat.payloadType, payloadType, "payloadType as expected");
+ is(stat.mimeType, `video/${codec.name}`, "mimeType as expected");
+ is(stat.codecType, "encode", "codecType as expected");
+ }
+ });
+ },
+ async function PC_REMOTE_TEST_CODEC() {
+ const stats = await test.pcRemote._pc.getStats();
+ let codecCount = 0;
+ stats.forEach(stat => {
+ if (stat.type == "codec") {
+ is(codecCount++, 0, "expected only one decode codec stat");
+ is(stat.payloadType, payloadType, "payloadType as expected");
+ is(stat.mimeType, `video/${codec.name}`, "mimeType as expected");
+ is(stat.codecType, "decode", "codecType as expected");
+ }
+ });
+ },
+ async function CHECK_VIDEO_FLOW() {
+ try {
+ const h = new VideoStreamHelper();
+ await h.checkVideoPlaying(
+ test.pcRemote.remoteMediaElements[0],
+ 10, 10, 128);
+ ok(true, `Got video flow for codec ${codec.name}, offset ${codec.offset}`);
+ } catch(e) {
+ ok(false, `No video flow for codec ${codec.name}, offset ${codec.offset}: ${e}`);
+ }
+ },
+ ]);
+
+ await test.run();
+ }
+
+ // We match the name against the sdp to figure out the payload type,
+ // so all other present codecs can be removed.
+ // Use `offset` when there are multiple instances of a codec expected in an sdp.
+ const codecs = [
+ { name: "VP8" },
+ { name: "VP9" },
+ { name: "H264" },
+ { name: "H264", offset: 1 },
+ ];
+
+ runNetworkTest(async (options) => {
+ // This test expects the video being captured will change color. Use fake
+ // video device as loopback does not currently change.
+ await pushPrefs(
+ ['media.video_loopback_dev', ''],
+ ['media.navigator.streams.fake', true]);
+ for (let codec of codecs) {
+ info(`Testing video for codec ${codec.name} offset ${codec.offset}`);
+ try {
+ let enc = SpecialPowers.getBoolPref('media.webrtc.platformencoder');
+ let dec = SpecialPowers.getBoolPref('media.navigator.mediadatadecoder_h264_enabled');
+ if (codec.name == "H264") {
+ await matchPlatformH264CodecPrefs();
+ if (codec.offset == 1) {
+ // Force fake GMP codec for H.264 mode 0 because not all platforms
+ // support slice size control. Re-enable it after
+ // a. SW encoder fallback support (bug 1726617), and
+ // b. returning valid bitstream from fake GMP encoder (bug 1509012).
+ await pushPrefs(
+ ['media.webrtc.platformencoder', false],
+ ['media.navigator.mediadatadecoder_h264_enabled', false],
+ );
+ }
+ }
+ await testVideoCodec(options, codec);
+ await pushPrefs(
+ ['media.webrtc.platformencoder', enc],
+ ['media.navigator.mediadatadecoder_h264_enabled', dec],
+ );
+ } catch(e) {
+ ok(false, `Error in test for codec ${codec.name}: ${e}\n${e.stack}`);
+ }
+ info(`Tested video for codec ${codec.name}`);
+ }
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_videoRenegotiationInactiveAnswer.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_videoRenegotiationInactiveAnswer.html
new file mode 100644
index 0000000000..b77633493d
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_videoRenegotiationInactiveAnswer.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+ <script type="application/javascript" src="sdpUtils.js"></script>
+ <script type="application/javascript" src="/tests/dom/canvas/test/captureStream_common.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ bug: "1213773",
+ title: "Renegotiation: answerer uses a=inactive for video"
+ });
+
+ runNetworkTest(async (options) => {
+ // [TODO] re-enable HW decoder after bug 1526207 is fixed.
+ if (navigator.userAgent.includes("Android")) {
+ await pushPrefs(["media.navigator.mediadatadecoder_vpx_enabled", false],
+ ["media.webrtc.hw.h264.enabled", false]);
+ }
+
+ const emitter = new VideoFrameEmitter();
+ const helper = new VideoStreamHelper();
+
+ const test = new PeerConnectionTest(options);
+
+ test.chain.replace("PC_LOCAL_GUM", [
+ function PC_LOCAL_CANVAS_CAPTURESTREAM(test) {
+ test.pcLocal.attachLocalStream(emitter.stream());
+ emitter.start();
+ }
+ ]);
+
+ var haveFirstUnmuteEvent;
+
+ test.chain.insertBefore("PC_REMOTE_SET_LOCAL_DESCRIPTION", [
+ function PC_REMOTE_SETUP_ONUNMUTE_1() {
+ haveFirstUnmuteEvent = haveEvent(test.pcRemote._pc.getReceivers()[0].track, "unmute");
+ }
+ ]);
+
+ test.chain.append([
+ function PC_REMOTE_CHECK_VIDEO_UNMUTED() {
+ return haveFirstUnmuteEvent;
+ },
+ function PC_REMOTE_WAIT_FOR_FRAMES() {
+ var vremote = test.pcRemote.remoteMediaElements[0];
+ ok(vremote, "Should have remote video element for pcRemote");
+ return addFinallyToPromise(helper.checkVideoPlaying(vremote))
+ .finally(() => emitter.stop());
+ }
+ ]);
+
+ addRenegotiation(test.chain, []);
+
+ test.chain.insertAfter("PC_LOCAL_GET_ANSWER", [
+ function PC_LOCAL_REWRITE_REMOTE_SDP_INACTIVE(test) {
+ test._remote_answer.sdp =
+ sdputils.setAllMsectionsInactive(test._remote_answer.sdp);
+ }
+ ], false, 1);
+
+ test.chain.append([
+ function PC_REMOTE_ENSURE_NO_FRAMES() {
+ var vremote = test.pcRemote.remoteMediaElements[0];
+ ok(vremote, "Should have remote video element for pcRemote");
+ emitter.start();
+ return addFinallyToPromise(helper.checkVideoPaused(vremote))
+ .finally(() => emitter.stop());
+ },
+ ]);
+
+ test.chain.remove("PC_REMOTE_CHECK_STATS", 1);
+ test.chain.remove("PC_LOCAL_CHECK_STATS", 1);
+
+ addRenegotiation(test.chain, []);
+
+ test.chain.append([
+ function PC_REMOTE_WAIT_FOR_FRAMES_2() {
+ var vremote = test.pcRemote.remoteMediaElements[0];
+ ok(vremote, "Should have remote video element for pcRemote");
+ emitter.start();
+ return addFinallyToPromise(helper.checkVideoPlaying(vremote))
+ .finally(() => emitter.stop());
+ }
+ ]);
+
+ test.setMediaConstraints([{video: true}], []);
+ await test.run();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_peerConnection_webAudio.html b/dom/media/webrtc/tests/mochitests/test_peerConnection_webAudio.html
new file mode 100644
index 0000000000..1d695ecbfa
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_webAudio.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ bug: "1081819",
+ title: "WebAudio on both input and output side of peerconnection"
+});
+
+// This tests WebAudio (a 700Hz OscillatorNode) as input to a PeerConnection.
+// It also tests that a PeerConnection works as input to WebAudio as the remote
+// stream is connected to an AnalyserNode and compared to the source node.
+
+runNetworkTest(function() {
+ const test = new PeerConnectionTest();
+ test.audioContext = new AudioContext();
+ test.setMediaConstraints([{audio: true}], []);
+ test.chain.replace("PC_LOCAL_GUM", [
+ function PC_LOCAL_WEBAUDIO_SOURCE(test) {
+ const oscillator = test.audioContext.createOscillator();
+ oscillator.type = 'sine';
+ oscillator.frequency.value = 700;
+ oscillator.start();
+ const dest = test.audioContext.createMediaStreamDestination();
+ oscillator.connect(dest);
+ test.pcLocal.attachLocalStream(dest.stream);
+ }
+ ]);
+ test.chain.append([
+ function CHECK_AUDIO_FLOW(test) {
+ return test.pcRemote.checkReceivingToneFrom(test.audioContext, test.pcLocal);
+ }
+ ]);
+ return test.run();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_selftest.html b/dom/media/webrtc/tests/mochitests/test_selftest.html
new file mode 100644
index 0000000000..3f1ce1402d
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_selftest.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="pc.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+ createHTML({
+ title: "Self-test of harness functions",
+ visible: true
+ });
+
+function TEST(test) {}
+
+var catcher = func => {
+ try {
+ func();
+ return null;
+ } catch (e) {
+ return e.message;
+ }
+};
+
+runNetworkTest(() => {
+ var test = new PeerConnectionTest();
+ test.setMediaConstraints([{video: true}], [{video: true}]);
+ is(catcher(() => test.chain.replace("PC_LOCAL_SET_LOCAL_DESCRIPTION", TEST)),
+ null, "test.chain.replace works");
+ is(catcher(() => test.chain.replace("FOO", TEST)),
+ "Unknown test: FOO", "test.chain.replace catches typos");
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_setSinkId.html b/dom/media/webrtc/tests/mochitests/test_setSinkId.html
new file mode 100644
index 0000000000..0d85114a0e
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_setSinkId.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+
+<script>
+ createHTML({
+ title: "SetSinkId in HTMLMediaElement",
+ bug: "934425",
+ });
+
+ const memoryReportPath = 'explicit/media/media-manager-aggregates';
+
+ /**
+ * Run a test to verify set sink id in audio element.
+ */
+ runTest(async () => {
+ await pushPrefs(["media.setsinkid.enabled", true]);
+
+ if (!SpecialPowers.getCharPref("media.audio_loopback_dev", "")) {
+ ok(false, "No loopback device set by framework. Try --use-test-media-devices");
+ return;
+ }
+
+ // Expose an audio output device.
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ await navigator.mediaDevices.selectAudioOutput();
+
+ const allDevices = await navigator.mediaDevices.enumerateDevices();
+ const audioDevices = allDevices.filter(({kind}) => kind == 'audiooutput');
+ is(audioDevices.length, 1, "Number of output devices found");
+
+ const audio = createMediaElement("audio", "audio");
+ document.body.appendChild(audio);
+
+ is(audio.sinkId, "", "Initial value is empty string");
+
+ const p = audio.setSinkId(audioDevices[0].deviceId);
+ is(audio.sinkId, "", "Value is unchanged upon function return");
+ is(await p, undefined, "promise resolves with undefined");
+ is(audio.sinkId, audioDevices[0].deviceId, `Sink device is set, id: ${audio.sinkId}`);
+
+ await audio.setSinkId(audioDevices[0].deviceId);
+ ok(true, `Sink device is set for 2nd time for the same id: ${audio.sinkId}`);
+
+ try {
+ await audio.setSinkId("dummy sink id");
+ ok(false, "Never enter here, this must fail");
+ } catch (error) {
+ ok(true, `Set sink id expected to fail: ${error}`);
+ is(error.name, "NotFoundError", "Verify correct error");
+ }
+
+ const {usage: usage1} =
+ await collectMemoryUsage(memoryReportPath); // Provided by head.js
+
+ ok(usage1 > 0, "MediaManager memory usage should be non-zero to store \
+device ids after enumerateDevices");
+
+ const p2 = audio.setSinkId("");
+ is(audio.sinkId, audioDevices[0].deviceId,
+ 'sinkId after setSinkId("") return');
+ is(await p2, undefined,
+ "promise resolution value when sinkId parameter is empty");
+ is(audio.sinkId, "", 'sinkId after setSinkId("") resolution');
+
+ await audio.setSinkId(audioDevices[0].deviceId);
+
+ const {usage: usage2, reportCount} =
+ await collectMemoryUsage(memoryReportPath);
+ is(reportCount, 1,
+ 'Expect only one MediaManager to report in content processes.');
+ is(usage2, usage1, "MediaManager memory usage should return to previous \
+value after promise resolution");
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_setSinkId_default_addTrack.html b/dom/media/webrtc/tests/mochitests/test_setSinkId_default_addTrack.html
new file mode 100644
index 0000000000..64db4cad7c
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_setSinkId_default_addTrack.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+
+<script>
+ createHTML({
+ title: "HTMLMediaElement.setSinkId with default device and adding a track",
+ bug: "1661649",
+ });
+
+ /**
+ * Run a test to verify set sink id in audio element.
+ */
+ runTest(async () => {
+ await pushPrefs(["media.setsinkid.enabled", true]);
+
+ // Expose an audio output device.
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ await navigator.mediaDevices.selectAudioOutput();
+
+ const allDevices = await navigator.mediaDevices.enumerateDevices();
+ const audioDevices = allDevices.filter(({kind}) => kind == 'audiooutput');
+ info(`Found ${audioDevices.length} output devices`);
+ isnot(audioDevices.length, 0, "Found output devices");
+
+ const audio = createMediaElement("audio", "audio");
+ document.body.appendChild(audio);
+
+ audio.srcObject = await navigator.mediaDevices.getUserMedia({audio: true});
+ audio.play();
+
+ await audio.setSinkId(audioDevices[0].deviceId);
+ await audio.setSinkId("");
+ is(audio.sinkId, "", "sinkId restored to default");
+
+ audio.srcObject.addTrack((await navigator.mediaDevices.getUserMedia({audio: true})).getTracks()[0]);
+
+ await wait(0);
+
+ for (let t of audio.srcObject.getTracks()) {
+ t.stop();
+ }
+ });
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_setSinkId_preMutedElement.html b/dom/media/webrtc/tests/mochitests/test_setSinkId_preMutedElement.html
new file mode 100644
index 0000000000..fb65c3312f
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_setSinkId_preMutedElement.html
@@ -0,0 +1,100 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+createHTML({
+ title: "Test changing sink and muting before the MediaStream is set",
+ bug: "1651049",
+ visible: true
+});
+
+let getOutputDeviceId = async () => {
+ let devices = await navigator.mediaDevices.enumerateDevices();
+ let audios = devices.filter(d => d.kind == "audiooutput");
+ ok(audios.length, "One or more output devices found.");
+ return audios[0].deviceId;
+}
+
+let verifyAudioTone = async (ac, stream, freq) => {
+ const toneAnalyser = new AudioStreamAnalyser(ac, stream);
+ return toneAnalyser.waitForAnalysisSuccess(array => {
+ const lowerFreq = freq / 2;
+ const upperFreq = freq + 1000;
+ const lowerMag = array[toneAnalyser.binIndexForFrequency(lowerFreq)];
+ const freqMag = array[toneAnalyser.binIndexForFrequency(freq)];
+ const upperMag = array[toneAnalyser.binIndexForFrequency(upperFreq)];
+ info("Audio tone expected. "
+ + lowerFreq + ": " + lowerMag + ", "
+ + freq + ": " + freqMag + ", "
+ + upperFreq + ": " + upperMag);
+ return lowerMag < 50 && freqMag > 200 && upperMag < 50;
+ });
+}
+
+let verifyNoAudioTone = async (ac, stream, freq) => {
+ const toneAnalyser = new AudioStreamAnalyser(ac, stream);
+ // repeat check 100 times to make sure that it is muted.
+ let retryCnt = 0;
+ return toneAnalyser.waitForAnalysisSuccess(array => {
+ const lowerFreq = freq / 2;
+ const upperFreq = freq + 1000;
+ const lowerMag = array[toneAnalyser.binIndexForFrequency(lowerFreq)];
+ const freqMag = array[toneAnalyser.binIndexForFrequency(freq)];
+ const upperMag = array[toneAnalyser.binIndexForFrequency(upperFreq)];
+ info("No audio tone expected. "
+ + lowerFreq + ": " + lowerMag + ", "
+ + freq + ": " + freqMag + ", "
+ + upperFreq + ": " + upperMag);
+ return lowerMag == 0 && freqMag == 0 && upperMag == 0 && ++retryCnt == 100;
+ });
+}
+
+runTest(async () => {
+ let audioDevice = SpecialPowers.getCharPref("media.audio_loopback_dev", "");
+ if (!audioDevice) {
+ todo(false, "No loopback device set by framework. Try --use-test-media-devices");
+ return;
+ }
+
+ await pushPrefs(["media.setsinkid.enabled", true]);
+
+ // Implicitly expose the loopback device sink by opening the source in the
+ // same group.
+ const verifyStream = await getUserMedia({audio: true});
+ // We gonna test our tone, stop the auto created one.
+ DefaultLoopbackTone.stop();
+
+ let sinkId = await getOutputDeviceId();
+ isnot(sinkId, "", "SinkId is not null");
+
+ let audioElement = createMediaElement('audio', 'audioElement');
+ audioElement.muted = true;
+ await audioElement.setSinkId(sinkId);
+ isnot(audioElement.sinkId, "", "sinkId property of the element is not null");
+
+ // The test stream is a sine tone of 1000 Hz
+ let ac = new AudioContext();
+ const frequency = 2000;
+ let stream = createOscillatorStream(ac, frequency);
+ await verifyAudioTone(ac, stream, frequency);
+
+ audioElement.srcObject = stream;
+ audioElement.play();
+
+ // Verify the silent output using the loopback device.
+ await verifyNoAudioTone(ac, verifyStream, frequency);
+ info("output is muted");
+
+ // Clean up
+ audioElement.pause();
+ audioElement.srcObject = null;
+ verifyStream.getTracks()[0].stop();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/test_unfocused_pref.html b/dom/media/webrtc/tests/mochitests/test_unfocused_pref.html
new file mode 100644
index 0000000000..22df020f7c
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/test_unfocused_pref.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<script>
+"use strict";
+
+createHTML({
+ // This pref exists only for a partner testing framework without WebDriver
+ // switch-to-window nor SpecialPowers to set the active window.
+ // Prefer "focusmanager.testmode".
+ title: "Test media.devices.unfocused.enabled",
+ bug: "1740824"
+});
+
+const blank_url = "/tests/docshell/test/navigation/blank.html";
+
+async function resolveOnEvent(target, name) {
+ return new Promise(r => target.addEventListener(name, r, {once: true}));
+}
+
+runTest(async () => {
+ ok(document.hasFocus(), "This test expects initial focus on the document.");
+ // 'resizable' is requested for a separate OS window on relevant platforms
+ // so that this test tests OS focus changes rather than document visibility.
+ const other = window.open(blank_url, "", "resizable");
+ SimpleTest.registerCleanupFunction(() => {
+ other.close();
+ return SimpleTest.promiseFocus(window);
+ });
+ await Promise.all([
+ resolveOnEvent(window, 'blur'),
+ SimpleTest.promiseFocus(other),
+ pushPrefs(["media.devices.unfocused.enabled", true]),
+ ]);
+ ok(!document.hasFocus(), "!document.hasFocus()");
+ await navigator.mediaDevices.enumerateDevices();
+ ok(true, "enumerateDevices() completes without focus.");
+ // The focus requirement with media.devices.unfocused.enabled false
+ // (default) is tested in
+ // testing/web-platform/mozilla/tests/mediacapture-streams/enumerateDevices-without-focus.https.html
+});
+
+</script>
+</body>
+</html>
diff --git a/dom/media/webrtc/tests/mochitests/turnConfig.js b/dom/media/webrtc/tests/mochitests/turnConfig.js
new file mode 100644
index 0000000000..1267de4ec5
--- /dev/null
+++ b/dom/media/webrtc/tests/mochitests/turnConfig.js
@@ -0,0 +1,16 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* An example of how to specify two TURN server configs:
+ *
+ * Note: If turn URL uses FQDN rather then an IP address the TURN relay
+ * verification step in checkStatsIceConnectionType might fail.
+ *
+ * var turnServers = {
+ * local: { iceServers: [{"username":"mozilla","credential":"mozilla","url":"turn:10.0.0.1"}] },
+ * remote: { iceServers: [{"username":"firefox","credential":"firefox","url":"turn:10.0.0.2"}] }
+ * };
+ */
+
+var turnServers = {};
diff --git a/dom/media/webrtc/third_party_build/README.md b/dom/media/webrtc/third_party_build/README.md
new file mode 100644
index 0000000000..9151445c87
--- /dev/null
+++ b/dom/media/webrtc/third_party_build/README.md
@@ -0,0 +1,17 @@
+# Vendoring libwebrtc and the fast-forward process
+
+Most of the important information about this process is contained on the fast-forward
+automation wiki page
+[here](https://wiki.mozilla.org/Media/WebRTC/libwebrtc_Update_Process/automation_plan).
+
+To skip the history and details and go directly to starting the libwebrtc fast-foward
+process, go to the
+[Operation Checklist](https://wiki.mozilla.org/Media/WebRTC/libwebrtc_Update_Process/automation_plan#Operation_Checklist).
+
+# Fixing errors reported in scripts
+
+In most cases, the scripts report errors including suggestions on how to resolve the
+issue. If you're seeing an error message referring you to this README.md file, the
+likely issue is that you're missing environment variables that should be set in a
+config_env file in .moz-fast-forward. The default for that file can be found at
+dom/media/webrtc/third_party_build/default_config_env.
diff --git a/dom/media/webrtc/third_party_build/build_no_op_commits.sh b/dom/media/webrtc/third_party_build/build_no_op_commits.sh
new file mode 100644
index 0000000000..eff61a5eb5
--- /dev/null
+++ b/dom/media/webrtc/third_party_build/build_no_op_commits.sh
@@ -0,0 +1,126 @@
+#!/bin/bash
+
+function show_error_msg()
+{
+ echo "*** ERROR *** $? line $1 $0 did not complete successfully!"
+ echo "$ERROR_HELP"
+}
+ERROR_HELP=""
+
+# Print an Error message if `set -eE` causes the script to exit due to a failed command
+trap 'show_error_msg $LINENO' ERR
+
+source dom/media/webrtc/third_party_build/use_config_env.sh
+
+echo "MOZ_LIBWEBRTC_SRC: $MOZ_LIBWEBRTC_SRC"
+
+# After this point:
+# * eE: All commands should succeed.
+# * u: All variables should be defined before use.
+# * o pipefail: All stages of all pipes should succeed.
+set -eEuo pipefail
+
+CURRENT_DIR=`pwd`
+cd $MOZ_LIBWEBRTC_SRC
+
+MANUAL_INTERVENTION_COMMIT_FILE="$TMP_DIR/manual_commits.txt"
+rm -f $MANUAL_INTERVENTION_COMMIT_FILE
+
+# Find the common commit between our previous work branch and trunk
+CURRENT_RELEASE_BASE=`git merge-base branch-heads/$MOZ_PRIOR_UPSTREAM_BRANCH_HEAD_NUM master`
+
+# Write no-op files for the cherry-picked release branch commits. For more
+# details on what this is doing, see make_upstream_revert_noop.sh.
+COMMITS=`git log -r $CURRENT_RELEASE_BASE..branch-heads/$MOZ_PRIOR_UPSTREAM_BRANCH_HEAD_NUM --format='%h'`
+for commit in $COMMITS; do
+
+ echo "Processing release branch commit $commit for no-op handling"
+
+ # Don't process the commit if the commit message is missing the customary
+ # line that shows which upstream commit is being cherry-picked.
+ CNT=`git show $commit | grep "cherry picked from commit" | wc -l | tr -d " " || true`
+ if [ $CNT != 1 ]; then
+ # record the commit to list at the end of this script as
+ # 'needing intervention'
+ echo " no cherry-pick info found, skipping commit $commit"
+ echo "$commit" >> $MANUAL_INTERVENTION_COMMIT_FILE
+ continue
+ fi
+
+ CHERRY_PICK_COMMIT=`git show $commit | grep "cherry picked from commit" | tr -d "()" | awk '{ print $5; }'`
+ SHORT_SHA=`git show --name-only $CHERRY_PICK_COMMIT --format='%h' | head -1`
+ echo " commit $commit cherry-picks $SHORT_SHA"
+
+ echo "We already cherry-picked this when we vendored $commit." \
+ > $STATE_DIR/$SHORT_SHA.no-op-cherry-pick-msg
+
+done
+
+# This section checks for commits that may have been cherry-picked in more
+# than one release branch.
+TARGET_RELEASE_BASE=`git merge-base $MOZ_TARGET_UPSTREAM_BRANCH_HEAD master`
+NEW_COMMITS=`git log -r $TARGET_RELEASE_BASE..$MOZ_TARGET_UPSTREAM_BRANCH_HEAD --format='%h'`
+
+# Convert the files that we've already generated for no-op detection into
+# something that we can use as a regular expression for searching.
+KNOWN_NO_OP_COMMITS=`cd $STATE_DIR ; \
+ ls *.no-op-cherry-pick-msg \
+ | sed 's/\.no-op-cherry-pick-msg//' \
+ | paste -sd '|' /dev/stdin`
+
+for commit in $NEW_COMMITS; do
+
+ echo "Processing next release branch commit $commit for no-op handling"
+
+ # Don't process the commit if the commit message is missing the customary
+ # line that shows which upstream commit is being cherry-picked.
+ CNT=`git show $commit | grep "cherry picked from commit" | wc -l | tr -d " " || true`
+ if [ $CNT != 1 ]; then
+ # record the commit to list at the end of this script as
+ # 'needing intervention'
+ echo " no cherry-pick info found, skipping commit $commit"
+ echo "$commit" >> $MANUAL_INTERVENTION_COMMIT_FILE
+ continue
+ fi
+
+ CHERRY_PICK_COMMIT=`git show $commit | grep "cherry picked from commit" | tr -d "()" | awk '{ print $5; }'`
+ SHORT_SHA=`git show --name-only $CHERRY_PICK_COMMIT --format='%h' | head -1`
+
+ # The trick here is that we only want to include no-op processing for the
+ # commits that appear both here _and_ in the previous release's cherry-pick
+ # commits. We check the known list of no-op commits to see if it was
+ # cherry picked in the previous release branch and then create another
+ # file for the new release branch commit that will ultimately be a no-op.
+ if [[ "$SHORT_SHA" =~ ^($KNOWN_NO_OP_COMMITS)$ ]]; then
+ echo " commit $commit cherry-picks $SHORT_SHA"
+ cp $STATE_DIR/$SHORT_SHA.no-op-cherry-pick-msg $STATE_DIR/$commit.no-op-cherry-pick-msg
+ fi
+
+done
+
+if [ ! -f $MANUAL_INTERVENTION_COMMIT_FILE ]; then
+ echo "No commits require manual intervention"
+ exit
+fi
+
+echo $"
+Each of the following commits requires manual intervention to
+verify the source of the cherry-pick or there may be errors
+reported during the fast-forward processing. Without this
+intervention, the common symptom is that the vendored commit
+file count (0) will not match the upstream commit file count.
+"
+
+for commit in `cat $MANUAL_INTERVENTION_COMMIT_FILE`; do
+ SUMMARY=`git show --oneline --name-only $commit | head -1`
+ echo " '$SUMMARY'"
+done
+
+echo $"
+To manually create the no-op tracking files needed,
+run the following command for each commit in question:
+ ( export UPSTREAM_COMMIT=\"{sha-of-upstream-commit}\" ; \\
+ export PICKED_COMMIT=\"{sha-of-already-used-commit}\" ; \\
+ echo \"We already cherry-picked this when we vendored \$PICKED_COMMIT.\" \\
+ > $STATE_DIR/\$UPSTREAM_COMMIT.no-op-cherry-pick-msg )
+"
diff --git a/dom/media/webrtc/third_party_build/commit-build-file-changes.sh b/dom/media/webrtc/third_party_build/commit-build-file-changes.sh
new file mode 100644
index 0000000000..2b4c791fca
--- /dev/null
+++ b/dom/media/webrtc/third_party_build/commit-build-file-changes.sh
@@ -0,0 +1,47 @@
+#!/bin/bash
+
+function show_error_msg()
+{
+ echo "*** ERROR *** $? line $1 $0 did not complete successfully!"
+ echo "$ERROR_HELP"
+}
+ERROR_HELP=""
+
+# Print an Error message if `set -eE` causes the script to exit due to a failed command
+trap 'show_error_msg $LINENO' ERR
+
+# All commands should be printed as they are executed
+# set -x
+
+# After this point:
+# * eE: All commands should succede.
+# * u: All variables should be defined before use.
+# * o pipefail: All stages of all pipes should succede.
+set -eEuo pipefail
+
+MOZ_BUILD_CHANGE_CNT=`hg status third_party/libwebrtc | wc -l | tr -d " "`
+echo "MOZ_BUILD_CHANGE_CNT: $MOZ_BUILD_CHANGE_CNT"
+if [ "x$MOZ_BUILD_CHANGE_CNT" != "x0" ]; then
+ CURRENT_COMMIT_SHA=`hg id -i | sed 's/+//'`
+ COMMIT_DESC=`hg --config alias.log=log log -T '{desc|firstline}' -r $CURRENT_COMMIT_SHA`
+
+ # since we have build file changes, touch the CLOBBER file
+ cat CLOBBER | egrep "^#|^$" > CLOBBER.new
+ mv CLOBBER.new CLOBBER
+ echo "Modified build files in third_party/libwebrtc - $COMMIT_DESC" >> CLOBBER
+
+ ADD_CNT=`hg status -nu third_party/libwebrtc | wc -l | tr -d " "`
+ DEL_CNT=`hg status -nd third_party/libwebrtc | wc -l | tr -d " "`
+ if [ "x$ADD_CNT" != "x0" ]; then
+ hg status -nu third_party/libwebrtc | xargs hg add
+ fi
+ if [ "x$DEL_CNT" != "x0" ]; then
+ hg status -nd third_party/libwebrtc | xargs hg rm
+ fi
+
+ hg commit -m \
+ "$COMMIT_DESC - moz.build file updates" \
+ third_party/libwebrtc CLOBBER
+fi
+
+echo "Done in $0"
diff --git a/dom/media/webrtc/third_party_build/default_config_env b/dom/media/webrtc/third_party_build/default_config_env
new file mode 100644
index 0000000000..a50694f0e3
--- /dev/null
+++ b/dom/media/webrtc/third_party_build/default_config_env
@@ -0,0 +1,42 @@
+#!/bin/bash
+
+# Edit {path-to} to match the location of your copy of Mozilla's
+# fork of libwebrtc (at https://github.com/mozilla/libwebrtc).
+export MOZ_LIBWEBRTC_SRC=$STATE_DIR/moz-libwebrtc
+
+# Fast-forwarding each Chromium version of libwebrtc should be done
+# under a separate bugzilla bug. This bug number is used when crafting
+# the commit summary as each upstream commit is vendored into the
+# mercurial repository. The bug used for the v106 fast-forward was
+# 1800920.
+export MOZ_FASTFORWARD_BUG="1833237"
+
+# MOZ_NEXT_LIBWEBRTC_MILESTONE and MOZ_NEXT_FIREFOX_REL_TARGET are
+# not used during fast-forward processing, but facilitate generating this
+# default config. To generate an default config for the next update, run
+# bash dom/media/webrtc/third_party_build/update_default_config_env.sh
+export MOZ_NEXT_LIBWEBRTC_MILESTONE=112
+export MOZ_NEXT_FIREFOX_REL_TARGET=116
+
+# For Chromium release branches, see:
+# https://chromiumdash.appspot.com/branches
+
+# Chromium's v111 release branch was 5563. This is used to pre-stack
+# the previous release branch's commits onto the appropriate base commit
+# (the first common commit between trunk and the release branch).
+export MOZ_PRIOR_UPSTREAM_BRANCH_HEAD_NUM="5563"
+
+# New target release branch for v112 is branch-heads/5615. This is used
+# to calculate the next upstream commit.
+export MOZ_TARGET_UPSTREAM_BRANCH_HEAD="branch-heads/5615"
+
+# For local development 'mozpatches' is fine for a branch name, but when
+# pushing the patch stack to github, it should be named something like
+# 'moz-mods-chr112-for-rel116'.
+export MOZ_LIBWEBRTC_BRANCH="mozpatches"
+
+# After elm has been merged to mozilla-central, the patch stack in
+# moz-libwebrtc should be pushed to github. The script
+# push_official_branch.sh uses this branch name when pushing to the
+# public repo.
+export MOZ_LIBWEBRTC_OFFICIAL_BRANCH="moz-mods-chr112-for-rel116"
diff --git a/dom/media/webrtc/third_party_build/detect_upstream_revert.sh b/dom/media/webrtc/third_party_build/detect_upstream_revert.sh
new file mode 100644
index 0000000000..2dc868952d
--- /dev/null
+++ b/dom/media/webrtc/third_party_build/detect_upstream_revert.sh
@@ -0,0 +1,93 @@
+#!/bin/bash
+
+function show_error_msg()
+{
+ echo "*** ERROR *** $? line $1 $0 did not complete successfully!"
+ echo "$ERROR_HELP"
+}
+ERROR_HELP=""
+
+# Print an Error message if `set -eE` causes the script to exit due to a failed command
+trap 'show_error_msg $LINENO' ERR
+
+source dom/media/webrtc/third_party_build/use_config_env.sh
+
+# If DEBUG_GEN is set all commands should be printed as they are executed
+if [ ! "x$DEBUG_GEN" = "x" ]; then
+ set -x
+fi
+
+if [ "x$MOZ_LIBWEBRTC_SRC" = "x" ]; then
+ echo "MOZ_LIBWEBRTC_SRC is not defined, see README.md"
+ exit
+fi
+
+if [ -d $MOZ_LIBWEBRTC_SRC ]; then
+ echo "MOZ_LIBWEBRTC_SRC is $MOZ_LIBWEBRTC_SRC"
+else
+ echo "Path $MOZ_LIBWEBRTC_SRC is not found, see README.md"
+ exit
+fi
+
+if [ "x$MOZ_LIBWEBRTC_BRANCH" = "x" ]; then
+ echo "MOZ_LIBWEBRTC_BRANCH is not defined, see README.md"
+ exit
+fi
+
+if [ "x$AUTO_FIX_REVERT_AS_NOOP" = "x" ]; then
+ AUTO_FIX_REVERT_AS_NOOP="0"
+fi
+
+find_base_commit
+find_next_commit
+
+MOZ_LIBWEBRTC_COMMIT_MSG=`cd $MOZ_LIBWEBRTC_SRC ; \
+git show --name-only --oneline $MOZ_LIBWEBRTC_NEXT_BASE \
+ | head -1 | sed 's/[^ ]* //'`
+
+echo "MOZ_LIBWEBRTC_BASE: $MOZ_LIBWEBRTC_BASE"
+echo "MOZ_LIBWEBRTC_NEXT_BASE: $MOZ_LIBWEBRTC_NEXT_BASE"
+echo "MOZ_LIBWEBRTC_COMMIT_MSG: $MOZ_LIBWEBRTC_COMMIT_MSG"
+export MOZ_LIBWEBRTC_REVERT_SHA=`cd $MOZ_LIBWEBRTC_SRC ; \
+git log --oneline -r $MOZ_LIBWEBRTC_BASE..$MOZ_TARGET_UPSTREAM_BRANCH_HEAD \
+ | grep -F "Revert \"$MOZ_LIBWEBRTC_COMMIT_MSG" \
+ | tail -1 | awk '{print $1;}' || true`
+
+echo "MOZ_LIBWEBRTC_REVERT_SHA: $MOZ_LIBWEBRTC_REVERT_SHA"
+
+if [ "x$MOZ_LIBWEBRTC_REVERT_SHA" == "x" ]; then
+ echo "no revert commit detected"
+ exit
+fi
+
+if [ "x$AUTO_FIX_REVERT_AS_NOOP" = "x1" ]; then
+ echo "AUTO_FIX_REVERT_AS_NOOP detected, fixing land/revert pair automatically"
+ bash $SCRIPT_DIR/make_upstream_revert_noop.sh
+ exit
+fi
+
+echo $"
+The next upstream commit has a corresponding future \"Revert\" commit.
+
+There are 2 common ways forward in this situation:
+1. If you're relatively certain there will not be rebase conflicts in the
+ github repo ($MOZ_LIBWEBRTC_SRC), simply run:
+ SKIP_NEXT_REVERT_CHK=1 bash $SCRIPT_DIR/loop-ff.sh
+
+2. The surer method for no rebase conflicts is to cherry-pick both the
+ next commit, and the commit that reverts the next commit onto the
+ bottom of our patch stack in github. This pushes the likely rebase
+ conflict into the future when the upstream fix is relanded, but
+ ensures we only have to deal with the conflict once. The following
+ commands will add the necessary commits to the bottom of our patch
+ stack in github, and leave indicator files in the home directory that
+ help loop-ff know when to invoke special no-op commit handling:
+
+ MOZ_LIBWEBRTC_BASE=$MOZ_LIBWEBRTC_BASE \\
+ MOZ_LIBWEBRTC_NEXT_BASE=$MOZ_LIBWEBRTC_NEXT_BASE \\
+ MOZ_LIBWEBRTC_REVERT_SHA=$MOZ_LIBWEBRTC_REVERT_SHA \\
+ bash $SCRIPT_DIR/make_upstream_revert_noop.sh
+
+ SKIP_NEXT_REVERT_CHK=1 bash $SCRIPT_DIR/loop-ff.sh
+"
+exit 1
diff --git a/dom/media/webrtc/third_party_build/elm_arcconfig.patch b/dom/media/webrtc/third_party_build/elm_arcconfig.patch
new file mode 100644
index 0000000000..46adb9c6f1
--- /dev/null
+++ b/dom/media/webrtc/third_party_build/elm_arcconfig.patch
@@ -0,0 +1,10 @@
+diff --git a/.arcconfig b/.arcconfig
+--- a/.arcconfig
++++ b/.arcconfig
+@@ -1,5 +1,5 @@
+ {
+ "phabricator.uri" : "https://phabricator.services.mozilla.com/",
+- "repository.callsign": "MOZILLACENTRAL",
++ "repository.callsign": "ELM",
+ "history.immutable": false
+ }
diff --git a/dom/media/webrtc/third_party_build/elm_rebase.sh b/dom/media/webrtc/third_party_build/elm_rebase.sh
new file mode 100644
index 0000000000..4d53373d35
--- /dev/null
+++ b/dom/media/webrtc/third_party_build/elm_rebase.sh
@@ -0,0 +1,247 @@
+#!/bin/bash
+
+# This script exists to help with the rebase process on elm. It rebases
+# each patch individually to make it easier to fix rebase conflicts
+# without jeopardizing earlier, sucessfully rebased commits. In order to
+# limit rebase conflicts around generated moz.build files, it regenerates
+# moz.build file commits. It also ensures any commits with 'FLOAT' in the
+# commit summary are pushed to the top of the fast-forward stack to help
+# the sheriffs more easily merge our commit stack from elm to moz-central.
+#
+# Occasionally, there will be upstream vendored commits that break the
+# build file generation with follow on commits that fix that error. In
+# order to allow the rebase process to work more smoothly, it is possible
+# to annotate a commit with the string '(skip-generation)' and normal
+# build file generation (detected with changes to BUILD.gn files) is
+# disabled for that commit. The script outputs instructions for handling
+# this situation.
+#
+# Note: the very first rebase operation will require some manual
+# intervention. The user will need to provide, at minimum, the commit that
+# corresponds to moz-central upon which the fast-forward stack is based.
+# It may also be necessary to provide the first commit of the
+# fast-forward stack. Example:
+# MOZ_BOTTOM_FF=30f0afb7e4c5 \
+# MOZ_CURRENT_CENTRAL=cad1bd47c273 \
+# bash dom/media/webrtc/third_party_build/elm_rebase.sh
+#
+# Assumes the top of the fast-forward stack to rebase is the current revision,
+# ".".
+
+function show_error_msg()
+{
+ echo "*** ERROR *** $? line $1 $0 did not complete successfully!"
+ echo "$ERROR_HELP"
+}
+ERROR_HELP=""
+
+# Print an Error message if `set -eE` causes the script to exit due to a failed command
+trap 'show_error_msg $LINENO' ERR
+
+source dom/media/webrtc/third_party_build/use_config_env.sh
+
+GENERATION_ERROR=$"
+Generating build files has failed. The most common reason for this
+failure is that the current commit has an upcoming '(fix-xxxxxx)' commit
+that will then allow the build file generation to complete. If the
+current situation seems to fit that pattern, adding a line with
+'(skip-generation)' to the commit message will ensure that future rebase
+operations do not attempt to generate build files for this commit. It may
+be as simple as running the following commands:
+ HGPLAIN=1 hg log -T '{desc}' -r tip > $TMP_DIR/commit_message.txt
+ ed -s $TMP_DIR/commit_message.txt <<< $'3i\n(skip-generation)\n\n.\nw\nq'
+ hg commit --amend -l $TMP_DIR/commit_message.txt
+ bash $0
+"
+COMMIT_LIST_FILE=$TMP_DIR/rebase-commit-list.txt
+export HGPLAIN=1
+
+# After this point:
+# * eE: All commands should succeed.
+# * o pipefail: All stages of all pipes should succeed.
+set -eEo pipefail
+
+if [ -f $STATE_DIR/rebase_resume_state ]; then
+ source $STATE_DIR/rebase_resume_state
+else
+
+ if [ "x" == "x$MOZ_TOP_FF" ]; then
+ MOZ_TOP_FF=`hg log -r . -T"{node|short}"`
+
+ ERROR_HELP=$"
+The topmost commit to be rebased is not in the public phase. Should it be
+pushed to elm first? If this is intentional, please rerun the command and pass
+it in explicitly:
+ MOZ_TOP_FF=$MOZ_TOP_FF bash $0
+"
+ if [[ $(hg phase -r .) != *public ]]; then
+ echo "$ERROR_HELP"
+ exit 1
+ fi
+ ERROR_HELP=""
+
+ ERROR_HELP=$"
+The topmost commit to be rebased is public but has descendants. If those
+descendants should not be rebased, please rerun the command and pass the commit
+in explicitly:
+ MOZ_TOP_FF=$MOZ_TOP_FF bash $0
+"
+ if [ "x" != "x$(hg log -r 'descendants(.) and !.' -T'{node|short}')" ]; then
+ echo "$ERROR_HELP"
+ exit 1
+ fi
+ ERROR_HELP=""
+ fi
+
+ ERROR_HELP=$"
+An error here is likely because no revision for central is found.
+One possible reason for this is this is your first rebase operation.
+To 'bootstrap' the first rebase operation, please find the
+moz-central commit that the vendoring commits is based on, and
+rerun the command:
+ MOZ_CURRENT_CENTRAL={central-sha} bash $0
+
+You may also need to provide the bottom commit of the fast-forward
+stack. The bottom commit means the commit following central. This
+could be the sha of the .arcconfig commit if it is the bottom commit.
+That command looks like:
+ MOZ_BOTTOM_FF={base-sha} MOZ_CURRENT_CENTRAL={central-sha} bash $0
+"
+ if [ "x" == "x$MOZ_CURRENT_CENTRAL" ]; then
+ MOZ_CURRENT_CENTRAL=`hg log -r central -T"{node|short}"`
+ fi
+ if [ "x" == "x$MOZ_BOTTOM_FF" ]; then
+ MOZ_BOTTOM_FF=`hg log -r $MOZ_CURRENT_CENTRAL~-1 -T"{node|short}"`
+ fi
+ ERROR_HELP=""
+
+ if [ "x" == "x$MOZ_BOTTOM_FF" ]; then
+ echo "No value found for the bottom commit of the fast-forward commit stack."
+ exit 1
+ fi
+
+ # After this point:
+ # * eE: All commands should succeed.
+ # * u: All variables should be defined before use.
+ # * o pipefail: All stages of all pipes should succeed.
+ set -eEuo pipefail
+
+ hg pull central
+ MOZ_NEW_CENTRAL=`hg log -r central -T"{node|short}"`
+
+ echo "moz-central in elm is currently $MOZ_CURRENT_CENTRAL"
+ echo "bottom of fast-foward tree is $MOZ_BOTTOM_FF"
+ echo "top of fast-forward tree (webrtc-fast-forward) is $MOZ_TOP_FF"
+ echo "new target for elm rebase $MOZ_NEW_CENTRAL (tip of moz-central)"
+
+ hg log -T '{rev}:{node|short} {desc|firstline}\n' \
+ -r $MOZ_BOTTOM_FF::$MOZ_TOP_FF > $COMMIT_LIST_FILE
+
+ # move all FLOAT lines to end of file, and delete the "empty" tilde line
+ # line at the beginning
+ ed -s $COMMIT_LIST_FILE <<< $'g/- FLOAT -/m$\ng/^~$/d\nw\nq'
+
+ MOZ_BOOKMARK=`date "+webrtc-fast-forward-%Y-%m-%d--%H-%M"`
+ hg bookmark -r elm $MOZ_BOOKMARK
+
+ hg update $MOZ_NEW_CENTRAL
+
+ # pre-work is complete, let's write out a temporary config file that allows
+ # us to resume
+ echo $"export MOZ_CURRENT_CENTRAL=$MOZ_CURRENT_CENTRAL
+export MOZ_BOTTOM_FF=$MOZ_BOTTOM_FF
+export MOZ_TOP_FF=$MOZ_TOP_FF
+export MOZ_NEW_CENTRAL=$MOZ_NEW_CENTRAL
+export MOZ_BOOKMARK=$MOZ_BOOKMARK
+" > $STATE_DIR/rebase_resume_state
+fi # if [ -f $STATE_DIR/rebase_resume_state ]; then ; else
+
+# grab all commits
+COMMITS=`cat $COMMIT_LIST_FILE | awk '{print $1;}'`
+
+echo -n "Commits: "
+for commit in $COMMITS; do
+echo -n "$commit "
+done
+echo ""
+
+for commit in $COMMITS; do
+ echo "Processing $commit"
+ FULL_COMMIT_LINE=`head -1 $COMMIT_LIST_FILE`
+
+ function remove_commit () {
+ echo "Removing from list '$FULL_COMMIT_LINE'"
+ ed -s $COMMIT_LIST_FILE <<< $'1d\nw\nq'
+ }
+
+ IS_BUILD_COMMIT=`hg log -T '{desc|firstline}' -r $commit \
+ | grep "file updates" | wc -l | tr -d " " || true`
+ echo "IS_BUILD_COMMIT: $IS_BUILD_COMMIT"
+ if [ "x$IS_BUILD_COMMIT" != "x0" ]; then
+ echo "Skipping $commit:"
+ hg log -T '{desc|firstline}' -r $commit
+ remove_commit
+ continue
+ fi
+
+ IS_SKIP_GEN_COMMIT=`hg log --verbose \
+ -r $commit \
+ | grep "skip-generation" | wc -l | tr -d " " || true`
+ echo "IS_SKIP_GEN_COMMIT: $IS_SKIP_GEN_COMMIT"
+
+ echo "Generate patch for: $commit"
+ hg export -r $commit > $TMP_DIR/rebase.patch
+
+ echo "Import patch for $commit"
+ hg import $TMP_DIR/rebase.patch || \
+ ( hg log -T '{desc}' -r $commit > $TMP_DIR/rebase_commit_message.txt ; \
+ remove_commit ; \
+ echo "Error importing: '$FULL_COMMIT_LINE'" ; \
+ echo "Please fix import errors, then:" ; \
+ echo " hg commit -l $TMP_DIR/rebase_commit_message.txt" ; \
+ echo " bash $0" ; \
+ exit 1 )
+
+ remove_commit
+
+ if [ "x$IS_SKIP_GEN_COMMIT" != "x0" ]; then
+ echo "Skipping build generation for $commit"
+ continue
+ fi
+
+ MODIFIED_BUILD_RELATED_FILE_CNT=`hg diff -c tip --stat \
+ --include 'third_party/libwebrtc/**BUILD.gn' \
+ --include 'third_party/libwebrtc/webrtc.gni' \
+ --include 'dom/media/webrtc/third_party_build/gn-configs/webrtc.json' \
+ | wc -l | tr -d " "`
+ echo "MODIFIED_BUILD_RELATED_FILE_CNT: $MODIFIED_BUILD_RELATED_FILE_CNT"
+ if [ "x$MODIFIED_BUILD_RELATED_FILE_CNT" != "x0" ]; then
+ echo "Regenerate build files"
+ ./mach python python/mozbuild/mozbuild/gn_processor.py \
+ dom/media/webrtc/third_party_build/gn-configs/webrtc.json || \
+ ( echo "$GENERATION_ERROR" ; exit 1 )
+
+ MOZ_BUILD_CHANGE_CNT=`hg status third_party/libwebrtc \
+ --include 'third_party/libwebrtc/**moz.build' | wc -l | tr -d " "`
+ if [ "x$MOZ_BUILD_CHANGE_CNT" != "x0" ]; then
+ bash dom/media/webrtc/third_party_build/commit-build-file-changes.sh
+ NEWEST_COMMIT=`hg log -T '{desc|firstline}' -r tip`
+ echo "NEWEST_COMMIT: $NEWEST_COMMIT"
+ echo "NEWEST_COMMIT: $NEWEST_COMMIT" >> $LOG_DIR/rebase-build-changes-commits.log
+ fi
+ echo "Done generating build files"
+ fi
+
+ echo "Done processing $commit"
+done
+
+rm $STATE_DIR/rebase_resume_state
+
+REMAINING_STEPS=$"
+The rebase process is complete. The following steps must be completed manually:
+ ./mach bootstrap --application=browser --no-system-changes
+ ./mach build
+ hg push -r tip --force
+ hg push -B $MOZ_BOOKMARK
+"
+echo "$REMAINING_STEPS"
diff --git a/dom/media/webrtc/third_party_build/extract-for-git.py b/dom/media/webrtc/third_party_build/extract-for-git.py
new file mode 100644
index 0000000000..d2701d7dff
--- /dev/null
+++ b/dom/media/webrtc/third_party_build/extract-for-git.py
@@ -0,0 +1,145 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+import argparse
+import os
+import re
+import subprocess
+
+# This script extracts commits that touch third party webrtc code so they can
+# be imported into Git. It filters out commits that are not part of upstream
+# code and rewrites the paths to match upstream. Finally, the commits are
+# combined into a mailbox file that can be applied with `git am`.
+LIBWEBRTC_DIR = "third_party/libwebrtc"
+
+
+def build_commit_list(revset, env):
+ """Build commit list from the specified revset.
+
+ The revset can be a single revision, e.g. 52bb9bb94661, or a range,
+ e.g. 8c08a5bb8a99::52bb9bb94661, or any other valid revset
+ (check hg help revset). Only commits that touch libwebrtc are included.
+ """
+ res = subprocess.run(
+ ["hg", "log", "-r", revset, "-M", "--template", "{node}\n", LIBWEBRTC_DIR],
+ capture_output=True,
+ text=True,
+ env=env,
+ )
+ return [line.strip() for line in res.stdout.strip().split("\n")]
+
+
+def extract_author_date(sha1, env):
+ res = subprocess.run(
+ ["hg", "log", "-r", sha1, "--template", "{author}|{date|isodate}"],
+ capture_output=True,
+ text=True,
+ env=env,
+ )
+ return res.stdout.split("|")
+
+
+def extract_description(sha1, env):
+ res = subprocess.run(
+ ["hg", "log", "-r", sha1, "--template", "{desc}"],
+ capture_output=True,
+ text=True,
+ env=env,
+ )
+ return res.stdout
+
+
+def extract_commit(sha1, env):
+ res = subprocess.run(
+ ["hg", "log", "-r", sha1, "-pg", "--template", "\n"],
+ capture_output=True,
+ text=True,
+ env=env,
+ )
+ return res.stdout
+
+
+def filter_nonwebrtc(commit):
+ filtered = []
+ skipping = False
+ for line in commit.split("\n"):
+ # Extract only patches affecting libwebrtc, but avoid commits that
+ # touch build, which is tracked by a separate repo, or that affect
+ # moz.build files which are code generated.
+ if (
+ line.startswith("diff --git a/" + LIBWEBRTC_DIR)
+ and not line.startswith("diff --git a/" + LIBWEBRTC_DIR + "/build")
+ and not line.startswith("diff --git a/" + LIBWEBRTC_DIR + "/third_party")
+ and not line.startswith(
+ "diff --git a/" + LIBWEBRTC_DIR + "/moz-patch-stack"
+ )
+ and not line.endswith("moz.build")
+ ):
+ skipping = False
+ elif line.startswith("diff --git"):
+ skipping = True
+
+ if not skipping:
+ filtered.append(line)
+ return "\n".join(filtered)
+
+
+def fixup_paths(commit):
+ # make sure we only rewrite paths in the diff-related or rename lines
+ commit = re.sub(
+ f"^rename (from|to) {LIBWEBRTC_DIR}/", "rename \\1 ", commit, flags=re.MULTILINE
+ )
+ return re.sub(f"( [ab])/{LIBWEBRTC_DIR}/", "\\1/", commit)
+
+
+def write_as_mbox(sha1, author, date, description, commit, ofile):
+ # Use same magic date as git format-patch
+ ofile.write("From {} Mon Sep 17 00:00:00 2001\n".format(sha1))
+ ofile.write("From: {}\n".format(author))
+ ofile.write("Date: {}\n".format(date))
+ description = description.split("\n")
+ ofile.write("Subject: {}\n".format(description[0]))
+ ofile.write("\n".join(description[1:]))
+ ofile.write(
+ "\nMercurial Revision: https://hg.mozilla.org/mozilla-central/rev/{}\n".format(
+ sha1
+ )
+ )
+ ofile.write(commit)
+ ofile.write("\n")
+ ofile.write("\n")
+
+
+if __name__ == "__main__":
+ commits = []
+ parser = argparse.ArgumentParser(
+ description="Format commits for upstream libwebrtc"
+ )
+ parser.add_argument(
+ "revsets", metavar="revset", type=str, nargs="+", help="A revset to process"
+ )
+ parser.add_argument(
+ "--target", choices=("libwebrtc", "build", "third_party"), default="libwebrtc"
+ )
+ args = parser.parse_args()
+
+ if args.target != "libwebrtc":
+ LIBWEBRTC_DIR = os.path.join(LIBWEBRTC_DIR, args.target)
+
+ # must run 'hg' with HGPLAIN=1 to ensure aliases don't interfere with
+ # command output.
+ env = os.environ.copy()
+ env["HGPLAIN"] = "1"
+
+ for revset in args.revsets:
+ commits.extend(build_commit_list(revset, env))
+
+ with open("mailbox.patch", "w") as ofile:
+ for sha1 in commits:
+ author, date = extract_author_date(sha1, env)
+ description = extract_description(sha1, env)
+ filtered_commit = filter_nonwebrtc(extract_commit(sha1, env))
+ if len(filtered_commit) == 0:
+ continue
+ fixedup_commit = fixup_paths(filtered_commit)
+ write_as_mbox(sha1, author, date, description, fixedup_commit, ofile)
diff --git a/dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh b/dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
new file mode 100644
index 0000000000..9ef40a41ac
--- /dev/null
+++ b/dom/media/webrtc/third_party_build/fast-forward-libwebrtc.sh
@@ -0,0 +1,256 @@
+#!/bin/bash
+
+function show_error_msg()
+{
+ echo "*** ERROR *** $? line $1 $0 did not complete successfully!"
+ echo "$ERROR_HELP"
+}
+ERROR_HELP=""
+
+# Print an Error message if `set -eE` causes the script to exit due to a failed command
+trap 'show_error_msg $LINENO' ERR
+
+source dom/media/webrtc/third_party_build/use_config_env.sh
+
+# If DEBUG_GEN is set all commands should be printed as they are executed
+if [ ! "x$DEBUG_GEN" = "x" ]; then
+ set -x
+fi
+
+if [ "x$MOZ_LIBWEBRTC_SRC" = "x" ]; then
+ echo "MOZ_LIBWEBRTC_SRC is not defined, see README.md"
+ exit
+fi
+
+if [ -d $MOZ_LIBWEBRTC_SRC ]; then
+ echo "MOZ_LIBWEBRTC_SRC is $MOZ_LIBWEBRTC_SRC"
+else
+ echo "Path $MOZ_LIBWEBRTC_SRC is not found, see README.md"
+ exit
+fi
+
+if [ "x$MOZ_LIBWEBRTC_BRANCH" = "x" ]; then
+ echo "MOZ_LIBWEBRTC_BRANCH is not defined, see README.md"
+ exit
+fi
+
+if [ "x$HANDLE_NOOP_COMMIT" = "x" ]; then
+ HANDLE_NOOP_COMMIT=""
+fi
+
+RESUME=""
+if [ -f $STATE_DIR/resume_state ]; then
+ RESUME=`tail -1 $STATE_DIR/resume_state`
+fi
+
+GIT_IS_REBASING=`cd $MOZ_LIBWEBRTC_SRC && git status | grep "interactive rebase in progress" | wc -l | tr -d " " || true`
+if [ "x$GIT_IS_REBASING" != "x0" ]; then
+ echo "There is currently a git rebase operation in progress at $MOZ_LIBWEBRTC_SRC."
+ echo "Please resolve the rebase before attempting to continue the fast-forward"
+ echo "operation."
+ exit 1
+fi
+
+if [ "x$RESUME" = "x" ]; then
+ SKIP_TO="run"
+ # Check for modified files and abort if present.
+ MODIFIED_FILES=`hg status --exclude "third_party/libwebrtc/**.orig" third_party/libwebrtc`
+ if [ "x$MODIFIED_FILES" = "x" ]; then
+ # Completely clean the mercurial checkout before proceeding
+ hg update -C -r .
+ hg purge
+ else
+ echo "There are modified files in the checkout. Cowardly aborting!"
+ echo "$MODIFIED_FILES"
+ exit 1
+ fi
+else
+ SKIP_TO=$RESUME
+ hg revert -C third_party/libwebrtc/README.moz-ff-commit &> /dev/null
+fi
+
+find_base_commit
+find_next_commit
+
+echo "looking for $STATE_DIR/$MOZ_LIBWEBRTC_NEXT_BASE.no-op-cherry-pick-msg"
+if [ -f $STATE_DIR/$MOZ_LIBWEBRTC_NEXT_BASE.no-op-cherry-pick-msg ]; then
+ echo "***"
+ echo "*** detected special commit msg, setting HANDLE_NOOP_COMMIT"
+ echo "***"
+ HANDLE_NOOP_COMMIT="1"
+fi
+
+UPSTREAM_ADDED_FILES=""
+
+# Grab the filtered changes from git based on what we vendor.
+FILTERED_GIT_CHANGES=`./mach python $SCRIPT_DIR/filter_git_changes.py \
+ --repo-path $MOZ_LIBWEBRTC_SRC --commit-sha $MOZ_LIBWEBRTC_NEXT_BASE`
+
+# After this point:
+# * eE: All commands should succeed.
+# * u: All variables should be defined before use.
+# * o pipefail: All stages of all pipes should succeed.
+set -eEuo pipefail
+
+echo " MOZ_LIBWEBRTC_BASE: $MOZ_LIBWEBRTC_BASE"
+echo "MOZ_LIBWEBRTC_NEXT_BASE: $MOZ_LIBWEBRTC_NEXT_BASE"
+echo "HANDLE_NOOP_COMMIT: $HANDLE_NOOP_COMMIT"
+echo " RESUME: $RESUME"
+echo "SKIP_TO: $SKIP_TO"
+
+echo "-------"
+echo "------- Write cmd-line to third_party/libwebrtc/README.moz-ff-commit"
+echo "-------"
+echo "# MOZ_LIBWEBRTC_SRC=$MOZ_LIBWEBRTC_SRC MOZ_LIBWEBRTC_BRANCH=$MOZ_LIBWEBRTC_BRANCH bash $0" \
+ >> third_party/libwebrtc/README.moz-ff-commit
+
+echo "-------"
+echo "------- Write new-base to last line of third_party/libwebrtc/README.moz-ff-commit"
+echo "-------"
+echo "# base of lastest vendoring" >> third_party/libwebrtc/README.moz-ff-commit
+echo "$MOZ_LIBWEBRTC_NEXT_BASE" >> third_party/libwebrtc/README.moz-ff-commit
+
+REBASE_HELP=$"
+The rebase operation onto $MOZ_LIBWEBRTC_NEXT_BASE has failed. Please
+resolve all the rebase conflicts. To fix this issue, you will need to
+jump to the github repo at $MOZ_LIBWEBRTC_SRC .
+When the github rebase is complete, re-run the script to resume the
+fast-forward process.
+"
+#"rebase_mozlibwebrtc_stack help for rebase failure"
+function rebase_mozlibwebrtc_stack {
+ echo "-------"
+ echo "------- Rebase $MOZ_LIBWEBRTC_BRANCH to $MOZ_LIBWEBRTC_NEXT_BASE"
+ echo "-------"
+ ERROR_HELP=$REBASE_HELP
+ ( cd $MOZ_LIBWEBRTC_SRC && \
+ git checkout -q $MOZ_LIBWEBRTC_BRANCH && \
+ git rebase $MOZ_LIBWEBRTC_NEXT_BASE \
+ &> $LOG_DIR/log-rebase-moz-libwebrtc.txt \
+ )
+ ERROR_HELP=""
+}
+
+function vendor_off_next_commit {
+ echo "-------"
+ echo "------- Vendor $MOZ_LIBWEBRTC_BRANCH from $MOZ_LIBWEBRTC_SRC"
+ echo "-------"
+ ./mach python $SCRIPT_DIR/vendor-libwebrtc.py \
+ --from-local $MOZ_LIBWEBRTC_SRC \
+ --commit $MOZ_LIBWEBRTC_BRANCH \
+ libwebrtc
+}
+
+# The vendoring script (called above in vendor_off_next_commit) replaces
+# the entire third_party/libwebrtc directory, which effectively removes
+# all the generated moz.build files. It is easier (less error prone),
+# to revert only those missing moz.build files rather than attempt to
+# rebuild them because the rebuild may need json updates to work properly.
+function regen_mozbuild_files {
+ echo "-------"
+ echo "------- Restore moz.build files from repo"
+ echo "-------"
+ hg revert --include "third_party/libwebrtc/**moz.build" \
+ third_party/libwebrtc &> $LOG_DIR/log-regen-mozbuild-files.txt
+}
+
+function add_new_upstream_files {
+ if [ "x$HANDLE_NOOP_COMMIT" == "x1" ]; then
+ return
+ fi
+ UPSTREAM_ADDED_FILES=`echo "$FILTERED_GIT_CHANGES" | grep "^A" \
+ | awk '{print $2;}' || true`
+ if [ "x$UPSTREAM_ADDED_FILES" != "x" ]; then
+ echo "-------"
+ echo "------- Add new upstream files"
+ echo "-------"
+ (cd third_party/libwebrtc && hg add $UPSTREAM_ADDED_FILES)
+ echo "$UPSTREAM_ADDED_FILES" &> $LOG_DIR/log-new-upstream-files.txt
+ fi
+}
+
+function remove_deleted_upstream_files {
+ if [ "x$HANDLE_NOOP_COMMIT" == "x1" ]; then
+ return
+ fi
+ UPSTREAM_DELETED_FILES=`echo "$FILTERED_GIT_CHANGES" | grep "^D" \
+ | awk '{print $2;}' || true`
+ if [ "x$UPSTREAM_DELETED_FILES" != "x" ]; then
+ echo "-------"
+ echo "------- Remove deleted upstream files"
+ echo "-------"
+ (cd third_party/libwebrtc && hg rm $UPSTREAM_DELETED_FILES)
+ echo "$UPSTREAM_DELETED_FILES" &> $LOG_DIR/log-deleted-upstream-files.txt
+ fi
+}
+
+function handle_renamed_upstream_files {
+ if [ "x$HANDLE_NOOP_COMMIT" == "x1" ]; then
+ return
+ fi
+ UPSTREAM_RENAMED_FILES=`echo "$FILTERED_GIT_CHANGES" | grep "^R" \
+ | awk '{print $2 " " $3;}' || true`
+ if [ "x$UPSTREAM_RENAMED_FILES" != "x" ]; then
+ echo "-------"
+ echo "------- Handle renamed upstream files"
+ echo "-------"
+ (cd third_party/libwebrtc && echo "$UPSTREAM_RENAMED_FILES" | while read line; do hg rename --after $line; done)
+ echo "$UPSTREAM_RENAMED_FILES" &> $LOG_DIR/log-renamed-upstream-files.txt
+ fi
+}
+
+if [ $SKIP_TO = "run" ]; then
+ echo "resume2" > $STATE_DIR/resume_state
+ rebase_mozlibwebrtc_stack;
+fi
+
+if [ $SKIP_TO = "resume2" ]; then SKIP_TO="run"; fi
+if [ $SKIP_TO = "run" ]; then
+ echo "resume3" > $STATE_DIR/resume_state
+ vendor_off_next_commit;
+fi
+
+if [ $SKIP_TO = "resume3" ]; then SKIP_TO="run"; fi
+if [ $SKIP_TO = "run" ]; then
+ echo "resume4" > $STATE_DIR/resume_state
+ regen_mozbuild_files;
+fi
+
+if [ $SKIP_TO = "resume4" ]; then SKIP_TO="run"; fi
+if [ $SKIP_TO = "run" ]; then
+ echo "resume5" > $STATE_DIR/resume_state
+ remove_deleted_upstream_files;
+fi
+
+if [ $SKIP_TO = "resume5" ]; then SKIP_TO="run"; fi
+if [ $SKIP_TO = "run" ]; then
+ echo "resume6" > $STATE_DIR/resume_state
+ add_new_upstream_files;
+fi
+
+if [ $SKIP_TO = "resume6" ]; then SKIP_TO="run"; fi
+if [ $SKIP_TO = "run" ]; then
+ echo "resume7" > $STATE_DIR/resume_state
+ handle_renamed_upstream_files;
+fi
+
+echo "" > $STATE_DIR/resume_state
+echo "-------"
+echo "------- Commit vendored changes from $MOZ_LIBWEBRTC_NEXT_BASE"
+echo "-------"
+UPSTREAM_SHA=`cd $MOZ_LIBWEBRTC_SRC && \
+ git show --name-only $MOZ_LIBWEBRTC_NEXT_BASE \
+ | grep "^commit " | awk '{ print $NF }'`
+echo "Bug $MOZ_FASTFORWARD_BUG - Vendor libwebrtc from $MOZ_LIBWEBRTC_NEXT_BASE" \
+ > $TMP_DIR/commit_msg.txt
+echo "" >> $TMP_DIR/commit_msg.txt
+if [ -f $STATE_DIR/$MOZ_LIBWEBRTC_NEXT_BASE.no-op-cherry-pick-msg ]; then
+ cat $STATE_DIR/$MOZ_LIBWEBRTC_NEXT_BASE.no-op-cherry-pick-msg >> $TMP_DIR/commit_msg.txt
+ echo "" >> $TMP_DIR/commit_msg.txt
+ rm $STATE_DIR/$MOZ_LIBWEBRTC_NEXT_BASE.no-op-cherry-pick-msg
+fi
+echo "Upstream commit: https://webrtc.googlesource.com/src/+/$UPSTREAM_SHA" >> $TMP_DIR/commit_msg.txt
+(cd $MOZ_LIBWEBRTC_SRC && \
+git show --name-only $MOZ_LIBWEBRTC_NEXT_BASE | grep "^ ") >> $TMP_DIR/commit_msg.txt
+
+hg commit -l $TMP_DIR/commit_msg.txt third_party/libwebrtc
diff --git a/dom/media/webrtc/third_party_build/fetch_github_repo.py b/dom/media/webrtc/third_party_build/fetch_github_repo.py
new file mode 100644
index 0000000000..bfa40f5c7b
--- /dev/null
+++ b/dom/media/webrtc/third_party_build/fetch_github_repo.py
@@ -0,0 +1,122 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+import argparse
+import os
+import re
+import shutil
+
+from run_operations import run_git, run_shell
+
+# This script fetches the moz-libwebrtc github repro with the expected
+# upstream remote and branch-heads setup. This is used by both the
+# prep_repo.sh script as well as the restore_patch_stack.py script.
+#
+# For speed and conservation of network resources, after fetching all
+# the data, a tar of the repo is made and used if available.
+
+
+def fetch_repo(github_path, force_fetch, tar_path):
+ capture_output = False
+
+ # check for pre-existing repo - make sure we force the removal
+ if force_fetch and os.path.exists(github_path):
+ print("Removing existing repo: {}".format(github_path))
+ shutil.rmtree(github_path)
+
+ # clone https://github.com/mozilla/libwebrtc
+ if not os.path.exists(github_path):
+ # check for pre-existing tar, use it if we have it
+ if os.path.exists(tar_path):
+ print("Using tar file to reconstitute repo")
+ cmd = "cd {} ; tar --extract --gunzip --file={}".format(
+ os.path.dirname(github_path), os.path.basename(tar_path)
+ )
+ run_shell(cmd, capture_output)
+ else:
+ print("Cloning github repo")
+ run_shell(
+ "git clone https://github.com/mozilla/libwebrtc {}".format(github_path),
+ capture_output,
+ )
+
+ # setup upstream (https://webrtc.googlesource.com/src)
+ stdout_lines = run_git("git config --local --list", github_path)
+ stdout_lines = [
+ path for path in stdout_lines if re.findall("^remote.upstream.url.*", path)
+ ]
+ if len(stdout_lines) == 0:
+ print("Fetching upstream")
+ run_git("git checkout master", github_path)
+ run_git(
+ "git remote add upstream https://webrtc.googlesource.com/src", github_path
+ )
+ run_git("git fetch upstream", github_path)
+ run_git("git merge upstream/master", github_path)
+ else:
+ print(
+ "Upstream remote (https://webrtc.googlesource.com/src) already configured"
+ )
+
+ # setup upstream branch-heads
+ stdout_lines = run_git(
+ "git config --local --get-all remote.upstream.fetch", github_path
+ )
+ if len(stdout_lines) == 1:
+ print("Fetching upstream branch-heads")
+ run_git(
+ "git config --local --add remote.upstream.fetch +refs/branch-heads/*:refs/remotes/branch-heads/*",
+ github_path,
+ )
+ run_git("git fetch upstream", github_path)
+ else:
+ print("Upstream remote branch-heads already configured")
+
+ # do a sanity fetch in case this was not a freshly cloned copy of the
+ # repo, meaning it may not have all the mozilla branches present.
+ run_git("git fetch --all", github_path)
+
+ # create tar to avoid time refetching
+ if not os.path.exists(tar_path):
+ print("Creating tar file for quicker restore")
+ cmd = "cd {} ; tar --create --gzip --file={} {}".format(
+ os.path.dirname(github_path),
+ os.path.basename(tar_path),
+ os.path.basename(github_path),
+ )
+ run_shell(cmd, capture_output)
+
+
+if __name__ == "__main__":
+ default_state_dir = ".moz-fast-forward"
+ default_tar_name = "moz-libwebrtc.tar.gz"
+
+ parser = argparse.ArgumentParser(
+ description="Restore moz-libwebrtc github patch stack"
+ )
+ parser.add_argument(
+ "--repo-path",
+ required=True,
+ help="path to libwebrtc repo",
+ )
+ parser.add_argument(
+ "--force-fetch",
+ action="store_true",
+ default=False,
+ help="force rebuild an existing repo directory",
+ )
+ parser.add_argument(
+ "--tar-name",
+ default=default_tar_name,
+ help="name of tar file (defaults to {})".format(default_tar_name),
+ )
+ parser.add_argument(
+ "--state-path",
+ default=default_state_dir,
+ help="path to state directory (defaults to {})".format(default_state_dir),
+ )
+ args = parser.parse_args()
+
+ fetch_repo(
+ args.repo_path, args.force_fetch, os.path.join(args.state_path, args.tar_name)
+ )
diff --git a/dom/media/webrtc/third_party_build/filter_git_changes.py b/dom/media/webrtc/third_party_build/filter_git_changes.py
new file mode 100644
index 0000000000..f8964e145b
--- /dev/null
+++ b/dom/media/webrtc/third_party_build/filter_git_changes.py
@@ -0,0 +1,72 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+import argparse
+import importlib
+import re
+import subprocess
+import sys
+
+sys.path.insert(0, "./dom/media/webrtc/third_party_build")
+vendor_libwebrtc = importlib.import_module("vendor-libwebrtc")
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(
+ description="Get relevant change count from an upstream git commit"
+ )
+ parser.add_argument(
+ "--repo-path",
+ required=True,
+ help="path to libwebrtc repo",
+ )
+ parser.add_argument("--commit-sha", required=True, help="sha of commit to examine")
+ parser.add_argument("--diff-filter", choices=("A", "D", "R"))
+ args = parser.parse_args()
+
+ command = [
+ "git",
+ "show",
+ "--oneline",
+ "--name-status",
+ "--pretty=format:",
+ None if not args.diff_filter else "--diff-filter={}".format(args.diff_filter),
+ args.commit_sha,
+ ]
+ # strip possible empty elements from command list
+ command = [x for x in command if x is not None]
+
+ # Get the list of changes in the upstream commit.
+ res = subprocess.run(
+ command,
+ capture_output=True,
+ text=True,
+ cwd=args.repo_path,
+ )
+ if res.returncode != 0:
+ sys.exit("error: {}".format(res.stderr.strip()))
+
+ changed_files = [line.strip() for line in res.stdout.strip().split("\n")]
+ changed_files = [line for line in changed_files if line != ""]
+
+ # Fetch the list of excludes and includes used in the vendoring script.
+ exclude_list = vendor_libwebrtc.get_excluded_paths()
+ include_list = vendor_libwebrtc.get_included_path_overrides()
+
+ # First, search for changes in files that are specifically included.
+ # Do this first, because some of these files might be filtered out
+ # by the exclude list.
+ regex_includes = "|".join(["^.\t{}".format(i) for i in include_list])
+ included_files = [
+ path for path in changed_files if re.findall(regex_includes, path)
+ ]
+
+ # Convert the exclude list to a regex string.
+ regex_excludes = "|".join(["^.\t{}".format(i) for i in exclude_list])
+
+ # Filter out the excluded files/paths.
+ files_not_excluded = [
+ path for path in changed_files if not re.findall(regex_excludes, path)
+ ]
+
+ for path in included_files + files_not_excluded:
+ print(path)
diff --git a/dom/media/webrtc/third_party_build/gn-configs/README.md b/dom/media/webrtc/third_party_build/gn-configs/README.md
new file mode 100644
index 0000000000..111d2d022e
--- /dev/null
+++ b/dom/media/webrtc/third_party_build/gn-configs/README.md
@@ -0,0 +1,16 @@
+# Generate new gn json files and moz.build files for building libwebrtc in our tree
+
+/!\ This is only supported on Linux and macOS. If you are on Windows, you can run
+the script under [WSL](https://docs.microsoft.com/en-us/windows/wsl/install).
+
+1. The script should be run from the top directory of our firefox tree.
+
+ ```
+ ./mach python python/mozbuild/mozbuild/gn_processor.py dom/media/webrtc/third_party_build/gn-configs/webrtc.json
+ ```
+
+2. Checkin all the generated/modified files and try your build!
+
+# Adding new configurations to the build
+
+Edit the `main` function in the `python/mozbuild/mozbuild/gn_processor.py` file.
diff --git a/dom/media/webrtc/third_party_build/gn-configs/webrtc.json b/dom/media/webrtc/third_party_build/gn-configs/webrtc.json
new file mode 100644
index 0000000000..9744558025
--- /dev/null
+++ b/dom/media/webrtc/third_party_build/gn-configs/webrtc.json
@@ -0,0 +1,83 @@
+{
+ "target_dir": "third_party/libwebrtc",
+ "gn_target": "//:webrtc",
+ "gn_sandbox_variables": {
+ "COMPILE_FLAGS": {
+ "WARNINGS_AS_ERRORS": []
+ },
+ "FINAL_LIBRARY": "webrtc"
+ },
+ "mozilla_flags": ["-fobjc-arc", "-mavx2", "-mfma", "-mfpu=neon", "-msse2"],
+ "write_mozbuild_variables": {
+ "INCLUDE_TK_CFLAGS_DIRS": [
+ "third_party/libwebrtc/modules/desktop_capture/desktop_capture_gn",
+ "third_party/libwebrtc/modules/portal/portal_gn"
+ ]
+ },
+ "non_unified_sources": [
+ "third_party/libwebrtc/api/audio_codecs/opus/audio_encoder_opus_config.cc",
+ "third_party/libwebrtc/api/video/i210_buffer.cc",
+ "third_party/libwebrtc/api/video/i410_buffer.cc",
+ "third_party/libwebrtc/api/video/i422_buffer.cc",
+ "third_party/libwebrtc/api/video/i444_buffer.cc",
+ "third_party/libwebrtc/audio/channel_send_frame_transformer_delegate.cc",
+ "third_party/libwebrtc/common_audio/vad/vad_core.c",
+ "third_party/libwebrtc/common_audio/vad/webrtc_vad.c",
+ "third_party/libwebrtc/common_audio/signal_processing/resample_by_2_mips.c",
+ "third_party/libwebrtc/modules/audio_coding/codecs/isac/fix/source/decode_plc.c",
+ "third_party/libwebrtc/modules/audio_coding/codecs/isac/fix/source/lpc_masking_model.c",
+ "third_party/libwebrtc/modules/audio_coding/codecs/isac/fix/source/pitch_filter.c",
+ "third_party/libwebrtc/modules/audio_coding/codecs/isac/fix/source/pitch_filter_c.c",
+ "third_party/libwebrtc/modules/audio_coding/neteq/audio_vector.cc",
+ "third_party/libwebrtc/modules/audio_coding/neteq/underrun_optimizer.cc",
+ "third_party/libwebrtc/modules/audio_device/android/audio_manager.cc",
+ "third_party/libwebrtc/modules/audio_device/android/audio_record_jni.cc",
+ "third_party/libwebrtc/modules/audio_device/android/audio_track_jni.cc",
+ "third_party/libwebrtc/modules/audio_device/android/opensles_player.cc",
+ "third_party/libwebrtc/modules/audio_device/linux/audio_device_pulse_linux.cc",
+ "third_party/libwebrtc/modules/audio_device/linux/audio_mixer_manager_pulse_linux.cc",
+ "third_party/libwebrtc/modules/audio_device/win/audio_device_core_win.cc",
+ "third_party/libwebrtc/modules/audio_processing/aecm/aecm_core.cc",
+ "third_party/libwebrtc/modules/audio_processing/aecm/aecm_core_c.cc",
+ "third_party/libwebrtc/modules/audio_processing/aecm/aecm_core_mips.cc",
+ "third_party/libwebrtc/modules/audio_processing/aecm/aecm_core_neon.cc",
+ "third_party/libwebrtc/modules/audio_processing/aecm/echo_control_mobile.cc",
+ "third_party/libwebrtc/modules/audio_processing/echo_control_mobile_impl.cc",
+ "third_party/libwebrtc/modules/audio_processing/echo_detector/normalized_covariance_estimator.cc",
+ "third_party/libwebrtc/modules/audio_processing/gain_control_impl.cc",
+ "third_party/libwebrtc/modules/audio_processing/rms_level.cc",
+ "third_party/libwebrtc/modules/congestion_controller/goog_cc/loss_based_bandwidth_estimation.cc",
+ "third_party/libwebrtc/modules/congestion_controller/goog_cc/send_side_bandwidth_estimation.cc",
+ "third_party/libwebrtc/modules/desktop_capture/fallback_desktop_capturer_wrapper.cc",
+ "third_party/libwebrtc/modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc",
+ "third_party/libwebrtc/modules/desktop_capture/linux/wayland/moz_base_capturer_pipewire.cc",
+ "third_party/libwebrtc/modules/desktop_capture/mouse_cursor_monitor_linux.cc",
+ "third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_gdi.cc",
+ "third_party/libwebrtc/modules/pacing/prioritized_packet_queue.cc",
+ "third_party/libwebrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_single_stream.cc",
+ "third_party/libwebrtc/modules/rtp_rtcp/source/flexfec_receiver.cc",
+ "third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbn.cc",
+ "third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_packet/tmmbr.cc",
+ "third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_sender.cc",
+ "third_party/libwebrtc/modules/rtp_rtcp/source/rtp_format_vp8.cc",
+ "third_party/libwebrtc/modules/rtp_rtcp/source/rtp_rtcp_impl2.cc",
+ "third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_egress.cc",
+ "third_party/libwebrtc/modules/rtp_rtcp/source/rtp_sender_video.cc",
+ "third_party/libwebrtc/modules/rtp_rtcp/source/rtp_util.cc",
+ "third_party/libwebrtc/modules/rtp_rtcp/source/ulpfec_generator.cc",
+ "third_party/libwebrtc/modules/rtp_rtcp/source/video_rtp_depacketizer_vp9.cc",
+ "third_party/libwebrtc/modules/third_party/g722/g722_encode.c",
+ "third_party/libwebrtc/modules/video_capture/windows/device_info_ds.cc",
+ "third_party/libwebrtc/modules/video_capture/windows/help_functions_ds.cc",
+ "third_party/libwebrtc/modules/video_capture/windows/sink_filter_ds.cc",
+ "third_party/libwebrtc/modules/video_coding/codecs/vp8/screenshare_layers.cc",
+ "third_party/libwebrtc/modules/video_coding/svc/scalability_structure_key_svc.cc",
+ "third_party/libwebrtc/modules/video_coding/svc/scalability_structure_simulcast.cc",
+ "third_party/libwebrtc/rtc_base/win/hstring.cc",
+ "third_party/libwebrtc/third_party/abseil-cpp/absl/strings/numbers.cc",
+ "third_party/libwebrtc/third_party/abseil-cpp/absl/synchronization/blocking_counter.cc",
+ "third_party/libwebrtc/third_party/abseil-cpp/absl/time/internal/cctz/src/time_zone_posix.cc",
+ "third_party/libwebrtc/third_party/abseil-cpp/absl/time/time.cc",
+ "third_party/libwebrtc/video/rtp_video_stream_receiver2.cc"
+ ]
+}
diff --git a/dom/media/webrtc/third_party_build/lookup_branch_head.py b/dom/media/webrtc/third_party_build/lookup_branch_head.py
new file mode 100644
index 0000000000..afd0e6c791
--- /dev/null
+++ b/dom/media/webrtc/third_party_build/lookup_branch_head.py
@@ -0,0 +1,98 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+import argparse
+import json
+import os
+import pathlib
+import sys
+import urllib.request
+
+# default cache file location in STATE_DIR location
+default_cache_path = ".moz-fast-forward/milestone.cache"
+
+
+def fetch_branch_head_dict():
+ milestone_url = (
+ "https://chromiumdash.appspot.com/fetch_milestones?only_branched=true"
+ )
+ uf = urllib.request.urlopen(milestone_url)
+ html = uf.read()
+ milestone_dict = json.loads(html)
+
+ # There is more information in the json dictionary, but we only care
+ # about the milestone (version) to branch "name" (webrtc_branch)
+ # info. For example:
+ # v106 -> 5249 (which translates to branch-heads/5249)
+ # v107 -> 5304 (which translates to branch-heads/5304)
+ #
+ # As returned from web query, milestones are integers and branch
+ # "names" are strings.
+ new_dict = {}
+ for row in milestone_dict:
+ new_dict[row["milestone"]] = row["webrtc_branch"]
+
+ return new_dict
+
+
+def read_dict_from_cache(cache_path):
+ if cache_path is not None and os.path.exists(cache_path):
+ with open(cache_path, "r") as ifile:
+ return json.loads(ifile.read(), object_hook=jsonKeys2int)
+ return {}
+
+
+def write_dict_to_cache(cache_path, milestones):
+ with open(cache_path, "w") as ofile:
+ ofile.write(json.dumps(milestones))
+
+
+def get_branch_head(milestone, cache_path=default_cache_path):
+ milestones = read_dict_from_cache(cache_path)
+
+ # if the cache didn't exist or is stale, try to fetch using a web query
+ if milestone not in milestones:
+ try:
+ milestones = fetch_branch_head_dict()
+ write_dict_to_cache(cache_path, milestones)
+ except Exception:
+ pass
+
+ if milestone in milestones:
+ return milestones[milestone]
+ return None
+
+
+# From https://stackoverflow.com/questions/1450957/pythons-json-module-converts-int-dictionary-keys-to-strings
+def jsonKeys2int(x):
+ if isinstance(x, dict):
+ return {int(k): v for k, v in x.items()}
+ return x
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(
+ description="Get libwebrtc branch-head for given chromium milestone"
+ )
+ parser.add_argument(
+ "milestone", type=int, help="integer chromium milestone (example: 106)"
+ )
+ parser.add_argument("-v", "--verbose", action="store_true")
+ parser.add_argument("-c", "--cache", type=pathlib.Path, help="path to cache file")
+ args = parser.parse_args()
+
+ # if the user provided a cache path use it, otherwise use the default
+ local_cache_path = args.cache or default_cache_path
+
+ branch_head = get_branch_head(args.milestone, local_cache_path)
+ if branch_head is None:
+ sys.exit("error: chromium milestone '{}' is not found.".format(args.milestone))
+
+ if args.verbose:
+ print(
+ "chromium milestone {} uses branch-heads/{}".format(
+ args.milestone, branch_head
+ )
+ )
+ else:
+ print(branch_head)
diff --git a/dom/media/webrtc/third_party_build/loop-ff.sh b/dom/media/webrtc/third_party_build/loop-ff.sh
new file mode 100644
index 0000000000..72a8d88091
--- /dev/null
+++ b/dom/media/webrtc/third_party_build/loop-ff.sh
@@ -0,0 +1,236 @@
+#!/bin/bash
+
+source dom/media/webrtc/third_party_build/use_config_env.sh
+
+# file for logging loop script output
+LOOP_OUTPUT_LOG=$LOG_DIR/log-loop-ff.txt
+
+function echo_log()
+{
+ echo "===loop-ff=== $@" 2>&1| tee -a $LOOP_OUTPUT_LOG
+}
+
+function show_error_msg()
+{
+ echo_log "*** ERROR *** $? line $1 $0 did not complete successfully!"
+ echo_log "$ERROR_HELP"
+}
+ERROR_HELP=""
+
+# Print an Error message if `set -eE` causes the script to exit due to a failed command
+trap 'show_error_msg $LINENO' ERR
+
+# If DEBUG_LOOP_FF is set all commands should be printed as they are executed
+if [ ! "x$DEBUG_LOOP_FF" = "x" ]; then
+ set -x
+fi
+
+if [ "x$MOZ_LIBWEBRTC_SRC" = "x" ]; then
+ echo "MOZ_LIBWEBRTC_SRC is not defined, see README.md"
+ exit
+fi
+
+if [ ! -d $MOZ_LIBWEBRTC_SRC ]; then
+ echo "Path $MOZ_LIBWEBRTC_SRC is not found, see README.md"
+ exit
+fi
+
+if [ "x$MOZ_LIBWEBRTC_BRANCH" = "x" ]; then
+ echo "MOZ_LIBWEBRTC_BRANCH is not defined, see README.md"
+ exit
+fi
+
+if [ "x$MOZ_STOP_AFTER_COMMIT" = "x" ]; then
+ MOZ_STOP_AFTER_COMMIT=`cd $MOZ_LIBWEBRTC_SRC ; git show $MOZ_TARGET_UPSTREAM_BRANCH_HEAD --format='%h' --name-only | head -1`
+ echo "No MOZ_STOP_AFTER_COMMIT variable defined - stopping at $MOZ_TARGET_UPSTREAM_BRANCH_HEAD"
+fi
+
+if [ "x$MOZ_ADVANCE_ONE_COMMIT" = "x" ]; then
+ MOZ_ADVANCE_ONE_COMMIT=""
+fi
+
+if [ "x$SKIP_NEXT_REVERT_CHK" = "x" ]; then
+ SKIP_NEXT_REVERT_CHK="0"
+fi
+
+MOZ_CHANGED=0
+GIT_CHANGED=0
+HANDLE_NOOP_COMMIT=""
+
+# After this point:
+# * eE: All commands should succeed.
+# * u: All variables should be defined before use.
+# * o pipefail: All stages of all pipes should succeed.
+set -eEuo pipefail
+
+# start a new log with every run of this script
+rm -f $LOOP_OUTPUT_LOG
+# make sure third_party/libwebrtc/README.moz-ff-commit is the committed version
+# so we properly determine MOZ_LIBWEBRTC_BASE and MOZ_LIBWEBRTC_NEXT_BASE
+# in the loop below
+hg revert -C third_party/libwebrtc/README.moz-ff-commit &> /dev/null
+
+# check for a resume situation from fast-forward-libwebrtc.sh
+RESUME=""
+if [ -f $STATE_DIR/resume_state ]; then
+ RESUME=`tail -1 $STATE_DIR/resume_state`
+fi
+
+ERROR_HELP=$"
+It appears that initial vendoring verification has failed.
+- If you have never run the fast-forward process before, you may need to
+ prepare the github repository by running prep_repo.sh.
+- If you have previously run loop-ff.sh successfully, there may be a new
+ change to third_party/libwebrtc that should be extracted and added to
+ the patch stack in github. It may be as easy as running:
+ ./mach python $SCRIPT_DIR/extract-for-git.py tip::tip
+ mv mailbox.patch $MOZ_LIBWEBRTC_SRC
+ (cd $MOZ_LIBWEBRTC_SRC && \\
+ git am mailbox.patch)
+"
+# if we're not in the resume situation from fast-forward-libwebrtc.sh
+if [ "x$RESUME" = "x" ]; then
+ # start off by verifying the vendoring process to make sure no changes have
+ # been added to elm to fix bugs.
+ echo_log "Verifying vendoring..."
+ bash $SCRIPT_DIR/verify_vendoring.sh
+ echo_log "Done verifying vendoring."
+fi
+ERROR_HELP=""
+
+for (( ; ; )); do
+
+find_base_commit
+find_next_commit
+
+if [ $MOZ_LIBWEBRTC_BASE == $MOZ_LIBWEBRTC_NEXT_BASE ]; then
+ echo_log "Processing complete, already at upstream $MOZ_LIBWEBRTC_BASE"
+ exit
+fi
+
+echo_log "==================="
+
+COMMITS_REMAINING=`cd $MOZ_LIBWEBRTC_SRC ; \
+ git log --oneline $MOZ_LIBWEBRTC_BASE..$MOZ_TARGET_UPSTREAM_BRANCH_HEAD \
+ | wc -l | tr -d " "`
+echo_log "Commits remaining: $COMMITS_REMAINING"
+
+ERROR_HELP=$"Some portion of the detection and/or fixing of upstream revert commits
+has failed. Please fix the state of the git hub repo at: $MOZ_LIBWEBRTC_SRC.
+When fixed, please resume this script with the following command:
+ SKIP_NEXT_REVERT_CHK=1 bash $SCRIPT_DIR/loop-ff.sh
+"
+if [ "x$SKIP_NEXT_REVERT_CHK" == "x0" ]; then
+ echo_log "Check for upcoming revert commit"
+ AUTO_FIX_REVERT_AS_NOOP=1 bash $SCRIPT_DIR/detect_upstream_revert.sh \
+ 2>&1| tee -a $LOOP_OUTPUT_LOG
+fi
+SKIP_NEXT_REVERT_CHK="0"
+ERROR_HELP=""
+
+echo_log "Looking for $STATE_DIR/$MOZ_LIBWEBRTC_NEXT_BASE.no-op-cherry-pick-msg"
+if [ -f $STATE_DIR/$MOZ_LIBWEBRTC_NEXT_BASE.no-op-cherry-pick-msg ]; then
+ echo_log "Detected special commit msg, setting HANDLE_NOOP_COMMIT=1"
+ HANDLE_NOOP_COMMIT="1"
+fi
+
+echo_log "Moving from moz-libwebrtc commit $MOZ_LIBWEBRTC_BASE to $MOZ_LIBWEBRTC_NEXT_BASE"
+bash $SCRIPT_DIR/fast-forward-libwebrtc.sh 2>&1| tee -a $LOOP_OUTPUT_LOG
+
+MOZ_CHANGED=`hg diff -c tip --stat \
+ | egrep -ve "README.moz-ff-commit|README.mozilla|files changed," \
+ | wc -l | tr -d " " || true`
+GIT_CHANGED=`./mach python $SCRIPT_DIR/filter_git_changes.py \
+ --repo-path $MOZ_LIBWEBRTC_SRC --commit-sha $MOZ_LIBWEBRTC_NEXT_BASE \
+ | wc -l | tr -d " "`
+FILE_CNT_MISMATCH_MSG=$"
+The number of files changed in the upstream commit ($GIT_CHANGED) does
+not match the number of files changed in the local Mozilla repo
+commit ($MOZ_CHANGED). This may indicate a mismatch between the vendoring
+script and this script, or it could be a true error in the import
+processing. Once the issue has been resolved, the following steps
+remain for this commit:
+ # generate moz.build files (may not be necessary)
+ ./mach python python/mozbuild/mozbuild/gn_processor.py \\
+ $SCRIPT_DIR/gn-configs/webrtc.json
+ # commit the updated moz.build files with the appropriate commit msg
+ bash $SCRIPT_DIR/commit-build-file-changes.sh
+ # do a (hopefully) quick test build
+ ./mach build
+"
+echo_log "Verify number of files changed MOZ($MOZ_CHANGED) GIT($GIT_CHANGED)"
+if [ "x$HANDLE_NOOP_COMMIT" == "x1" ]; then
+ echo_log "NO-OP commit detected, we expect file changed counts to differ"
+elif [ $MOZ_CHANGED -ne $GIT_CHANGED ]; then
+ echo_log "MOZ_CHANGED $MOZ_CHANGED should equal GIT_CHANGED $GIT_CHANGED"
+ echo "$FILE_CNT_MISMATCH_MSG"
+ exit 1
+fi
+HANDLE_NOOP_COMMIT=""
+
+# save the current patch stack in case we need to reconstitute it later
+./mach python $SCRIPT_DIR/save_patch_stack.py \
+ --repo-path $MOZ_LIBWEBRTC_SRC \
+ --branch $MOZ_LIBWEBRTC_BRANCH \
+ --patch-path "third_party/libwebrtc/moz-patch-stack" \
+ --state-path $STATE_DIR \
+ --target-branch-head $MOZ_TARGET_UPSTREAM_BRANCH_HEAD \
+ 2>&1| tee -a $LOOP_OUTPUT_LOG
+
+MODIFIED_BUILD_RELATED_FILE_CNT=`hg diff -c tip --stat \
+ --include 'third_party/libwebrtc/**BUILD.gn' \
+ --include 'third_party/libwebrtc/webrtc.gni' \
+ | grep -v "files changed" \
+ | wc -l | tr -d " " || true`
+ERROR_HELP=$"
+Generating build files has failed. This likely means changes to one or more
+BUILD.gn files are required. Commit those changes following the instructions
+in https://wiki.mozilla.org/Media/WebRTC/libwebrtc_Update_Process#Operational_notes
+Then complete these steps:
+ # generate moz.build files (may not be necessary)
+ ./mach python python/mozbuild/mozbuild/gn_processor.py \\
+ $SCRIPT_DIR/gn-configs/webrtc.json
+ # commit the updated moz.build files with the appropriate commit msg
+ bash $SCRIPT_DIR/commit-build-file-changes.sh
+ # do a (hopefully) quick test build
+ ./mach build
+After a successful build, you may resume this script.
+"
+echo_log "Modified BUILD.gn (or webrtc.gni) files: $MODIFIED_BUILD_RELATED_FILE_CNT"
+if [ "x$MODIFIED_BUILD_RELATED_FILE_CNT" != "x0" ]; then
+ echo_log "Regenerate build files"
+ ./mach python python/mozbuild/mozbuild/gn_processor.py \
+ $SCRIPT_DIR/gn-configs/webrtc.json 2>&1| tee -a $LOOP_OUTPUT_LOG
+
+ MOZ_BUILD_CHANGE_CNT=`hg status third_party/libwebrtc \
+ --include 'third_party/libwebrtc/**moz.build' | wc -l | tr -d " "`
+ if [ "x$MOZ_BUILD_CHANGE_CNT" != "x0" ]; then
+ echo_log "Detected modified moz.build files, commiting"
+ fi
+
+ bash $SCRIPT_DIR/commit-build-file-changes.sh 2>&1| tee -a $LOOP_OUTPUT_LOG
+fi
+ERROR_HELP=""
+
+ERROR_HELP=$"
+The test build has failed. Most likely this is due to an upstream api change that
+must be reflected in Mozilla code outside of the third_party/libwebrtc directory.
+"
+echo_log "Test build"
+./mach build 2>&1| tee -a $LOOP_OUTPUT_LOG
+ERROR_HELP=""
+
+if [ ! "x$MOZ_STOP_AFTER_COMMIT" = "x" ]; then
+if [ $MOZ_LIBWEBRTC_NEXT_BASE = $MOZ_STOP_AFTER_COMMIT ]; then
+ break
+fi
+fi
+
+if [ ! "x$MOZ_ADVANCE_ONE_COMMIT" = "x" ]; then
+ echo_log "Done advancing one commit."
+ exit
+fi
+
+done
+
+echo_log "Completed fast-foward to $MOZ_STOP_AFTER_COMMIT"
diff --git a/dom/media/webrtc/third_party_build/make_upstream_revert_noop.sh b/dom/media/webrtc/third_party_build/make_upstream_revert_noop.sh
new file mode 100755
index 0000000000..9f5fdd7b82
--- /dev/null
+++ b/dom/media/webrtc/third_party_build/make_upstream_revert_noop.sh
@@ -0,0 +1,101 @@
+#!/bin/bash
+
+# This script takes the current base sha, the next base sha, and the sha
+# of the commit that reverts the next base as determined by
+# detect_upstream_revert.sh and "inserts" two commits at the bottom of the
+# moz_libwebrtc GitHub patch stack. The two commits are exact copies of
+# the upcoming commit and its corresponding revert commit, with commit
+# messages indicating they are temporary commits. Additionally, 2 files
+# are written that act as markers for the fast-forward script and contain
+# supplemental commit message text.
+#
+# When the fast-forward script runs, it will rebase onto the next base
+# sha. Since we have a corresponding, identical temp commit at the bottom
+# of our patch stack, the temp commit will be absorbed as unnecessary.
+# Since the patch stack now has the temp revert commit at the bottom, this
+# results in a “no-op” commit. The marker file indicates that specially
+# handling should occur in the fast-forward-libwebrtc.sh and loop-ff.sh
+# scripts. This special handling includes adding the supplemental commit
+# text that explains why the commit is a no-op (or empty) commit and
+# skipping the verification of the number of files changed between our
+# mercurial repository and the GitHub repository.
+
+function show_error_msg()
+{
+ echo "*** ERROR *** $? line $1 $0 did not complete successfully!"
+ echo "$ERROR_HELP"
+}
+ERROR_HELP=""
+
+# Print an Error message if `set -eE` causes the script to exit due to a failed command
+trap 'show_error_msg $LINENO' ERR
+
+source dom/media/webrtc/third_party_build/use_config_env.sh
+
+# If DEBUG_GEN is set all commands should be printed as they are executed
+if [ ! "x$DEBUG_GEN" = "x" ]; then
+ set -x
+fi
+
+if [ "x$MOZ_LIBWEBRTC_SRC" = "x" ]; then
+ echo "MOZ_LIBWEBRTC_SRC is not defined, see README.md"
+ exit
+fi
+
+if [ -d $MOZ_LIBWEBRTC_SRC ]; then
+ echo "MOZ_LIBWEBRTC_SRC is $MOZ_LIBWEBRTC_SRC"
+else
+ echo "Path $MOZ_LIBWEBRTC_SRC is not found, see README.md"
+ exit
+fi
+
+if [ "x$MOZ_LIBWEBRTC_BRANCH" = "x" ]; then
+ echo "MOZ_LIBWEBRTC_BRANCH is not defined, see README.md"
+ exit
+fi
+
+# After this point:
+# * eE: All commands should succeed.
+# * u: All variables should be defined before use.
+# * o pipefail: All stages of all pipes should succeed.
+set -eEuo pipefail
+
+find_base_commit
+find_next_commit
+echo "MOZ_LIBWEBRTC_BASE: $MOZ_LIBWEBRTC_BASE"
+echo "MOZ_LIBWEBRTC_NEXT_BASE: $MOZ_LIBWEBRTC_NEXT_BASE"
+echo "MOZ_LIBWEBRTC_REVERT_SHA: $MOZ_LIBWEBRTC_REVERT_SHA"
+
+# These files serve dual purposes:
+# 1) They serve as marker/indicator files to loop-ff.sh to
+# know to process the commit differently, accounting for
+# the no-op nature of the commit and it's corresponding
+# revert commit.
+# 2) The contain supplemental commit message text to explain
+# why the commits are essentially empty.
+# They are written first on the off chance that the rebase
+# operation below fails and requires manual intervention,
+# thus avoiding the operator of these scripts to remember to
+# generate these two files.
+echo $"Essentially a no-op since we're going to see this change
+reverted when we vendor in $MOZ_LIBWEBRTC_REVERT_SHA." \
+> $STATE_DIR/$MOZ_LIBWEBRTC_NEXT_BASE.no-op-cherry-pick-msg
+
+echo "We already cherry-picked this when we vendored $MOZ_LIBWEBRTC_NEXT_BASE." \
+> $STATE_DIR/$MOZ_LIBWEBRTC_REVERT_SHA.no-op-cherry-pick-msg
+
+cd $MOZ_LIBWEBRTC_SRC
+rm -f $TMP_DIR/*.patch $TMP_DIR/*.patch.bak
+git checkout -b moz-cherry-pick $MOZ_LIBWEBRTC_BASE
+git format-patch -o $TMP_DIR -k --start-number 1 \
+ $MOZ_LIBWEBRTC_NEXT_BASE^..$MOZ_LIBWEBRTC_NEXT_BASE
+git format-patch -o $TMP_DIR -k --start-number 2 \
+ $MOZ_LIBWEBRTC_REVERT_SHA^..$MOZ_LIBWEBRTC_REVERT_SHA
+sed -i.bak -e "/^Subject: / s/$/ ($MOZ_LIBWEBRTC_NEXT_BASE)/" $TMP_DIR/0001*.patch
+sed -i.bak -e "/^Subject: / s/$/ ($MOZ_LIBWEBRTC_REVERT_SHA)/" $TMP_DIR/0002*.patch
+sed -i.bak -e 's/^Subject: /Subject: (tmp-cherry-pick) /' $TMP_DIR/*.patch
+git am $TMP_DIR/*.patch
+git checkout $MOZ_LIBWEBRTC_BRANCH
+git rebase moz-cherry-pick
+git branch -d moz-cherry-pick
+
diff --git a/dom/media/webrtc/third_party_build/pre-warmed-milestone.cache b/dom/media/webrtc/third_party_build/pre-warmed-milestone.cache
new file mode 100644
index 0000000000..826268db47
--- /dev/null
+++ b/dom/media/webrtc/third_party_build/pre-warmed-milestone.cache
@@ -0,0 +1 @@
+{"109": "5414", "108": "5359", "107": "5304", "106": "5249", "105": "5195"}
diff --git a/dom/media/webrtc/third_party_build/prep_repo.sh b/dom/media/webrtc/third_party_build/prep_repo.sh
new file mode 100644
index 0000000000..9f22f63253
--- /dev/null
+++ b/dom/media/webrtc/third_party_build/prep_repo.sh
@@ -0,0 +1,93 @@
+#!/bin/bash
+
+function show_error_msg()
+{
+ echo "*** ERROR *** $? line $1 $0 did not complete successfully!"
+ echo "$ERROR_HELP"
+}
+ERROR_HELP=""
+
+# Print an Error message if `set -eE` causes the script to exit due to a failed command
+trap 'show_error_msg $LINENO' ERR
+
+source dom/media/webrtc/third_party_build/use_config_env.sh
+
+echo "MOZ_LIBWEBRTC_SRC: $MOZ_LIBWEBRTC_SRC"
+echo "MOZ_LIBWEBRTC_BRANCH: $MOZ_LIBWEBRTC_BRANCH"
+echo "MOZ_FASTFORWARD_BUG: $MOZ_FASTFORWARD_BUG"
+
+# After this point:
+# * eE: All commands should succeed.
+# * u: All variables should be defined before use.
+# * o pipefail: All stages of all pipes should succeed.
+set -eEuo pipefail
+
+# wipe resume_state for new run
+rm -f $STATE_DIR/resume_state
+
+# If there is no cache file for the branch-head lookups done in
+# update_default_config.sh, go ahead and copy our small pre-warmed
+# version.
+if [ ! -f $STATE_DIR/milestone.cache ]; then
+ cp $SCRIPT_DIR/pre-warmed-milestone.cache $STATE_DIR/milestone.cache
+fi
+
+# fetch the github repro
+./mach python $SCRIPT_DIR/fetch_github_repo.py \
+ --repo-path $MOZ_LIBWEBRTC_SRC \
+ --state-path $STATE_DIR
+
+CURRENT_DIR=`pwd`
+cd $MOZ_LIBWEBRTC_SRC
+
+# clear any possible previous patches
+rm -f *.patch
+
+# create a new work branch and "export" a new patch stack to rebase
+# find the common commit between our upstream release branch and trunk
+CHERRY_PICK_BASE=`git merge-base branch-heads/$MOZ_PRIOR_UPSTREAM_BRANCH_HEAD_NUM master`
+echo "common commit: $CHERRY_PICK_BASE"
+
+# create a new branch at the common commit and checkout the new branch
+ERROR_HELP=$"
+Unable to create branch '$MOZ_LIBWEBRTC_BRANCH'. This probably means
+that prep_repo.sh is being called on a repo that already has a patch
+stack in progress. If you're sure you want to do this, the following
+commands will allow the process to continue:
+ ( cd $MOZ_LIBWEBRTC_SRC && \\
+ git checkout $MOZ_LIBWEBRTC_BRANCH && \\
+ git checkout -b $MOZ_LIBWEBRTC_BRANCH-old && \\
+ git branch -D $MOZ_LIBWEBRTC_BRANCH ) && \\
+ bash $0
+"
+git branch $MOZ_LIBWEBRTC_BRANCH $CHERRY_PICK_BASE
+ERROR_HELP=""
+git checkout $MOZ_LIBWEBRTC_BRANCH
+
+# make sure we're starting with a clean tmp directory
+rm -f $TMP_DIR/*.patch $TMP_DIR/*.patch.bak
+
+# grab the patches for all the commits in chrome's release branch for libwebrtc
+git format-patch -o $TMP_DIR -k $CHERRY_PICK_BASE..branch-heads/$MOZ_PRIOR_UPSTREAM_BRANCH_HEAD_NUM
+# tweak the release branch commit summaries to show they were cherry picked
+sed -i.bak -e "/^Subject: / s/^Subject: /Subject: (cherry-pick-branch-heads\/$MOZ_PRIOR_UPSTREAM_BRANCH_HEAD_NUM) /" $TMP_DIR/*.patch
+git am $TMP_DIR/*.patch # applies to branch mozpatches
+rm $TMP_DIR/*.patch $TMP_DIR/*.patch.bak
+
+# we don't use restore_patch_stack.py here because it would overwrite the patches
+# from the previous release branch we just added in the above step.
+
+# grab all the moz patches and apply
+git am $CURRENT_DIR/third_party/libwebrtc/moz-patch-stack/*.patch
+
+cd $CURRENT_DIR
+
+# cp all the no-op files to STATE_DIR
+NO_OP_FILE_COUNT=`ls third_party/libwebrtc/moz-patch-stack \
+ | grep "no-op-cherry-pick-msg" | wc -l | tr -d " " || true`
+if [ "x$NO_OP_FILE_COUNT" != "x0" ]; then
+ cp $CURRENT_DIR/third_party/libwebrtc/moz-patch-stack/*.no-op-cherry-pick-msg \
+ $STATE_DIR
+fi
+
+bash $SCRIPT_DIR/verify_vendoring.sh || true
diff --git a/dom/media/webrtc/third_party_build/push_official_branch.sh b/dom/media/webrtc/third_party_build/push_official_branch.sh
new file mode 100644
index 0000000000..e7ed17df2b
--- /dev/null
+++ b/dom/media/webrtc/third_party_build/push_official_branch.sh
@@ -0,0 +1,50 @@
+#!/bin/bash
+
+# This script is a simple helper script that creates a branch name
+# in moz-libwebrtc and pushes it to origin. You will need permissions
+# to the mozilla fork of libwebrtc in order to complete this operation.
+# The repo is: https://github.com/mozilla/libwebrtc/
+#
+# Note: this should only be run after elm has been merged to mozilla-central
+
+function show_error_msg()
+{
+ echo "*** ERROR *** $? line $1 $0 did not complete successfully!"
+ echo "$ERROR_HELP"
+}
+ERROR_HELP=""
+
+# Print an Error message if `set -eE` causes the script to exit due to a failed command
+trap 'show_error_msg $LINENO' ERR
+
+source dom/media/webrtc/third_party_build/use_config_env.sh
+
+# After this point:
+# * eE: All commands should succeed.
+# * o pipefail: All stages of all pipes should succeed.
+set -eEo pipefail
+
+cd $MOZ_LIBWEBRTC_SRC
+
+git fetch
+
+if git show-ref --quiet refs/remotes/origin/$MOZ_LIBWEBRTC_OFFICIAL_BRANCH; then
+ echo "Branch '$MOZ_LIBWEBRTC_OFFICIAL_BRANCH' already exists remotely. Nothing to do."
+
+ echo "Please ensure this branch information can be found on Bug $MOZ_FASTFORWARD_BUG"
+ echo "https://github.com/mozilla/libwebrtc/tree/$MOZ_LIBWEBRTC_OFFICIAL_BRANCH"
+ exit 0
+fi
+
+if git show-ref --quiet refs/heads/$MOZ_LIBWEBRTC_OFFICIAL_BRANCH; then
+ echo "Branch '$MOZ_LIBWEBRTC_OFFICIAL_BRANCH' already exists locally. No need to create it."
+else
+ echo "Creating branch '$MOZ_LIBWEBRTC_OFFICIAL_BRANCH'"
+ git branch $MOZ_LIBWEBRTC_OFFICIAL_BRANCH
+fi
+
+echo "Pushing the branch to https://github.com/mozilla/libwebrtc"
+git push origin $MOZ_LIBWEBRTC_OFFICIAL_BRANCH
+
+echo "Please add this new branch information to Bug $MOZ_FASTFORWARD_BUG"
+echo "https://github.com/mozilla/libwebrtc/tree/$MOZ_LIBWEBRTC_OFFICIAL_BRANCH"
diff --git a/dom/media/webrtc/third_party_build/restore_elm_arcconfig.py b/dom/media/webrtc/third_party_build/restore_elm_arcconfig.py
new file mode 100644
index 0000000000..00c0bd7309
--- /dev/null
+++ b/dom/media/webrtc/third_party_build/restore_elm_arcconfig.py
@@ -0,0 +1,27 @@
+#!/bin/env python
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+
+from subprocess import run
+
+# This script sets the Arcanist configuration for the elm repo. This script
+# should be run after each repository reset.
+#
+# Usage: from the root of the repo `./mach python dom/media/webrtc/third_party_build/restore_elm_arcconfig.py`
+#
+
+ret = run(
+ [
+ "hg",
+ "import",
+ "-m",
+ "Bug 1729988 - FLOAT - REPO-elm - update .arcconfig repo callsign r=bgrins",
+ "dom/media/webrtc/third_party_build/elm_arcconfig.patch",
+ ]
+).returncode
+if ret != 0:
+ raise Exception(f"Failed to add FLOATing arcconfig patch for ELM: { ret }")
+else:
+ print("ELM .arcconfig restored. Please push this change to ELM")
diff --git a/dom/media/webrtc/third_party_build/restore_patch_stack.py b/dom/media/webrtc/third_party_build/restore_patch_stack.py
new file mode 100644
index 0000000000..6db3a64192
--- /dev/null
+++ b/dom/media/webrtc/third_party_build/restore_patch_stack.py
@@ -0,0 +1,107 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+import argparse
+import os
+import re
+import shutil
+
+from fetch_github_repo import fetch_repo
+from run_operations import run_git, run_shell
+
+# This script restores the mozilla patch stack and no-op commit tracking
+# files. In the case of repo corruption or a mistake made during
+# various rebase conflict resolution operations, the patch-stack can be
+# restored rather than requiring the user to restart the fast-forward
+# process from the beginning.
+
+
+def get_last_line(file_path):
+ # technique from https://stackoverflow.com/questions/46258499/how-to-read-the-last-line-of-a-file-in-python
+ with open(file_path, "rb") as f:
+ try: # catch OSError in case of a one line file
+ f.seek(-2, os.SEEK_END)
+ while f.read(1) != b"\n":
+ f.seek(-2, os.SEEK_CUR)
+ except OSError:
+ f.seek(0)
+ return f.readline().decode().strip()
+
+
+def restore_patch_stack(
+ github_path, github_branch, patch_directory, state_directory, tar_name
+):
+ # first, refetch the repo (hopefully utilizing the tarfile for speed) so
+ # the patches apply cleanly
+ fetch_repo(github_path, True, os.path.join(state_directory, tar_name))
+
+ # remove any stale no-op-cherry-pick-msg files in state_directory
+ run_shell("rm {}/*.no-op-cherry-pick-msg || true".format(state_directory))
+
+ # lookup latest vendored commit from third_party/libwebrtc/README.moz-ff-commit
+ file = os.path.abspath("third_party/libwebrtc/README.moz-ff-commit")
+ last_vendored_commit = get_last_line(file)
+
+ # checkout the previous vendored commit with proper branch name
+ cmd = "git checkout -b {} {}".format(github_branch, last_vendored_commit)
+ run_git(cmd, github_path)
+
+ # restore the patches to moz-libwebrtc repo, use run_shell instead of
+ # run_hg to allow filepath wildcard
+ print("Restoring patch stack")
+ run_shell("cd {} && git am {}/*.patch".format(github_path, patch_directory))
+
+ # it is also helpful to restore the no-op-cherry-pick-msg files to
+ # the state directory so that if we're restoring a patch-stack we
+ # also restore the possibly consumed no-op tracking files.
+ no_op_files = [
+ path
+ for path in os.listdir(patch_directory)
+ if re.findall(".*no-op-cherry-pick-msg$", path)
+ ]
+ for file in no_op_files:
+ shutil.copy(os.path.join(patch_directory, file), state_directory)
+
+
+if __name__ == "__main__":
+ default_patch_dir = "third_party/libwebrtc/moz-patch-stack"
+ default_state_dir = ".moz-fast-forward"
+ default_tar_name = "moz-libwebrtc.tar.gz"
+
+ parser = argparse.ArgumentParser(
+ description="Restore moz-libwebrtc github patch stack"
+ )
+ parser.add_argument(
+ "--repo-path",
+ required=True,
+ help="path to libwebrtc repo",
+ )
+ parser.add_argument(
+ "--branch",
+ default="mozpatches",
+ help="moz-libwebrtc branch (defaults to mozpatches)",
+ )
+ parser.add_argument(
+ "--patch-path",
+ default=default_patch_dir,
+ help="path to save patches (defaults to {})".format(default_patch_dir),
+ )
+ parser.add_argument(
+ "--tar-name",
+ default=default_tar_name,
+ help="name of tar file (defaults to {})".format(default_tar_name),
+ )
+ parser.add_argument(
+ "--state-path",
+ default=default_state_dir,
+ help="path to state directory (defaults to {})".format(default_state_dir),
+ )
+ args = parser.parse_args()
+
+ restore_patch_stack(
+ args.repo_path,
+ args.branch,
+ os.path.abspath(args.patch_path),
+ args.state_path,
+ args.tar_name,
+ )
diff --git a/dom/media/webrtc/third_party_build/run_operations.py b/dom/media/webrtc/third_party_build/run_operations.py
new file mode 100644
index 0000000000..794ea268d8
--- /dev/null
+++ b/dom/media/webrtc/third_party_build/run_operations.py
@@ -0,0 +1,78 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+import os
+import subprocess
+import sys
+
+# This is a collection of helper functions that use the subprocess.run
+# command to execute commands. In the future, if we have cases where
+# we find common python functionality in utilizing the data returned
+# from these functions, that functionality can live here for easy reuse
+# between other python scripts in dom/media/webrtc/third_party_build.
+
+
+# must run 'hg' with HGPLAIN=1 to ensure aliases don't interfere with
+# command output.
+env = os.environ.copy()
+env["HGPLAIN"] = "1"
+
+
+def run_hg(cmd):
+ cmd_list = cmd.split(" ")
+ res = subprocess.run(
+ cmd_list,
+ capture_output=True,
+ text=True,
+ env=env,
+ )
+ if res.returncode != 0:
+ print(
+ "Hit return code {} running '{}'. Aborting.".format(res.returncode, cmd),
+ file=sys.stderr,
+ )
+ print(res.stderr)
+ sys.exit(1)
+ stdout = res.stdout.strip()
+ return [] if len(stdout) == 0 else stdout.split("\n")
+
+
+def run_git(cmd, working_dir):
+ cmd_list = cmd.split(" ")
+ res = subprocess.run(
+ cmd_list,
+ capture_output=True,
+ text=True,
+ cwd=working_dir,
+ )
+ if res.returncode != 0:
+ print(
+ "Hit return code {} running '{}'. Aborting.".format(res.returncode, cmd),
+ file=sys.stderr,
+ )
+ print(res.stderr)
+ sys.exit(1)
+ stdout = res.stdout.strip()
+ return [] if len(stdout) == 0 else stdout.split("\n")
+
+
+def run_shell(cmd, capture_output=True):
+ res = subprocess.run(
+ cmd,
+ shell=True,
+ capture_output=capture_output,
+ text=True,
+ )
+ if res.returncode != 0:
+ print(
+ "Hit return code {} running '{}'. Aborting.".format(res.returncode, cmd),
+ file=sys.stderr,
+ )
+ print(res.stderr)
+ sys.exit(1)
+ output_lines = []
+ if capture_output:
+ stdout = res.stdout.strip()
+ output_lines = [] if len(stdout) == 0 else stdout.split("\n")
+
+ return output_lines
diff --git a/dom/media/webrtc/third_party_build/save_patch_stack.py b/dom/media/webrtc/third_party_build/save_patch_stack.py
new file mode 100644
index 0000000000..11c007109c
--- /dev/null
+++ b/dom/media/webrtc/third_party_build/save_patch_stack.py
@@ -0,0 +1,142 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+import argparse
+import os
+import re
+import shutil
+
+from run_operations import run_git, run_hg, run_shell
+
+# This script saves the mozilla patch stack and no-op commit tracking
+# files. This makes our fast-forward process much more resilient by
+# saving the intermediate state after each upstream commit is processed.
+
+
+def save_patch_stack(
+ github_path,
+ github_branch,
+ patch_directory,
+ state_directory,
+ target_branch_head,
+ bug_number,
+):
+ # remove the current patch files
+ files_to_remove = os.listdir(patch_directory)
+ for file in files_to_remove:
+ os.remove(os.path.join(patch_directory, file))
+
+ # find the base of the patch stack
+ cmd = "git merge-base {} {}".format(github_branch, target_branch_head)
+ stdout_lines = run_git(cmd, github_path)
+ merge_base = stdout_lines[0]
+
+ # grab patch stack
+ cmd = "git format-patch --keep-subject --output-directory {} {}..{}".format(
+ patch_directory, merge_base, github_branch
+ )
+ run_git(cmd, github_path)
+
+ # remove the commit summary from the file name
+ patches_to_rename = os.listdir(patch_directory)
+ for file in patches_to_rename:
+ shortened_name = re.sub("^(\d\d\d\d)-.*\.patch", "\\1.patch", file)
+ os.rename(
+ os.path.join(patch_directory, file),
+ os.path.join(patch_directory, shortened_name),
+ )
+
+ # remove the unhelpful first line of the patch files that only
+ # causes diff churn. For reasons why we can't skip creating backup
+ # files during the in-place editing, see:
+ # https://stackoverflow.com/questions/5694228/sed-in-place-flag-that-works-both-on-mac-bsd-and-linux
+ run_shell("sed -i'.bak' -e '1d' {}/*.patch".format(patch_directory))
+ run_shell("rm {}/*.patch.bak".format(patch_directory))
+
+ # it is also helpful to save the no-op-cherry-pick-msg files from
+ # the state directory so that if we're restoring a patch-stack we
+ # also restore the possibly consumed no-op tracking files.
+ no_op_files = [
+ path
+ for path in os.listdir(state_directory)
+ if re.findall(".*no-op-cherry-pick-msg$", path)
+ ]
+ for file in no_op_files:
+ shutil.copy(os.path.join(state_directory, file), patch_directory)
+
+ # get missing files (that should be marked removed)
+ cmd = "hg status --no-status --deleted {}".format(patch_directory)
+ stdout_lines = run_hg(cmd)
+ if len(stdout_lines) != 0:
+ cmd = "hg rm {}".format(" ".join(stdout_lines))
+ run_hg(cmd)
+
+ # get unknown files (that should be marked added)
+ cmd = "hg status --no-status --unknown {}".format(patch_directory)
+ stdout_lines = run_hg(cmd)
+ if len(stdout_lines) != 0:
+ cmd = "hg add {}".format(" ".join(stdout_lines))
+ run_hg(cmd)
+
+ # if any files are marked for add/remove/modify, commit them
+ cmd = "hg status --added --removed --modified {}".format(patch_directory)
+ stdout_lines = run_hg(cmd)
+ if (len(stdout_lines)) != 0:
+ print("Updating {} files in {}".format(len(stdout_lines), patch_directory))
+ if bug_number is None:
+ run_hg("hg amend")
+ else:
+ run_shell(
+ "hg commit --message 'Bug {} - updated libwebrtc patch stack'".format(
+ bug_number
+ )
+ )
+
+
+if __name__ == "__main__":
+ default_patch_dir = "third_party/libwebrtc/moz-patch-stack"
+ default_state_dir = ".moz-fast-forward"
+
+ parser = argparse.ArgumentParser(
+ description="Save moz-libwebrtc github patch stack"
+ )
+ parser.add_argument(
+ "--repo-path",
+ required=True,
+ help="path to libwebrtc repo",
+ )
+ parser.add_argument(
+ "--branch",
+ default="mozpatches",
+ help="moz-libwebrtc branch (defaults to mozpatches)",
+ )
+ parser.add_argument(
+ "--patch-path",
+ default=default_patch_dir,
+ help="path to save patches (defaults to {})".format(default_patch_dir),
+ )
+ parser.add_argument(
+ "--state-path",
+ default=default_state_dir,
+ help="path to state directory (defaults to {})".format(default_state_dir),
+ )
+ parser.add_argument(
+ "--target-branch-head",
+ required=True,
+ help="target branch head for fast-forward, should match MOZ_TARGET_UPSTREAM_BRANCH_HEAD in config_env",
+ )
+ parser.add_argument(
+ "--separate-commit-bug-number",
+ type=int,
+ help="integer Bugzilla number (example: 1800920), if provided will write patch stack as separate commit",
+ )
+ args = parser.parse_args()
+
+ save_patch_stack(
+ args.repo_path,
+ args.branch,
+ os.path.abspath(args.patch_path),
+ args.state_path,
+ args.target_branch_head,
+ args.separate_commit_bug_number,
+ )
diff --git a/dom/media/webrtc/third_party_build/update_default_config.sh b/dom/media/webrtc/third_party_build/update_default_config.sh
new file mode 100644
index 0000000000..f4020f9b45
--- /dev/null
+++ b/dom/media/webrtc/third_party_build/update_default_config.sh
@@ -0,0 +1,46 @@
+#!/bin/bash
+
+function show_error_msg()
+{
+ echo "*** ERROR *** $? line $1 $0 did not complete successfully!"
+ echo "$ERROR_HELP"
+}
+ERROR_HELP=""
+
+# Print an Error message if `set -eE` causes the script to exit due to a failed command
+trap 'show_error_msg $LINENO' ERR
+
+if [ "x" = "x$NEW_BUG_NUMBER" ]; then
+ echo "NEW_BUG_NUMBER is not defined. You should probably have a new bug"
+ echo "number defined for the next fast-forward update. Then do:"
+ echo " NEW_BUG_NUMBER={new-bug-number} bash $0"
+ exit
+fi
+
+source dom/media/webrtc/third_party_build/use_config_env.sh
+
+if [ "x$MOZ_NEXT_LIBWEBRTC_MILESTONE" = "x" ]; then
+ echo "MOZ_NEXT_LIBWEBRTC_MILESTONE is not defined, see README.md"
+ exit
+fi
+
+if [ "x$MOZ_NEXT_FIREFOX_REL_TARGET" = "x" ]; then
+ echo "MOZ_NEXT_FIREFOX_REL_TARGET is not defined, see README.md"
+ exit
+fi
+
+
+# After this point:
+# * eE: All commands should succeed.
+# * u: All variables should be defined before use.
+# * o pipefail: All stages of all pipes should succeed.
+set -eEuo pipefail
+
+ERROR_HELP=$"
+An error has occurred running $SCRIPT_DIR/write_default_config.py
+"
+./mach python $SCRIPT_DIR/write_default_config.py \
+ --bug-number $NEW_BUG_NUMBER \
+ --milestone $MOZ_NEXT_LIBWEBRTC_MILESTONE \
+ --release-target $MOZ_NEXT_FIREFOX_REL_TARGET \
+ > $SCRIPT_DIR/default_config_env
diff --git a/dom/media/webrtc/third_party_build/use_config_env.sh b/dom/media/webrtc/third_party_build/use_config_env.sh
new file mode 100644
index 0000000000..bd78dc7e0e
--- /dev/null
+++ b/dom/media/webrtc/third_party_build/use_config_env.sh
@@ -0,0 +1,89 @@
+#!/bin/bash
+
+# Assume that if STATE_DIR is already defined, we do not need to
+# execute this file again.
+if [ "x$STATE_DIR" != "x" ]; then
+ # no need to run script since we've already run
+ return
+fi
+
+export SCRIPT_DIR="dom/media/webrtc/third_party_build"
+# first, make sure we're running from the top of moz-central repo
+if [ ! -d $SCRIPT_DIR ]; then
+ echo "Error: unable to find directory $SCRIPT_DIR"
+ exit 1
+fi
+
+# Should we tie the location of the STATE_DIR to the path
+# in MOZ_CONFIG_PATH? Probably.
+export STATE_DIR=`pwd`/.moz-fast-forward
+export LOG_DIR=$STATE_DIR/logs
+export TMP_DIR=$STATE_DIR/tmp
+
+if [ ! -d $STATE_DIR ]; then
+ echo "Creating missing $STATE_DIR"
+ mkdir -p $STATE_DIR
+ if [ ! -d $STATE_DIR ]; then
+ echo "error: unable to find (or create) $STATE_DIR"
+ exit 1
+ fi
+fi
+echo "Using STATE_DIR=$STATE_DIR"
+
+if [ ! -d $LOG_DIR ]; then
+ echo "Creating missing $LOG_DIR"
+ mkdir -p $LOG_DIR
+ if [ ! -d $LOG_DIR ]; then
+ echo "error: unable to find (or create) $LOG_DIR"
+ exit 1
+ fi
+fi
+echo "Using LOG_DIR=$LOG_DIR"
+
+if [ ! -d $TMP_DIR ]; then
+ echo "Creating missing $TMP_DIR"
+ mkdir -p $TMP_DIR
+ if [ ! -d $TMP_DIR ]; then
+ echo "error: unable to find (or create) $TMP_DIR"
+ exit 1
+ fi
+fi
+echo "Using TMP_DIR=$TMP_DIR"
+
+# Allow user to override default path to config_env
+if [ "x$MOZ_CONFIG_PATH" = "x" ]; then
+ MOZ_CONFIG_PATH=$STATE_DIR/config_env
+ echo "Using default MOZ_CONFIG_PATH=$MOZ_CONFIG_PATH"
+fi
+
+if [ ! -f $MOZ_CONFIG_PATH ]; then
+ echo "Creating default config file at $MOZ_CONFIG_PATH"
+ cp $SCRIPT_DIR/default_config_env $MOZ_CONFIG_PATH
+fi
+source $MOZ_CONFIG_PATH
+
+
+function find_base_commit()
+{
+ # read the last line of README.moz-ff-commit to retrieve our current base
+ # commit in moz-libwebrtc
+ MOZ_LIBWEBRTC_BASE=`tail -1 third_party/libwebrtc/README.moz-ff-commit`
+ echo "prelim MOZ_LIBWEBRTC_BASE: $MOZ_LIBWEBRTC_BASE"
+ # if we've advanced into a chrome release branch, we need to adjust the
+ # MOZ_LIBWEBRTC_BASE to the last common commit so we can now advance up
+ # the trunk commits.
+ MOZ_LIBWEBRTC_BASE=`cd $MOZ_LIBWEBRTC_SRC ; git merge-base $MOZ_LIBWEBRTC_BASE $MOZ_TARGET_UPSTREAM_BRANCH_HEAD`
+ # now make it a short hash
+ MOZ_LIBWEBRTC_BASE=`cd $MOZ_LIBWEBRTC_SRC ; git rev-parse --short $MOZ_LIBWEBRTC_BASE`
+ echo "adjusted MOZ_LIBWEBRTC_BASE: $MOZ_LIBWEBRTC_BASE"
+}
+export -f find_base_commit
+
+function find_next_commit()
+{
+ # identify the next commit above our current base commit
+ MOZ_LIBWEBRTC_NEXT_BASE=`cd $MOZ_LIBWEBRTC_SRC ; \
+ git log --oneline --ancestry-path $MOZ_LIBWEBRTC_BASE^..$MOZ_TARGET_UPSTREAM_BRANCH_HEAD \
+ | tail -2 | head -1 | awk '{print $1;}'`
+}
+export -f find_next_commit
diff --git a/dom/media/webrtc/third_party_build/vendor-libwebrtc.py b/dom/media/webrtc/third_party_build/vendor-libwebrtc.py
new file mode 100644
index 0000000000..99723849c7
--- /dev/null
+++ b/dom/media/webrtc/third_party_build/vendor-libwebrtc.py
@@ -0,0 +1,419 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+import argparse
+import datetime
+import os
+import shutil
+import stat
+import subprocess
+import sys
+import tarfile
+
+import requests
+
+THIRDPARTY_USED_IN_FIREFOX = [
+ "abseil-cpp",
+ "google_benchmark",
+ "pffft",
+ "rnnoise",
+]
+
+LIBWEBRTC_DIR = os.path.normpath("third_party/libwebrtc")
+
+
+def get_excluded_paths():
+ return [
+ ".clang-format",
+ ".git-blame-ignore-revs",
+ ".gitignore",
+ ".vpython",
+ "CODE_OF_CONDUCT.md",
+ "ENG_REVIEW_OWNERS",
+ "PRESUBMIT.py",
+ "README.chromium",
+ "WATCHLISTS",
+ "codereview.settings",
+ "license_template.txt",
+ "native-api.md",
+ "presubmit_test.py",
+ "presubmit_test_mocks.py",
+ "pylintrc",
+ # Only the camera code under sdk/android/api/org/webrtc is used, so
+ # we remove sdk/android and add back the specific files we want.
+ "sdk/android",
+ ]
+
+
+# Paths in this list are included even if their parent directory is
+# excluded in get_excluded_paths()
+def get_included_path_overrides():
+ return [
+ "sdk/android/src/java/org/webrtc/NativeLibrary.java",
+ "sdk/android/src/java/org/webrtc/FramerateBitrateAdjuster.java",
+ "sdk/android/src/java/org/webrtc/MediaCodecVideoDecoderFactory.java",
+ "sdk/android/src/java/org/webrtc/BitrateAdjuster.java",
+ "sdk/android/src/java/org/webrtc/MediaCodecWrapperFactory.java",
+ "sdk/android/src/java/org/webrtc/WebRtcClassLoader.java",
+ "sdk/android/src/java/org/webrtc/audio/WebRtcAudioRecord.java",
+ "sdk/android/src/java/org/webrtc/audio/WebRtcAudioTrack.java",
+ "sdk/android/src/java/org/webrtc/audio/WebRtcAudioManager.java",
+ "sdk/android/src/java/org/webrtc/audio/LowLatencyAudioBufferManager.java",
+ "sdk/android/src/java/org/webrtc/audio/WebRtcAudioUtils.java",
+ "sdk/android/src/java/org/webrtc/audio/WebRtcAudioEffects.java",
+ "sdk/android/src/java/org/webrtc/audio/VolumeLogger.java",
+ "sdk/android/src/java/org/webrtc/NativeCapturerObserver.java",
+ "sdk/android/src/java/org/webrtc/MediaCodecWrapper.java",
+ "sdk/android/src/java/org/webrtc/CalledByNative.java",
+ "sdk/android/src/java/org/webrtc/Histogram.java",
+ "sdk/android/src/java/org/webrtc/EglBase10Impl.java",
+ "sdk/android/src/java/org/webrtc/EglBase14Impl.java",
+ "sdk/android/src/java/org/webrtc/MediaCodecWrapperFactoryImpl.java",
+ "sdk/android/src/java/org/webrtc/AndroidVideoDecoder.java",
+ "sdk/android/src/java/org/webrtc/BaseBitrateAdjuster.java",
+ "sdk/android/src/java/org/webrtc/HardwareVideoEncoder.java",
+ "sdk/android/src/java/org/webrtc/VideoCodecMimeType.java",
+ "sdk/android/src/java/org/webrtc/NativeAndroidVideoTrackSource.java",
+ "sdk/android/src/java/org/webrtc/VideoDecoderWrapper.java",
+ "sdk/android/src/java/org/webrtc/JNILogging.java",
+ "sdk/android/src/java/org/webrtc/CameraCapturer.java",
+ "sdk/android/src/java/org/webrtc/CameraSession.java",
+ "sdk/android/src/java/org/webrtc/H264Utils.java",
+ "sdk/android/src/java/org/webrtc/Empty.java",
+ "sdk/android/src/java/org/webrtc/DynamicBitrateAdjuster.java",
+ "sdk/android/src/java/org/webrtc/Camera1Session.java",
+ "sdk/android/src/java/org/webrtc/JniCommon.java",
+ "sdk/android/src/java/org/webrtc/NV12Buffer.java",
+ "sdk/android/src/java/org/webrtc/WrappedNativeI420Buffer.java",
+ "sdk/android/src/java/org/webrtc/GlGenericDrawer.java",
+ "sdk/android/src/java/org/webrtc/RefCountDelegate.java",
+ "sdk/android/src/java/org/webrtc/Camera2Session.java",
+ "sdk/android/src/java/org/webrtc/MediaCodecUtils.java",
+ "sdk/android/src/java/org/webrtc/CalledByNativeUnchecked.java",
+ "sdk/android/src/java/org/webrtc/VideoEncoderWrapper.java",
+ "sdk/android/src/java/org/webrtc/NV21Buffer.java",
+ "sdk/android/api/org/webrtc/RendererCommon.java",
+ "sdk/android/api/org/webrtc/YuvHelper.java",
+ "sdk/android/api/org/webrtc/LibvpxVp9Encoder.java",
+ "sdk/android/api/org/webrtc/Metrics.java",
+ "sdk/android/api/org/webrtc/CryptoOptions.java",
+ "sdk/android/api/org/webrtc/MediaConstraints.java",
+ "sdk/android/api/org/webrtc/YuvConverter.java",
+ "sdk/android/api/org/webrtc/JavaI420Buffer.java",
+ "sdk/android/api/org/webrtc/VideoDecoder.java",
+ "sdk/android/api/org/webrtc/WrappedNativeVideoDecoder.java",
+ "sdk/android/api/org/webrtc/Camera2Enumerator.java",
+ "sdk/android/api/org/webrtc/SurfaceTextureHelper.java",
+ "sdk/android/api/org/webrtc/EglBase10.java",
+ "sdk/android/api/org/webrtc/DataChannel.java",
+ "sdk/android/api/org/webrtc/audio/JavaAudioDeviceModule.java",
+ "sdk/android/api/org/webrtc/audio/AudioDeviceModule.java",
+ "sdk/android/api/org/webrtc/audio/LegacyAudioDeviceModule.java",
+ "sdk/android/api/org/webrtc/SessionDescription.java",
+ "sdk/android/api/org/webrtc/GlUtil.java",
+ "sdk/android/api/org/webrtc/VideoSource.java",
+ "sdk/android/api/org/webrtc/AudioTrack.java",
+ "sdk/android/api/org/webrtc/EglRenderer.java",
+ "sdk/android/api/org/webrtc/VideoEncoder.java",
+ "sdk/android/api/org/webrtc/VideoCapturer.java",
+ "sdk/android/api/org/webrtc/SoftwareVideoDecoderFactory.java",
+ "sdk/android/api/org/webrtc/AudioSource.java",
+ "sdk/android/api/org/webrtc/GlRectDrawer.java",
+ "sdk/android/api/org/webrtc/StatsReport.java",
+ "sdk/android/api/org/webrtc/CameraVideoCapturer.java",
+ "sdk/android/api/org/webrtc/NetEqFactoryFactory.java",
+ "sdk/android/api/org/webrtc/AudioProcessingFactory.java",
+ "sdk/android/api/org/webrtc/Camera2Capturer.java",
+ "sdk/android/api/org/webrtc/ScreenCapturerAndroid.java",
+ "sdk/android/api/org/webrtc/RefCounted.java",
+ "sdk/android/api/org/webrtc/VideoEncoderFallback.java",
+ "sdk/android/api/org/webrtc/AudioEncoderFactoryFactory.java",
+ "sdk/android/api/org/webrtc/EglBase14.java",
+ "sdk/android/api/org/webrtc/SoftwareVideoEncoderFactory.java",
+ "sdk/android/api/org/webrtc/VideoEncoderFactory.java",
+ "sdk/android/api/org/webrtc/StatsObserver.java",
+ "sdk/android/api/org/webrtc/PlatformSoftwareVideoDecoderFactory.java",
+ "sdk/android/api/org/webrtc/Camera1Capturer.java",
+ "sdk/android/api/org/webrtc/AddIceObserver.java",
+ "sdk/android/api/org/webrtc/SurfaceViewRenderer.java",
+ "sdk/android/api/org/webrtc/CameraEnumerator.java",
+ "sdk/android/api/org/webrtc/CameraEnumerationAndroid.java",
+ "sdk/android/api/org/webrtc/VideoDecoderFallback.java",
+ "sdk/android/api/org/webrtc/FileVideoCapturer.java",
+ "sdk/android/api/org/webrtc/NativeLibraryLoader.java",
+ "sdk/android/api/org/webrtc/Camera1Enumerator.java",
+ "sdk/android/api/org/webrtc/NativePeerConnectionFactory.java",
+ "sdk/android/api/org/webrtc/LibaomAv1Encoder.java",
+ "sdk/android/api/org/webrtc/BuiltinAudioEncoderFactoryFactory.java",
+ "sdk/android/api/org/webrtc/AudioDecoderFactoryFactory.java",
+ "sdk/android/api/org/webrtc/FecControllerFactoryFactoryInterface.java",
+ "sdk/android/api/org/webrtc/VideoFrameBufferType.java",
+ "sdk/android/api/org/webrtc/SdpObserver.java",
+ "sdk/android/api/org/webrtc/Predicate.java",
+ "sdk/android/api/org/webrtc/VideoFileRenderer.java",
+ "sdk/android/api/org/webrtc/WrappedNativeVideoEncoder.java",
+ "sdk/android/api/org/webrtc/LibvpxVp8Encoder.java",
+ "sdk/android/api/org/webrtc/DtmfSender.java",
+ "sdk/android/api/org/webrtc/VideoTrack.java",
+ "sdk/android/api/org/webrtc/LibvpxVp8Decoder.java",
+ "sdk/android/api/org/webrtc/GlShader.java",
+ "sdk/android/api/org/webrtc/FrameEncryptor.java",
+ "sdk/android/api/org/webrtc/EglBase.java",
+ "sdk/android/api/org/webrtc/VideoProcessor.java",
+ "sdk/android/api/org/webrtc/SSLCertificateVerifier.java",
+ "sdk/android/api/org/webrtc/VideoSink.java",
+ "sdk/android/api/org/webrtc/MediaSource.java",
+ "sdk/android/api/org/webrtc/DefaultVideoDecoderFactory.java",
+ "sdk/android/api/org/webrtc/VideoCodecInfo.java",
+ "sdk/android/api/org/webrtc/FrameDecryptor.java",
+ "sdk/android/api/org/webrtc/VideoDecoderFactory.java",
+ "sdk/android/api/org/webrtc/TextureBufferImpl.java",
+ "sdk/android/api/org/webrtc/VideoFrame.java",
+ "sdk/android/api/org/webrtc/IceCandidateErrorEvent.java",
+ "sdk/android/api/org/webrtc/CapturerObserver.java",
+ "sdk/android/api/org/webrtc/MediaStreamTrack.java",
+ "sdk/android/api/org/webrtc/GlTextureFrameBuffer.java",
+ "sdk/android/api/org/webrtc/TurnCustomizer.java",
+ "sdk/android/api/org/webrtc/TimestampAligner.java",
+ "sdk/android/api/org/webrtc/BuiltinAudioDecoderFactoryFactory.java",
+ "sdk/android/api/org/webrtc/LibvpxVp9Decoder.java",
+ "sdk/android/api/org/webrtc/SurfaceEglRenderer.java",
+ "sdk/android/api/org/webrtc/HardwareVideoDecoderFactory.java",
+ "sdk/android/api/org/webrtc/VideoCodecStatus.java",
+ "sdk/android/api/org/webrtc/Dav1dDecoder.java",
+ "sdk/android/api/org/webrtc/VideoFrameDrawer.java",
+ "sdk/android/api/org/webrtc/CallSessionFileRotatingLogSink.java",
+ "sdk/android/api/org/webrtc/EncodedImage.java",
+ ]
+
+
+def make_github_url(repo, commit):
+ if not repo.endswith("/"):
+ repo += "/"
+ return repo + "archive/" + commit + ".tar.gz"
+
+
+def make_googlesource_url(target, commit):
+ if target == "libwebrtc":
+ return "https://webrtc.googlesource.com/src.git/+archive/" + commit + ".tar.gz"
+ elif target == "build":
+ return (
+ "https://chromium.googlesource.com/chromium/src/build/+archive/"
+ + commit
+ + ".tar.gz"
+ )
+ elif target == "third_party":
+ return (
+ "https://chromium.googlesource.com/chromium/src/third_party/+archive/"
+ + commit
+ + ".tar.gz"
+ )
+
+
+def fetch(target, url):
+ print("Fetching commit from {}".format(url))
+ req = requests.get(url)
+ if req.status_code == 200:
+ with open(target + ".tar.gz", "wb") as f:
+ f.write(req.content)
+ else:
+ print(
+ "Hit status code {} fetching commit. Aborting.".format(req.status_code),
+ file=sys.stderr,
+ )
+ sys.exit(1)
+ with open(os.path.join(LIBWEBRTC_DIR, "README.mozilla"), "a") as f:
+ # write the the command line used
+ f.write("# ./mach python {}\n".format(" ".join(sys.argv[0:])))
+ f.write(
+ "{} updated from commit {} on {}.\n".format(
+ target, url, datetime.datetime.utcnow().isoformat()
+ )
+ )
+
+
+def fetch_local(target, path, commit):
+ target_archive = target + ".tar.gz"
+ cp = subprocess.run(["git", "archive", "-o", target_archive, commit], cwd=path)
+ if cp.returncode != 0:
+ print(
+ "Hit return code {} fetching commit. Aborting.".format(cp.returncode),
+ file=sys.stderr,
+ )
+ sys.exit(1)
+
+ with open(os.path.join(LIBWEBRTC_DIR, "README.mozilla"), "a") as f:
+ # write the the command line used
+ f.write("# ./mach python {}\n".format(" ".join(sys.argv[0:])))
+ f.write(
+ "{} updated from {} commit {} on {}.\n".format(
+ target, path, commit, datetime.datetime.utcnow().isoformat()
+ )
+ )
+ shutil.move(os.path.join(path, target_archive), target_archive)
+
+
+def validate_tar_member(member, path):
+ def _is_within_directory(directory, target):
+ real_directory = os.path.realpath(directory)
+ real_target = os.path.realpath(target)
+ prefix = os.path.commonprefix([real_directory, real_target])
+ return prefix == real_directory
+
+ member_path = os.path.join(path, member.name)
+ if not _is_within_directory(path, member_path):
+ raise Exception("Attempted path traversal in tar file: " + member.name)
+ if member.issym():
+ link_path = os.path.join(os.path.dirname(member_path), member.linkname)
+ if not _is_within_directory(path, link_path):
+ raise Exception("Attempted link path traversal in tar file: " + member.name)
+ if member.mode & (stat.S_ISUID | stat.S_ISGID):
+ raise Exception("Attempted setuid or setgid in tar file: " + member.name)
+
+
+def safe_extract(tar, path=".", *, numeric_owner=False):
+ def _files(tar, path):
+ for member in tar:
+ validate_tar_member(member, path)
+ yield member
+
+ tar.extractall(path, members=_files(tar, path), numeric_owner=numeric_owner)
+
+
+def unpack(target):
+ target_archive = target + ".tar.gz"
+ target_path = "tmp-" + target
+ try:
+ shutil.rmtree(target_path)
+ except FileNotFoundError:
+ pass
+ with tarfile.open(target_archive) as t:
+ safe_extract(t, path=target_path)
+
+ if target == "libwebrtc":
+ # use the top level directories from the tarfile and
+ # delete those directories in LIBWEBRTC_DIR
+ libwebrtc_used_in_firefox = os.listdir(target_path)
+ for path in libwebrtc_used_in_firefox:
+ try:
+ shutil.rmtree(os.path.join(LIBWEBRTC_DIR, path))
+ except FileNotFoundError:
+ pass
+ except NotADirectoryError:
+ pass
+
+ unused_libwebrtc_in_firefox = get_excluded_paths()
+ forced_used_in_firefox = get_included_path_overrides()
+
+ # adjust target_path if GitHub packaging is involved
+ if not os.path.exists(os.path.join(target_path, libwebrtc_used_in_firefox[0])):
+ # GitHub packs everything inside a separate directory
+ target_path = os.path.join(target_path, os.listdir(target_path)[0])
+
+ # remove any entries found in unused_libwebrtc_in_firefox from the
+ # tarfile
+ for path in unused_libwebrtc_in_firefox:
+ if os.path.isdir(os.path.join(target_path, path)):
+ shutil.rmtree(os.path.join(target_path, path))
+ else:
+ os.remove(os.path.join(target_path, path))
+
+ # move remaining top level entries from the tarfile to LIBWEBRTC_DIR
+ for path in os.listdir(target_path):
+ shutil.move(
+ os.path.join(target_path, path), os.path.join(LIBWEBRTC_DIR, path)
+ )
+
+ # An easy, but inefficient way to accomplish including specific
+ # files from directories otherwise removed. Re-extract the tar
+ # file, and only copy over the exact files requested.
+ shutil.rmtree(target_path)
+ with tarfile.open(target_archive) as t:
+ safe_extract(t, path=target_path)
+
+ # Copy the force included files. Note: the instinctual action
+ # is to do this prior to removing the excluded paths to avoid
+ # reextracting the tar file. However, this causes errors due to
+ # pre-existing paths when other directories are moved out of the
+ # tar file in the "move all the top level entries from the
+ # tarfile" phase above.
+ for path in forced_used_in_firefox:
+ dest_path = os.path.join(LIBWEBRTC_DIR, path)
+ dir_path = os.path.dirname(dest_path)
+ if not os.path.exists(dir_path):
+ os.makedirs(dir_path)
+ shutil.move(os.path.join(target_path, path), dest_path)
+ elif target == "build":
+ try:
+ shutil.rmtree(os.path.join(LIBWEBRTC_DIR, "build"))
+ except FileNotFoundError:
+ pass
+ os.makedirs(os.path.join(LIBWEBRTC_DIR, "build"))
+
+ if os.path.exists(os.path.join(target_path, "linux")):
+ for path in os.listdir(target_path):
+ shutil.move(
+ os.path.join(target_path, path),
+ os.path.join(LIBWEBRTC_DIR, "build", path),
+ )
+ else:
+ # GitHub packs everything inside a separate directory
+ target_path = os.path.join(target_path, os.listdir(target_path)[0])
+ for path in os.listdir(target_path):
+ shutil.move(
+ os.path.join(target_path, path),
+ os.path.join(LIBWEBRTC_DIR, "build", path),
+ )
+ elif target == "third_party":
+ try:
+ shutil.rmtree(os.path.join(LIBWEBRTC_DIR, "third_party"))
+ except FileNotFoundError:
+ pass
+ except NotADirectoryError:
+ pass
+
+ if os.path.exists(os.path.join(target_path, THIRDPARTY_USED_IN_FIREFOX[0])):
+ for path in THIRDPARTY_USED_IN_FIREFOX:
+ shutil.move(
+ os.path.join(target_path, path),
+ os.path.join(LIBWEBRTC_DIR, "third_party", path),
+ )
+ else:
+ # GitHub packs everything inside a separate directory
+ target_path = os.path.join(target_path, os.listdir(target_path)[0])
+ for path in THIRDPARTY_USED_IN_FIREFOX:
+ shutil.move(
+ os.path.join(target_path, path),
+ os.path.join(LIBWEBRTC_DIR, "third_party", path),
+ )
+
+
+def cleanup(target):
+ os.remove(target + ".tar.gz")
+ shutil.rmtree("tmp-" + target)
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description="Update libwebrtc")
+ parser.add_argument("target", choices=("libwebrtc", "build", "third_party"))
+ group = parser.add_mutually_exclusive_group(required=True)
+ group.add_argument("--from-github", type=str)
+ group.add_argument("--from-googlesource", action="store_true", default=False)
+ group.add_argument("--from-local", type=str)
+ parser.add_argument("--commit", type=str, default="master")
+ parser.add_argument("--skip-fetch", action="store_true", default=False)
+ parser.add_argument("--skip-cleanup", action="store_true", default=False)
+ args = parser.parse_args()
+
+ os.makedirs(LIBWEBRTC_DIR, exist_ok=True)
+
+ if not args.skip_fetch:
+ if args.from_github:
+ fetch(args.target, make_github_url(args.from_github, args.commit))
+ elif args.from_googlesource:
+ fetch(args.target, make_googlesource_url(args.target, args.commit))
+ elif args.from_local:
+ fetch_local(args.target, args.from_local, args.commit)
+ unpack(args.target)
+ if not args.skip_cleanup:
+ cleanup(args.target)
diff --git a/dom/media/webrtc/third_party_build/verify_vendoring.sh b/dom/media/webrtc/third_party_build/verify_vendoring.sh
new file mode 100644
index 0000000000..3d2c161075
--- /dev/null
+++ b/dom/media/webrtc/third_party_build/verify_vendoring.sh
@@ -0,0 +1,55 @@
+#!/bin/bash
+
+function show_error_msg()
+{
+ echo "*** ERROR *** $? line $1 $0 did not complete successfully!"
+ echo "$ERROR_HELP"
+}
+ERROR_HELP=""
+
+# Print an Error message if `set -eE` causes the script to exit due to a failed command
+trap 'show_error_msg $LINENO' ERR
+
+source dom/media/webrtc/third_party_build/use_config_env.sh
+
+echo "MOZ_LIBWEBRTC_SRC: $MOZ_LIBWEBRTC_SRC"
+echo "MOZ_LIBWEBRTC_BRANCH: $MOZ_LIBWEBRTC_BRANCH"
+echo "MOZ_FASTFORWARD_BUG: $MOZ_FASTFORWARD_BUG"
+
+# After this point:
+# * eE: All commands should succeed.
+# * u: All variables should be defined before use.
+# * o pipefail: All stages of all pipes should succeed.
+set -eEuo pipefail
+
+./mach python $SCRIPT_DIR/vendor-libwebrtc.py \
+ --from-local $MOZ_LIBWEBRTC_SRC \
+ --commit $MOZ_LIBWEBRTC_BRANCH \
+ libwebrtc
+
+hg revert -q \
+ --include "third_party/libwebrtc/**moz.build" \
+ --include "third_party/libwebrtc/README.mozilla" \
+ third_party/libwebrtc
+
+FILE_CHANGE_CNT=`hg status third_party/libwebrtc | wc -l | tr -d " "`
+if [ "x$FILE_CHANGE_CNT" != "x0" ]; then
+ echo "***"
+ echo "There are changes after vendoring - running extract-for-git.py"
+ echo "is recommended. First, find the mercurial commit after the"
+ echo "previous fast-forward landing. The commands you want will look"
+ echo "something like:"
+ echo " ./mach python $SCRIPT_DIR/extract-for-git.py {after-ff-commit}::{tip-of-central}"
+ echo " mv mailbox.patch $MOZ_LIBWEBRTC_SRC"
+ echo " (cd $MOZ_LIBWEBRTC_SRC && \\"
+ echo " git am mailbox.patch)"
+ echo ""
+ echo "After adding the new changes from moz-central to the moz-libwebrtc"
+ echo "patch stack, you may re-run this command to verify vendoring:"
+ echo " bash $0"
+
+ exit 1
+fi
+
+
+echo "Done - vendoring has been verified."
diff --git a/dom/media/webrtc/third_party_build/webrtc.mozbuild b/dom/media/webrtc/third_party_build/webrtc.mozbuild
new file mode 100644
index 0000000000..30169c36c2
--- /dev/null
+++ b/dom/media/webrtc/third_party_build/webrtc.mozbuild
@@ -0,0 +1,40 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# This file mimics the defines used by any headers in libwebrtc that get
+# included in Mozilla code.
+# We currently audit these by hand. Searching for the defines upstream is a good
+# start:
+# https://source.chromium.org/search?q=usage:%23if.*def.*%5B%5E_%5D$%20AND%20path:%5C.h%20AND%20path:%5Ethird_party%2Fwebrtc&ss=chromium
+
+if CONFIG['MOZ_WEBRTC']:
+ DEFINES['HAVE_UINT64_T'] = True
+ DEFINES['WEBRTC_MOZILLA_BUILD'] = True
+ DEFINES['RTC_ENABLE_VP9'] = True
+
+ if CONFIG['OS_TARGET'] != 'WINNT':
+ DEFINES['WEBRTC_POSIX'] = True
+ DEFINES['WEBRTC_BUILD_LIBEVENT'] = True
+
+ if CONFIG['OS_TARGET'] == 'Linux':
+ DEFINES['WEBRTC_LINUX'] = True
+ elif CONFIG['OS_TARGET'] == 'Darwin':
+ DEFINES['WEBRTC_MAC'] = True
+ elif CONFIG['OS_TARGET'] == 'WINNT':
+ DEFINES['WEBRTC_WIN'] = True
+ DEFINES['RTC_ENABLE_WIN_WGC'] = False
+ DEFINES['HAVE_WINSOCK2_H'] = True
+ elif CONFIG['OS_TARGET'] in ('DragonFly', 'FreeBSD', 'NetBSD', 'OpenBSD'):
+ DEFINES['WEBRTC_BSD'] = True
+ elif CONFIG['OS_TARGET'] == 'Android':
+ DEFINES['WEBRTC_LINUX'] = True
+ DEFINES['WEBRTC_ANDROID'] = True
+
+ if CONFIG['MOZ_X11']:
+ DEFINES['WEBRTC_USE_X11'] = True
+
+ if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ DEFINES['WEBRTC_USE_PIPEWIRE'] = True
diff --git a/dom/media/webrtc/third_party_build/write_default_config.py b/dom/media/webrtc/third_party_build/write_default_config.py
new file mode 100644
index 0000000000..204789e052
--- /dev/null
+++ b/dom/media/webrtc/third_party_build/write_default_config.py
@@ -0,0 +1,104 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+import argparse
+import sys
+from string import Template
+
+sys.path.insert(0, "./dom/media/webrtc/third_party_build")
+import lookup_branch_head
+
+text = """#!/bin/bash
+
+# Edit {path-to} to match the location of your copy of Mozilla's
+# fork of libwebrtc (at https://github.com/mozilla/libwebrtc).
+export MOZ_LIBWEBRTC_SRC=$$STATE_DIR/moz-libwebrtc
+
+# Fast-forwarding each Chromium version of libwebrtc should be done
+# under a separate bugzilla bug. This bug number is used when crafting
+# the commit summary as each upstream commit is vendored into the
+# mercurial repository. The bug used for the v106 fast-forward was
+# 1800920.
+export MOZ_FASTFORWARD_BUG="$bugnum"
+
+# MOZ_NEXT_LIBWEBRTC_MILESTONE and MOZ_NEXT_FIREFOX_REL_TARGET are
+# not used during fast-forward processing, but facilitate generating this
+# default config. To generate an default config for the next update, run
+# bash dom/media/webrtc/third_party_build/update_default_config_env.sh
+export MOZ_NEXT_LIBWEBRTC_MILESTONE=$m2
+export MOZ_NEXT_FIREFOX_REL_TARGET=$t2
+
+# For Chromium release branches, see:
+# https://chromiumdash.appspot.com/branches
+
+# Chromium's v$m1 release branch was $bh1. This is used to pre-stack
+# the previous release branch's commits onto the appropriate base commit
+# (the first common commit between trunk and the release branch).
+export MOZ_PRIOR_UPSTREAM_BRANCH_HEAD_NUM="$bh1"
+
+# New target release branch for v$m2 is branch-heads/$bh2. This is used
+# to calculate the next upstream commit.
+export MOZ_TARGET_UPSTREAM_BRANCH_HEAD="branch-heads/$bh2"
+
+# For local development 'mozpatches' is fine for a branch name, but when
+# pushing the patch stack to github, it should be named something like
+# 'moz-mods-chr$m2-for-rel$t2'.
+export MOZ_LIBWEBRTC_BRANCH="mozpatches"
+
+# After elm has been merged to mozilla-central, the patch stack in
+# moz-libwebrtc should be pushed to github. The script
+# push_official_branch.sh uses this branch name when pushing to the
+# public repo.
+export MOZ_LIBWEBRTC_OFFICIAL_BRANCH="moz-mods-chr$m2-for-rel$t2"
+"""
+
+
+def build_default_config_env(bug_number, milestone, target):
+ prior_branch_head = lookup_branch_head.get_branch_head(milestone)
+ if prior_branch_head is None:
+ sys.exit("error: chromium milestone '{}' is not found.".format(milestone))
+ new_branch_head = lookup_branch_head.get_branch_head(milestone + 1)
+ if new_branch_head is None:
+ sys.exit(
+ "error: next chromium milestone '{}' is not found.".format(milestone + 1)
+ )
+
+ s = Template(text)
+ return s.substitute(
+ bugnum=bug_number,
+ m1=milestone,
+ m2=milestone + 1,
+ t2=target + 1,
+ bh1=prior_branch_head,
+ bh2=new_branch_head,
+ )
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(
+ description="Updates the default_config_env file for new release/milestone"
+ )
+ parser.add_argument(
+ "--bug-number",
+ required=True,
+ type=int,
+ help="integer Bugzilla number (example: 1800920)",
+ )
+ parser.add_argument(
+ "--milestone",
+ required=True,
+ type=int,
+ help="integer chromium milestone (example: 106)",
+ )
+ parser.add_argument(
+ "--release-target",
+ required=True,
+ type=int,
+ help="integer firefox release (example: 110)",
+ )
+ args = parser.parse_args()
+
+ print(
+ build_default_config_env(args.bug_number, args.milestone, args.release_target),
+ end="",
+ )
diff --git a/dom/media/webrtc/transport/README b/dom/media/webrtc/transport/README
new file mode 100644
index 0000000000..3630b2aa1f
--- /dev/null
+++ b/dom/media/webrtc/transport/README
@@ -0,0 +1,45 @@
+This is a generic media transport system for WebRTC.
+
+The basic model is that you have a TransportFlow which contains a
+series of TransportLayers, each of which gets an opportunity to
+manipulate data up and down the stack (think SysV STREAMS or a
+standard networking stack). You can also address individual
+sublayers to manipulate them or to bypass reading and writing
+at an upper layer; WebRTC uses this to implement DTLS-SRTP.
+
+
+DATAFLOW MODEL
+Unlike the existing nsSocket I/O system, this is a push rather
+than a pull system. Clients of the interface do writes downward
+with SendPacket() and receive notification of incoming packets
+via callbacks registed via sigslot.h. It is the responsibility
+of the bottom layer (or any other layer which needs to reference
+external events) to arrange for that somehow; typically by
+using nsITimer or the SocketTansportService.
+
+This sort of push model is a much better fit for the demands
+of WebRTC, expecially because ICE contexts span multiple
+network transports.
+
+
+THREADING MODEL
+There are no thread locks. It is the responsibility of the caller to
+arrange that any given TransportLayer/TransportFlow is only
+manipulated in one thread at once. One good way to do this is to run
+everything on the STS thread. Many of the existing layer implementations
+(TransportLayerIce, TransportLayerLoopback) already run on STS so in those
+cases you must run on STS, though you can do setup on the main thread and
+then activate them on the STS.
+
+
+EXISTING TRANSPORT LAYERS
+The following transport layers are currently implemented:
+
+* DTLS -- a wrapper around NSS's DTLS [RFC 6347] stack
+* ICE -- a wrapper around the nICEr ICE [RFC 5245] stack.
+* Loopback -- a loopback IO mechanism
+* Logging -- a passthrough that just logs its data
+
+The last two are primarily for debugging.
+
+
diff --git a/dom/media/webrtc/transport/SrtpFlow.cpp b/dom/media/webrtc/transport/SrtpFlow.cpp
new file mode 100644
index 0000000000..827d1a0f6d
--- /dev/null
+++ b/dom/media/webrtc/transport/SrtpFlow.cpp
@@ -0,0 +1,259 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+
+#include "logging.h"
+#include "SrtpFlow.h"
+
+#include "srtp.h"
+
+#include "transportlayerdtls.h"
+
+#include "mozilla/RefPtr.h"
+
+using namespace mozilla;
+
+namespace mozilla {
+
+MOZ_MTLOG_MODULE("mtransport")
+bool SrtpFlow::initialized; // Static
+
+SrtpFlow::~SrtpFlow() {
+ if (session_) {
+ srtp_dealloc(session_);
+ }
+}
+
+unsigned int SrtpFlow::KeySize(int cipher_suite) {
+ srtp_profile_t profile = static_cast<srtp_profile_t>(cipher_suite);
+ return srtp_profile_get_master_key_length(profile);
+}
+
+unsigned int SrtpFlow::SaltSize(int cipher_suite) {
+ srtp_profile_t profile = static_cast<srtp_profile_t>(cipher_suite);
+ return srtp_profile_get_master_salt_length(profile);
+}
+
+RefPtr<SrtpFlow> SrtpFlow::Create(int cipher_suite, bool inbound,
+ const void* key, size_t key_len) {
+ nsresult res = Init();
+ if (!NS_SUCCEEDED(res)) return nullptr;
+
+ RefPtr<SrtpFlow> flow = new SrtpFlow();
+
+ if (!key) {
+ MOZ_MTLOG(ML_ERROR, "Null SRTP key specified");
+ return nullptr;
+ }
+
+ if ((key_len > SRTP_MAX_KEY_LENGTH) || (key_len < SRTP_MIN_KEY_LENGTH)) {
+ MOZ_ASSERT(false, "Invalid SRTP key length");
+ return nullptr;
+ }
+
+ srtp_policy_t policy;
+ memset(&policy, 0, sizeof(srtp_policy_t));
+
+ // Note that we set the same cipher suite for RTP and RTCP
+ // since any flow can only have one cipher suite with DTLS-SRTP
+ switch (cipher_suite) {
+ case kDtlsSrtpAeadAes256Gcm:
+ MOZ_MTLOG(ML_DEBUG, "Setting SRTP cipher suite SRTP_AEAD_AES_256_GCM");
+ srtp_crypto_policy_set_aes_gcm_256_16_auth(&policy.rtp);
+ srtp_crypto_policy_set_aes_gcm_256_16_auth(&policy.rtcp);
+ break;
+ case kDtlsSrtpAeadAes128Gcm:
+ MOZ_MTLOG(ML_DEBUG, "Setting SRTP cipher suite SRTP_AEAD_AES_128_GCM");
+ srtp_crypto_policy_set_aes_gcm_128_16_auth(&policy.rtp);
+ srtp_crypto_policy_set_aes_gcm_128_16_auth(&policy.rtcp);
+ break;
+ case kDtlsSrtpAes128CmHmacSha1_80:
+ MOZ_MTLOG(ML_DEBUG,
+ "Setting SRTP cipher suite SRTP_AES128_CM_HMAC_SHA1_80");
+ srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtp);
+ srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtcp);
+ break;
+ case kDtlsSrtpAes128CmHmacSha1_32:
+ MOZ_MTLOG(ML_DEBUG,
+ "Setting SRTP cipher suite SRTP_AES128_CM_HMAC_SHA1_32");
+ srtp_crypto_policy_set_aes_cm_128_hmac_sha1_32(&policy.rtp);
+ srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(
+ &policy.rtcp); // 80-bit per RFC 5764
+ break; // S 4.1.2.
+ default:
+ MOZ_MTLOG(ML_ERROR, "Request to set unknown SRTP cipher suite");
+ return nullptr;
+ }
+ // This key is copied into the srtp_t object, so we don't
+ // need to keep it.
+ policy.key =
+ const_cast<unsigned char*>(static_cast<const unsigned char*>(key));
+ policy.ssrc.type = inbound ? ssrc_any_inbound : ssrc_any_outbound;
+ policy.ssrc.value = 0;
+ policy.window_size =
+ 1024; // Use the Chrome value. Needs to be revisited. Default is 128
+ policy.allow_repeat_tx = 1; // Use Chrome value; needed for NACK mode to work
+ policy.next = nullptr;
+
+ // Now make the session
+ srtp_err_status_t r = srtp_create(&flow->session_, &policy);
+ if (r != srtp_err_status_ok) {
+ MOZ_MTLOG(ML_ERROR, "Error creating srtp session");
+ return nullptr;
+ }
+
+ return flow;
+}
+
+nsresult SrtpFlow::CheckInputs(bool protect, void* in, int in_len, int max_len,
+ int* out_len) {
+ MOZ_ASSERT(in);
+ if (!in) {
+ MOZ_MTLOG(ML_ERROR, "NULL input value");
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ if (in_len < 0) {
+ MOZ_MTLOG(ML_ERROR, "Input length is negative");
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ if (max_len < 0) {
+ MOZ_MTLOG(ML_ERROR, "Max output length is negative");
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ if (protect) {
+ if ((max_len < SRTP_MAX_EXPANSION) ||
+ ((max_len - SRTP_MAX_EXPANSION) < in_len)) {
+ MOZ_MTLOG(ML_ERROR, "Output too short");
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ } else {
+ if (in_len > max_len) {
+ MOZ_MTLOG(ML_ERROR, "Output too short");
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult SrtpFlow::ProtectRtp(void* in, int in_len, int max_len, int* out_len) {
+ nsresult res = CheckInputs(true, in, in_len, max_len, out_len);
+ if (NS_FAILED(res)) return res;
+
+ int len = in_len;
+ srtp_err_status_t r = srtp_protect(session_, in, &len);
+
+ if (r != srtp_err_status_ok) {
+ MOZ_MTLOG(ML_ERROR, "Error protecting SRTP packet");
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(len <= max_len);
+ *out_len = len;
+
+ MOZ_MTLOG(ML_DEBUG,
+ "Successfully protected an SRTP packet of len " << *out_len);
+
+ return NS_OK;
+}
+
+nsresult SrtpFlow::UnprotectRtp(void* in, int in_len, int max_len,
+ int* out_len) {
+ nsresult res = CheckInputs(false, in, in_len, max_len, out_len);
+ if (NS_FAILED(res)) return res;
+
+ int len = in_len;
+ srtp_err_status_t r = srtp_unprotect(session_, in, &len);
+
+ if (r != srtp_err_status_ok) {
+ MOZ_MTLOG(ML_ERROR, "Error unprotecting SRTP packet error=" << (int)r);
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(len <= max_len);
+ *out_len = len;
+
+ MOZ_MTLOG(ML_DEBUG,
+ "Successfully unprotected an SRTP packet of len " << *out_len);
+
+ return NS_OK;
+}
+
+nsresult SrtpFlow::ProtectRtcp(void* in, int in_len, int max_len,
+ int* out_len) {
+ nsresult res = CheckInputs(true, in, in_len, max_len, out_len);
+ if (NS_FAILED(res)) return res;
+
+ int len = in_len;
+ srtp_err_status_t r = srtp_protect_rtcp(session_, in, &len);
+
+ if (r != srtp_err_status_ok) {
+ MOZ_MTLOG(ML_ERROR, "Error protecting SRTCP packet");
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(len <= max_len);
+ *out_len = len;
+
+ MOZ_MTLOG(ML_DEBUG,
+ "Successfully protected an SRTCP packet of len " << *out_len);
+
+ return NS_OK;
+}
+
+nsresult SrtpFlow::UnprotectRtcp(void* in, int in_len, int max_len,
+ int* out_len) {
+ nsresult res = CheckInputs(false, in, in_len, max_len, out_len);
+ if (NS_FAILED(res)) return res;
+
+ int len = in_len;
+ srtp_err_status_t r = srtp_unprotect_rtcp(session_, in, &len);
+
+ if (r != srtp_err_status_ok) {
+ MOZ_MTLOG(ML_ERROR, "Error unprotecting SRTCP packet error=" << (int)r);
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_ASSERT(len <= max_len);
+ *out_len = len;
+
+ MOZ_MTLOG(ML_DEBUG,
+ "Successfully unprotected an SRTCP packet of len " << *out_len);
+
+ return NS_OK;
+}
+
+// Statics
+void SrtpFlow::srtp_event_handler(srtp_event_data_t* data) {
+ // TODO(ekr@rtfm.com): Implement this
+ MOZ_CRASH();
+}
+
+nsresult SrtpFlow::Init() {
+ if (!initialized) {
+ srtp_err_status_t r = srtp_init();
+ if (r != srtp_err_status_ok) {
+ MOZ_MTLOG(ML_ERROR, "Could not initialize SRTP");
+ MOZ_ASSERT(PR_FALSE);
+ return NS_ERROR_FAILURE;
+ }
+
+ r = srtp_install_event_handler(&SrtpFlow::srtp_event_handler);
+ if (r != srtp_err_status_ok) {
+ MOZ_MTLOG(ML_ERROR, "Could not install SRTP event handler");
+ MOZ_ASSERT(PR_FALSE);
+ return NS_ERROR_FAILURE;
+ }
+
+ initialized = true;
+ }
+
+ return NS_OK;
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/transport/SrtpFlow.h b/dom/media/webrtc/transport/SrtpFlow.h
new file mode 100644
index 0000000000..92fbfcf1a5
--- /dev/null
+++ b/dom/media/webrtc/transport/SrtpFlow.h
@@ -0,0 +1,69 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+
+#ifndef srtpflow_h__
+#define srtpflow_h__
+
+#include "mozilla/RefPtr.h"
+#include "nsISupportsImpl.h"
+#include "srtp.h"
+
+namespace mozilla {
+
+#define SRTP_ICM_MASTER_KEY_LENGTH 16
+#define SRTP_ICM_MASTER_SALT_LENGTH 14
+#define SRTP_ICM_MAX_MASTER_LENGTH \
+ (SRTP_ICM_MASTER_KEY_LENGTH + SRTP_ICM_MASTER_SALT_LENGTH)
+
+#define SRTP_GCM_MASTER_KEY_MIN_LENGTH 16
+#define SRTP_GCM_MASTER_KEY_MAX_LENGTH 32
+#define SRTP_GCM_MASTER_SALT_LENGTH 12
+
+#define SRTP_GCM_MIN_MASTER_LENGTH \
+ (SRTP_GCM_MASTER_KEY_MIN_LENGTH + SRTP_GCM_MASTER_SALT_LENGTH)
+#define SRTP_GCM_MAX_MASTER_LENGTH \
+ (SRTP_GCM_MASTER_KEY_MAX_LENGTH + SRTP_GCM_MASTER_SALT_LENGTH)
+
+#define SRTP_MIN_KEY_LENGTH SRTP_GCM_MIN_MASTER_LENGTH
+#define SRTP_MAX_KEY_LENGTH SRTP_GCM_MAX_MASTER_LENGTH
+
+// SRTCP requires an auth tag *plus* a 4-byte index-plus-'E'-bit value (see
+// RFC 3711)
+#define SRTP_MAX_EXPANSION (SRTP_MAX_TRAILER_LEN + 4)
+
+class SrtpFlow {
+ ~SrtpFlow();
+
+ public:
+ static unsigned int KeySize(int cipher_suite);
+ static unsigned int SaltSize(int cipher_suite);
+
+ static RefPtr<SrtpFlow> Create(int cipher_suite, bool inbound,
+ const void* key, size_t key_len);
+
+ nsresult ProtectRtp(void* in, int in_len, int max_len, int* out_len);
+ nsresult UnprotectRtp(void* in, int in_len, int max_len, int* out_len);
+ nsresult ProtectRtcp(void* in, int in_len, int max_len, int* out_len);
+ nsresult UnprotectRtcp(void* in, int in_len, int max_len, int* out_len);
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SrtpFlow)
+
+ static void srtp_event_handler(srtp_event_data_t* data);
+
+ private:
+ SrtpFlow() : session_(nullptr) {}
+
+ nsresult CheckInputs(bool protect, void* in, int in_len, int max_len,
+ int* out_len);
+
+ static nsresult Init();
+ static bool initialized; // Was libsrtp initialized? Only happens once.
+
+ srtp_t session_;
+};
+
+} // namespace mozilla
+#endif
diff --git a/dom/media/webrtc/transport/WebrtcTCPSocketWrapper.cpp b/dom/media/webrtc/transport/WebrtcTCPSocketWrapper.cpp
new file mode 100644
index 0000000000..2bff019878
--- /dev/null
+++ b/dom/media/webrtc/transport/WebrtcTCPSocketWrapper.cpp
@@ -0,0 +1,123 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebrtcTCPSocketWrapper.h"
+
+#include "mozilla/net/WebrtcTCPSocketChild.h"
+
+#include "nsNetCID.h"
+#include "nsProxyRelease.h"
+#include "nsServiceManagerUtils.h"
+
+#include "nr_socket_proxy_config.h"
+
+namespace mozilla::net {
+
+using std::shared_ptr;
+
+WebrtcTCPSocketWrapper::WebrtcTCPSocketWrapper(
+ WebrtcTCPSocketCallback* aCallbacks)
+ : mProxyCallbacks(aCallbacks),
+ mWebrtcTCPSocket(nullptr),
+ mMainThread(nullptr),
+ mSocketThread(nullptr) {
+ mMainThread = GetMainThreadSerialEventTarget();
+ mSocketThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
+ MOZ_RELEASE_ASSERT(mMainThread, "no main thread");
+ MOZ_RELEASE_ASSERT(mSocketThread, "no socket thread");
+}
+
+WebrtcTCPSocketWrapper::~WebrtcTCPSocketWrapper() {
+ MOZ_ASSERT(!mWebrtcTCPSocket, "webrtc TCP socket non-null");
+
+ // If we're never opened then we never get an OnClose from our parent process.
+ // We need to release our callbacks here safely.
+ NS_ProxyRelease("WebrtcTCPSocketWrapper::CleanUpCallbacks", mSocketThread,
+ mProxyCallbacks.forget());
+}
+
+void WebrtcTCPSocketWrapper::AsyncOpen(
+ const nsCString& aHost, const int& aPort, const nsCString& aLocalAddress,
+ const int& aLocalPort, bool aUseTls,
+ const shared_ptr<NrSocketProxyConfig>& aConfig) {
+ if (!NS_IsMainThread()) {
+ MOZ_ALWAYS_SUCCEEDS(mMainThread->Dispatch(
+ NewRunnableMethod<const nsCString, const int, const nsCString,
+ const int, bool,
+ const shared_ptr<NrSocketProxyConfig>>(
+ "WebrtcTCPSocketWrapper::AsyncOpen", this,
+ &WebrtcTCPSocketWrapper::AsyncOpen, aHost, aPort, aLocalAddress,
+ aLocalPort, aUseTls, aConfig)));
+ return;
+ }
+
+ MOZ_ASSERT(!mWebrtcTCPSocket, "wrapper already open");
+ mWebrtcTCPSocket = new WebrtcTCPSocketChild(this);
+ mWebrtcTCPSocket->AsyncOpen(aHost, aPort, aLocalAddress, aLocalPort, aUseTls,
+ aConfig);
+}
+
+void WebrtcTCPSocketWrapper::SendWrite(nsTArray<uint8_t>&& aReadData) {
+ if (!NS_IsMainThread()) {
+ MOZ_ALWAYS_SUCCEEDS(
+ mMainThread->Dispatch(NewRunnableMethod<nsTArray<uint8_t>&&>(
+ "WebrtcTCPSocketWrapper::SendWrite", this,
+ &WebrtcTCPSocketWrapper::SendWrite, std::move(aReadData))));
+ return;
+ }
+
+ MOZ_ASSERT(mWebrtcTCPSocket, "webrtc TCP socket should be non-null");
+ mWebrtcTCPSocket->SendWrite(aReadData);
+}
+
+void WebrtcTCPSocketWrapper::Close() {
+ if (!NS_IsMainThread()) {
+ MOZ_ALWAYS_SUCCEEDS(mMainThread->Dispatch(
+ NewRunnableMethod("WebrtcTCPSocketWrapper::Close", this,
+ &WebrtcTCPSocketWrapper::Close)));
+ return;
+ }
+
+ // We're only open if we have a channel. Also Close() should be idempotent.
+ if (mWebrtcTCPSocket) {
+ RefPtr<WebrtcTCPSocketChild> child = mWebrtcTCPSocket;
+ mWebrtcTCPSocket = nullptr;
+ child->SendClose();
+ }
+}
+
+void WebrtcTCPSocketWrapper::OnClose(nsresult aReason) {
+ MOZ_ASSERT(NS_IsMainThread(), "not on main thread");
+ MOZ_ASSERT(mProxyCallbacks, "webrtc TCP callbacks should be non-null");
+
+ MOZ_ALWAYS_SUCCEEDS(mSocketThread->Dispatch(NewRunnableMethod<nsresult>(
+ "WebrtcTCPSocketWrapper::OnClose", mProxyCallbacks,
+ &WebrtcTCPSocketCallback::OnClose, aReason)));
+
+ NS_ProxyRelease("WebrtcTCPSocketWrapper::CleanUpCallbacks", mSocketThread,
+ mProxyCallbacks.forget());
+}
+
+void WebrtcTCPSocketWrapper::OnRead(nsTArray<uint8_t>&& aReadData) {
+ MOZ_ASSERT(NS_IsMainThread(), "not on main thread");
+ MOZ_ASSERT(mProxyCallbacks, "webrtc TCP callbacks should be non-null");
+
+ MOZ_ALWAYS_SUCCEEDS(
+ mSocketThread->Dispatch(NewRunnableMethod<nsTArray<uint8_t>&&>(
+ "WebrtcTCPSocketWrapper::OnRead", mProxyCallbacks,
+ &WebrtcTCPSocketCallback::OnRead, std::move(aReadData))));
+}
+
+void WebrtcTCPSocketWrapper::OnConnected(const nsACString& aProxyType) {
+ MOZ_ASSERT(NS_IsMainThread(), "not on main thread");
+ MOZ_ASSERT(mProxyCallbacks, "webrtc TCP callbacks should be non-null");
+
+ MOZ_ALWAYS_SUCCEEDS(mSocketThread->Dispatch(NewRunnableMethod<nsCString>(
+ "WebrtcTCPSocketWrapper::OnConnected", mProxyCallbacks,
+ &WebrtcTCPSocketCallback::OnConnected, aProxyType)));
+}
+
+} // namespace mozilla::net
diff --git a/dom/media/webrtc/transport/WebrtcTCPSocketWrapper.h b/dom/media/webrtc/transport/WebrtcTCPSocketWrapper.h
new file mode 100644
index 0000000000..c0775ee9aa
--- /dev/null
+++ b/dom/media/webrtc/transport/WebrtcTCPSocketWrapper.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef webrtc_tcp_socket_wrapper__
+#define webrtc_tcp_socket_wrapper__
+
+#include <memory>
+
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+
+#include "mozilla/net/WebrtcTCPSocketCallback.h"
+
+class nsIEventTarget;
+
+namespace mozilla {
+
+class NrSocketProxyConfig;
+
+namespace net {
+
+class WebrtcTCPSocketChild;
+
+/**
+ * WebrtcTCPSocketWrapper is a protector class for mtransport and IPDL.
+ * mtransport and IPDL cannot include headers from each other due to conflicting
+ * typedefs. Also it helps users by dispatching calls to the appropriate thread
+ * based on mtransport's and IPDL's threading requirements.
+ *
+ * WebrtcTCPSocketWrapper is only used in the child process.
+ * WebrtcTCPSocketWrapper does not dispatch for the parent process.
+ * WebrtcTCPSocketCallback calls are dispatched to the STS thread.
+ * IPDL calls are dispatched to the main thread.
+ */
+class WebrtcTCPSocketWrapper : public WebrtcTCPSocketCallback {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebrtcTCPSocketWrapper, override)
+
+ explicit WebrtcTCPSocketWrapper(WebrtcTCPSocketCallback* aCallbacks);
+
+ virtual void AsyncOpen(const nsCString& aHost, const int& aPort,
+ const nsCString& aLocalAddress, const int& aLocalPort,
+ bool aUseTls,
+ const std::shared_ptr<NrSocketProxyConfig>& aConfig);
+ virtual void SendWrite(nsTArray<uint8_t>&& aReadData);
+ virtual void Close();
+
+ // WebrtcTCPSocketCallback
+ virtual void OnClose(nsresult aReason) override;
+ virtual void OnConnected(const nsACString& aProxyType) override;
+ virtual void OnRead(nsTArray<uint8_t>&& aReadData) override;
+
+ protected:
+ RefPtr<WebrtcTCPSocketCallback> mProxyCallbacks;
+ RefPtr<WebrtcTCPSocketChild> mWebrtcTCPSocket;
+
+ nsCOMPtr<nsIEventTarget> mMainThread;
+ nsCOMPtr<nsIEventTarget> mSocketThread;
+
+ virtual ~WebrtcTCPSocketWrapper();
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // webrtc_tcp_socket_wrapper__
diff --git a/dom/media/webrtc/transport/build/moz.build b/dom/media/webrtc/transport/build/moz.build
new file mode 100644
index 0000000000..7349ee5d71
--- /dev/null
+++ b/dom/media/webrtc/transport/build/moz.build
@@ -0,0 +1,44 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+EXPORTS.transport += [
+ "../dtlsidentity.h",
+ "../m_cpp_utils.h",
+ "../mediapacket.h",
+ "../nr_socket_proxy_config.h",
+ "../nricectx.h",
+ "../nricemediastream.h",
+ "../nriceresolverfake.h",
+ "../nricestunaddr.h",
+ "../rlogconnector.h",
+ "../runnable_utils.h",
+ "../sigslot.h",
+ "../simpletokenbucket.h",
+ "../SrtpFlow.h",
+ "../stun_socket_filter.h",
+ "../transportflow.h",
+ "../transportlayer.h",
+ "../transportlayerdtls.h",
+ "../transportlayerice.h",
+ "../transportlayerlog.h",
+ "../transportlayerloopback.h",
+ "../transportlayersrtp.h",
+]
+
+include("../common.build")
+
+# Add libFuzzer configuration directives
+include("/tools/fuzzing/libfuzzer-config.mozbuild")
+
+# These files cannot be built in unified mode because of the redefinition of
+# getLogModule, UNIMPLEMENTED, nr_socket_long_term_violation_time,
+# nr_socket_short_term_violation_time, nrappkit/IPDL typedef conflicts in
+# PBrowserOrId and WebrtcTCPSocketChild.
+SOURCES += transport_cppsrcs
+
+FINAL_LIBRARY = "xul"
diff --git a/dom/media/webrtc/transport/common.build b/dom/media/webrtc/transport/common.build
new file mode 100644
index 0000000000..b907b57ee8
--- /dev/null
+++ b/dom/media/webrtc/transport/common.build
@@ -0,0 +1,94 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+transport_lcppsrcs = [
+ "dtlsidentity.cpp",
+ "mediapacket.cpp",
+ "nr_socket_proxy_config.cpp",
+ "nr_socket_prsock.cpp",
+ "nr_socket_tcp.cpp",
+ "nr_timer.cpp",
+ "nricectx.cpp",
+ "nricemediastream.cpp",
+ "nriceresolver.cpp",
+ "nriceresolverfake.cpp",
+ "nricestunaddr.cpp",
+ "nrinterfaceprioritizer.cpp",
+ "rlogconnector.cpp",
+ "simpletokenbucket.cpp",
+ "SrtpFlow.cpp",
+ "stun_socket_filter.cpp",
+ "test_nr_socket.cpp",
+ "transportflow.cpp",
+ "transportlayer.cpp",
+ "transportlayerdtls.cpp",
+ "transportlayerice.cpp",
+ "transportlayerlog.cpp",
+ "transportlayerloopback.cpp",
+ "transportlayersrtp.cpp",
+ "WebrtcTCPSocketWrapper.cpp",
+]
+
+transport_cppsrcs = [
+ "/dom/media/webrtc/transport/%s" % s for s in sorted(transport_lcppsrcs)
+]
+
+LOCAL_INCLUDES += [
+ "/dom/media/webrtc/transport/",
+ "/dom/media/webrtc/transport/third_party/",
+ "/dom/media/webrtc/transport/third_party/nICEr/src/crypto",
+ "/dom/media/webrtc/transport/third_party/nICEr/src/ice",
+ "/dom/media/webrtc/transport/third_party/nICEr/src/net",
+ "/dom/media/webrtc/transport/third_party/nICEr/src/stun",
+ "/dom/media/webrtc/transport/third_party/nICEr/src/util",
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/event",
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/log",
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/plugin",
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/port/generic/include",
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/registry",
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/share",
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/stats",
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr",
+ "/third_party/libsrtp/src/crypto/include",
+ "/third_party/libsrtp/src/include",
+]
+
+if CONFIG["OS_TARGET"] in ["Darwin", "DragonFly", "FreeBSD", "NetBSD", "OpenBSD"]:
+ if CONFIG["OS_TARGET"] == "Darwin":
+ DEFINES["DARWIN"] = True
+ else:
+ DEFINES["BSD"] = True
+ LOCAL_INCLUDES += [
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/port/darwin/include",
+ ]
+elif CONFIG["OS_TARGET"] == "Linux":
+ DEFINES["LINUX"] = True
+ LOCAL_INCLUDES += [
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/port/linux/include",
+ ]
+elif CONFIG["OS_TARGET"] == "Android":
+ DEFINES["LINUX"] = True
+ DEFINES["ANDROID"] = True
+ LOCAL_INCLUDES += [
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/port/android/include",
+ ]
+elif CONFIG["OS_TARGET"] == "WINNT":
+ DEFINES["WIN"] = True
+ # for stun.h
+ DEFINES["WIN32"] = True
+ LOCAL_INCLUDES += [
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/port/win32/include",
+ ]
+
+for var in ("HAVE_STRDUP", "NR_SOCKET_IS_VOID_PTR"):
+ DEFINES[var] = True
+
+DEFINES["R_DEFINED_INT2"] = "int16_t"
+DEFINES["R_DEFINED_UINT2"] = "uint16_t"
+DEFINES["R_DEFINED_INT4"] = "int32_t"
+DEFINES["R_DEFINED_UINT4"] = "uint32_t"
+DEFINES["R_DEFINED_INT8"] = "int64_t"
+DEFINES["R_DEFINED_UINT8"] = "uint64_t"
diff --git a/dom/media/webrtc/transport/dtlsidentity.cpp b/dom/media/webrtc/transport/dtlsidentity.cpp
new file mode 100644
index 0000000000..c466ce7d88
--- /dev/null
+++ b/dom/media/webrtc/transport/dtlsidentity.cpp
@@ -0,0 +1,331 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "dtlsidentity.h"
+
+#include "cert.h"
+#include "cryptohi.h"
+#include "keyhi.h"
+#include "nsError.h"
+#include "pk11pub.h"
+#include "sechash.h"
+#include "mozpkix/nss_scoped_ptrs.h"
+#include "secerr.h"
+#include "sslerr.h"
+
+#include "mozilla/Sprintf.h"
+
+namespace mozilla {
+
+SECItem* WrapPrivateKeyInfoWithEmptyPassword(
+ SECKEYPrivateKey* pk) /* encrypt this private key */
+{
+ if (!pk) {
+ PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
+ return nullptr;
+ }
+
+ UniquePK11SlotInfo slot(PK11_GetInternalSlot());
+ if (!slot) {
+ return nullptr;
+ }
+
+ // For private keys, NSS cannot export anything other than RSA, but we need EC
+ // also. So, we use the private key encryption function to serialize instead,
+ // using a hard-coded dummy password; this is not intended to provide any
+ // additional security, it just works around a limitation in NSS.
+ SECItem dummyPassword = {siBuffer, nullptr, 0};
+ UniqueSECKEYEncryptedPrivateKeyInfo epki(PK11_ExportEncryptedPrivKeyInfo(
+ slot.get(), SEC_OID_AES_128_CBC, &dummyPassword, pk, 1, nullptr));
+
+ if (!epki) {
+ return nullptr;
+ }
+
+ return SEC_ASN1EncodeItem(
+ nullptr, nullptr, epki.get(),
+ NSS_Get_SECKEY_EncryptedPrivateKeyInfoTemplate(nullptr, false));
+}
+
+SECStatus UnwrapPrivateKeyInfoWithEmptyPassword(
+ SECItem* derPKI, const UniqueCERTCertificate& aCert,
+ SECKEYPrivateKey** privk) {
+ if (!derPKI || !aCert || !privk) {
+ PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
+ return SECFailure;
+ }
+
+ UniqueSECKEYPublicKey publicKey(CERT_ExtractPublicKey(aCert.get()));
+ // This is a pointer to data inside publicKey
+ SECItem* publicValue = nullptr;
+ switch (publicKey->keyType) {
+ case dsaKey:
+ publicValue = &publicKey->u.dsa.publicValue;
+ break;
+ case dhKey:
+ publicValue = &publicKey->u.dh.publicValue;
+ break;
+ case rsaKey:
+ publicValue = &publicKey->u.rsa.modulus;
+ break;
+ case ecKey:
+ publicValue = &publicKey->u.ec.publicValue;
+ break;
+ default:
+ MOZ_ASSERT(false);
+ PR_SetError(SSL_ERROR_BAD_CERTIFICATE, 0);
+ return SECFailure;
+ }
+
+ UniquePK11SlotInfo slot(PK11_GetInternalSlot());
+ if (!slot) {
+ return SECFailure;
+ }
+
+ UniquePLArenaPool temparena(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+ if (!temparena) {
+ return SECFailure;
+ }
+
+ SECKEYEncryptedPrivateKeyInfo* epki =
+ PORT_ArenaZNew(temparena.get(), SECKEYEncryptedPrivateKeyInfo);
+ if (!epki) {
+ return SECFailure;
+ }
+
+ SECStatus rv = SEC_ASN1DecodeItem(
+ temparena.get(), epki,
+ NSS_Get_SECKEY_EncryptedPrivateKeyInfoTemplate(nullptr, false), derPKI);
+ if (rv != SECSuccess) {
+ // If SEC_ASN1DecodeItem fails, we cannot assume anything about the
+ // validity of the data in epki. The best we can do is free the arena
+ // and return.
+ return rv;
+ }
+
+ // See comment in WrapPrivateKeyInfoWithEmptyPassword about this
+ // dummy password stuff.
+ SECItem dummyPassword = {siBuffer, nullptr, 0};
+ return PK11_ImportEncryptedPrivateKeyInfoAndReturnKey(
+ slot.get(), epki, &dummyPassword, nullptr, publicValue, false, false,
+ publicKey->keyType, KU_ALL, privk, nullptr);
+}
+
+nsresult DtlsIdentity::Serialize(nsTArray<uint8_t>* aKeyDer,
+ nsTArray<uint8_t>* aCertDer) {
+ ScopedSECItem derPki(WrapPrivateKeyInfoWithEmptyPassword(private_key_.get()));
+ if (!derPki) {
+ return NS_ERROR_FAILURE;
+ }
+
+ aKeyDer->AppendElements(derPki->data, derPki->len);
+ aCertDer->AppendElements(cert_->derCert.data, cert_->derCert.len);
+ return NS_OK;
+}
+
+/* static */
+RefPtr<DtlsIdentity> DtlsIdentity::Deserialize(
+ const nsTArray<uint8_t>& aKeyDer, const nsTArray<uint8_t>& aCertDer,
+ SSLKEAType authType) {
+ SECItem certDer = {siBuffer, const_cast<uint8_t*>(aCertDer.Elements()),
+ static_cast<unsigned int>(aCertDer.Length())};
+ UniqueCERTCertificate cert(CERT_NewTempCertificate(
+ CERT_GetDefaultCertDB(), &certDer, nullptr, true, true));
+
+ SECItem derPKI = {siBuffer, const_cast<uint8_t*>(aKeyDer.Elements()),
+ static_cast<unsigned int>(aKeyDer.Length())};
+
+ SECKEYPrivateKey* privateKey;
+ if (UnwrapPrivateKeyInfoWithEmptyPassword(&derPKI, cert, &privateKey) !=
+ SECSuccess) {
+ MOZ_ASSERT(false);
+ return nullptr;
+ }
+
+ return new DtlsIdentity(UniqueSECKEYPrivateKey(privateKey), std::move(cert),
+ authType);
+}
+
+RefPtr<DtlsIdentity> DtlsIdentity::Generate() {
+ UniquePK11SlotInfo slot(PK11_GetInternalSlot());
+ if (!slot) {
+ return nullptr;
+ }
+
+ uint8_t random_name[16];
+
+ SECStatus rv =
+ PK11_GenerateRandomOnSlot(slot.get(), random_name, sizeof(random_name));
+ if (rv != SECSuccess) return nullptr;
+
+ std::string name;
+ char chunk[3];
+ for (unsigned char r_name : random_name) {
+ SprintfLiteral(chunk, "%.2x", r_name);
+ name += chunk;
+ }
+
+ std::string subject_name_string = "CN=" + name;
+ UniqueCERTName subject_name(CERT_AsciiToName(subject_name_string.c_str()));
+ if (!subject_name) {
+ return nullptr;
+ }
+
+ unsigned char paramBuf[12]; // OIDs are small
+ SECItem ecdsaParams = {siBuffer, paramBuf, sizeof(paramBuf)};
+ SECOidData* oidData = SECOID_FindOIDByTag(SEC_OID_SECG_EC_SECP256R1);
+ if (!oidData || (oidData->oid.len > (sizeof(paramBuf) - 2))) {
+ return nullptr;
+ }
+ ecdsaParams.data[0] = SEC_ASN1_OBJECT_ID;
+ ecdsaParams.data[1] = oidData->oid.len;
+ memcpy(ecdsaParams.data + 2, oidData->oid.data, oidData->oid.len);
+ ecdsaParams.len = oidData->oid.len + 2;
+
+ SECKEYPublicKey* pubkey;
+ UniqueSECKEYPrivateKey private_key(
+ PK11_GenerateKeyPair(slot.get(), CKM_EC_KEY_PAIR_GEN, &ecdsaParams,
+ &pubkey, PR_FALSE, PR_TRUE, nullptr));
+ if (private_key == nullptr) return nullptr;
+ UniqueSECKEYPublicKey public_key(pubkey);
+ pubkey = nullptr;
+
+ UniqueCERTSubjectPublicKeyInfo spki(
+ SECKEY_CreateSubjectPublicKeyInfo(public_key.get()));
+ if (!spki) {
+ return nullptr;
+ }
+
+ UniqueCERTCertificateRequest certreq(
+ CERT_CreateCertificateRequest(subject_name.get(), spki.get(), nullptr));
+ if (!certreq) {
+ return nullptr;
+ }
+
+ // From 1 day before todayto 30 days after.
+ // This is a sort of arbitrary range designed to be valid
+ // now with some slack in case the other side expects
+ // some before expiry.
+ //
+ // Note: explicit casts necessary to avoid
+ // warning C4307: '*' : integral constant overflow
+ static const PRTime oneDay = PRTime(PR_USEC_PER_SEC) * PRTime(60) // sec
+ * PRTime(60) // min
+ * PRTime(24); // hours
+ PRTime now = PR_Now();
+ PRTime notBefore = now - oneDay;
+ PRTime notAfter = now + (PRTime(30) * oneDay);
+
+ UniqueCERTValidity validity(CERT_CreateValidity(notBefore, notAfter));
+ if (!validity) {
+ return nullptr;
+ }
+
+ unsigned long serial;
+ // Note: This serial in principle could collide, but it's unlikely
+ rv = PK11_GenerateRandomOnSlot(
+ slot.get(), reinterpret_cast<unsigned char*>(&serial), sizeof(serial));
+ if (rv != SECSuccess) {
+ return nullptr;
+ }
+
+ // NB: CERTCertificates created with CERT_CreateCertificate are not safe to
+ // use with other NSS functions like CERT_DupCertificate.
+ // The strategy here is to create a tbsCertificate ("to-be-signed
+ // certificate"), encode it, and sign it, resulting in a signed DER
+ // certificate that can be decoded into a CERTCertificate.
+ UniqueCERTCertificate tbsCertificate(CERT_CreateCertificate(
+ serial, subject_name.get(), validity.get(), certreq.get()));
+ if (!tbsCertificate) {
+ return nullptr;
+ }
+
+ PLArenaPool* arena = tbsCertificate->arena;
+
+ rv = SECOID_SetAlgorithmID(arena, &tbsCertificate->signature,
+ SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE, nullptr);
+ if (rv != SECSuccess) return nullptr;
+
+ // Set version to X509v3.
+ *(tbsCertificate->version.data) = SEC_CERTIFICATE_VERSION_3;
+ tbsCertificate->version.len = 1;
+
+ SECItem innerDER;
+ innerDER.len = 0;
+ innerDER.data = nullptr;
+
+ if (!SEC_ASN1EncodeItem(arena, &innerDER, tbsCertificate.get(),
+ SEC_ASN1_GET(CERT_CertificateTemplate))) {
+ return nullptr;
+ }
+
+ SECItem* certDer = PORT_ArenaZNew(arena, SECItem);
+ if (!certDer) {
+ return nullptr;
+ }
+
+ rv = SEC_DerSignData(arena, certDer, innerDER.data, innerDER.len,
+ private_key.get(),
+ SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE);
+ if (rv != SECSuccess) {
+ return nullptr;
+ }
+
+ UniqueCERTCertificate certificate(CERT_NewTempCertificate(
+ CERT_GetDefaultCertDB(), certDer, nullptr, false, true));
+
+ return new DtlsIdentity(std::move(private_key), std::move(certificate),
+ ssl_kea_ecdh);
+}
+
+const std::string DtlsIdentity::DEFAULT_HASH_ALGORITHM = "sha-256";
+
+nsresult DtlsIdentity::ComputeFingerprint(DtlsDigest* digest) const {
+ const UniqueCERTCertificate& c = cert();
+ MOZ_ASSERT(c);
+
+ return ComputeFingerprint(c, digest);
+}
+
+nsresult DtlsIdentity::ComputeFingerprint(const UniqueCERTCertificate& cert,
+ DtlsDigest* digest) {
+ MOZ_ASSERT(cert);
+
+ HASH_HashType ht;
+
+ if (digest->algorithm_ == "sha-1") {
+ ht = HASH_AlgSHA1;
+ } else if (digest->algorithm_ == "sha-224") {
+ ht = HASH_AlgSHA224;
+ } else if (digest->algorithm_ == "sha-256") {
+ ht = HASH_AlgSHA256;
+ } else if (digest->algorithm_ == "sha-384") {
+ ht = HASH_AlgSHA384;
+ } else if (digest->algorithm_ == "sha-512") {
+ ht = HASH_AlgSHA512;
+ } else {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ const SECHashObject* ho = HASH_GetHashObject(ht);
+ MOZ_ASSERT(ho);
+ if (!ho) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ MOZ_ASSERT(ho->length >= 20); // Double check
+ digest->value_.resize(ho->length);
+
+ SECStatus rv = HASH_HashBuf(ho->type, digest->value_.data(),
+ cert->derCert.data, cert->derCert.len);
+ if (rv != SECSuccess) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/transport/dtlsidentity.h b/dom/media/webrtc/transport/dtlsidentity.h
new file mode 100644
index 0000000000..b4f7686618
--- /dev/null
+++ b/dom/media/webrtc/transport/dtlsidentity.h
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef dtls_identity_h__
+#define dtls_identity_h__
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "ScopedNSSTypes.h"
+#include "m_cpp_utils.h"
+#include "mozilla/RefPtr.h"
+#include "nsISupportsImpl.h"
+#include "nsTArray.h"
+#include "sslt.h"
+
+// All code in this module requires NSS to be live.
+// Callers must initialize NSS and implement the nsNSSShutdownObject
+// protocol.
+namespace mozilla {
+
+class DtlsDigest {
+ public:
+ const static size_t kMaxDtlsDigestLength = HASH_LENGTH_MAX;
+ DtlsDigest() = default;
+ explicit DtlsDigest(const std::string& algorithm) : algorithm_(algorithm) {}
+ DtlsDigest(const std::string& algorithm, const std::vector<uint8_t>& value)
+ : algorithm_(algorithm), value_(value) {
+ MOZ_ASSERT(value.size() <= kMaxDtlsDigestLength);
+ }
+ ~DtlsDigest() = default;
+
+ bool operator!=(const DtlsDigest& rhs) const { return !operator==(rhs); }
+
+ bool operator==(const DtlsDigest& rhs) const {
+ if (algorithm_ != rhs.algorithm_) {
+ return false;
+ }
+
+ return value_ == rhs.value_;
+ }
+
+ std::string algorithm_;
+ std::vector<uint8_t> value_;
+};
+
+typedef std::vector<DtlsDigest> DtlsDigestList;
+
+class DtlsIdentity final {
+ public:
+ // This constructor takes ownership of privkey and cert.
+ DtlsIdentity(UniqueSECKEYPrivateKey privkey, UniqueCERTCertificate cert,
+ SSLKEAType authType)
+ : private_key_(std::move(privkey)),
+ cert_(std::move(cert)),
+ auth_type_(authType) {}
+
+ // Allows serialization/deserialization; cannot write IPC serialization code
+ // directly for DtlsIdentity, since IPC-able types need to be constructable
+ // on the stack.
+ nsresult Serialize(nsTArray<uint8_t>* aKeyDer, nsTArray<uint8_t>* aCertDer);
+ static RefPtr<DtlsIdentity> Deserialize(const nsTArray<uint8_t>& aKeyDer,
+ const nsTArray<uint8_t>& aCertDer,
+ SSLKEAType authType);
+
+ // This is only for use in tests, or for external linkage. It makes a (bad)
+ // instance of this class.
+ static RefPtr<DtlsIdentity> Generate();
+
+ // These don't create copies or transfer ownership. If you want these to live
+ // on, make a copy.
+ const UniqueCERTCertificate& cert() const { return cert_; }
+ const UniqueSECKEYPrivateKey& privkey() const { return private_key_; }
+ // Note: this uses SSLKEAType because that is what the libssl API requires.
+ // This is a giant confusing mess, but libssl indexes certificates based on a
+ // key exchange type, not authentication type (as you might have reasonably
+ // expected).
+ SSLKEAType auth_type() const { return auth_type_; }
+
+ nsresult ComputeFingerprint(DtlsDigest* digest) const;
+ static nsresult ComputeFingerprint(const UniqueCERTCertificate& cert,
+ DtlsDigest* digest);
+
+ static const std::string DEFAULT_HASH_ALGORITHM;
+ enum { HASH_ALGORITHM_MAX_LENGTH = 64 };
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DtlsIdentity)
+
+ private:
+ ~DtlsIdentity() = default;
+ DISALLOW_COPY_ASSIGN(DtlsIdentity);
+
+ UniqueSECKEYPrivateKey private_key_;
+ UniqueCERTCertificate cert_;
+ SSLKEAType auth_type_;
+};
+} // namespace mozilla
+#endif
diff --git a/dom/media/webrtc/transport/fuzztest/moz.build b/dom/media/webrtc/transport/fuzztest/moz.build
new file mode 100644
index 0000000000..f22a5a702b
--- /dev/null
+++ b/dom/media/webrtc/transport/fuzztest/moz.build
@@ -0,0 +1,31 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Library("FuzzingStun")
+
+DEFINES["HAVE_STRDUP"] = True
+
+LOCAL_INCLUDES += [
+ "/dom/media/webrtc/transport/third_party/nICEr/src/net",
+ "/dom/media/webrtc/transport/third_party/nICEr/src/stun",
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/event",
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/log",
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/plugin",
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/port/darwin/include",
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/share",
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/stats",
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr",
+ "/ipc/chromium/src",
+]
+
+# Add libFuzzer configuration directives
+include("/tools/fuzzing/libfuzzer-config.mozbuild")
+
+SOURCES += [
+ "stun_parser_libfuzz.cpp",
+]
+
+FINAL_LIBRARY = "xul-gtest"
diff --git a/dom/media/webrtc/transport/fuzztest/stun_parser_libfuzz.cpp b/dom/media/webrtc/transport/fuzztest/stun_parser_libfuzz.cpp
new file mode 100644
index 0000000000..73bae5024c
--- /dev/null
+++ b/dom/media/webrtc/transport/fuzztest/stun_parser_libfuzz.cpp
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <string>
+
+#include "gtest/gtest.h"
+
+#include "FuzzingInterface.h"
+
+extern "C" {
+#include <csi_platform.h>
+#include "stun_msg.h"
+#include "stun_codec.h"
+}
+
+int FuzzingInitStunParser(int* argc, char*** argv) { return 0; }
+
+static int RunStunParserFuzzing(const uint8_t* data, size_t size) {
+ nr_stun_message* req = 0;
+
+ UCHAR* mes = (UCHAR*)data;
+
+ if (!nr_stun_message_create2(&req, mes, size)) {
+ nr_stun_decode_message(req, nullptr, nullptr);
+ nr_stun_message_destroy(&req);
+ }
+
+ return 0;
+}
+
+MOZ_FUZZING_INTERFACE_RAW(FuzzingInitStunParser, RunStunParserFuzzing,
+ StunParser);
diff --git a/dom/media/webrtc/transport/ipc/NrIceStunAddrMessageUtils.h b/dom/media/webrtc/transport/ipc/NrIceStunAddrMessageUtils.h
new file mode 100644
index 0000000000..34ec46726e
--- /dev/null
+++ b/dom/media/webrtc/transport/ipc/NrIceStunAddrMessageUtils.h
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_NrIceStunAddrMessageUtils_h
+#define mozilla_net_NrIceStunAddrMessageUtils_h
+
+// forward declare NrIceStunAddr for --disable-webrtc builds where
+// the header will not be available.
+namespace mozilla {
+class NrIceStunAddr;
+} // namespace mozilla
+
+#include "ipc/IPCMessageUtils.h"
+#ifdef MOZ_WEBRTC
+# include "transport/nricestunaddr.h"
+#endif
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mozilla::NrIceStunAddr> {
+ static void Write(MessageWriter* aWriter,
+ const mozilla::NrIceStunAddr& aParam) {
+#ifdef MOZ_WEBRTC
+ const size_t bufSize = aParam.SerializationBufferSize();
+ char* buffer = new char[bufSize];
+ aParam.Serialize(buffer, bufSize);
+ aWriter->WriteBytes((void*)buffer, bufSize);
+ delete[] buffer;
+#endif
+ }
+
+ static bool Read(MessageReader* aReader, mozilla::NrIceStunAddr* aResult) {
+#ifdef MOZ_WEBRTC
+ const size_t bufSize = aResult->SerializationBufferSize();
+ char* buffer = new char[bufSize];
+ bool result = aReader->ReadBytesInto((void*)buffer, bufSize);
+
+ if (result) {
+ result = result && (NS_OK == aResult->Deserialize(buffer, bufSize));
+ }
+ delete[] buffer;
+
+ return result;
+#else
+ return false;
+#endif
+ }
+};
+
+} // namespace IPC
+
+#endif // mozilla_net_NrIceStunAddrMessageUtils_h
diff --git a/dom/media/webrtc/transport/ipc/PStunAddrsParams.h b/dom/media/webrtc/transport/ipc/PStunAddrsParams.h
new file mode 100644
index 0000000000..315925609d
--- /dev/null
+++ b/dom/media/webrtc/transport/ipc/PStunAddrsParams.h
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef PStunAddrsParams_h
+#define PStunAddrsParams_h
+
+#include "mozilla/Maybe.h"
+#include "nsTArray.h"
+
+#ifdef MOZ_WEBRTC
+# include "transport/nricestunaddr.h"
+#endif
+
+namespace mozilla {
+namespace net {
+
+// Need to define typedef in .h file--can't seem to in ipdl.h file?
+#ifdef MOZ_WEBRTC
+typedef nsTArray<NrIceStunAddr> NrIceStunAddrArray;
+#else
+// a "dummy" typedef for --disabled-webrtc builds when the definition
+// for NrIceStunAddr is not available (otherwise we get complaints
+// about missing definitions for contructor and destructor)
+typedef nsTArray<int> NrIceStunAddrArray;
+#endif
+
+typedef Maybe<nsCString> MaybeNsCString;
+
+} // namespace net
+} // namespace mozilla
+
+#endif // PStunAddrsParams_h
diff --git a/dom/media/webrtc/transport/ipc/PStunAddrsRequest.ipdl b/dom/media/webrtc/transport/ipc/PStunAddrsRequest.ipdl
new file mode 100644
index 0000000000..c775f73f99
--- /dev/null
+++ b/dom/media/webrtc/transport/ipc/PStunAddrsRequest.ipdl
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PNecko;
+
+using mozilla::net::NrIceStunAddrArray from "mozilla/net/PStunAddrsParams.h";
+using mozilla::net::MaybeNsCString from "mozilla/net/PStunAddrsParams.h";
+
+include "mozilla/net/NrIceStunAddrMessageUtils.h";
+
+namespace mozilla {
+namespace net {
+
+[ManualDealloc, ChildImpl=virtual, ParentImpl=virtual]
+async protocol PStunAddrsRequest
+{
+ manager PNecko;
+
+parent:
+ async GetStunAddrs();
+
+ async RegisterMDNSHostname(nsCString hostname, nsCString address);
+ async QueryMDNSHostname(nsCString hostname);
+ async UnregisterMDNSHostname(nsCString hostname);
+
+ async __delete__();
+
+child:
+ async OnMDNSQueryComplete(nsCString hostname, MaybeNsCString address);
+ async OnStunAddrsAvailable(NrIceStunAddrArray iceStunAddrs);
+};
+
+} // namespace net
+} // namespace mozilla
diff --git a/dom/media/webrtc/transport/ipc/PWebrtcTCPSocket.ipdl b/dom/media/webrtc/transport/ipc/PWebrtcTCPSocket.ipdl
new file mode 100644
index 0000000000..3bc2a89828
--- /dev/null
+++ b/dom/media/webrtc/transport/ipc/PWebrtcTCPSocket.ipdl
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PNecko;
+include protocol PSocketProcess;
+
+include WebrtcProxyConfig;
+
+using mozilla::dom::TabId from "mozilla/dom/ipc/IdType.h";
+
+namespace mozilla {
+namespace net {
+
+[ManualDealloc, ChildImpl=virtual, ParentImpl=virtual]
+async protocol PWebrtcTCPSocket
+{
+ manager PNecko or PSocketProcess;
+
+parent:
+ async AsyncOpen(nsCString aHost,
+ int32_t aPort,
+ nsCString aLocalAddress,
+ int32_t aLocalPort,
+ bool aUseTls,
+ WebrtcProxyConfig? aProxyConfig);
+ async Write(uint8_t[] aWriteData);
+ async Close();
+
+child:
+ async OnClose(nsresult aReason);
+ async OnConnected(nsCString aProxyType);
+ async OnRead(uint8_t[] aReadData);
+
+ async __delete__();
+};
+
+} // namespace net
+} // namespace mozilla
diff --git a/dom/media/webrtc/transport/ipc/StunAddrsRequestChild.cpp b/dom/media/webrtc/transport/ipc/StunAddrsRequestChild.cpp
new file mode 100644
index 0000000000..057fcbd330
--- /dev/null
+++ b/dom/media/webrtc/transport/ipc/StunAddrsRequestChild.cpp
@@ -0,0 +1,45 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "StunAddrsRequestChild.h"
+
+#include "mozilla/net/NeckoChild.h"
+#include "nsISerialEventTarget.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla::net {
+
+StunAddrsRequestChild::StunAddrsRequestChild(StunAddrsListener* listener)
+ : mListener(listener) {
+ gNeckoChild->SendPStunAddrsRequestConstructor(this);
+ // IPDL holds a reference until IPDL channel gets destroyed
+ AddIPDLReference();
+}
+
+mozilla::ipc::IPCResult StunAddrsRequestChild::RecvOnMDNSQueryComplete(
+ const nsACString& hostname, const Maybe<nsCString>& address) {
+ if (mListener) {
+ mListener->OnMDNSQueryComplete(PromiseFlatCString(hostname), address);
+ }
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult StunAddrsRequestChild::RecvOnStunAddrsAvailable(
+ const NrIceStunAddrArray& addrs) {
+ if (mListener) {
+ mListener->OnStunAddrsAvailable(addrs);
+ }
+ return IPC_OK();
+}
+
+void StunAddrsRequestChild::Cancel() { mListener = nullptr; }
+
+NS_IMPL_ADDREF(StunAddrsRequestChild)
+NS_IMPL_RELEASE(StunAddrsRequestChild)
+
+NS_IMPL_ADDREF(StunAddrsListener)
+NS_IMPL_RELEASE(StunAddrsListener)
+
+} // namespace mozilla::net
diff --git a/dom/media/webrtc/transport/ipc/StunAddrsRequestChild.h b/dom/media/webrtc/transport/ipc/StunAddrsRequestChild.h
new file mode 100644
index 0000000000..f487f52baf
--- /dev/null
+++ b/dom/media/webrtc/transport/ipc/StunAddrsRequestChild.h
@@ -0,0 +1,64 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_StunAddrsRequestChild_h
+#define mozilla_net_StunAddrsRequestChild_h
+
+#include "mozilla/net/PStunAddrsRequestChild.h"
+
+class nsISerialEventTarget;
+
+namespace mozilla::net {
+
+class StunAddrsListener {
+ public:
+ virtual void OnMDNSQueryComplete(const nsCString& hostname,
+ const Maybe<nsCString>& address) = 0;
+ virtual void OnStunAddrsAvailable(const NrIceStunAddrArray& addrs) = 0;
+
+ NS_IMETHOD_(MozExternalRefCountType) AddRef();
+ NS_IMETHOD_(MozExternalRefCountType) Release();
+
+ protected:
+ virtual ~StunAddrsListener() = default;
+
+ ThreadSafeAutoRefCnt mRefCnt;
+ NS_DECL_OWNINGTHREAD
+};
+
+class StunAddrsRequestChild final : public PStunAddrsRequestChild {
+ friend class PStunAddrsRequestChild;
+
+ public:
+ explicit StunAddrsRequestChild(StunAddrsListener* listener);
+
+ NS_IMETHOD_(MozExternalRefCountType) AddRef();
+ NS_IMETHOD_(MozExternalRefCountType) Release();
+
+ // Not sure why AddIPDLReference & ReleaseIPDLReference don't come
+ // from PStunAddrsRequestChild since the IPC plumbing seem to
+ // expect this.
+ void AddIPDLReference() { AddRef(); }
+ void ReleaseIPDLReference() { Release(); }
+
+ void Cancel();
+
+ protected:
+ virtual ~StunAddrsRequestChild() = default;
+
+ virtual mozilla::ipc::IPCResult RecvOnMDNSQueryComplete(
+ const nsACString& aHostname, const Maybe<nsCString>& aAddress) override;
+
+ virtual mozilla::ipc::IPCResult RecvOnStunAddrsAvailable(
+ const NrIceStunAddrArray& addrs) override;
+
+ RefPtr<StunAddrsListener> mListener;
+
+ ThreadSafeAutoRefCnt mRefCnt;
+ NS_DECL_OWNINGTHREAD
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_StunAddrsRequestChild_h
diff --git a/dom/media/webrtc/transport/ipc/StunAddrsRequestParent.cpp b/dom/media/webrtc/transport/ipc/StunAddrsRequestParent.cpp
new file mode 100644
index 0000000000..23ef6dea73
--- /dev/null
+++ b/dom/media/webrtc/transport/ipc/StunAddrsRequestParent.cpp
@@ -0,0 +1,262 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "StunAddrsRequestParent.h"
+
+#include "../runnable_utils.h"
+#include "mozilla/StaticPtr.h"
+#include "nsIThread.h"
+#include "nsNetUtil.h"
+
+#include "transport/nricectx.h"
+#include "transport/nricemediastream.h" // needed only for including nricectx.h
+#include "transport/nricestunaddr.h"
+
+#include "../mdns_service/mdns_service.h"
+
+extern "C" {
+#include "local_addr.h"
+}
+
+using namespace mozilla::ipc;
+
+namespace mozilla::net {
+
+static void mdns_service_resolved(void* cb, const char* hostname,
+ const char* addr) {
+ StunAddrsRequestParent* self = static_cast<StunAddrsRequestParent*>(cb);
+ self->OnQueryComplete(nsCString(hostname), Some(nsCString(addr)));
+}
+
+void mdns_service_timedout(void* cb, const char* hostname) {
+ StunAddrsRequestParent* self = static_cast<StunAddrsRequestParent*>(cb);
+ self->OnQueryComplete(nsCString(hostname), Nothing());
+}
+
+StunAddrsRequestParent::StunAddrsRequestParent() : mIPCClosed(false) {
+ NS_GetMainThread(getter_AddRefs(mMainThread));
+
+ nsresult res;
+ mSTSThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &res);
+ MOZ_ASSERT(mSTSThread);
+}
+
+StunAddrsRequestParent::~StunAddrsRequestParent() {
+ ASSERT_ON_THREAD(mMainThread);
+}
+
+mozilla::ipc::IPCResult StunAddrsRequestParent::RecvGetStunAddrs() {
+ ASSERT_ON_THREAD(mMainThread);
+
+ if (mIPCClosed) {
+ return IPC_OK();
+ }
+
+ RUN_ON_THREAD(mSTSThread,
+ WrapRunnable(RefPtr<StunAddrsRequestParent>(this),
+ &StunAddrsRequestParent::GetStunAddrs_s),
+ NS_DISPATCH_NORMAL);
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult StunAddrsRequestParent::RecvRegisterMDNSHostname(
+ const nsACString& aHostname, const nsACString& aAddress) {
+ ASSERT_ON_THREAD(mMainThread);
+
+ if (mIPCClosed) {
+ return IPC_OK();
+ }
+
+ if (mSharedMDNSService) {
+ mSharedMDNSService->RegisterHostname(aHostname.BeginReading(),
+ aAddress.BeginReading());
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult StunAddrsRequestParent::RecvQueryMDNSHostname(
+ const nsACString& aHostname) {
+ ASSERT_ON_THREAD(mMainThread);
+
+ if (mIPCClosed) {
+ return IPC_OK();
+ }
+
+ if (mSharedMDNSService) {
+ mSharedMDNSService->QueryHostname(this, aHostname.BeginReading());
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult StunAddrsRequestParent::RecvUnregisterMDNSHostname(
+ const nsACString& aHostname) {
+ ASSERT_ON_THREAD(mMainThread);
+
+ if (mIPCClosed) {
+ return IPC_OK();
+ }
+
+ if (mSharedMDNSService) {
+ mSharedMDNSService->UnregisterHostname(aHostname.BeginReading());
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult StunAddrsRequestParent::Recv__delete__() {
+ // see note below in ActorDestroy
+ mIPCClosed = true;
+ return IPC_OK();
+}
+
+void StunAddrsRequestParent::OnQueryComplete(const nsACString& hostname,
+ const Maybe<nsCString>& address) {
+ RUN_ON_THREAD(mMainThread,
+ WrapRunnable(RefPtr<StunAddrsRequestParent>(this),
+ &StunAddrsRequestParent::OnQueryComplete_m,
+ nsCString(hostname), address),
+ NS_DISPATCH_NORMAL);
+}
+
+void StunAddrsRequestParent::ActorDestroy(ActorDestroyReason why) {
+ // We may still have refcount>0 if we haven't made it through
+ // GetStunAddrs_s and SendStunAddrs_m yet, but child process
+ // has crashed. We must not send any more msgs to child, or
+ // IPDL will kill chrome process, too.
+ mIPCClosed = true;
+
+ // We need to stop the mDNS service here to ensure that we don't
+ // end up with any messages queued for the main thread after the
+ // destructors run. Because of Bug 1569311, all of the
+ // StunAddrsRequestParent instances end up being destroyed one
+ // after the other, so it is ok to free the shared service when
+ // the first one is destroyed rather than waiting for the last one.
+ // If this behaviour changes, we would potentially end up starting
+ // and stopping instances repeatedly and should add a refcount and
+ // a way of cancelling pending queries to avoid churn in that case.
+ if (mSharedMDNSService) {
+ mSharedMDNSService = nullptr;
+ }
+}
+
+void StunAddrsRequestParent::GetStunAddrs_s() {
+ ASSERT_ON_THREAD(mSTSThread);
+
+ // get the stun addresses while on STS thread
+ NrIceStunAddrArray addrs = NrIceCtx::GetStunAddrs();
+
+ if (mIPCClosed) {
+ return;
+ }
+
+ // in order to return the result over IPC, we need to be on main thread
+ RUN_ON_THREAD(
+ mMainThread,
+ WrapRunnable(RefPtr<StunAddrsRequestParent>(this),
+ &StunAddrsRequestParent::SendStunAddrs_m, std::move(addrs)),
+ NS_DISPATCH_NORMAL);
+}
+
+void StunAddrsRequestParent::SendStunAddrs_m(const NrIceStunAddrArray& addrs) {
+ ASSERT_ON_THREAD(mMainThread);
+
+ if (mIPCClosed) {
+ // nothing to do: child probably crashed
+ return;
+ }
+
+ // This means that the mDNS service will continue running until shutdown
+ // once started. The StunAddrsRequestParent destructor does not run until
+ // shutdown anyway (see Bug 1569311), so there is not much we can do about
+ // this here. One option would be to add a check if there are no hostnames
+ // registered after UnregisterHostname is called, and if so, stop the mDNS
+ // service at that time (see Bug 1569955.)
+ if (!mSharedMDNSService) {
+ std::ostringstream o;
+ char buffer[16];
+ for (auto& addr : addrs) {
+ if (addr.localAddr().addr.ip_version == NR_IPV4 &&
+ !nr_transport_addr_is_loopback(&addr.localAddr().addr)) {
+ nr_transport_addr_get_addrstring(&addr.localAddr().addr, buffer, 16);
+ o << buffer << ";";
+ }
+ }
+ std::string addrstring = o.str();
+ if (!addrstring.empty()) {
+ mSharedMDNSService = new MDNSServiceWrapper(addrstring);
+ }
+ }
+
+ // send the new addresses back to the child
+ Unused << SendOnStunAddrsAvailable(addrs);
+}
+
+void StunAddrsRequestParent::OnQueryComplete_m(
+ const nsACString& hostname, const Maybe<nsCString>& address) {
+ ASSERT_ON_THREAD(mMainThread);
+
+ if (mIPCClosed) {
+ // nothing to do: child probably crashed
+ return;
+ }
+
+ // send the hostname and address back to the child
+ Unused << SendOnMDNSQueryComplete(hostname, address);
+}
+
+StaticRefPtr<StunAddrsRequestParent::MDNSServiceWrapper>
+ StunAddrsRequestParent::mSharedMDNSService;
+
+NS_IMPL_ADDREF(StunAddrsRequestParent)
+NS_IMPL_RELEASE(StunAddrsRequestParent)
+
+StunAddrsRequestParent::MDNSServiceWrapper::MDNSServiceWrapper(
+ const std::string& ifaddr)
+ : ifaddr(ifaddr) {}
+
+void StunAddrsRequestParent::MDNSServiceWrapper::RegisterHostname(
+ const char* hostname, const char* address) {
+ StartIfRequired();
+ if (mMDNSService) {
+ mdns_service_register_hostname(mMDNSService, hostname, address);
+ }
+}
+
+void StunAddrsRequestParent::MDNSServiceWrapper::QueryHostname(
+ void* data, const char* hostname) {
+ StartIfRequired();
+ if (mMDNSService) {
+ mdns_service_query_hostname(mMDNSService, data, mdns_service_resolved,
+ mdns_service_timedout, hostname);
+ }
+}
+
+void StunAddrsRequestParent::MDNSServiceWrapper::UnregisterHostname(
+ const char* hostname) {
+ StartIfRequired();
+ if (mMDNSService) {
+ mdns_service_unregister_hostname(mMDNSService, hostname);
+ }
+}
+
+StunAddrsRequestParent::MDNSServiceWrapper::~MDNSServiceWrapper() {
+ if (mMDNSService) {
+ mdns_service_stop(mMDNSService);
+ mMDNSService = nullptr;
+ }
+}
+
+void StunAddrsRequestParent::MDNSServiceWrapper::StartIfRequired() {
+ if (!mMDNSService) {
+ mMDNSService = mdns_service_start(ifaddr.c_str());
+ }
+}
+
+NS_IMPL_ADDREF(StunAddrsRequestParent::MDNSServiceWrapper)
+NS_IMPL_RELEASE(StunAddrsRequestParent::MDNSServiceWrapper)
+
+} // namespace mozilla::net
diff --git a/dom/media/webrtc/transport/ipc/StunAddrsRequestParent.h b/dom/media/webrtc/transport/ipc/StunAddrsRequestParent.h
new file mode 100644
index 0000000000..33e71abbc7
--- /dev/null
+++ b/dom/media/webrtc/transport/ipc/StunAddrsRequestParent.h
@@ -0,0 +1,82 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_StunAddrsRequestParent_h
+#define mozilla_net_StunAddrsRequestParent_h
+
+#include "mozilla/net/PStunAddrsRequestParent.h"
+
+struct MDNSService;
+
+namespace mozilla::net {
+
+class StunAddrsRequestParent : public PStunAddrsRequestParent {
+ friend class PStunAddrsRequestParent;
+
+ public:
+ StunAddrsRequestParent();
+
+ NS_IMETHOD_(MozExternalRefCountType) AddRef();
+ NS_IMETHOD_(MozExternalRefCountType) Release();
+
+ mozilla::ipc::IPCResult Recv__delete__() override;
+
+ void OnQueryComplete(const nsACString& hostname,
+ const Maybe<nsCString>& address);
+
+ protected:
+ virtual ~StunAddrsRequestParent();
+
+ virtual mozilla::ipc::IPCResult RecvGetStunAddrs() override;
+ virtual mozilla::ipc::IPCResult RecvRegisterMDNSHostname(
+ const nsACString& hostname, const nsACString& address) override;
+ virtual mozilla::ipc::IPCResult RecvQueryMDNSHostname(
+ const nsACString& hostname) override;
+ virtual mozilla::ipc::IPCResult RecvUnregisterMDNSHostname(
+ const nsACString& hostname) override;
+ virtual void ActorDestroy(ActorDestroyReason why) override;
+
+ nsCOMPtr<nsIThread> mMainThread;
+ nsCOMPtr<nsISerialEventTarget> mSTSThread;
+
+ void GetStunAddrs_s();
+ void SendStunAddrs_m(const NrIceStunAddrArray& addrs);
+
+ void OnQueryComplete_m(const nsACString& hostname,
+ const Maybe<nsCString>& address);
+
+ ThreadSafeAutoRefCnt mRefCnt;
+ NS_DECL_OWNINGTHREAD
+
+ private:
+ bool mIPCClosed; // true if IPDL channel has been closed (child crash)
+
+ class MDNSServiceWrapper {
+ public:
+ explicit MDNSServiceWrapper(const std::string& ifaddr);
+ void RegisterHostname(const char* hostname, const char* address);
+ void QueryHostname(void* data, const char* hostname);
+ void UnregisterHostname(const char* hostname);
+
+ NS_IMETHOD_(MozExternalRefCountType) AddRef();
+ NS_IMETHOD_(MozExternalRefCountType) Release();
+
+ protected:
+ ThreadSafeAutoRefCnt mRefCnt;
+ NS_DECL_OWNINGTHREAD
+
+ private:
+ virtual ~MDNSServiceWrapper();
+ void StartIfRequired();
+
+ std::string ifaddr;
+ MDNSService* mMDNSService = nullptr;
+ };
+
+ static StaticRefPtr<MDNSServiceWrapper> mSharedMDNSService;
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_StunAddrsRequestParent_h
diff --git a/dom/media/webrtc/transport/ipc/WebrtcProxyConfig.ipdlh b/dom/media/webrtc/transport/ipc/WebrtcProxyConfig.ipdlh
new file mode 100644
index 0000000000..e93e82a6a3
--- /dev/null
+++ b/dom/media/webrtc/transport/ipc/WebrtcProxyConfig.ipdlh
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+using mozilla::dom::TabId from "mozilla/dom/ipc/IdType.h";
+include NeckoChannelParams;
+
+namespace mozilla {
+namespace net {
+
+struct WebrtcProxyConfig {
+ TabId tabId;
+ nsCString alpn;
+ LoadInfoArgs loadInfoArgs;
+ bool forceProxy;
+};
+
+} // namespace net
+} // namespace mozilla
+
diff --git a/dom/media/webrtc/transport/ipc/WebrtcTCPSocket.cpp b/dom/media/webrtc/transport/ipc/WebrtcTCPSocket.cpp
new file mode 100644
index 0000000000..447b4cc741
--- /dev/null
+++ b/dom/media/webrtc/transport/ipc/WebrtcTCPSocket.cpp
@@ -0,0 +1,785 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebrtcTCPSocket.h"
+
+#include "nsHttpChannel.h"
+#include "nsIChannel.h"
+#include "nsIClassOfService.h"
+#include "nsIContentPolicy.h"
+#include "nsIIOService.h"
+#include "nsILoadInfo.h"
+#include "nsIProtocolProxyService.h"
+#include "nsIURIMutator.h"
+#include "nsICookieJarSettings.h"
+#include "nsProxyRelease.h"
+#include "nsString.h"
+#include "mozilla/dom/ContentProcessManager.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "nsISocketTransportService.h"
+#include "nsICancelable.h"
+#include "nsSocketTransportService2.h"
+
+#include "WebrtcTCPSocketCallback.h"
+#include "WebrtcTCPSocketLog.h"
+
+namespace mozilla::net {
+
+class WebrtcTCPData {
+ public:
+ explicit WebrtcTCPData(nsTArray<uint8_t>&& aData) : mData(std::move(aData)) {
+ MOZ_COUNT_CTOR(WebrtcTCPData);
+ }
+
+ MOZ_COUNTED_DTOR(WebrtcTCPData)
+
+ const nsTArray<uint8_t>& GetData() const { return mData; }
+
+ private:
+ nsTArray<uint8_t> mData;
+};
+
+NS_IMPL_ISUPPORTS(WebrtcTCPSocket, nsIAuthPromptProvider,
+ nsIHttpUpgradeListener, nsIInputStreamCallback,
+ nsIInterfaceRequestor, nsIOutputStreamCallback,
+ nsIRequestObserver, nsIStreamListener,
+ nsIProtocolProxyCallback)
+
+WebrtcTCPSocket::WebrtcTCPSocket(WebrtcTCPSocketCallback* aCallbacks)
+ : mProxyCallbacks(aCallbacks),
+ mClosed(false),
+ mOpened(false),
+ mWriteOffset(0),
+ mAuthProvider(nullptr),
+ mTransport(nullptr),
+ mSocketIn(nullptr),
+ mSocketOut(nullptr) {
+ LOG(("WebrtcTCPSocket::WebrtcTCPSocket %p\n", this));
+ mMainThread = GetMainThreadSerialEventTarget();
+ mSocketThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
+ MOZ_RELEASE_ASSERT(mMainThread, "no main thread");
+ MOZ_RELEASE_ASSERT(mSocketThread, "no socket thread");
+}
+
+WebrtcTCPSocket::~WebrtcTCPSocket() {
+ LOG(("WebrtcTCPSocket::~WebrtcTCPSocket %p\n", this));
+
+ NS_ProxyRelease("WebrtcTCPSocket::CleanUpAuthProvider", mMainThread,
+ mAuthProvider.forget());
+}
+
+void WebrtcTCPSocket::SetTabId(dom::TabId aTabId) {
+ MOZ_ASSERT(NS_IsMainThread());
+ dom::ContentProcessManager* cpm = dom::ContentProcessManager::GetSingleton();
+ if (cpm) {
+ dom::ContentParentId cpId = cpm->GetTabProcessId(aTabId);
+ mAuthProvider = cpm->GetBrowserParentByProcessAndTabId(cpId, aTabId);
+ }
+}
+
+nsresult WebrtcTCPSocket::Write(nsTArray<uint8_t>&& aWriteData) {
+ LOG(("WebrtcTCPSocket::Write %p\n", this));
+ MOZ_ASSERT(NS_IsMainThread());
+ nsresult rv = mSocketThread->Dispatch(NewRunnableMethod<nsTArray<uint8_t>&&>(
+ "WebrtcTCPSocket::Write", this, &WebrtcTCPSocket::EnqueueWrite_s,
+ std::move(aWriteData)));
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to dispatch to STS");
+
+ return rv;
+}
+
+nsresult WebrtcTCPSocket::Close() {
+ LOG(("WebrtcTCPSocket::Close %p\n", this));
+
+ CloseWithReason(NS_OK);
+
+ return NS_OK;
+}
+
+void WebrtcTCPSocket::CloseWithReason(nsresult aReason) {
+ LOG(("WebrtcTCPSocket::CloseWithReason %p reason=%u\n", this,
+ static_cast<uint32_t>(aReason)));
+
+ if (!OnSocketThread()) {
+ MOZ_ASSERT(NS_IsMainThread(), "not on main thread");
+
+ // Let's pretend we got an open even if we didn't to prevent an Open later.
+ mOpened = true;
+
+ DebugOnly<nsresult> rv =
+ mSocketThread->Dispatch(NewRunnableMethod<nsresult>(
+ "WebrtcTCPSocket::CloseWithReason", this,
+ &WebrtcTCPSocket::CloseWithReason, aReason));
+
+ // This was MOZ_ALWAYS_SUCCEEDS, but that now uses NS_WARNING_ASSERTION.
+ // In order to convert this back to MOZ_ALWAYS_SUCCEEDS we would need
+ // OnSocketThread to return true if we're shutting down and doing the
+ // "running all of STS's queued events on main" thing.
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to dispatch to STS");
+
+ return;
+ }
+
+ if (mClosed) {
+ return;
+ }
+
+ mClosed = true;
+
+ if (mSocketIn) {
+ mSocketIn->AsyncWait(nullptr, 0, 0, nullptr);
+ mSocketIn = nullptr;
+ }
+
+ if (mSocketOut) {
+ mSocketOut->AsyncWait(nullptr, 0, 0, nullptr);
+ mSocketOut = nullptr;
+ }
+
+ if (mTransport) {
+ mTransport->Close(NS_BASE_STREAM_CLOSED);
+ mTransport = nullptr;
+ }
+
+ NS_ProxyRelease("WebrtcTCPSocket::CleanUpAuthProvider", mMainThread,
+ mAuthProvider.forget());
+ InvokeOnClose(aReason);
+}
+
+nsresult WebrtcTCPSocket::Open(
+ const nsACString& aHost, const int& aPort, const nsACString& aLocalAddress,
+ const int& aLocalPort, bool aUseTls,
+ const Maybe<net::WebrtcProxyConfig>& aProxyConfig) {
+ LOG(("WebrtcTCPSocket::Open %p remote-host=%s local-addr=%s local-port=%d",
+ this, PromiseFlatCString(aHost).get(),
+ PromiseFlatCString(aLocalAddress).get(), aLocalPort));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (NS_WARN_IF(mOpened)) {
+ LOG(("WebrtcTCPSocket %p: TCP socket already open\n", this));
+ CloseWithReason(NS_ERROR_FAILURE);
+ return NS_ERROR_FAILURE;
+ }
+
+ mOpened = true;
+ const nsLiteralCString schemePrefix = aUseTls ? "https://"_ns : "http://"_ns;
+ nsAutoCString spec(schemePrefix);
+
+ bool ipv6Literal = aHost.Find(":") != kNotFound;
+ if (ipv6Literal) {
+ spec += "[";
+ spec += aHost;
+ spec += "]";
+ } else {
+ spec += aHost;
+ }
+
+ nsresult rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID)
+ .SetSpec(spec)
+ .SetPort(aPort)
+ .Finalize(mURI);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ CloseWithReason(NS_ERROR_FAILURE);
+ return NS_ERROR_FAILURE;
+ }
+
+ mTls = aUseTls;
+ mLocalAddress = aLocalAddress;
+ mLocalPort = aLocalPort;
+ mProxyConfig = aProxyConfig;
+
+ if (!mProxyConfig.isSome()) {
+ OpenWithoutHttpProxy(nullptr);
+ return NS_OK;
+ }
+
+ // We need to figure out whether a proxy needs to be used for mURI before
+ // we can start on establishing a connection.
+ rv = DoProxyConfigLookup();
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ CloseWithReason(rv);
+ }
+
+ return rv;
+}
+
+nsresult WebrtcTCPSocket::DoProxyConfigLookup() {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsresult rv;
+ nsCOMPtr<nsIProtocolProxyService> pps =
+ do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel), mURI,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = pps->AsyncResolve(channel,
+ nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY |
+ nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL,
+ this, nullptr, getter_AddRefs(mProxyRequest));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // We pick back up in OnProxyAvailable
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP WebrtcTCPSocket::OnProxyAvailable(nsICancelable* aRequest,
+ nsIChannel* aChannel,
+ nsIProxyInfo* aProxyinfo,
+ nsresult aResult) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mProxyRequest = nullptr;
+
+ if (NS_SUCCEEDED(aResult) && aProxyinfo) {
+ nsresult rv = aProxyinfo->GetType(mProxyType);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ CloseWithReason(rv);
+ return rv;
+ }
+
+ if (mProxyType == "http" || mProxyType == "https") {
+ rv = OpenWithHttpProxy();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ CloseWithReason(rv);
+ }
+ return rv;
+ }
+
+ if (mProxyType == "socks" || mProxyType == "socks4" ||
+ mProxyType == "direct") {
+ OpenWithoutHttpProxy(aProxyinfo);
+ return NS_OK;
+ }
+ }
+
+ OpenWithoutHttpProxy(nullptr);
+
+ return NS_OK;
+}
+
+void WebrtcTCPSocket::OpenWithoutHttpProxy(nsIProxyInfo* aSocksProxyInfo) {
+ if (!OnSocketThread()) {
+ DebugOnly<nsresult> rv =
+ mSocketThread->Dispatch(NewRunnableMethod<nsCOMPtr<nsIProxyInfo>>(
+ "WebrtcTCPSocket::OpenWithoutHttpProxy", this,
+ &WebrtcTCPSocket::OpenWithoutHttpProxy, aSocksProxyInfo));
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to dispatch to STS");
+ return;
+ }
+
+ LOG(("WebrtcTCPSocket::OpenWithoutHttpProxy %p\n", this));
+
+ if (mClosed) {
+ return;
+ }
+
+ if (NS_WARN_IF(mProxyConfig.isSome() && mProxyConfig->forceProxy() &&
+ !aSocksProxyInfo)) {
+ CloseWithReason(NS_ERROR_FAILURE);
+ return;
+ }
+
+ nsCString host;
+ int32_t port;
+
+ nsresult rv = mURI->GetHost(host);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ CloseWithReason(rv);
+ return;
+ }
+
+ rv = mURI->GetPort(&port);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ CloseWithReason(rv);
+ return;
+ }
+
+ AutoTArray<nsCString, 1> socketTypes;
+ if (mTls) {
+ socketTypes.AppendElement("ssl"_ns);
+ }
+
+ nsCOMPtr<nsISocketTransportService> sts =
+ do_GetService("@mozilla.org/network/socket-transport-service;1");
+ rv = sts->CreateTransport(socketTypes, host, port, aSocksProxyInfo, nullptr,
+ getter_AddRefs(mTransport));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ CloseWithReason(rv);
+ return;
+ }
+
+ mTransport->SetReuseAddrPort(true);
+
+ PRNetAddr prAddr;
+ if (NS_WARN_IF(PR_SUCCESS !=
+ PR_InitializeNetAddr(PR_IpAddrAny, mLocalPort, &prAddr))) {
+ CloseWithReason(NS_ERROR_FAILURE);
+ return;
+ }
+
+ if (NS_WARN_IF(PR_SUCCESS !=
+ PR_StringToNetAddr(mLocalAddress.BeginReading(), &prAddr))) {
+ CloseWithReason(NS_ERROR_FAILURE);
+ return;
+ }
+
+ mozilla::net::NetAddr addr(&prAddr);
+ rv = mTransport->Bind(&addr);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ CloseWithReason(rv);
+ return;
+ }
+
+ // Binding to a V4 address is not sufficient to cause this socket to use
+ // V4, and the same goes for V6. So, we disable as needed here.
+ uint32_t flags = 0;
+ if (addr.raw.family == AF_INET) {
+ flags |= nsISocketTransport::DISABLE_IPV6;
+ } else if (addr.raw.family == AF_INET6) {
+ flags |= nsISocketTransport::DISABLE_IPV4;
+ } else {
+ MOZ_CRASH();
+ }
+
+ mTransport->SetConnectionFlags(flags);
+
+ nsCOMPtr<nsIInputStream> socketIn;
+ rv = mTransport->OpenInputStream(0, 0, 0, getter_AddRefs(socketIn));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ CloseWithReason(rv);
+ return;
+ }
+ mSocketIn = do_QueryInterface(socketIn);
+ if (NS_WARN_IF(!mSocketIn)) {
+ CloseWithReason(NS_ERROR_NULL_POINTER);
+ return;
+ }
+
+ nsCOMPtr<nsIOutputStream> socketOut;
+ rv = mTransport->OpenOutputStream(nsITransport::OPEN_UNBUFFERED, 0, 0,
+ getter_AddRefs(socketOut));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ CloseWithReason(rv);
+ return;
+ }
+ mSocketOut = do_QueryInterface(socketOut);
+ if (NS_WARN_IF(!mSocketOut)) {
+ CloseWithReason(NS_ERROR_NULL_POINTER);
+ return;
+ }
+
+ FinishOpen();
+}
+
+nsresult WebrtcTCPSocket::OpenWithHttpProxy() {
+ MOZ_ASSERT(NS_IsMainThread(), "not on main thread");
+ LOG(("WebrtcTCPSocket::OpenWithHttpProxy %p\n", this));
+ nsresult rv;
+ nsCOMPtr<nsIIOService> ioService;
+ ioService = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ LOG(("WebrtcTCPSocket %p: io service missing\n", this));
+ return rv;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ Maybe<net::LoadInfoArgs> loadInfoArgs = Some(mProxyConfig->loadInfoArgs());
+
+ // FIXME: We don't know the remote type of the process which provided these
+ // LoadInfoArgs. Pass in `NOT_REMOTE_TYPE` as the origin process to blindly
+ // accept whatever value was passed by the other side for now, as we aren't
+ // using it for security checks here.
+ // If this code ever starts checking the triggering remote type, this needs to
+ // be changed.
+ rv = ipc::LoadInfoArgsToLoadInfo(loadInfoArgs, NOT_REMOTE_TYPE,
+ getter_AddRefs(loadInfo));
+ if (NS_FAILED(rv)) {
+ LOG(("WebrtcTCPSocket %p: could not init load info\n", this));
+ return rv;
+ }
+
+ // -need to always tunnel since we're using a proxy
+ // -there shouldn't be an opportunity to send cookies, but explicitly disallow
+ // them anyway.
+ // -the previous proxy tunnel didn't support redirects e.g. 307. don't need to
+ // introduce new behavior. can't follow redirects on connect anyway.
+ nsCOMPtr<nsIChannel> localChannel;
+ rv = ioService->NewChannelFromURIWithProxyFlags(
+ mURI, nullptr,
+ // Proxy flags are overridden by SetConnectOnly()
+ 0, loadInfo->LoadingNode(), loadInfo->GetLoadingPrincipal(),
+ loadInfo->TriggeringPrincipal(),
+ nsILoadInfo::SEC_COOKIES_OMIT |
+ // We need this flag to allow loads from any origin since this channel
+ // is being used to CONNECT to an HTTP proxy.
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_PROXIED_WEBRTC_MEDIA,
+ getter_AddRefs(localChannel));
+ if (NS_FAILED(rv)) {
+ LOG(("WebrtcTCPSocket %p: bad open channel\n", this));
+ return rv;
+ }
+
+ nsCOMPtr<nsILoadInfo> channelLoadInfo = localChannel->LoadInfo();
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
+ loadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings));
+ channelLoadInfo->SetCookieJarSettings(cookieJarSettings);
+
+ RefPtr<nsHttpChannel> httpChannel;
+ CallQueryInterface(localChannel, httpChannel.StartAssignment());
+
+ if (!httpChannel) {
+ LOG(("WebrtcTCPSocket %p: not an http channel\n", this));
+ return NS_ERROR_FAILURE;
+ }
+
+ httpChannel->SetNotificationCallbacks(this);
+
+ // don't block webrtc proxy setup with other requests
+ // often more than one of these channels will be created all at once by ICE
+ nsCOMPtr<nsIClassOfService> cos = do_QueryInterface(localChannel);
+ if (cos) {
+ cos->AddClassFlags(nsIClassOfService::Unblocked |
+ nsIClassOfService::DontThrottle);
+ } else {
+ LOG(("WebrtcTCPSocket %p: could not set class of service\n", this));
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = httpChannel->HTTPUpgrade(mProxyConfig->alpn(), this);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ rv = httpChannel->SetConnectOnly();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = httpChannel->AsyncOpen(this);
+
+ if (NS_FAILED(rv)) {
+ LOG(("WebrtcTCPSocket %p: cannot async open\n", this));
+ return rv;
+ }
+
+ // This picks back up in OnTransportAvailable once we have connected to the
+ // proxy, and performed the http upgrade to switch the proxy into passthrough
+ // mode.
+
+ return NS_OK;
+}
+
+void WebrtcTCPSocket::EnqueueWrite_s(nsTArray<uint8_t>&& aWriteData) {
+ LOG(("WebrtcTCPSocket::EnqueueWrite %p\n", this));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+
+ if (mClosed) {
+ return;
+ }
+
+ mWriteQueue.emplace_back(std::move(aWriteData));
+
+ if (mSocketOut) {
+ mSocketOut->AsyncWait(this, 0, 0, nullptr);
+ }
+}
+
+void WebrtcTCPSocket::InvokeOnClose(nsresult aReason) {
+ LOG(("WebrtcTCPSocket::InvokeOnClose %p\n", this));
+
+ if (!NS_IsMainThread()) {
+ MOZ_ALWAYS_SUCCEEDS(mMainThread->Dispatch(
+ NewRunnableMethod<nsresult>("WebrtcTCPSocket::InvokeOnClose", this,
+ &WebrtcTCPSocket::InvokeOnClose, aReason)));
+ return;
+ }
+
+ MOZ_ASSERT(mProxyCallbacks, "webrtc TCP callback should be non-null");
+
+ if (mProxyRequest) {
+ mProxyRequest->Cancel(aReason);
+ mProxyRequest = nullptr;
+ }
+
+ mProxyCallbacks->OnClose(aReason);
+ mProxyCallbacks = nullptr;
+}
+
+void WebrtcTCPSocket::InvokeOnConnected() {
+ LOG(("WebrtcTCPSocket::InvokeOnConnected %p\n", this));
+
+ if (!NS_IsMainThread()) {
+ MOZ_ALWAYS_SUCCEEDS(mMainThread->Dispatch(
+ NewRunnableMethod("WebrtcTCPSocket::InvokeOnConnected", this,
+ &WebrtcTCPSocket::InvokeOnConnected)));
+ return;
+ }
+
+ MOZ_ASSERT(mProxyCallbacks, "webrtc TCP callback should be non-null");
+
+ mProxyCallbacks->OnConnected(mProxyType);
+}
+
+void WebrtcTCPSocket::InvokeOnRead(nsTArray<uint8_t>&& aReadData) {
+ LOG(("WebrtcTCPSocket::InvokeOnRead %p count=%zu\n", this,
+ aReadData.Length()));
+
+ if (!NS_IsMainThread()) {
+ MOZ_ALWAYS_SUCCEEDS(
+ mMainThread->Dispatch(NewRunnableMethod<nsTArray<uint8_t>&&>(
+ "WebrtcTCPSocket::InvokeOnRead", this,
+ &WebrtcTCPSocket::InvokeOnRead, std::move(aReadData))));
+ return;
+ }
+
+ MOZ_ASSERT(mProxyCallbacks, "webrtc TCP callback should be non-null");
+
+ mProxyCallbacks->OnRead(std::move(aReadData));
+}
+
+// nsIHttpUpgradeListener
+NS_IMETHODIMP
+WebrtcTCPSocket::OnTransportAvailable(nsISocketTransport* aTransport,
+ nsIAsyncInputStream* aSocketIn,
+ nsIAsyncOutputStream* aSocketOut) {
+ // This is called only in the http proxy case, once we have connected to the
+ // http proxy and performed the http upgrade to switch it over to passthrough
+ // mode. That process is started async by OpenWithHttpProxy.
+ LOG(("WebrtcTCPSocket::OnTransportAvailable %p\n", this));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(!mTransport,
+ "already called transport available on webrtc TCP socket");
+
+ // Cancel any pending callbacks. The caller doesn't always cancel these
+ // awaits. We need to make sure they don't get them.
+ aSocketIn->AsyncWait(nullptr, 0, 0, nullptr);
+ aSocketOut->AsyncWait(nullptr, 0, 0, nullptr);
+
+ if (mClosed) {
+ LOG(("WebrtcTCPSocket::OnTransportAvailable %p closed\n", this));
+ return NS_OK;
+ }
+
+ mTransport = aTransport;
+ mSocketIn = aSocketIn;
+ mSocketOut = aSocketOut;
+
+ // pulled from nr_socket_prsock.cpp
+ uint32_t minBufferSize = 256 * 1024;
+ nsresult rv = mTransport->SetSendBufferSize(minBufferSize);
+ if (NS_FAILED(rv)) {
+ LOG(("WebrtcProxyChannel::OnTransportAvailable %p send failed\n", this));
+ CloseWithReason(rv);
+ return rv;
+ }
+ rv = mTransport->SetRecvBufferSize(minBufferSize);
+ if (NS_FAILED(rv)) {
+ LOG(("WebrtcProxyChannel::OnTransportAvailable %p recv failed\n", this));
+ CloseWithReason(rv);
+ return rv;
+ }
+
+ FinishOpen();
+ return NS_OK;
+}
+
+void WebrtcTCPSocket::FinishOpen() {
+ MOZ_ASSERT(OnSocketThread());
+ // mTransport, mSocketIn, and mSocketOut are all set. We may have set them in
+ // OnTransportAvailable (in the http/https proxy case), or in
+ // OpenWithoutHttpProxy. From here on out, this class functions the same for
+ // these two cases.
+
+ mSocketIn->AsyncWait(this, 0, 0, nullptr);
+
+ InvokeOnConnected();
+}
+
+NS_IMETHODIMP
+WebrtcTCPSocket::OnUpgradeFailed(nsresult aErrorCode) {
+ LOG(("WebrtcTCPSocket::OnUpgradeFailed %p\n", this));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(!mTransport,
+ "already called transport available on webrtc TCP socket");
+
+ if (mClosed) {
+ LOG(("WebrtcTCPSocket::OnUpgradeFailed %p closed\n", this));
+ return NS_OK;
+ }
+
+ CloseWithReason(aErrorCode);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebrtcTCPSocket::OnWebSocketConnectionAvailable(
+ WebSocketConnectionBase* aConnection) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// nsIRequestObserver (from nsIStreamListener)
+NS_IMETHODIMP
+WebrtcTCPSocket::OnStartRequest(nsIRequest* aRequest) {
+ LOG(("WebrtcTCPSocket::OnStartRequest %p\n", this));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+WebrtcTCPSocket::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
+ LOG(("WebrtcTCPSocket::OnStopRequest %p status=%u\n", this,
+ static_cast<uint32_t>(aStatusCode)));
+
+ // see nsHttpChannel::ProcessFailedProxyConnect for most error codes
+ if (NS_FAILED(aStatusCode)) {
+ CloseWithReason(aStatusCode);
+ return aStatusCode;
+ }
+
+ return NS_OK;
+}
+
+// nsIStreamListener
+NS_IMETHODIMP
+WebrtcTCPSocket::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream, uint64_t aOffset,
+ uint32_t aCount) {
+ LOG(("WebrtcTCPSocket::OnDataAvailable %p count=%u\n", this, aCount));
+ MOZ_ASSERT(0, "unreachable data available");
+ return NS_OK;
+}
+
+// nsIInputStreamCallback
+NS_IMETHODIMP
+WebrtcTCPSocket::OnInputStreamReady(nsIAsyncInputStream* in) {
+ LOG(("WebrtcTCPSocket::OnInputStreamReady %p unwritten=%zu\n", this,
+ CountUnwrittenBytes()));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(!mClosed, "webrtc TCP socket closed");
+ MOZ_ASSERT(mTransport, "webrtc TCP socket not connected");
+ MOZ_ASSERT(mSocketIn == in, "wrong input stream");
+
+ char buffer[9216];
+ uint32_t remainingCapacity = sizeof(buffer);
+ uint32_t read = 0;
+
+ while (remainingCapacity > 0) {
+ uint32_t count = 0;
+ nsresult rv = mSocketIn->Read(buffer + read, remainingCapacity, &count);
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ break;
+ }
+
+ if (NS_FAILED(rv)) {
+ LOG(("WebrtcTCPSocket::OnInputStreamReady %p failed %u\n", this,
+ static_cast<uint32_t>(rv)));
+ CloseWithReason(rv);
+ return rv;
+ }
+
+ // base stream closed
+ if (count == 0) {
+ LOG(("WebrtcTCPSocket::OnInputStreamReady %p connection closed\n", this));
+ CloseWithReason(NS_ERROR_FAILURE);
+ return NS_OK;
+ }
+
+ remainingCapacity -= count;
+ read += count;
+ }
+
+ if (read > 0) {
+ nsTArray<uint8_t> array(read);
+ array.AppendElements(buffer, read);
+
+ InvokeOnRead(std::move(array));
+ }
+
+ mSocketIn->AsyncWait(this, 0, 0, nullptr);
+
+ return NS_OK;
+}
+
+// nsIOutputStreamCallback
+NS_IMETHODIMP
+WebrtcTCPSocket::OnOutputStreamReady(nsIAsyncOutputStream* out) {
+ LOG(("WebrtcTCPSocket::OnOutputStreamReady %p unwritten=%zu\n", this,
+ CountUnwrittenBytes()));
+ MOZ_ASSERT(OnSocketThread(), "not on socket thread");
+ MOZ_ASSERT(!mClosed, "webrtc TCP socket closed");
+ MOZ_ASSERT(mTransport, "webrtc TCP socket not connected");
+ MOZ_ASSERT(mSocketOut == out, "wrong output stream");
+
+ while (!mWriteQueue.empty()) {
+ const WebrtcTCPData& data = mWriteQueue.front();
+
+ char* buffer = reinterpret_cast<char*>(
+ const_cast<uint8_t*>(data.GetData().Elements())) +
+ mWriteOffset;
+ uint32_t toWrite = data.GetData().Length() - mWriteOffset;
+
+ uint32_t wrote = 0;
+ nsresult rv = mSocketOut->Write(buffer, toWrite, &wrote);
+ if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
+ mSocketOut->AsyncWait(this, 0, 0, nullptr);
+ return NS_OK;
+ }
+
+ if (NS_FAILED(rv)) {
+ LOG(("WebrtcTCPSocket::OnOutputStreamReady %p failed %u\n", this,
+ static_cast<uint32_t>(rv)));
+ CloseWithReason(rv);
+ return NS_OK;
+ }
+
+ mWriteOffset += wrote;
+
+ if (toWrite == wrote) {
+ mWriteOffset = 0;
+ mWriteQueue.pop_front();
+ }
+ }
+
+ return NS_OK;
+}
+
+// nsIInterfaceRequestor
+NS_IMETHODIMP
+WebrtcTCPSocket::GetInterface(const nsIID& iid, void** result) {
+ LOG(("WebrtcTCPSocket::GetInterface %p\n", this));
+
+ return QueryInterface(iid, result);
+}
+
+size_t WebrtcTCPSocket::CountUnwrittenBytes() const {
+ size_t count = 0;
+
+ for (const WebrtcTCPData& data : mWriteQueue) {
+ count += data.GetData().Length();
+ }
+
+ MOZ_ASSERT(count >= mWriteOffset, "offset exceeds write buffer length");
+
+ count -= mWriteOffset;
+
+ return count;
+}
+
+} // namespace mozilla::net
diff --git a/dom/media/webrtc/transport/ipc/WebrtcTCPSocket.h b/dom/media/webrtc/transport/ipc/WebrtcTCPSocket.h
new file mode 100644
index 0000000000..632ba47d32
--- /dev/null
+++ b/dom/media/webrtc/transport/ipc/WebrtcTCPSocket.h
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef webrtc_tcp_socket_h__
+#define webrtc_tcp_socket_h__
+
+#include <list>
+
+#include "nsCOMPtr.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIAuthPromptProvider.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIStreamListener.h"
+#include "nsStringFwd.h"
+#include "nsTArray.h"
+#include "nsIProtocolProxyCallback.h"
+#include "mozilla/net/WebrtcProxyConfig.h"
+
+class nsISocketTransport;
+
+namespace mozilla::net {
+
+class WebrtcTCPSocketCallback;
+class WebrtcTCPData;
+
+class WebrtcTCPSocket : public nsIHttpUpgradeListener,
+ public nsIStreamListener,
+ public nsIInputStreamCallback,
+ public nsIOutputStreamCallback,
+ public nsIInterfaceRequestor,
+ public nsIAuthPromptProvider,
+ public nsIProtocolProxyCallback {
+ public:
+ NS_DECL_NSIHTTPUPGRADELISTENER
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+ NS_DECL_NSIINTERFACEREQUESTOR
+ NS_DECL_NSIOUTPUTSTREAMCALLBACK
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_FORWARD_SAFE_NSIAUTHPROMPTPROVIDER(mAuthProvider)
+ NS_DECL_NSIPROTOCOLPROXYCALLBACK
+
+ explicit WebrtcTCPSocket(WebrtcTCPSocketCallback* aCallbacks);
+
+ void SetTabId(dom::TabId aTabId);
+ nsresult Open(const nsACString& aHost, const int& aPort,
+ const nsACString& aLocalAddress, const int& aLocalPort,
+ bool aUseTls,
+ const Maybe<net::WebrtcProxyConfig>& aProxyConfig);
+ nsresult Write(nsTArray<uint8_t>&& aBytes);
+ nsresult Close();
+
+ size_t CountUnwrittenBytes() const;
+
+ protected:
+ virtual ~WebrtcTCPSocket();
+
+ // protected for gtests
+ virtual void InvokeOnClose(nsresult aReason);
+ virtual void InvokeOnConnected();
+ virtual void InvokeOnRead(nsTArray<uint8_t>&& aReadData);
+
+ RefPtr<WebrtcTCPSocketCallback> mProxyCallbacks;
+
+ private:
+ bool mClosed;
+ bool mOpened;
+ nsCOMPtr<nsIURI> mURI;
+ bool mTls = false;
+ Maybe<WebrtcProxyConfig> mProxyConfig;
+ nsCString mLocalAddress;
+ uint16_t mLocalPort = 0;
+ nsCString mProxyType;
+
+ nsresult DoProxyConfigLookup();
+ nsresult OpenWithHttpProxy();
+ void OpenWithoutHttpProxy(nsIProxyInfo* aSocksProxyInfo);
+ void FinishOpen();
+ void EnqueueWrite_s(nsTArray<uint8_t>&& aWriteData);
+
+ void CloseWithReason(nsresult aReason);
+
+ size_t mWriteOffset;
+ std::list<WebrtcTCPData> mWriteQueue;
+ nsCOMPtr<nsIAuthPromptProvider> mAuthProvider;
+
+ // Indicates that the channel is CONNECTed
+ nsCOMPtr<nsISocketTransport> mTransport;
+ nsCOMPtr<nsIAsyncInputStream> mSocketIn;
+ nsCOMPtr<nsIAsyncOutputStream> mSocketOut;
+ nsCOMPtr<nsIEventTarget> mMainThread;
+ nsCOMPtr<nsIEventTarget> mSocketThread;
+ nsCOMPtr<nsICancelable> mProxyRequest;
+};
+
+} // namespace mozilla::net
+
+#endif // webrtc_tcp_socket_h__
diff --git a/dom/media/webrtc/transport/ipc/WebrtcTCPSocketCallback.h b/dom/media/webrtc/transport/ipc/WebrtcTCPSocketCallback.h
new file mode 100644
index 0000000000..1929e55ac2
--- /dev/null
+++ b/dom/media/webrtc/transport/ipc/WebrtcTCPSocketCallback.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef webrtc_tcp_socket_callback_h__
+#define webrtc_tcp_socket_callback_h__
+
+#include "nsTArray.h"
+
+namespace mozilla::net {
+
+class WebrtcTCPSocketCallback {
+ public:
+ NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
+
+ virtual void OnClose(nsresult aReason) = 0;
+ virtual void OnConnected(const nsACString& aProxyType) = 0;
+ virtual void OnRead(nsTArray<uint8_t>&& aReadData) = 0;
+
+ protected:
+ virtual ~WebrtcTCPSocketCallback() = default;
+};
+
+} // namespace mozilla::net
+
+#endif // webrtc_tcp_socket_callback_h__
diff --git a/dom/media/webrtc/transport/ipc/WebrtcTCPSocketChild.cpp b/dom/media/webrtc/transport/ipc/WebrtcTCPSocketChild.cpp
new file mode 100644
index 0000000000..52d1ba8ab2
--- /dev/null
+++ b/dom/media/webrtc/transport/ipc/WebrtcTCPSocketChild.cpp
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebrtcTCPSocketChild.h"
+
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/net/SocketProcessChild.h"
+
+#include "LoadInfo.h"
+
+#include "WebrtcTCPSocketLog.h"
+#include "WebrtcTCPSocketCallback.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla::net {
+
+mozilla::ipc::IPCResult WebrtcTCPSocketChild::RecvOnClose(
+ const nsresult& aReason) {
+ LOG(("WebrtcTCPSocketChild::RecvOnClose %p\n", this));
+
+ MOZ_ASSERT(mProxyCallbacks, "webrtc TCP callbacks should be non-null");
+ mProxyCallbacks->OnClose(aReason);
+ mProxyCallbacks = nullptr;
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebrtcTCPSocketChild::RecvOnConnected(
+ const nsACString& aProxyType) {
+ LOG(("WebrtcTCPSocketChild::RecvOnConnected %p\n", this));
+
+ MOZ_ASSERT(mProxyCallbacks, "webrtc TCP callbacks should be non-null");
+ mProxyCallbacks->OnConnected(aProxyType);
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebrtcTCPSocketChild::RecvOnRead(
+ nsTArray<uint8_t>&& aReadData) {
+ LOG(("WebrtcTCPSocketChild::RecvOnRead %p\n", this));
+
+ MOZ_ASSERT(mProxyCallbacks, "webrtc TCP callbacks should be non-null");
+ mProxyCallbacks->OnRead(std::move(aReadData));
+
+ return IPC_OK();
+}
+
+WebrtcTCPSocketChild::WebrtcTCPSocketChild(
+ WebrtcTCPSocketCallback* aProxyCallbacks)
+ : mProxyCallbacks(aProxyCallbacks) {
+ MOZ_COUNT_CTOR(WebrtcTCPSocketChild);
+
+ LOG(("WebrtcTCPSocketChild::WebrtcTCPSocketChild %p\n", this));
+}
+
+WebrtcTCPSocketChild::~WebrtcTCPSocketChild() {
+ MOZ_COUNT_DTOR(WebrtcTCPSocketChild);
+
+ LOG(("WebrtcTCPSocketChild::~WebrtcTCPSocketChild %p\n", this));
+}
+
+void WebrtcTCPSocketChild::AsyncOpen(
+ const nsACString& aHost, const int& aPort, const nsACString& aLocalAddress,
+ const int& aLocalPort, bool aUseTls,
+ const std::shared_ptr<NrSocketProxyConfig>& aProxyConfig) {
+ LOG(("WebrtcTCPSocketChild::AsyncOpen %p %s:%d\n", this,
+ PromiseFlatCString(aHost).get(), aPort));
+
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ AddIPDLReference();
+
+ Maybe<net::WebrtcProxyConfig> proxyConfig;
+ Maybe<dom::TabId> tabId;
+ if (aProxyConfig) {
+ proxyConfig = Some(aProxyConfig->GetConfig());
+ tabId = Some(proxyConfig->tabId());
+ }
+
+ if (IsNeckoChild()) {
+ // We're on a content process
+ gNeckoChild->SendPWebrtcTCPSocketConstructor(this, tabId);
+ } else if (IsSocketProcessChild()) {
+ // We're on a socket process
+ SocketProcessChild::GetSingleton()->SendPWebrtcTCPSocketConstructor(this,
+ tabId);
+ }
+
+ SendAsyncOpen(aHost, aPort, aLocalAddress, aLocalPort, aUseTls, proxyConfig);
+}
+
+} // namespace mozilla::net
diff --git a/dom/media/webrtc/transport/ipc/WebrtcTCPSocketChild.h b/dom/media/webrtc/transport/ipc/WebrtcTCPSocketChild.h
new file mode 100644
index 0000000000..638ffcaac3
--- /dev/null
+++ b/dom/media/webrtc/transport/ipc/WebrtcTCPSocketChild.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_WebrtcTCPSocketChild_h
+#define mozilla_net_WebrtcTCPSocketChild_h
+
+#include "mozilla/net/PWebrtcTCPSocketChild.h"
+#include "mozilla/dom/ipc/IdType.h"
+#include "transport/nr_socket_proxy_config.h"
+
+namespace mozilla::net {
+
+class WebrtcTCPSocketCallback;
+
+class WebrtcTCPSocketChild : public PWebrtcTCPSocketChild {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebrtcTCPSocketChild)
+
+ mozilla::ipc::IPCResult RecvOnClose(const nsresult& aReason) override;
+
+ mozilla::ipc::IPCResult RecvOnConnected(
+ const nsACString& aProxyType) override;
+
+ mozilla::ipc::IPCResult RecvOnRead(nsTArray<uint8_t>&& aReadData) override;
+
+ explicit WebrtcTCPSocketChild(WebrtcTCPSocketCallback* aProxyCallbacks);
+
+ void AsyncOpen(const nsACString& aHost, const int& aPort,
+ const nsACString& aLocalAddress, const int& aLocalPort,
+ bool aUseTls,
+ const std::shared_ptr<NrSocketProxyConfig>& aProxyConfig);
+
+ void AddIPDLReference() { AddRef(); }
+ void ReleaseIPDLReference() { Release(); }
+
+ protected:
+ virtual ~WebrtcTCPSocketChild();
+
+ RefPtr<WebrtcTCPSocketCallback> mProxyCallbacks;
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_WebrtcTCPSocketChild_h
diff --git a/dom/media/webrtc/transport/ipc/WebrtcTCPSocketLog.cpp b/dom/media/webrtc/transport/ipc/WebrtcTCPSocketLog.cpp
new file mode 100644
index 0000000000..a6b0dcdb75
--- /dev/null
+++ b/dom/media/webrtc/transport/ipc/WebrtcTCPSocketLog.cpp
@@ -0,0 +1,11 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebrtcTCPSocketLog.h"
+
+namespace mozilla::net {
+LazyLogModule webrtcTCPSocketLog("WebrtcTCPSocket");
+} // namespace mozilla::net
diff --git a/dom/media/webrtc/transport/ipc/WebrtcTCPSocketLog.h b/dom/media/webrtc/transport/ipc/WebrtcTCPSocketLog.h
new file mode 100644
index 0000000000..72d3d0064b
--- /dev/null
+++ b/dom/media/webrtc/transport/ipc/WebrtcTCPSocketLog.h
@@ -0,0 +1,20 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef webrtc_tcp_socket_log_h__
+#define webrtc_tcp_socket_log_h__
+
+#include "mozilla/Logging.h"
+
+namespace mozilla::net {
+extern LazyLogModule webrtcTCPSocketLog;
+} // namespace mozilla::net
+
+#undef LOG
+#define LOG(args) \
+ MOZ_LOG(mozilla::net::webrtcTCPSocketLog, mozilla::LogLevel::Debug, args)
+
+#endif // webrtc_tcp_socket_log_h__
diff --git a/dom/media/webrtc/transport/ipc/WebrtcTCPSocketParent.cpp b/dom/media/webrtc/transport/ipc/WebrtcTCPSocketParent.cpp
new file mode 100644
index 0000000000..0df8962757
--- /dev/null
+++ b/dom/media/webrtc/transport/ipc/WebrtcTCPSocketParent.cpp
@@ -0,0 +1,122 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebrtcTCPSocketParent.h"
+
+#include "mozilla/net/NeckoParent.h"
+
+#include "WebrtcTCPSocket.h"
+#include "WebrtcTCPSocketLog.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::ipc;
+
+namespace mozilla::net {
+
+mozilla::ipc::IPCResult WebrtcTCPSocketParent::RecvAsyncOpen(
+ const nsACString& aHost, const int& aPort, const nsACString& aLocalAddress,
+ const int& aLocalPort, const bool& aUseTls,
+ const Maybe<WebrtcProxyConfig>& aProxyConfig) {
+ LOG(("WebrtcTCPSocketParent::RecvAsyncOpen %p to %s:%d\n", this,
+ PromiseFlatCString(aHost).get(), aPort));
+
+ MOZ_ASSERT(mChannel, "webrtc TCP socket should be non-null");
+ if (!mChannel) {
+ return IPC_FAIL(this, "Called with null channel.");
+ }
+
+ mChannel->Open(aHost, aPort, aLocalAddress, aLocalPort, aUseTls,
+ aProxyConfig);
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebrtcTCPSocketParent::RecvWrite(
+ nsTArray<uint8_t>&& aWriteData) {
+ LOG(("WebrtcTCPSocketParent::RecvWrite %p for %zu\n", this,
+ aWriteData.Length()));
+
+ // Need to check this here in case there are Writes in the queue after OnClose
+ if (mChannel) {
+ mChannel->Write(std::move(aWriteData));
+ }
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult WebrtcTCPSocketParent::RecvClose() {
+ LOG(("WebrtcTCPSocketParent::RecvClose %p\n", this));
+
+ CleanupChannel();
+
+ IProtocol* mgr = Manager();
+ if (!Send__delete__(this)) {
+ return IPC_FAIL_NO_REASON(mgr);
+ }
+
+ return IPC_OK();
+}
+
+void WebrtcTCPSocketParent::ActorDestroy(ActorDestroyReason aWhy) {
+ LOG(("WebrtcTCPSocketParent::ActorDestroy %p for %d\n", this, aWhy));
+
+ CleanupChannel();
+}
+
+WebrtcTCPSocketParent::WebrtcTCPSocketParent(const Maybe<dom::TabId>& aTabId) {
+ MOZ_COUNT_CTOR(WebrtcTCPSocketParent);
+
+ LOG(("WebrtcTCPSocketParent::WebrtcTCPSocketParent %p\n", this));
+
+ mChannel = new WebrtcTCPSocket(this);
+ if (aTabId.isSome()) {
+ mChannel->SetTabId(*aTabId);
+ }
+}
+
+WebrtcTCPSocketParent::~WebrtcTCPSocketParent() {
+ MOZ_COUNT_DTOR(WebrtcTCPSocketParent);
+
+ LOG(("WebrtcTCPSocketParent::~WebrtcTCPSocketParent %p\n", this));
+
+ CleanupChannel();
+}
+
+// WebrtcTCPSocketCallback
+void WebrtcTCPSocketParent::OnClose(nsresult aReason) {
+ LOG(("WebrtcTCPSocketParent::OnClose %p\n", this));
+
+ if (mChannel) {
+ Unused << SendOnClose(aReason);
+ }
+
+ CleanupChannel();
+}
+
+void WebrtcTCPSocketParent::OnRead(nsTArray<uint8_t>&& aReadData) {
+ LOG(("WebrtcTCPSocketParent::OnRead %p %zu\n", this, aReadData.Length()));
+
+ if (mChannel && !SendOnRead(std::move(aReadData))) {
+ CleanupChannel();
+ }
+}
+
+void WebrtcTCPSocketParent::OnConnected(const nsACString& aProxyType) {
+ LOG(("WebrtcTCPSocketParent::OnConnected %p\n", this));
+
+ if (mChannel && !SendOnConnected(aProxyType)) {
+ CleanupChannel();
+ }
+}
+
+void WebrtcTCPSocketParent::CleanupChannel() {
+ if (mChannel) {
+ mChannel->Close();
+ mChannel = nullptr;
+ }
+}
+
+} // namespace mozilla::net
diff --git a/dom/media/webrtc/transport/ipc/WebrtcTCPSocketParent.h b/dom/media/webrtc/transport/ipc/WebrtcTCPSocketParent.h
new file mode 100644
index 0000000000..df4462609d
--- /dev/null
+++ b/dom/media/webrtc/transport/ipc/WebrtcTCPSocketParent.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_WebrtcTCPSocketParent_h
+#define mozilla_net_WebrtcTCPSocketParent_h
+
+#include "mozilla/net/PWebrtcTCPSocketParent.h"
+
+#include "WebrtcTCPSocketCallback.h"
+
+class nsIAuthPromptProvider;
+
+namespace mozilla::net {
+
+class WebrtcTCPSocket;
+
+class WebrtcTCPSocketParent : public PWebrtcTCPSocketParent,
+ public WebrtcTCPSocketCallback {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebrtcTCPSocketParent, override)
+
+ mozilla::ipc::IPCResult RecvAsyncOpen(
+ const nsACString& aHost, const int& aPort,
+ const nsACString& aLocalAddress, const int& aLocalPort,
+ const bool& aUseTls,
+ const Maybe<WebrtcProxyConfig>& aProxyConfig) override;
+
+ mozilla::ipc::IPCResult RecvWrite(nsTArray<uint8_t>&& aWriteData) override;
+
+ mozilla::ipc::IPCResult RecvClose() override;
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ explicit WebrtcTCPSocketParent(const Maybe<dom::TabId>& aTabId);
+
+ // WebrtcTCPSocketCallback
+ void OnClose(nsresult aReason) override;
+ void OnConnected(const nsACString& aProxyType) override;
+ void OnRead(nsTArray<uint8_t>&& bytes) override;
+
+ void AddIPDLReference() { AddRef(); }
+ void ReleaseIPDLReference() { Release(); }
+
+ protected:
+ virtual ~WebrtcTCPSocketParent();
+
+ private:
+ void CleanupChannel();
+
+ // Indicates that IPC is open.
+ RefPtr<WebrtcTCPSocket> mChannel;
+};
+
+} // namespace mozilla::net
+
+#endif // mozilla_net_WebrtcTCPSocketParent_h
diff --git a/dom/media/webrtc/transport/ipc/moz.build b/dom/media/webrtc/transport/ipc/moz.build
new file mode 100644
index 0000000000..a2a72bb624
--- /dev/null
+++ b/dom/media/webrtc/transport/ipc/moz.build
@@ -0,0 +1,54 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS.mozilla.net += [
+ "NrIceStunAddrMessageUtils.h",
+ "PStunAddrsParams.h",
+ "StunAddrsRequestChild.h",
+ "StunAddrsRequestParent.h",
+ "WebrtcTCPSocket.h",
+ "WebrtcTCPSocketCallback.h",
+ "WebrtcTCPSocketChild.h",
+ "WebrtcTCPSocketParent.h",
+]
+
+UNIFIED_SOURCES += [
+ "StunAddrsRequestChild.cpp",
+ "StunAddrsRequestParent.cpp",
+ "WebrtcTCPSocket.cpp",
+ "WebrtcTCPSocketChild.cpp",
+ "WebrtcTCPSocketLog.cpp",
+ "WebrtcTCPSocketParent.cpp",
+]
+
+IPDL_SOURCES += [
+ "PStunAddrsRequest.ipdl",
+ "PWebrtcTCPSocket.ipdl",
+ "WebrtcProxyConfig.ipdlh",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+DEFINES["R_DEFINED_INT2"] = "int16_t"
+DEFINES["R_DEFINED_UINT2"] = "uint16_t"
+DEFINES["R_DEFINED_INT4"] = "int32_t"
+DEFINES["R_DEFINED_UINT4"] = "uint32_t"
+# These are defined to avoid a conflict between typedefs in winsock2.h and
+# r_types.h. This is safe because these types are unused by the code here,
+# but still deeply unfortunate. There is similar code in the win32 version of
+# csi_platform.h, but that trick does not work here, even if that file is
+# directly included.
+DEFINES["R_DEFINED_INT8"] = "int8_t"
+DEFINES["R_DEFINED_UINT8"] = "uint8_t"
+
+LOCAL_INCLUDES += [
+ "/dom/media/webrtc/jsapi",
+ "/dom/media/webrtc/transport/third_party/nICEr/src/net",
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr",
+ "/media/webrtc",
+ "/netwerk/base",
+ "/netwerk/protocol/http",
+]
diff --git a/dom/media/webrtc/transport/logging.h b/dom/media/webrtc/transport/logging.h
new file mode 100644
index 0000000000..98cbd13819
--- /dev/null
+++ b/dom/media/webrtc/transport/logging.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+
+#ifndef logging_h__
+#define logging_h__
+
+#include <sstream>
+#include "mozilla/Logging.h"
+
+#ifdef MOZILLA_INTERNAL_API
+
+# define ML_ERROR mozilla::LogLevel::Error
+# define ML_WARNING mozilla::LogLevel::Warning
+# define ML_NOTICE mozilla::LogLevel::Info
+# define ML_INFO mozilla::LogLevel::Debug
+# define ML_DEBUG mozilla::LogLevel::Verbose
+
+# define MOZ_MTLOG_MODULE(n) \
+ static mozilla::LogModule* getLogModule() { \
+ static mozilla::LazyLogModule log(n); \
+ return static_cast<mozilla::LogModule*>(log); \
+ }
+
+# define MOZ_MTLOG(level, b) \
+ do { \
+ if (MOZ_LOG_TEST(getLogModule(), level)) { \
+ std::stringstream str; \
+ str << b; \
+ MOZ_LOG(getLogModule(), level, ("%s", str.str().c_str())); \
+ } \
+ } while (0)
+#else
+// When building mtransport outside of XUL, for example in stand-alone gtests,
+// PR_Logging needs to be used instead of mozilla logging.
+
+# include "prlog.h"
+
+# define ML_ERROR PR_LOG_ERROR
+# define ML_WARNING PR_LOG_WARNING
+# define ML_NOTICE PR_LOG_INFO
+# define ML_INFO PR_LOG_DEBUG
+# define ML_DEBUG PR_LOG_VERBOSE
+
+# define MOZ_MTLOG_MODULE(n) \
+ static PRLogModuleInfo* getLogModule() { \
+ static PRLogModuleInfo* log; \
+ if (!log) log = PR_NewLogModule(n); \
+ return log; \
+ }
+
+# define MOZ_MTLOG(level, b) \
+ do { \
+ if (PR_LOG_TEST(getLogModule(), level)) { \
+ std::stringstream str; \
+ str << b; \
+ PR_LOG(getLogModule(), level, ("%s", str.str().c_str())); \
+ } \
+ } while (0)
+#endif // MOZILLA_INTERNAL_API
+#endif // logging_h__
diff --git a/dom/media/webrtc/transport/m_cpp_utils.h b/dom/media/webrtc/transport/m_cpp_utils.h
new file mode 100644
index 0000000000..a1469f798a
--- /dev/null
+++ b/dom/media/webrtc/transport/m_cpp_utils.h
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+
+#ifndef m_cpp_utils_h__
+#define m_cpp_utils_h__
+
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+
+#define DISALLOW_ASSIGNMENT(T) void operator=(const T& other) = delete
+
+#define DISALLOW_COPY(T) T(const T& other) = delete
+
+#define DISALLOW_COPY_ASSIGN(T) \
+ DISALLOW_COPY(T); \
+ DISALLOW_ASSIGNMENT(T)
+
+} // namespace mozilla
+#endif
diff --git a/dom/media/webrtc/transport/mdns_service/Cargo.toml b/dom/media/webrtc/transport/mdns_service/Cargo.toml
new file mode 100644
index 0000000000..ec7e182d4b
--- /dev/null
+++ b/dom/media/webrtc/transport/mdns_service/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "mdns_service"
+version = "0.1.1"
+authors = ["Dan Minor <dminor@mozilla.com>"]
+edition = "2018"
+license = "MPL-2.0"
+
+[dependencies]
+byteorder = "1.3.1"
+dns-parser = "0.8.0"
+gecko-profiler = { path = "../../../../../tools/profiler/rust-api" }
+log = "0.4"
+socket2 = { version = "0.4", features = ["all"] }
+uuid = { version = "1.0", features = ["v4"] }
diff --git a/dom/media/webrtc/transport/mdns_service/mdns_service.h b/dom/media/webrtc/transport/mdns_service/mdns_service.h
new file mode 100644
index 0000000000..5e8c252ebc
--- /dev/null
+++ b/dom/media/webrtc/transport/mdns_service/mdns_service.h
@@ -0,0 +1,28 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <cstdarg>
+#include <cstdint>
+#include <cstdlib>
+#include <new>
+
+struct MDNSService;
+
+extern "C" {
+
+void mdns_service_register_hostname(MDNSService* serv, const char* hostname,
+ const char* addr);
+
+MDNSService* mdns_service_start(const char* ifaddr);
+
+void mdns_service_stop(MDNSService* serv);
+
+void mdns_service_query_hostname(
+ MDNSService* serv, void* data,
+ void (*resolved)(void* data, const char* hostname, const char* address),
+ void (*timedout)(void* data, const char* hostname), const char* hostname);
+
+void mdns_service_unregister_hostname(MDNSService* serv, const char* hostname);
+
+} // extern "C"
diff --git a/dom/media/webrtc/transport/mdns_service/src/lib.rs b/dom/media/webrtc/transport/mdns_service/src/lib.rs
new file mode 100644
index 0000000000..fbd07b45f8
--- /dev/null
+++ b/dom/media/webrtc/transport/mdns_service/src/lib.rs
@@ -0,0 +1,843 @@
+/* -*- Mode: rust; rust-indent-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+use byteorder::{BigEndian, WriteBytesExt};
+use socket2::{Domain, Socket, Type};
+use std::collections::HashMap;
+use std::collections::LinkedList;
+use std::ffi::{c_void, CStr, CString};
+use std::io;
+use std::net;
+use std::os::raw::c_char;
+use std::sync::mpsc::channel;
+use std::thread;
+use std::time;
+use uuid::Uuid;
+
+#[macro_use]
+extern crate log;
+
+struct Callback {
+ data: *const c_void,
+ resolved: unsafe extern "C" fn(*const c_void, *const c_char, *const c_char),
+ timedout: unsafe extern "C" fn(*const c_void, *const c_char),
+}
+
+unsafe impl Send for Callback {}
+
+fn hostname_resolved(callback: &Callback, hostname: &str, addr: &str) {
+ if let Ok(hostname) = CString::new(hostname) {
+ if let Ok(addr) = CString::new(addr) {
+ unsafe {
+ (callback.resolved)(callback.data, hostname.as_ptr(), addr.as_ptr());
+ }
+ }
+ }
+}
+
+fn hostname_timedout(callback: &Callback, hostname: &str) {
+ if let Ok(hostname) = CString::new(hostname) {
+ unsafe {
+ (callback.timedout)(callback.data, hostname.as_ptr());
+ }
+ }
+}
+
+// This code is derived from code for creating questions in the dns-parser
+// crate. It would be nice to upstream this, or something similar.
+fn create_answer(id: u16, answers: &[(String, &[u8])]) -> Result<Vec<u8>, io::Error> {
+ let mut buf = Vec::with_capacity(512);
+ let head = dns_parser::Header {
+ id,
+ query: false,
+ opcode: dns_parser::Opcode::StandardQuery,
+ authoritative: true,
+ truncated: false,
+ recursion_desired: false,
+ recursion_available: false,
+ authenticated_data: false,
+ checking_disabled: false,
+ response_code: dns_parser::ResponseCode::NoError,
+ questions: 0,
+ answers: answers.len() as u16,
+ nameservers: 0,
+ additional: 0,
+ };
+
+ buf.extend([0u8; 12].iter());
+ head.write(&mut buf[..12]);
+
+ for (name, addr) in answers {
+ for part in name.split('.') {
+ if part.len() > 62 {
+ return Err(io::Error::new(
+ io::ErrorKind::Other,
+ "Name part length too long",
+ ));
+ }
+ let ln = part.len() as u8;
+ buf.push(ln);
+ buf.extend(part.as_bytes());
+ }
+ buf.push(0);
+
+ if addr.len() == 4 {
+ buf.write_u16::<BigEndian>(dns_parser::Type::A as u16)?;
+ } else {
+ buf.write_u16::<BigEndian>(dns_parser::Type::AAAA as u16)?;
+ }
+ // set cache flush bit
+ buf.write_u16::<BigEndian>(dns_parser::Class::IN as u16 | (0x1 << 15))?;
+ buf.write_u32::<BigEndian>(120)?;
+ buf.write_u16::<BigEndian>(addr.len() as u16)?;
+ buf.extend(*addr);
+ }
+
+ Ok(buf)
+}
+
+fn create_query(id: u16, queries: &[String]) -> Result<Vec<u8>, io::Error> {
+ let mut buf = Vec::with_capacity(512);
+ let head = dns_parser::Header {
+ id,
+ query: true,
+ opcode: dns_parser::Opcode::StandardQuery,
+ authoritative: false,
+ truncated: false,
+ recursion_desired: false,
+ recursion_available: false,
+ authenticated_data: false,
+ checking_disabled: false,
+ response_code: dns_parser::ResponseCode::NoError,
+ questions: queries.len() as u16,
+ answers: 0,
+ nameservers: 0,
+ additional: 0,
+ };
+
+ buf.extend([0u8; 12].iter());
+ head.write(&mut buf[..12]);
+
+ for name in queries {
+ for part in name.split('.') {
+ assert!(part.len() < 63);
+ let ln = part.len() as u8;
+ buf.push(ln);
+ buf.extend(part.as_bytes());
+ }
+ buf.push(0);
+
+ buf.write_u16::<BigEndian>(dns_parser::QueryType::A as u16)?;
+ buf.write_u16::<BigEndian>(dns_parser::QueryClass::IN as u16)?;
+ }
+
+ Ok(buf)
+}
+
+fn handle_queries(
+ socket: &std::net::UdpSocket,
+ mdns_addr: &std::net::SocketAddr,
+ pending_queries: &mut HashMap<String, Query>,
+ unsent_queries: &mut LinkedList<Query>,
+) {
+ if pending_queries.len() < 50 {
+ let mut queries: Vec<Query> = Vec::new();
+ while queries.len() < 5 && !unsent_queries.is_empty() {
+ if let Some(query) = unsent_queries.pop_front() {
+ if !pending_queries.contains_key(&query.hostname) {
+ queries.push(query);
+ }
+ }
+ }
+ if !queries.is_empty() {
+ let query_hostnames: Vec<String> =
+ queries.iter().map(|q| q.hostname.to_string()).collect();
+
+ if let Ok(buf) = create_query(0, &query_hostnames) {
+ match socket.send_to(&buf, &mdns_addr) {
+ Ok(_) => {
+ for query in queries {
+ pending_queries.insert(query.hostname.to_string(), query);
+ }
+ }
+ Err(err) => {
+ warn!("Sending mDNS query failed: {}", err);
+ if err.kind() != io::ErrorKind::PermissionDenied {
+ for query in queries {
+ unsent_queries.push_back(query);
+ }
+ } else {
+ for query in queries {
+ hostname_timedout(&query.callback, &query.hostname);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ let now = time::Instant::now();
+ let expired: Vec<String> = pending_queries
+ .iter()
+ .filter(|(_, query)| now.duration_since(query.timestamp).as_secs() >= 3)
+ .map(|(hostname, _)| hostname.to_string())
+ .collect();
+ for hostname in expired {
+ if let Some(mut query) = pending_queries.remove(&hostname) {
+ query.attempts += 1;
+ if query.attempts < 3 {
+ query.timestamp = now;
+ unsent_queries.push_back(query);
+ } else {
+ hostname_timedout(&query.callback, &hostname);
+ }
+ }
+ }
+}
+
+fn handle_mdns_socket(
+ socket: &std::net::UdpSocket,
+ mdns_addr: &std::net::SocketAddr,
+ mut buffer: &mut [u8],
+ hosts: &mut HashMap<String, Vec<u8>>,
+ pending_queries: &mut HashMap<String, Query>,
+) -> bool {
+ // Record a simple marker to see how often this is called.
+ gecko_profiler::add_untyped_marker(
+ "handle_mdns_socket",
+ gecko_profiler::gecko_profiler_category!(Network),
+ Default::default(),
+ );
+
+ match socket.recv_from(&mut buffer) {
+ Ok((amt, _)) => {
+ if amt > 0 {
+ let buffer = &buffer[0..amt];
+ match dns_parser::Packet::parse(&buffer) {
+ Ok(parsed) => {
+ let mut answers: Vec<(String, &[u8])> = Vec::new();
+
+ // If a packet contains both both questions and
+ // answers, the questions should be ignored.
+ if parsed.answers.is_empty() {
+ parsed
+ .questions
+ .iter()
+ .filter(|question| question.qtype == dns_parser::QueryType::A)
+ .for_each(|question| {
+ let qname = question.qname.to_string();
+ trace!("mDNS question: {} {:?}", qname, question.qtype);
+ if let Some(octets) = hosts.get(&qname) {
+ trace!("Sending mDNS answer for {}: {:?}", qname, octets);
+ answers.push((qname, &octets));
+ }
+ });
+ }
+ for answer in parsed.answers {
+ let hostname = answer.name.to_string();
+ match pending_queries.get(&hostname) {
+ Some(query) => {
+ match answer.data {
+ dns_parser::RData::A(dns_parser::rdata::a::Record(
+ addr,
+ )) => {
+ let addr = addr.to_string();
+ trace!("mDNS response: {} {}", hostname, addr);
+ hostname_resolved(&query.callback, &hostname, &addr);
+ }
+ dns_parser::RData::AAAA(
+ dns_parser::rdata::aaaa::Record(addr),
+ ) => {
+ let addr = addr.to_string();
+ trace!("mDNS response: {} {}", hostname, addr);
+ hostname_resolved(&query.callback, &hostname, &addr);
+ }
+ _ => {}
+ }
+ pending_queries.remove(&hostname);
+ }
+ None => {
+ continue;
+ }
+ }
+ }
+ // TODO: If we did not answer every query in this
+ // question, we should wait for a random amount of time
+ // so as to not collide with someone else responding to
+ // this query.
+ if !answers.is_empty() {
+ if let Ok(buf) = create_answer(parsed.header.id, &answers) {
+ if let Err(err) = socket.send_to(&buf, &mdns_addr) {
+ warn!("Sending mDNS answer failed: {}", err);
+ }
+ }
+ }
+ }
+ Err(err) => {
+ warn!("Could not parse mDNS packet: {}", err);
+ }
+ }
+ }
+ }
+ Err(err) => {
+ if err.kind() != io::ErrorKind::Interrupted
+ && err.kind() != io::ErrorKind::TimedOut
+ && err.kind() != io::ErrorKind::WouldBlock
+ {
+ error!("Socket error: {}", err);
+ return false;
+ }
+ }
+ }
+
+ true
+}
+
+fn validate_hostname(hostname: &str) -> bool {
+ match hostname.find(".local") {
+ Some(index) => match hostname.get(0..index) {
+ Some(uuid) => match uuid.get(0..36) {
+ Some(initial) => match Uuid::parse_str(initial) {
+ Ok(_) => {
+ // Oddly enough, Safari does not generate valid UUIDs,
+ // the last part sometimes contains more than 12 digits.
+ match uuid.get(36..) {
+ Some(trailing) => {
+ for c in trailing.chars() {
+ if !c.is_ascii_hexdigit() {
+ return false;
+ }
+ }
+ true
+ }
+ None => true,
+ }
+ }
+ Err(_) => false,
+ },
+ None => false,
+ },
+ None => false,
+ },
+ None => false,
+ }
+}
+
+enum ServiceControl {
+ Register {
+ hostname: String,
+ address: String,
+ },
+ Query {
+ callback: Callback,
+ hostname: String,
+ },
+ Unregister {
+ hostname: String,
+ },
+ Stop,
+}
+
+struct Query {
+ hostname: String,
+ callback: Callback,
+ timestamp: time::Instant,
+ attempts: i32,
+}
+
+impl Query {
+ fn new(hostname: &str, callback: Callback) -> Query {
+ Query {
+ hostname: hostname.to_string(),
+ callback,
+ timestamp: time::Instant::now(),
+ attempts: 0,
+ }
+ }
+}
+
+pub struct MDNSService {
+ handle: Option<std::thread::JoinHandle<()>>,
+ sender: Option<std::sync::mpsc::Sender<ServiceControl>>,
+}
+
+impl MDNSService {
+ fn register_hostname(&mut self, hostname: &str, address: &str) {
+ if let Some(sender) = &self.sender {
+ if let Err(err) = sender.send(ServiceControl::Register {
+ hostname: hostname.to_string(),
+ address: address.to_string(),
+ }) {
+ warn!(
+ "Could not send register hostname {} message: {}",
+ hostname, err
+ );
+ }
+ }
+ }
+
+ fn query_hostname(&mut self, callback: Callback, hostname: &str) {
+ if let Some(sender) = &self.sender {
+ if let Err(err) = sender.send(ServiceControl::Query {
+ callback,
+ hostname: hostname.to_string(),
+ }) {
+ warn!(
+ "Could not send query hostname {} message: {}",
+ hostname, err
+ );
+ }
+ }
+ }
+
+ fn unregister_hostname(&mut self, hostname: &str) {
+ if let Some(sender) = &self.sender {
+ if let Err(err) = sender.send(ServiceControl::Unregister {
+ hostname: hostname.to_string(),
+ }) {
+ warn!(
+ "Could not send unregister hostname {} message: {}",
+ hostname, err
+ );
+ }
+ }
+ }
+
+ fn start(&mut self, addrs: Vec<std::net::Ipv4Addr>) -> io::Result<()> {
+ let (sender, receiver) = channel();
+ self.sender = Some(sender);
+
+ let mdns_addr = std::net::Ipv4Addr::new(224, 0, 0, 251);
+ let port = 5353;
+
+ let socket = Socket::new(Domain::IPV4, Type::DGRAM, None)?;
+ socket.set_reuse_address(true)?;
+
+ #[cfg(not(target_os = "windows"))]
+ socket.set_reuse_port(true)?;
+ socket.bind(&socket2::SockAddr::from(std::net::SocketAddr::from((
+ [0, 0, 0, 0],
+ port,
+ ))))?;
+
+ let socket = std::net::UdpSocket::from(socket);
+ socket.set_multicast_loop_v4(true)?;
+ socket.set_read_timeout(Some(time::Duration::from_millis(1)))?;
+ socket.set_write_timeout(Some(time::Duration::from_millis(1)))?;
+ for addr in addrs {
+ if let Err(err) = socket.join_multicast_v4(&mdns_addr, &addr) {
+ warn!(
+ "Could not join multicast group on interface: {:?}: {}",
+ addr, err
+ );
+ }
+ }
+
+ let thread_name = "mdns_service";
+ let builder = thread::Builder::new().name(thread_name.into());
+ self.handle = Some(builder.spawn(move || {
+ gecko_profiler::register_thread(thread_name);
+ let mdns_addr = std::net::SocketAddr::from(([224, 0, 0, 251], port));
+ let mut buffer: [u8; 9_000] = [0; 9_000];
+ let mut hosts = HashMap::new();
+ let mut unsent_queries = LinkedList::new();
+ let mut pending_queries = HashMap::new();
+ loop {
+ match receiver.try_recv() {
+ Ok(msg) => match msg {
+ ServiceControl::Register { hostname, address } => {
+ if !validate_hostname(&hostname) {
+ warn!("Not registering invalid hostname: {}", hostname);
+ continue;
+ }
+ trace!("Registering {} for: {}", hostname, address);
+ match address.parse().and_then(|ip| {
+ Ok(match ip {
+ net::IpAddr::V4(ip) => ip.octets().to_vec(),
+ net::IpAddr::V6(ip) => ip.octets().to_vec(),
+ })
+ }) {
+ Ok(octets) => {
+ let mut v = Vec::new();
+ v.extend(octets);
+ hosts.insert(hostname, v);
+ }
+ Err(err) => {
+ warn!(
+ "Could not parse address for {}: {}: {}",
+ hostname, address, err
+ );
+ }
+ }
+ }
+ ServiceControl::Query { callback, hostname } => {
+ trace!("Querying {}", hostname);
+ if !validate_hostname(&hostname) {
+ warn!("Not sending mDNS query for invalid hostname: {}", hostname);
+ continue;
+ }
+ unsent_queries.push_back(Query::new(&hostname, callback));
+ }
+ ServiceControl::Unregister { hostname } => {
+ trace!("Unregistering {}", hostname);
+ hosts.remove(&hostname);
+ }
+ ServiceControl::Stop => {
+ trace!("Stopping");
+ break;
+ }
+ },
+ Err(std::sync::mpsc::TryRecvError::Disconnected) => {
+ break;
+ }
+ Err(std::sync::mpsc::TryRecvError::Empty) => {}
+ }
+
+ handle_queries(
+ &socket,
+ &mdns_addr,
+ &mut pending_queries,
+ &mut unsent_queries,
+ );
+
+ if !handle_mdns_socket(
+ &socket,
+ &mdns_addr,
+ &mut buffer,
+ &mut hosts,
+ &mut pending_queries,
+ ) {
+ break;
+ }
+ }
+ gecko_profiler::unregister_thread();
+ })?);
+
+ Ok(())
+ }
+
+ fn stop(self) {
+ if let Some(sender) = self.sender {
+ if let Err(err) = sender.send(ServiceControl::Stop) {
+ warn!("Could not stop mDNS Service: {}", err);
+ }
+ if let Some(handle) = self.handle {
+ if handle.join().is_err() {
+ error!("Error on thread join");
+ }
+ }
+ }
+ }
+
+ fn new() -> MDNSService {
+ MDNSService {
+ handle: None,
+ sender: None,
+ }
+ }
+}
+
+/// # Safety
+///
+/// This function must only be called with a valid MDNSService pointer.
+/// This hostname and address arguments must be zero terminated strings.
+#[no_mangle]
+pub unsafe extern "C" fn mdns_service_register_hostname(
+ serv: *mut MDNSService,
+ hostname: *const c_char,
+ address: *const c_char,
+) {
+ assert!(!serv.is_null());
+ assert!(!hostname.is_null());
+ assert!(!address.is_null());
+ let hostname = CStr::from_ptr(hostname).to_string_lossy();
+ let address = CStr::from_ptr(address).to_string_lossy();
+ (*serv).register_hostname(&hostname, &address);
+}
+
+/// # Safety
+///
+/// This ifaddrs argument must be a zero terminated string.
+#[no_mangle]
+pub unsafe extern "C" fn mdns_service_start(ifaddrs: *const c_char) -> *mut MDNSService {
+ assert!(!ifaddrs.is_null());
+ let mut r = Box::new(MDNSService::new());
+ let ifaddrs = CStr::from_ptr(ifaddrs).to_string_lossy();
+ let addrs: Vec<std::net::Ipv4Addr> =
+ ifaddrs.split(';').filter_map(|x| x.parse().ok()).collect();
+
+ if addrs.is_empty() {
+ warn!("Could not parse interface addresses from: {}", ifaddrs);
+ } else if let Err(err) = r.start(addrs) {
+ warn!("Could not start mDNS Service: {}", err);
+ }
+
+ Box::into_raw(r)
+}
+
+/// # Safety
+///
+/// This function must only be called with a valid MDNSService pointer.
+#[no_mangle]
+pub unsafe extern "C" fn mdns_service_stop(serv: *mut MDNSService) {
+ assert!(!serv.is_null());
+ let boxed = Box::from_raw(serv);
+ boxed.stop();
+}
+
+/// # Safety
+///
+/// This function must only be called with a valid MDNSService pointer.
+/// The data argument will be passed back into the resolved and timedout
+/// functions. The object it points to must not be freed until the MDNSService
+/// has stopped.
+#[no_mangle]
+pub unsafe extern "C" fn mdns_service_query_hostname(
+ serv: *mut MDNSService,
+ data: *const c_void,
+ resolved: unsafe extern "C" fn(*const c_void, *const c_char, *const c_char),
+ timedout: unsafe extern "C" fn(*const c_void, *const c_char),
+ hostname: *const c_char,
+) {
+ assert!(!serv.is_null());
+ assert!(!data.is_null());
+ assert!(!hostname.is_null());
+ let hostname = CStr::from_ptr(hostname).to_string_lossy();
+ let callback = Callback {
+ data,
+ resolved,
+ timedout,
+ };
+ (*serv).query_hostname(callback, &hostname);
+}
+
+/// # Safety
+///
+/// This function must only be called with a valid MDNSService pointer.
+/// This function should only be called once per hostname.
+#[no_mangle]
+pub unsafe extern "C" fn mdns_service_unregister_hostname(
+ serv: *mut MDNSService,
+ hostname: *const c_char,
+) {
+ assert!(!serv.is_null());
+ assert!(!hostname.is_null());
+ let hostname = CStr::from_ptr(hostname).to_string_lossy();
+ (*serv).unregister_hostname(&hostname);
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::create_query;
+ use crate::validate_hostname;
+ use crate::Callback;
+ use crate::MDNSService;
+ use socket2::{Domain, Socket, Type};
+ use std::collections::HashSet;
+ use std::ffi::c_void;
+ use std::io;
+ use std::iter::FromIterator;
+ use std::os::raw::c_char;
+ use std::thread;
+ use std::time;
+ use uuid::Uuid;
+
+ #[no_mangle]
+ pub unsafe extern "C" fn mdns_service_resolved(
+ _: *const c_void,
+ _: *const c_char,
+ _: *const c_char,
+ ) -> () {
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn mdns_service_timedout(_: *const c_void, _: *const c_char) -> () {}
+
+ fn listen_until(addr: &std::net::Ipv4Addr, stop: u64) -> thread::JoinHandle<Vec<String>> {
+ let port = 5353;
+
+ let socket = Socket::new(Domain::IPV4, Type::DGRAM, None).unwrap();
+ socket.set_reuse_address(true).unwrap();
+
+ #[cfg(not(target_os = "windows"))]
+ socket.set_reuse_port(true).unwrap();
+ socket
+ .bind(&socket2::SockAddr::from(std::net::SocketAddr::from((
+ [0, 0, 0, 0],
+ port,
+ ))))
+ .unwrap();
+
+ let socket = std::net::UdpSocket::from(socket);
+ socket.set_multicast_loop_v4(true).unwrap();
+ socket
+ .set_read_timeout(Some(time::Duration::from_millis(10)))
+ .unwrap();
+ socket
+ .set_write_timeout(Some(time::Duration::from_millis(10)))
+ .unwrap();
+ socket
+ .join_multicast_v4(&std::net::Ipv4Addr::new(224, 0, 0, 251), &addr)
+ .unwrap();
+
+ let mut buffer: [u8; 9_000] = [0; 9_000];
+ thread::spawn(move || {
+ let start = time::Instant::now();
+ let mut questions = Vec::new();
+ while time::Instant::now().duration_since(start).as_secs() < stop {
+ match socket.recv_from(&mut buffer) {
+ Ok((amt, _)) => {
+ if amt > 0 {
+ let buffer = &buffer[0..amt];
+ match dns_parser::Packet::parse(&buffer) {
+ Ok(parsed) => {
+ parsed
+ .questions
+ .iter()
+ .filter(|question| {
+ question.qtype == dns_parser::QueryType::A
+ })
+ .for_each(|question| {
+ let qname = question.qname.to_string();
+ questions.push(qname);
+ });
+ }
+ Err(err) => {
+ warn!("Could not parse mDNS packet: {}", err);
+ }
+ }
+ }
+ }
+ Err(err) => {
+ if err.kind() != io::ErrorKind::WouldBlock
+ && err.kind() != io::ErrorKind::TimedOut
+ {
+ error!("Socket error: {}", err);
+ break;
+ }
+ }
+ }
+ }
+ questions
+ })
+ }
+
+ #[test]
+ fn test_validate_hostname() {
+ assert_eq!(
+ validate_hostname("e17f08d4-689a-4df6-ba31-35bb9f041100.local"),
+ true
+ );
+ assert_eq!(
+ validate_hostname("62240723-ae6d-4f6a-99b8-94a233e3f84a2.local"),
+ true
+ );
+ assert_eq!(
+ validate_hostname("62240723-ae6d-4f6a-99b8.94e3f84a2.local"),
+ false
+ );
+ assert_eq!(validate_hostname("hi there"), false);
+ }
+
+ #[test]
+ fn start_stop() {
+ let mut service = MDNSService::new();
+ let addr = "127.0.0.1".parse().unwrap();
+ service.start(vec![addr]).unwrap();
+ service.stop();
+ }
+
+ #[test]
+ fn simple_query() {
+ let mut service = MDNSService::new();
+ let addr = "127.0.0.1".parse().unwrap();
+ let handle = listen_until(&addr, 1);
+
+ service.start(vec![addr]).unwrap();
+
+ let callback = Callback {
+ data: 0 as *const c_void,
+ resolved: mdns_service_resolved,
+ timedout: mdns_service_timedout,
+ };
+ let hostname = Uuid::new_v4().as_hyphenated().to_string() + ".local";
+ service.query_hostname(callback, &hostname);
+ service.stop();
+ let questions = handle.join().unwrap();
+ assert!(questions.contains(&hostname));
+ }
+
+ #[test]
+ fn rate_limited_query() {
+ let mut service = MDNSService::new();
+ let addr = "127.0.0.1".parse().unwrap();
+ let handle = listen_until(&addr, 1);
+
+ service.start(vec![addr]).unwrap();
+
+ let mut hostnames = HashSet::new();
+ for _ in 0..100 {
+ let callback = Callback {
+ data: 0 as *const c_void,
+ resolved: mdns_service_resolved,
+ timedout: mdns_service_timedout,
+ };
+ let hostname = Uuid::new_v4().as_hyphenated().to_string() + ".local";
+ service.query_hostname(callback, &hostname);
+ hostnames.insert(hostname);
+ }
+ service.stop();
+ let questions = HashSet::from_iter(handle.join().unwrap().iter().map(|x| x.to_string()));
+ let intersection: HashSet<&String> = questions.intersection(&hostnames).collect();
+ assert_eq!(intersection.len(), 50);
+ }
+
+ #[test]
+ fn repeat_failed_query() {
+ let mut service = MDNSService::new();
+ let addr = "127.0.0.1".parse().unwrap();
+ let handle = listen_until(&addr, 4);
+
+ service.start(vec![addr]).unwrap();
+
+ let hostname = Uuid::new_v4().as_hyphenated().to_string() + ".local";
+ let callback = Callback {
+ data: 0 as *const c_void,
+ resolved: mdns_service_resolved,
+ timedout: mdns_service_timedout,
+ };
+ service.query_hostname(callback, &hostname);
+ thread::sleep(time::Duration::from_secs(4));
+ service.stop();
+
+ let questions: Vec<String> = handle
+ .join()
+ .unwrap()
+ .iter()
+ .filter(|x| *x == &hostname)
+ .map(|x| x.to_string())
+ .collect();
+ assert_eq!(questions.len(), 2);
+ }
+
+ #[test]
+ fn multiple_queries_in_a_single_packet() {
+ let mut hostnames: Vec<String> = Vec::new();
+ for _ in 0..100 {
+ let hostname = Uuid::new_v4().as_hyphenated().to_string() + ".local";
+ hostnames.push(hostname);
+ }
+
+ match create_query(42, &hostnames) {
+ Ok(q) => match dns_parser::Packet::parse(&q) {
+ Ok(parsed) => {
+ assert_eq!(parsed.questions.len(), 100);
+ }
+ Err(_) => assert!(false),
+ },
+ Err(_) => assert!(false),
+ }
+ }
+}
diff --git a/dom/media/webrtc/transport/mediapacket.cpp b/dom/media/webrtc/transport/mediapacket.cpp
new file mode 100644
index 0000000000..38ab65ed2a
--- /dev/null
+++ b/dom/media/webrtc/transport/mediapacket.cpp
@@ -0,0 +1,145 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mediapacket.h"
+
+#include <cstring>
+#include "ipc/IPCMessageUtils.h"
+
+namespace mozilla {
+
+void MediaPacket::Copy(const uint8_t* data, size_t len, size_t capacity) {
+ if (capacity < len) {
+ capacity = len;
+ }
+ data_.reset(new uint8_t[capacity]);
+ len_ = len;
+ capacity_ = capacity;
+ memcpy(data_.get(), data, len);
+}
+
+MediaPacket MediaPacket::Clone() const {
+ MediaPacket newPacket;
+ newPacket.type_ = type_;
+ newPacket.sdp_level_ = sdp_level_;
+ newPacket.Copy(data_.get(), len_, capacity_);
+ return newPacket;
+}
+
+void MediaPacket::Serialize(IPC::MessageWriter* aWriter) const {
+ aWriter->WriteUInt32(len_);
+ aWriter->WriteUInt32(capacity_);
+ if (len_) {
+ aWriter->WriteBytes(data_.get(), len_);
+ }
+ aWriter->WriteUInt32(encrypted_len_);
+ if (encrypted_len_) {
+ aWriter->WriteBytes(encrypted_data_.get(), encrypted_len_);
+ }
+ aWriter->WriteInt32(sdp_level_.isSome() ? *sdp_level_ : -1);
+ aWriter->WriteInt32(type_);
+}
+
+bool MediaPacket::Deserialize(IPC::MessageReader* aReader) {
+ Reset();
+ uint32_t len;
+ if (!aReader->ReadUInt32(&len)) {
+ return false;
+ }
+ uint32_t capacity;
+ if (!aReader->ReadUInt32(&capacity)) {
+ return false;
+ }
+ if (len) {
+ MOZ_RELEASE_ASSERT(capacity >= len);
+ UniquePtr<uint8_t[]> data(new uint8_t[capacity]);
+ if (!aReader->ReadBytesInto(data.get(), len)) {
+ return false;
+ }
+ data_ = std::move(data);
+ len_ = len;
+ capacity_ = capacity;
+ }
+
+ if (!aReader->ReadUInt32(&len)) {
+ return false;
+ }
+ if (len) {
+ UniquePtr<uint8_t[]> data(new uint8_t[len]);
+ if (!aReader->ReadBytesInto(data.get(), len)) {
+ return false;
+ }
+ encrypted_data_ = std::move(data);
+ encrypted_len_ = len;
+ }
+
+ int32_t sdp_level;
+ if (!aReader->ReadInt32(&sdp_level)) {
+ return false;
+ }
+
+ if (sdp_level >= 0) {
+ sdp_level_ = Some(sdp_level);
+ }
+
+ int32_t type;
+ if (!aReader->ReadInt32(&type)) {
+ return false;
+ }
+ type_ = static_cast<Type>(type);
+ return true;
+}
+
+static bool IsRtp(const uint8_t* data, size_t len) {
+ if (len < 2) return false;
+
+ // Check if this is a RTCP packet. Logic based on the types listed in
+ // media/webrtc/trunk/src/modules/rtp_rtcp/source/rtp_utility.cc
+
+ // Anything outside this range is RTP.
+ if ((data[1] < 192) || (data[1] > 207)) return true;
+
+ if (data[1] == 192) // FIR
+ return false;
+
+ if (data[1] == 193) // NACK, but could also be RTP. This makes us sad
+ return true; // but it's how webrtc.org behaves.
+
+ if (data[1] == 194) return true;
+
+ if (data[1] == 195) // IJ.
+ return false;
+
+ if ((data[1] > 195) && (data[1] < 200)) // the > 195 is redundant
+ return true;
+
+ if ((data[1] >= 200) && (data[1] <= 207)) // SR, RR, SDES, BYE,
+ return false; // APP, RTPFB, PSFB, XR
+
+ MOZ_ASSERT(false); // Not reached, belt and suspenders.
+ return true;
+}
+
+void MediaPacket::Categorize() {
+ SetType(MediaPacket::UNCLASSIFIED);
+
+ if (!data_ || len_ < 4) {
+ return;
+ }
+
+ if (data_[0] >= 20 && data_[0] <= 63) {
+ // DTLS per RFC 7983
+ SetType(MediaPacket::DTLS);
+ } else if (data_[0] > 127 && data_[0] < 192) {
+ // RTP/RTCP per RFC 7983
+ if (IsRtp(data_.get(), len_)) {
+ SetType(MediaPacket::SRTP);
+ } else {
+ SetType(MediaPacket::SRTCP);
+ }
+ }
+}
+} // namespace mozilla
diff --git a/dom/media/webrtc/transport/mediapacket.h b/dom/media/webrtc/transport/mediapacket.h
new file mode 100644
index 0000000000..563056c4a4
--- /dev/null
+++ b/dom/media/webrtc/transport/mediapacket.h
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mediapacket_h__
+#define mediapacket_h__
+
+#include <cstddef>
+#include <cstdint>
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Maybe.h"
+
+class PickleIterator;
+
+namespace IPC {
+class Message;
+class MessageReader;
+class MessageWriter;
+} // namespace IPC
+
+namespace mozilla {
+
+// TODO: It might be worthwhile to teach this class how to "borrow" a buffer.
+// That would make it easier to misuse, however, so maybe not worth it.
+class MediaPacket {
+ public:
+ MediaPacket() = default;
+ MediaPacket(MediaPacket&& orig) = default;
+ MediaPacket& operator=(MediaPacket&& orig) = default;
+
+ MediaPacket Clone() const;
+
+ // Takes ownership of the passed-in data
+ void Take(UniquePtr<uint8_t[]>&& data, size_t len, size_t capacity = 0) {
+ data_ = std::move(data);
+ len_ = len;
+ if (capacity < len) {
+ capacity = len;
+ }
+ capacity_ = capacity;
+ }
+
+ void Reset() {
+ data_.reset();
+ len_ = 0;
+ capacity_ = 0;
+ encrypted_data_.reset();
+ encrypted_len_ = 0;
+ sdp_level_.reset();
+ }
+
+ // Copies the passed-in data
+ void Copy(const uint8_t* data, size_t len, size_t capacity = 0);
+
+ uint8_t* data() const { return data_.get(); }
+
+ size_t len() const { return len_; }
+
+ void SetLength(size_t length) { len_ = length; }
+
+ size_t capacity() const { return capacity_; }
+
+ Maybe<size_t>& sdp_level() { return sdp_level_; }
+
+ void CopyDataToEncrypted() {
+ encrypted_data_ = std::move(data_);
+ encrypted_len_ = len_;
+ Copy(encrypted_data_.get(), len_);
+ }
+
+ const uint8_t* encrypted_data() const { return encrypted_data_.get(); }
+
+ size_t encrypted_len() const { return encrypted_len_; }
+
+ enum Type { UNCLASSIFIED, SRTP, SRTCP, DTLS, RTP, RTCP, SCTP };
+
+ void Categorize();
+
+ void SetType(Type type) { type_ = type; }
+
+ Type type() const { return type_; }
+
+ void Serialize(IPC::MessageWriter* aWriter) const;
+ bool Deserialize(IPC::MessageReader* aReader);
+
+ private:
+ UniquePtr<uint8_t[]> data_;
+ size_t len_ = 0;
+ size_t capacity_ = 0;
+ // Encrypted form of the data, if there is one.
+ UniquePtr<uint8_t[]> encrypted_data_;
+ size_t encrypted_len_ = 0;
+ // SDP level that this packet belongs to, if known.
+ Maybe<size_t> sdp_level_;
+ Type type_ = UNCLASSIFIED;
+};
+} // namespace mozilla
+
+namespace IPC {
+template <typename>
+struct ParamTraits;
+
+template <>
+struct ParamTraits<mozilla::MediaPacket> {
+ static void Write(MessageWriter* aWriter,
+ const mozilla::MediaPacket& aParam) {
+ aParam.Serialize(aWriter);
+ }
+
+ static bool Read(MessageReader* aReader, mozilla::MediaPacket* aResult) {
+ return aResult->Deserialize(aReader);
+ }
+};
+} // namespace IPC
+#endif // mediapacket_h__
diff --git a/dom/media/webrtc/transport/moz.build b/dom/media/webrtc/transport/moz.build
new file mode 100644
index 0000000000..fcf265e22f
--- /dev/null
+++ b/dom/media/webrtc/transport/moz.build
@@ -0,0 +1,23 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "WebRTC: Networking")
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+DIRS += [
+ "/dom/media/webrtc/transport/third_party",
+ "/dom/media/webrtc/transport/build",
+ "/dom/media/webrtc/transport/ipc",
+ "/dom/media/webrtc/transport/srtp",
+]
+
+# Add libFuzzer configuration directives
+include("/tools/fuzzing/libfuzzer-config.mozbuild")
+
+if CONFIG["FUZZING_INTERFACES"]:
+ TEST_DIRS += ["fuzztest"]
diff --git a/dom/media/webrtc/transport/nr_socket_proxy_config.cpp b/dom/media/webrtc/transport/nr_socket_proxy_config.cpp
new file mode 100644
index 0000000000..8c91a5d975
--- /dev/null
+++ b/dom/media/webrtc/transport/nr_socket_proxy_config.cpp
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nr_socket_proxy_config.h"
+
+#include "mozilla/net/WebrtcProxyConfig.h"
+
+namespace mozilla {
+
+class NrSocketProxyConfig::Private {
+ public:
+ net::WebrtcProxyConfig mProxyConfig;
+};
+
+NrSocketProxyConfig::NrSocketProxyConfig(
+ const net::WebrtcProxyConfig& aProxyConfig)
+ : mPrivate(new Private({aProxyConfig})) {}
+
+NrSocketProxyConfig::NrSocketProxyConfig(NrSocketProxyConfig&& aOrig)
+ : mPrivate(std::move(aOrig.mPrivate)) {}
+
+NrSocketProxyConfig::~NrSocketProxyConfig() = default;
+
+const net::WebrtcProxyConfig& NrSocketProxyConfig::GetConfig() const {
+ return mPrivate->mProxyConfig;
+}
+
+bool NrSocketProxyConfig::GetForceProxy() const {
+ return mPrivate->mProxyConfig.forceProxy();
+}
+} // namespace mozilla
diff --git a/dom/media/webrtc/transport/nr_socket_proxy_config.h b/dom/media/webrtc/transport/nr_socket_proxy_config.h
new file mode 100644
index 0000000000..55ef4fdcb1
--- /dev/null
+++ b/dom/media/webrtc/transport/nr_socket_proxy_config.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nr_socket_proxy_config__
+#define nr_socket_proxy_config__
+
+#include <memory>
+
+namespace mozilla {
+namespace net {
+class WebrtcProxyConfig;
+}
+
+class NrSocketProxyConfig {
+ public:
+ explicit NrSocketProxyConfig(const net::WebrtcProxyConfig& aProxyConfig);
+
+ // We need to actually write the default impl ourselves, because the compiler
+ // needs to know how to destroy mPrivate in case an exception is thrown, even
+ // though we disable exceptions in our build.
+ NrSocketProxyConfig(NrSocketProxyConfig&& aOrig);
+
+ ~NrSocketProxyConfig();
+
+ const net::WebrtcProxyConfig& GetConfig() const;
+ bool GetForceProxy() const;
+
+ private:
+ // dom::ProxyConfig includes stuff that conflicts with nICEr includes.
+ // Make it possible to include this header file without tripping over this
+ // problem.
+ class Private;
+ std::unique_ptr<Private> mPrivate;
+};
+
+} // namespace mozilla
+
+#endif // nr_socket_proxy_config__
diff --git a/dom/media/webrtc/transport/nr_socket_prsock.cpp b/dom/media/webrtc/transport/nr_socket_prsock.cpp
new file mode 100644
index 0000000000..3c99a2d0c8
--- /dev/null
+++ b/dom/media/webrtc/transport/nr_socket_prsock.cpp
@@ -0,0 +1,1785 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+/*
+Modified version of nr_socket_local, adapted for NSPR
+*/
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+Original code from nICEr and nrappkit.
+
+nICEr copyright:
+
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+
+
+nrappkit copyright:
+
+ Copyright (C) 2001-2003, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ ekr@rtfm.com Thu Dec 20 20:14:49 2001
+*/
+
+#include <csi_platform.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <assert.h>
+#include <errno.h>
+#include <string>
+
+#include "nspr.h"
+#include "prerror.h"
+#include "prio.h"
+#include "prnetdb.h"
+
+#include "mozilla/net/DNS.h"
+#include "nsCOMPtr.h"
+#include "nsASocketHandler.h"
+#include "nsISocketTransportService.h"
+#include "nsNetCID.h"
+#include "nsISupportsImpl.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsXPCOM.h"
+#include "nsXULAppAPI.h"
+#include "runnable_utils.h"
+#include "mozilla/SyncRunnable.h"
+#include "nsTArray.h"
+#include "nsISocketFilter.h"
+#include "nsDebug.h"
+#include "nsNetUtil.h"
+
+#ifdef XP_WIN
+# include "mozilla/WindowsVersion.h"
+#endif
+
+#if defined(MOZILLA_INTERNAL_API)
+// csi_platform.h deep in nrappkit defines LOG_INFO and LOG_WARNING
+# ifdef LOG_INFO
+# define LOG_TEMP_INFO LOG_INFO
+# undef LOG_INFO
+# endif
+# ifdef LOG_WARNING
+# define LOG_TEMP_WARNING LOG_WARNING
+# undef LOG_WARNING
+# endif
+# if defined(LOG_DEBUG)
+# define LOG_TEMP_DEBUG LOG_DEBUG
+# undef LOG_DEBUG
+# endif
+# undef strlcpy
+
+# include "mozilla/dom/network/UDPSocketChild.h"
+
+# ifdef LOG_TEMP_INFO
+# define LOG_INFO LOG_TEMP_INFO
+# endif
+# ifdef LOG_TEMP_WARNING
+# define LOG_WARNING LOG_TEMP_WARNING
+# endif
+
+# ifdef LOG_TEMP_DEBUG
+# define LOG_DEBUG LOG_TEMP_DEBUG
+# endif
+# ifdef XP_WIN
+# ifdef LOG_DEBUG
+# undef LOG_DEBUG
+# endif
+// cloned from csi_platform.h. Win32 doesn't like how we hide symbols
+# define LOG_DEBUG 7
+# endif
+#endif
+
+extern "C" {
+#include "nr_api.h"
+#include "async_wait.h"
+#include "nr_socket.h"
+#include "nr_socket_local.h"
+#include "stun_hint.h"
+}
+#include "nr_socket_prsock.h"
+#include "simpletokenbucket.h"
+#include "test_nr_socket.h"
+#include "nr_socket_tcp.h"
+#include "nr_socket_proxy_config.h"
+
+// Implement the nsISupports ref counting
+namespace mozilla {
+
+#if defined(MOZILLA_INTERNAL_API)
+class SingletonThreadHolder final {
+ private:
+ ~SingletonThreadHolder() {
+ r_log(LOG_GENERIC, LOG_DEBUG, "Deleting SingletonThreadHolder");
+ if (mThread) {
+ // Likely a connection is somehow being held in CC or GC
+ NS_WARNING(
+ "SingletonThreads should be Released and shut down before exit!");
+ mThread->Shutdown();
+ mThread = nullptr;
+ }
+ }
+
+ DISALLOW_COPY_ASSIGN(SingletonThreadHolder);
+
+ public:
+ // Must be threadsafe for StaticRefPtr/ClearOnShutdown
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SingletonThreadHolder)
+
+ explicit SingletonThreadHolder(const nsACString& aName) : mName(aName) {
+ mParentThread = NS_GetCurrentThread();
+ }
+
+ nsIThread* GetThread() { return mThread; }
+
+ /*
+ * Keep track of how many instances are using a SingletonThreadHolder.
+ * When no one is using it, shut it down
+ */
+ void AddUse() {
+ MOZ_ASSERT(mParentThread == NS_GetCurrentThread());
+ MOZ_ASSERT(int32_t(mUseCount) >= 0, "illegal refcnt");
+ nsrefcnt count = ++mUseCount;
+ if (count == 1) {
+ // idle -> in-use
+ nsresult rv = NS_NewNamedThread(mName, getter_AddRefs(mThread));
+ MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv) && mThread,
+ "Should successfully create mtransport I/O thread");
+ r_log(LOG_GENERIC, LOG_DEBUG, "Created wrapped SingletonThread %p",
+ mThread.get());
+ }
+ r_log(LOG_GENERIC, LOG_DEBUG, "AddUse_i: %lu", (unsigned long)count);
+ }
+
+ void ReleaseUse() {
+ MOZ_ASSERT(mParentThread == NS_GetCurrentThread());
+ nsrefcnt count = --mUseCount;
+ MOZ_ASSERT(int32_t(mUseCount) >= 0, "illegal refcnt");
+ if (mThread && count == 0) {
+ // in-use -> idle -- no one forcing it to remain instantiated
+ r_log(LOG_GENERIC, LOG_DEBUG, "Shutting down wrapped SingletonThread %p",
+ mThread.get());
+ mThread->AsyncShutdown();
+ mThread = nullptr;
+ // It'd be nice to use a timer instead... But be careful of
+ // xpcom-shutdown-threads in that case
+ }
+ r_log(LOG_GENERIC, LOG_DEBUG, "ReleaseUse_i: %lu", (unsigned long)count);
+ }
+
+ private:
+ nsCString mName;
+ nsAutoRefCnt mUseCount;
+ nsCOMPtr<nsIThread> mParentThread;
+ nsCOMPtr<nsIThread> mThread;
+};
+
+static StaticRefPtr<SingletonThreadHolder> sThread;
+
+static void ClearSingletonOnShutdown() {
+ // We expect everybody to have done ReleaseUse() at the latest during
+ // xpcom-shutdown-threads. So we need to live longer than that.
+ ClearOnShutdown(&sThread, ShutdownPhase::XPCOMShutdownFinal);
+}
+#endif
+
+static nsIThread* GetIOThreadAndAddUse_s() {
+ // Always runs on STS thread!
+#if defined(MOZILLA_INTERNAL_API)
+ // We need to safely release this on shutdown to avoid leaks
+ if (!sThread) {
+ sThread = new SingletonThreadHolder("mtransport"_ns);
+ NS_DispatchToMainThread(mozilla::WrapRunnableNM(&ClearSingletonOnShutdown));
+ }
+ // Mark that we're using the shared thread and need it to stick around
+ sThread->AddUse();
+ return sThread->GetThread();
+#else
+ static nsCOMPtr<nsIThread> sThread;
+ if (!sThread) {
+ (void)NS_NewNamedThread("mtransport", getter_AddRefs(sThread));
+ }
+ return sThread;
+#endif
+}
+
+NrSocketIpc::NrSocketIpc(nsIEventTarget* aThread) : io_thread_(aThread) {}
+
+static TimeStamp nr_socket_short_term_violation_time;
+static TimeStamp nr_socket_long_term_violation_time;
+
+TimeStamp NrSocketBase::short_term_violation_time() {
+ return nr_socket_short_term_violation_time;
+}
+
+TimeStamp NrSocketBase::long_term_violation_time() {
+ return nr_socket_long_term_violation_time;
+}
+
+// NrSocketBase implementation
+// async_event APIs
+int NrSocketBase::async_wait(int how, NR_async_cb cb, void* cb_arg,
+ char* function, int line) {
+ uint16_t flag;
+
+ switch (how) {
+ case NR_ASYNC_WAIT_READ:
+ flag = PR_POLL_READ;
+ break;
+ case NR_ASYNC_WAIT_WRITE:
+ flag = PR_POLL_WRITE;
+ break;
+ default:
+ return R_BAD_ARGS;
+ }
+
+ cbs_[how] = cb;
+ cb_args_[how] = cb_arg;
+ poll_flags_ |= flag;
+
+ return 0;
+}
+
+int NrSocketBase::cancel(int how) {
+ uint16_t flag;
+
+ switch (how) {
+ case NR_ASYNC_WAIT_READ:
+ flag = PR_POLL_READ;
+ break;
+ case NR_ASYNC_WAIT_WRITE:
+ flag = PR_POLL_WRITE;
+ break;
+ default:
+ return R_BAD_ARGS;
+ }
+
+ poll_flags_ &= ~flag;
+
+ return 0;
+}
+
+void NrSocketBase::fire_callback(int how) {
+ // This can't happen unless we are armed because we only set
+ // the flags if we are armed
+ MOZ_ASSERT(cbs_[how]);
+
+ // Now cancel so that we need to be re-armed. Note that
+ // the re-arming probably happens in the callback we are
+ // about to fire.
+ cancel(how);
+
+ cbs_[how](this, how, cb_args_[how]);
+}
+
+// NrSocket implementation
+NS_IMPL_QUERY_INTERFACE0(NrSocket)
+
+// The nsASocket callbacks
+void NrSocket::OnSocketReady(PRFileDesc* fd, int16_t outflags) {
+ if (outflags & PR_POLL_READ & poll_flags()) fire_callback(NR_ASYNC_WAIT_READ);
+ if (outflags & PR_POLL_WRITE & poll_flags())
+ fire_callback(NR_ASYNC_WAIT_WRITE);
+ if (outflags & (PR_POLL_ERR | PR_POLL_NVAL | PR_POLL_HUP))
+ // TODO: Bug 946423: how do we notify the upper layers about this?
+ close();
+}
+
+void NrSocket::OnSocketDetached(PRFileDesc* fd) {
+ r_log(LOG_GENERIC, LOG_DEBUG, "Socket %p detached", fd);
+}
+
+void NrSocket::IsLocal(bool* aIsLocal) {
+ // TODO(jesup): better check? Does it matter? (likely no)
+ *aIsLocal = false;
+}
+
+// async_event APIs
+int NrSocket::async_wait(int how, NR_async_cb cb, void* cb_arg, char* function,
+ int line) {
+ int r = NrSocketBase::async_wait(how, cb, cb_arg, function, line);
+
+ if (!r) {
+ mPollFlags = poll_flags();
+ }
+
+ return r;
+}
+
+int NrSocket::cancel(int how) {
+ int r = NrSocketBase::cancel(how);
+
+ if (!r) {
+ mPollFlags = poll_flags();
+ }
+
+ return r;
+}
+
+// Helper functions for addresses
+static int nr_transport_addr_to_praddr(const nr_transport_addr* addr,
+ PRNetAddr* naddr) {
+ int _status;
+
+ memset(naddr, 0, sizeof(*naddr));
+
+ switch (addr->protocol) {
+ case IPPROTO_TCP:
+ break;
+ case IPPROTO_UDP:
+ break;
+ default:
+ ABORT(R_BAD_ARGS);
+ }
+
+ switch (addr->ip_version) {
+ case NR_IPV4:
+ naddr->inet.family = PR_AF_INET;
+ naddr->inet.port = addr->u.addr4.sin_port;
+ naddr->inet.ip = addr->u.addr4.sin_addr.s_addr;
+ break;
+ case NR_IPV6:
+ naddr->ipv6.family = PR_AF_INET6;
+ naddr->ipv6.port = addr->u.addr6.sin6_port;
+ naddr->ipv6.flowinfo = addr->u.addr6.sin6_flowinfo;
+ memcpy(&naddr->ipv6.ip, &addr->u.addr6.sin6_addr, sizeof(in6_addr));
+ naddr->ipv6.scope_id = addr->u.addr6.sin6_scope_id;
+ break;
+ default:
+ ABORT(R_BAD_ARGS);
+ }
+
+ _status = 0;
+abort:
+ return (_status);
+}
+
+// XXX schien@mozilla.com: copy from PRNetAddrToNetAddr,
+// should be removed after fix the link error in signaling_unittests
+static int praddr_to_netaddr(const PRNetAddr* prAddr, net::NetAddr* addr) {
+ int _status;
+
+ switch (prAddr->raw.family) {
+ case PR_AF_INET:
+ addr->inet.family = AF_INET;
+ addr->inet.port = prAddr->inet.port;
+ addr->inet.ip = prAddr->inet.ip;
+ break;
+ case PR_AF_INET6:
+ addr->inet6.family = AF_INET6;
+ addr->inet6.port = prAddr->ipv6.port;
+ addr->inet6.flowinfo = prAddr->ipv6.flowinfo;
+ memcpy(&addr->inet6.ip, &prAddr->ipv6.ip, sizeof(addr->inet6.ip.u8));
+ addr->inet6.scope_id = prAddr->ipv6.scope_id;
+ break;
+ default:
+ MOZ_ASSERT(false);
+ ABORT(R_BAD_ARGS);
+ }
+
+ _status = 0;
+abort:
+ return (_status);
+}
+
+static int nr_transport_addr_to_netaddr(const nr_transport_addr* addr,
+ net::NetAddr* naddr) {
+ int r, _status;
+ PRNetAddr praddr;
+
+ if ((r = nr_transport_addr_to_praddr(addr, &praddr))) {
+ ABORT(r);
+ }
+
+ if ((r = praddr_to_netaddr(&praddr, naddr))) {
+ ABORT(r);
+ }
+
+ _status = 0;
+abort:
+ return (_status);
+}
+
+int nr_netaddr_to_transport_addr(const net::NetAddr* netaddr,
+ nr_transport_addr* addr, int protocol) {
+ int _status;
+ int r;
+
+ switch (netaddr->raw.family) {
+ case AF_INET:
+ if ((r = nr_ip4_port_to_transport_addr(ntohl(netaddr->inet.ip),
+ ntohs(netaddr->inet.port),
+ protocol, addr)))
+ ABORT(r);
+ break;
+ case AF_INET6:
+ if ((r = nr_ip6_port_to_transport_addr((in6_addr*)&netaddr->inet6.ip.u8,
+ ntohs(netaddr->inet6.port),
+ protocol, addr)))
+ ABORT(r);
+ break;
+ default:
+ MOZ_ASSERT(false);
+ ABORT(R_BAD_ARGS);
+ }
+ _status = 0;
+abort:
+ return (_status);
+}
+
+int nr_praddr_to_transport_addr(const PRNetAddr* praddr,
+ nr_transport_addr* addr, int protocol,
+ int keep) {
+ int _status;
+ int r;
+ struct sockaddr_in ip4;
+ struct sockaddr_in6 ip6;
+
+ switch (praddr->raw.family) {
+ case PR_AF_INET:
+ ip4.sin_family = PF_INET;
+ ip4.sin_addr.s_addr = praddr->inet.ip;
+ ip4.sin_port = praddr->inet.port;
+ if ((r = nr_sockaddr_to_transport_addr((sockaddr*)&ip4, protocol, keep,
+ addr)))
+ ABORT(r);
+ break;
+ case PR_AF_INET6:
+ ip6.sin6_family = PF_INET6;
+ ip6.sin6_port = praddr->ipv6.port;
+ ip6.sin6_flowinfo = praddr->ipv6.flowinfo;
+ memcpy(&ip6.sin6_addr, &praddr->ipv6.ip, sizeof(in6_addr));
+ ip6.sin6_scope_id = praddr->ipv6.scope_id;
+ if ((r = nr_sockaddr_to_transport_addr((sockaddr*)&ip6, protocol, keep,
+ addr)))
+ ABORT(r);
+ break;
+ default:
+ MOZ_ASSERT(false);
+ ABORT(R_BAD_ARGS);
+ }
+
+ _status = 0;
+abort:
+ return (_status);
+}
+
+/*
+ * nr_transport_addr_get_addrstring_and_port
+ * convert nr_transport_addr to IP address string and port number
+ */
+int nr_transport_addr_get_addrstring_and_port(const nr_transport_addr* addr,
+ nsACString* host, int32_t* port) {
+ int r, _status;
+ char addr_string[64];
+
+ // We cannot directly use |nr_transport_addr.as_string| because it contains
+ // more than ip address, therefore, we need to explicity convert it
+ // from |nr_transport_addr_get_addrstring|.
+ if ((r = nr_transport_addr_get_addrstring(addr, addr_string,
+ sizeof(addr_string)))) {
+ ABORT(r);
+ }
+
+ if ((r = nr_transport_addr_get_port(addr, port))) {
+ ABORT(r);
+ }
+
+ *host = addr_string;
+
+ _status = 0;
+abort:
+ return (_status);
+}
+
+// nr_socket APIs (as member functions)
+int NrSocket::create(nr_transport_addr* addr) {
+ int r, _status;
+
+ PRStatus status;
+ PRNetAddr naddr;
+
+ nsresult rv;
+ nsCOMPtr<nsISocketTransportService> stservice =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+
+ if (!NS_SUCCEEDED(rv)) {
+ ABORT(R_INTERNAL);
+ }
+
+ if ((r = nr_transport_addr_to_praddr(addr, &naddr))) ABORT(r);
+
+ switch (addr->protocol) {
+ case IPPROTO_UDP:
+ if (!(fd_ = PR_OpenUDPSocket(naddr.raw.family))) {
+ r_log(LOG_GENERIC, LOG_CRIT,
+ "Couldn't create UDP socket, "
+ "family=%d, err=%d",
+ naddr.raw.family, PR_GetError());
+ ABORT(R_INTERNAL);
+ }
+#ifdef XP_WIN
+ if (!mozilla::IsWin8OrLater()) {
+ // Increase default send and receive buffer sizes on <= Win7 to be able
+ // to receive and send an unpaced HD (>= 720p = 1280x720 - I Frame ~ 21K
+ // size) stream without losing packets. Manual testing showed that 100K
+ // buffer size was not enough and the packet loss dis-appeared with 256K
+ // buffer size. See bug 1252769 for future improvements of this.
+ PRSize min_buffer_size = 256 * 1024;
+ PRSocketOptionData opt_rcvbuf;
+ opt_rcvbuf.option = PR_SockOpt_RecvBufferSize;
+ if ((status = PR_GetSocketOption(fd_, &opt_rcvbuf)) == PR_SUCCESS) {
+ if (opt_rcvbuf.value.recv_buffer_size < min_buffer_size) {
+ opt_rcvbuf.value.recv_buffer_size = min_buffer_size;
+ if ((status = PR_SetSocketOption(fd_, &opt_rcvbuf)) != PR_SUCCESS) {
+ r_log(LOG_GENERIC, LOG_CRIT,
+ "Couldn't set socket receive buffer size: %d", status);
+ }
+ } else {
+ r_log(LOG_GENERIC, LOG_INFO,
+ "Socket receive buffer size is already: %d",
+ opt_rcvbuf.value.recv_buffer_size);
+ }
+ } else {
+ r_log(LOG_GENERIC, LOG_CRIT,
+ "Couldn't get socket receive buffer size: %d", status);
+ }
+ PRSocketOptionData opt_sndbuf;
+ opt_sndbuf.option = PR_SockOpt_SendBufferSize;
+ if ((status = PR_GetSocketOption(fd_, &opt_sndbuf)) == PR_SUCCESS) {
+ if (opt_sndbuf.value.recv_buffer_size < min_buffer_size) {
+ opt_sndbuf.value.recv_buffer_size = min_buffer_size;
+ if ((status = PR_SetSocketOption(fd_, &opt_sndbuf)) != PR_SUCCESS) {
+ r_log(LOG_GENERIC, LOG_CRIT,
+ "Couldn't set socket send buffer size: %d", status);
+ }
+ } else {
+ r_log(LOG_GENERIC, LOG_INFO,
+ "Socket send buffer size is already: %d",
+ opt_sndbuf.value.recv_buffer_size);
+ }
+ } else {
+ r_log(LOG_GENERIC, LOG_CRIT,
+ "Couldn't get socket send buffer size: %d", status);
+ }
+ }
+#endif
+ break;
+ case IPPROTO_TCP:
+ // TODO: Rewrite this to use WebrtcTcpSocket.
+ // Also use the same logic for TLS.
+ if (my_addr_.fqdn[0] != '\0') ABORT(R_INTERNAL);
+
+ if (!(fd_ = PR_OpenTCPSocket(naddr.raw.family))) {
+ r_log(LOG_GENERIC, LOG_CRIT,
+ "Couldn't create TCP socket, "
+ "family=%d, err=%d",
+ naddr.raw.family, PR_GetError());
+ ABORT(R_INTERNAL);
+ }
+ // Set ReuseAddr for TCP sockets to enable having several
+ // sockets bound to same local IP and port
+ PRSocketOptionData opt_reuseaddr;
+ opt_reuseaddr.option = PR_SockOpt_Reuseaddr;
+ opt_reuseaddr.value.reuse_addr = PR_TRUE;
+ status = PR_SetSocketOption(fd_, &opt_reuseaddr);
+ if (status != PR_SUCCESS) {
+ r_log(LOG_GENERIC, LOG_CRIT,
+ "Couldn't set reuse addr socket option: %d", status);
+ ABORT(R_INTERNAL);
+ }
+ // And also set ReusePort for platforms supporting this socket option
+ PRSocketOptionData opt_reuseport;
+ opt_reuseport.option = PR_SockOpt_Reuseport;
+ opt_reuseport.value.reuse_port = PR_TRUE;
+ status = PR_SetSocketOption(fd_, &opt_reuseport);
+ if (status != PR_SUCCESS) {
+ if (PR_GetError() != PR_OPERATION_NOT_SUPPORTED_ERROR) {
+ r_log(LOG_GENERIC, LOG_CRIT,
+ "Couldn't set reuse port socket option: %d", status);
+ ABORT(R_INTERNAL);
+ }
+ }
+ // Try to speedup packet delivery by disabling TCP Nagle
+ PRSocketOptionData opt_nodelay;
+ opt_nodelay.option = PR_SockOpt_NoDelay;
+ opt_nodelay.value.no_delay = PR_TRUE;
+ status = PR_SetSocketOption(fd_, &opt_nodelay);
+ if (status != PR_SUCCESS) {
+ r_log(LOG_GENERIC, LOG_WARNING,
+ "Couldn't set Nodelay socket option: %d", status);
+ }
+ break;
+ default:
+ ABORT(R_INTERNAL);
+ }
+
+ status = PR_Bind(fd_, &naddr);
+ if (status != PR_SUCCESS) {
+ r_log(LOG_GENERIC, LOG_CRIT, "Couldn't bind socket to address %s",
+ addr->as_string);
+ ABORT(R_INTERNAL);
+ }
+
+ r_log(LOG_GENERIC, LOG_DEBUG, "Creating socket %p with addr %s", fd_,
+ addr->as_string);
+ nr_transport_addr_copy(&my_addr_, addr);
+
+ /* If we have a wildcard port, patch up the addr */
+ if (nr_transport_addr_is_wildcard(addr)) {
+ status = PR_GetSockName(fd_, &naddr);
+ if (status != PR_SUCCESS) {
+ r_log(LOG_GENERIC, LOG_CRIT, "Couldn't get sock name for socket");
+ ABORT(R_INTERNAL);
+ }
+
+ if ((r = nr_praddr_to_transport_addr(&naddr, &my_addr_, addr->protocol, 1)))
+ ABORT(r);
+ }
+
+ // Set nonblocking
+ PRSocketOptionData opt_nonblock;
+ opt_nonblock.option = PR_SockOpt_Nonblocking;
+ opt_nonblock.value.non_blocking = PR_TRUE;
+ status = PR_SetSocketOption(fd_, &opt_nonblock);
+ if (status != PR_SUCCESS) {
+ r_log(LOG_GENERIC, LOG_CRIT, "Couldn't make socket nonblocking");
+ ABORT(R_INTERNAL);
+ }
+
+ // Remember our thread.
+ ststhread_ = do_QueryInterface(stservice, &rv);
+ if (!NS_SUCCEEDED(rv)) ABORT(R_INTERNAL);
+
+ // Finally, register with the STS
+ rv = stservice->AttachSocket(fd_, this);
+ if (!NS_SUCCEEDED(rv)) {
+ r_log(LOG_GENERIC, LOG_CRIT, "Couldn't attach socket to STS, rv=%u",
+ static_cast<unsigned>(rv));
+ ABORT(R_INTERNAL);
+ }
+
+ _status = 0;
+
+abort:
+ return (_status);
+}
+
+static int ShouldDrop(size_t len) {
+ // Global rate limiting for stun requests, to mitigate the ice hammer DoS
+ // (see http://tools.ietf.org/html/draft-thomson-mmusic-ice-webrtc)
+
+ // Tolerate rate of 8k/sec, for one second.
+ static SimpleTokenBucket burst(16384 * 1, 16384);
+ // Tolerate rate of 7.2k/sec over twenty seconds.
+ static SimpleTokenBucket sustained(7372 * 20, 7372);
+
+ // Check number of tokens in each bucket.
+ if (burst.getTokens(UINT32_MAX) < len) {
+ r_log(LOG_GENERIC, LOG_ERR,
+ "Short term global rate limit for STUN requests exceeded.");
+#ifdef MOZILLA_INTERNAL_API
+ nr_socket_short_term_violation_time = TimeStamp::Now();
+#endif
+
+// Bug 1013007
+#if !EARLY_BETA_OR_EARLIER
+ return R_WOULDBLOCK;
+#else
+ MOZ_ASSERT(false,
+ "Short term global rate limit for STUN requests exceeded. Go "
+ "bug bcampen@mozilla.com if you weren't intentionally "
+ "spamming ICE candidates, or don't know what that means.");
+#endif
+ }
+
+ if (sustained.getTokens(UINT32_MAX) < len) {
+ r_log(LOG_GENERIC, LOG_ERR,
+ "Long term global rate limit for STUN requests exceeded.");
+#ifdef MOZILLA_INTERNAL_API
+ nr_socket_long_term_violation_time = TimeStamp::Now();
+#endif
+// Bug 1013007
+#if !EARLY_BETA_OR_EARLIER
+ return R_WOULDBLOCK;
+#else
+ MOZ_ASSERT(false,
+ "Long term global rate limit for STUN requests exceeded. Go "
+ "bug bcampen@mozilla.com if you weren't intentionally "
+ "spamming ICE candidates, or don't know what that means.");
+#endif
+ }
+
+ // Take len tokens from both buckets.
+ // (not threadsafe, but no problem since this is only called from STS)
+ burst.getTokens(len);
+ sustained.getTokens(len);
+ return 0;
+}
+
+// This should be called on the STS thread.
+int NrSocket::sendto(const void* msg, size_t len, int flags,
+ const nr_transport_addr* to) {
+ ASSERT_ON_THREAD(ststhread_);
+ int r, _status;
+ PRNetAddr naddr;
+ int32_t status;
+
+ if ((r = nr_transport_addr_to_praddr(to, &naddr))) ABORT(r);
+
+ if (fd_ == nullptr) ABORT(R_EOD);
+
+ if (nr_is_stun_request_message((UCHAR*)msg, len) && ShouldDrop(len)) {
+ ABORT(R_WOULDBLOCK);
+ }
+
+ // TODO: Convert flags?
+ status = PR_SendTo(fd_, msg, len, flags, &naddr, PR_INTERVAL_NO_WAIT);
+ if (status < 0 || (size_t)status != len) {
+ if (PR_GetError() == PR_WOULD_BLOCK_ERROR) ABORT(R_WOULDBLOCK);
+
+ r_log(LOG_GENERIC, LOG_INFO, "Error in sendto %s: %d", to->as_string,
+ PR_GetError());
+ ABORT(R_IO_ERROR);
+ }
+
+ _status = 0;
+abort:
+ return (_status);
+}
+
+int NrSocket::recvfrom(void* buf, size_t maxlen, size_t* len, int flags,
+ nr_transport_addr* from) {
+ ASSERT_ON_THREAD(ststhread_);
+ int r, _status;
+ PRNetAddr nfrom;
+ int32_t status;
+
+ status = PR_RecvFrom(fd_, buf, maxlen, flags, &nfrom, PR_INTERVAL_NO_WAIT);
+ if (status <= 0) {
+ if (PR_GetError() == PR_WOULD_BLOCK_ERROR) ABORT(R_WOULDBLOCK);
+ r_log(LOG_GENERIC, LOG_INFO, "Error in recvfrom: %d", (int)PR_GetError());
+ ABORT(R_IO_ERROR);
+ }
+ *len = status;
+
+ if ((r = nr_praddr_to_transport_addr(&nfrom, from, my_addr_.protocol, 0)))
+ ABORT(r);
+
+ // r_log(LOG_GENERIC,LOG_DEBUG,"Read %d bytes from %s",*len,addr->as_string);
+
+ _status = 0;
+abort:
+ return (_status);
+}
+
+int NrSocket::getaddr(nr_transport_addr* addrp) {
+ ASSERT_ON_THREAD(ststhread_);
+ return nr_transport_addr_copy(addrp, &my_addr_);
+}
+
+// Close the socket so that the STS will detach and then kill it
+void NrSocket::close() {
+ ASSERT_ON_THREAD(ststhread_);
+ mCondition = NS_BASE_STREAM_CLOSED;
+ cancel(NR_ASYNC_WAIT_READ);
+ cancel(NR_ASYNC_WAIT_WRITE);
+}
+
+int NrSocket::connect(const nr_transport_addr* addr) {
+ ASSERT_ON_THREAD(ststhread_);
+ int r, _status;
+ PRNetAddr naddr;
+ int32_t connect_status, getsockname_status;
+
+ if ((r = nr_transport_addr_to_praddr(addr, &naddr))) ABORT(r);
+
+ if (!fd_) ABORT(R_EOD);
+
+ // Note: this just means we tried to connect, not that we
+ // are actually live.
+ connect_invoked_ = true;
+ connect_status = PR_Connect(fd_, &naddr, PR_INTERVAL_NO_WAIT);
+ if (connect_status != PR_SUCCESS) {
+ if (PR_GetError() != PR_IN_PROGRESS_ERROR) {
+ r_log(LOG_GENERIC, LOG_CRIT, "PR_Connect failed: %d", PR_GetError());
+ ABORT(R_IO_ERROR);
+ }
+ }
+
+ // If our local address is wildcard, then fill in the
+ // address now.
+ if (nr_transport_addr_is_wildcard(&my_addr_)) {
+ getsockname_status = PR_GetSockName(fd_, &naddr);
+ if (getsockname_status != PR_SUCCESS) {
+ r_log(LOG_GENERIC, LOG_CRIT, "Couldn't get sock name for socket");
+ ABORT(R_INTERNAL);
+ }
+
+ if ((r = nr_praddr_to_transport_addr(&naddr, &my_addr_, addr->protocol, 1)))
+ ABORT(r);
+ }
+
+ // Now return the WOULDBLOCK if needed.
+ if (connect_status != PR_SUCCESS) {
+ ABORT(R_WOULDBLOCK);
+ }
+
+ _status = 0;
+abort:
+ return (_status);
+}
+
+int NrSocket::write(const void* msg, size_t len, size_t* written) {
+ ASSERT_ON_THREAD(ststhread_);
+ int _status;
+ int32_t status;
+
+ if (!connect_invoked_) ABORT(R_FAILED);
+
+ status = PR_Write(fd_, msg, len);
+ if (status < 0) {
+ if (PR_GetError() == PR_WOULD_BLOCK_ERROR) ABORT(R_WOULDBLOCK);
+ r_log(LOG_GENERIC, LOG_INFO, "Error in write");
+ ABORT(R_IO_ERROR);
+ }
+
+ *written = status;
+
+ _status = 0;
+abort:
+ return _status;
+}
+
+int NrSocket::read(void* buf, size_t maxlen, size_t* len) {
+ ASSERT_ON_THREAD(ststhread_);
+ int _status;
+ int32_t status;
+
+ if (!connect_invoked_) ABORT(R_FAILED);
+
+ status = PR_Read(fd_, buf, maxlen);
+ if (status < 0) {
+ if (PR_GetError() == PR_WOULD_BLOCK_ERROR) ABORT(R_WOULDBLOCK);
+ r_log(LOG_GENERIC, LOG_INFO, "Error in read");
+ ABORT(R_IO_ERROR);
+ }
+ if (status == 0) ABORT(R_EOD);
+
+ *len = (size_t)status; // Guaranteed to be > 0
+ _status = 0;
+abort:
+ return (_status);
+}
+
+int NrSocket::listen(int backlog) {
+ ASSERT_ON_THREAD(ststhread_);
+ int32_t status;
+ int _status;
+
+ assert(fd_);
+ status = PR_Listen(fd_, backlog);
+ if (status != PR_SUCCESS) {
+ r_log(LOG_GENERIC, LOG_CRIT, "%s: PR_GetError() == %d", __FUNCTION__,
+ PR_GetError());
+ ABORT(R_IO_ERROR);
+ }
+
+ _status = 0;
+abort:
+ return (_status);
+}
+
+int NrSocket::accept(nr_transport_addr* addrp, nr_socket** sockp) {
+ ASSERT_ON_THREAD(ststhread_);
+ int _status, r;
+ PRStatus status;
+ PRFileDesc* prfd;
+ PRNetAddr nfrom;
+ NrSocket* sock = nullptr;
+ nsresult rv;
+ PRSocketOptionData opt_nonblock, opt_nodelay;
+ nsCOMPtr<nsISocketTransportService> stservice =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+
+ if (NS_FAILED(rv)) {
+ ABORT(R_INTERNAL);
+ }
+
+ if (!fd_) ABORT(R_EOD);
+
+ prfd = PR_Accept(fd_, &nfrom, PR_INTERVAL_NO_WAIT);
+
+ if (!prfd) {
+ if (PR_GetError() == PR_WOULD_BLOCK_ERROR) ABORT(R_WOULDBLOCK);
+
+ ABORT(R_IO_ERROR);
+ }
+
+ sock = new NrSocket();
+
+ sock->fd_ = prfd;
+ nr_transport_addr_copy(&sock->my_addr_, &my_addr_);
+
+ if ((r = nr_praddr_to_transport_addr(&nfrom, addrp, my_addr_.protocol, 0)))
+ ABORT(r);
+
+ // Set nonblocking
+ opt_nonblock.option = PR_SockOpt_Nonblocking;
+ opt_nonblock.value.non_blocking = PR_TRUE;
+ status = PR_SetSocketOption(prfd, &opt_nonblock);
+ if (status != PR_SUCCESS) {
+ r_log(LOG_GENERIC, LOG_CRIT,
+ "Failed to make accepted socket nonblocking: %d", status);
+ ABORT(R_INTERNAL);
+ }
+ // Disable TCP Nagle
+ opt_nodelay.option = PR_SockOpt_NoDelay;
+ opt_nodelay.value.no_delay = PR_TRUE;
+ status = PR_SetSocketOption(prfd, &opt_nodelay);
+ if (status != PR_SUCCESS) {
+ r_log(LOG_GENERIC, LOG_WARNING,
+ "Failed to set Nodelay on accepted socket: %d", status);
+ }
+
+ // Should fail only with OOM
+ if ((r = nr_socket_create_int(static_cast<void*>(sock), sock->vtbl(), sockp)))
+ ABORT(r);
+
+ // Remember our thread.
+ sock->ststhread_ = do_QueryInterface(stservice, &rv);
+ if (NS_FAILED(rv)) ABORT(R_INTERNAL);
+
+ // Finally, register with the STS
+ rv = stservice->AttachSocket(prfd, sock);
+ if (NS_FAILED(rv)) {
+ ABORT(R_INTERNAL);
+ }
+
+ sock->connect_invoked_ = true;
+
+ // Add a reference so that we can delete it in destroy()
+ sock->AddRef();
+ _status = 0;
+abort:
+ if (_status) {
+ delete sock;
+ }
+
+ return (_status);
+}
+
+NS_IMPL_ISUPPORTS(NrUdpSocketIpcProxy, nsIUDPSocketInternal)
+
+nsresult NrUdpSocketIpcProxy::Init(const RefPtr<NrUdpSocketIpc>& socket) {
+ nsresult rv;
+ sts_thread_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT(false, "Failed to get STS thread");
+ return rv;
+ }
+
+ socket_ = socket;
+ return NS_OK;
+}
+
+NrUdpSocketIpcProxy::~NrUdpSocketIpcProxy() {
+ // Send our ref to STS to be released
+ RUN_ON_THREAD(sts_thread_, mozilla::WrapRelease(socket_.forget()),
+ NS_DISPATCH_NORMAL);
+}
+
+// IUDPSocketInternal interfaces
+// callback while error happened in UDP socket operation
+NS_IMETHODIMP NrUdpSocketIpcProxy::CallListenerError(const nsACString& message,
+ const nsACString& filename,
+ uint32_t line_number) {
+ return socket_->CallListenerError(message, filename, line_number);
+}
+
+// callback while receiving UDP packet
+NS_IMETHODIMP NrUdpSocketIpcProxy::CallListenerReceivedData(
+ const nsACString& host, uint16_t port, const nsTArray<uint8_t>& data) {
+ return socket_->CallListenerReceivedData(host, port, data);
+}
+
+// callback while UDP socket is opened
+NS_IMETHODIMP NrUdpSocketIpcProxy::CallListenerOpened() {
+ return socket_->CallListenerOpened();
+}
+
+// callback while UDP socket is connected
+NS_IMETHODIMP NrUdpSocketIpcProxy::CallListenerConnected() {
+ return socket_->CallListenerConnected();
+}
+
+// callback while UDP socket is closed
+NS_IMETHODIMP NrUdpSocketIpcProxy::CallListenerClosed() {
+ return socket_->CallListenerClosed();
+}
+
+// NrUdpSocketIpc Implementation
+NrUdpSocketIpc::NrUdpSocketIpc()
+ : NrSocketIpc(GetIOThreadAndAddUse_s()),
+ monitor_("NrUdpSocketIpc"),
+ err_(false),
+ state_(NR_INIT) {}
+
+NrUdpSocketIpc::~NrUdpSocketIpc() = default;
+
+void NrUdpSocketIpc::Destroy() {
+#if defined(MOZILLA_INTERNAL_API)
+ // destroy_i also dispatches back to STS to call ReleaseUse, to avoid shutting
+ // down the IO thread before close() runs.
+ // We use a NonOwning runnable because our refcount has already gone to 0.
+ io_thread_->Dispatch(NewNonOwningRunnableMethod(
+ "NrUdpSocketIpc::Destroy", this, &NrUdpSocketIpc::destroy_i));
+#endif
+}
+
+// IUDPSocketInternal interfaces
+// callback while error happened in UDP socket operation
+NS_IMETHODIMP NrUdpSocketIpc::CallListenerError(const nsACString& message,
+ const nsACString& filename,
+ uint32_t line_number) {
+ ASSERT_ON_THREAD(io_thread_);
+
+ r_log(LOG_GENERIC, LOG_ERR, "UDP socket error:%s at %s:%d this=%p",
+ message.BeginReading(), filename.BeginReading(), line_number,
+ (void*)this);
+
+ ReentrantMonitorAutoEnter mon(monitor_);
+ err_ = true;
+ monitor_.NotifyAll();
+
+ return NS_OK;
+}
+
+// callback while receiving UDP packet
+NS_IMETHODIMP NrUdpSocketIpc::CallListenerReceivedData(
+ const nsACString& host, uint16_t port, const nsTArray<uint8_t>& data) {
+ ASSERT_ON_THREAD(io_thread_);
+
+ PRNetAddr addr;
+ memset(&addr, 0, sizeof(addr));
+
+ {
+ ReentrantMonitorAutoEnter mon(monitor_);
+
+ if (PR_SUCCESS != PR_StringToNetAddr(host.BeginReading(), &addr)) {
+ err_ = true;
+ MOZ_ASSERT(false, "Failed to convert remote host to PRNetAddr");
+ return NS_OK;
+ }
+
+ // Use PR_IpAddrNull to avoid address being reset to 0.
+ if (PR_SUCCESS !=
+ PR_SetNetAddr(PR_IpAddrNull, addr.raw.family, port, &addr)) {
+ err_ = true;
+ MOZ_ASSERT(false, "Failed to set port in PRNetAddr");
+ return NS_OK;
+ }
+ }
+
+ auto buf = MakeUnique<MediaPacket>();
+ buf->Copy(data.Elements(), data.Length());
+ RefPtr<nr_udp_message> msg(new nr_udp_message(addr, std::move(buf)));
+
+ RUN_ON_THREAD(sts_thread_,
+ mozilla::WrapRunnable(RefPtr<NrUdpSocketIpc>(this),
+ &NrUdpSocketIpc::recv_callback_s, msg),
+ NS_DISPATCH_NORMAL);
+ return NS_OK;
+}
+
+nsresult NrUdpSocketIpc::SetAddress() {
+ uint16_t port = socket_child_->LocalPort();
+
+ nsAutoCString address(socket_child_->LocalAddress());
+
+ PRNetAddr praddr;
+ if (PR_SUCCESS != PR_InitializeNetAddr(PR_IpAddrAny, port, &praddr)) {
+ err_ = true;
+ MOZ_ASSERT(false, "Failed to set port in PRNetAddr");
+ return NS_OK;
+ }
+
+ if (PR_SUCCESS != PR_StringToNetAddr(address.BeginReading(), &praddr)) {
+ err_ = true;
+ MOZ_ASSERT(false, "Failed to convert local host to PRNetAddr");
+ return NS_OK;
+ }
+
+ nr_transport_addr expected_addr;
+ if (nr_transport_addr_copy(&expected_addr, &my_addr_)) {
+ err_ = true;
+ MOZ_ASSERT(false, "Failed to copy my_addr_");
+ }
+
+ if (nr_praddr_to_transport_addr(&praddr, &my_addr_, IPPROTO_UDP, 1)) {
+ err_ = true;
+ MOZ_ASSERT(false, "Failed to copy local host to my_addr_");
+ }
+
+ if (!nr_transport_addr_is_wildcard(&expected_addr) &&
+ nr_transport_addr_cmp(&expected_addr, &my_addr_,
+ NR_TRANSPORT_ADDR_CMP_MODE_ADDR)) {
+ err_ = true;
+ MOZ_ASSERT(false, "Address of opened socket is not expected");
+ }
+
+ return NS_OK;
+}
+
+// callback while UDP socket is opened
+NS_IMETHODIMP NrUdpSocketIpc::CallListenerOpened() {
+ ASSERT_ON_THREAD(io_thread_);
+ ReentrantMonitorAutoEnter mon(monitor_);
+
+ r_log(LOG_GENERIC, LOG_DEBUG, "UDP socket opened this=%p", (void*)this);
+ nsresult rv = SetAddress();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ mon.NotifyAll();
+
+ return NS_OK;
+}
+
+// callback while UDP socket is connected
+NS_IMETHODIMP NrUdpSocketIpc::CallListenerConnected() {
+ ASSERT_ON_THREAD(io_thread_);
+
+ ReentrantMonitorAutoEnter mon(monitor_);
+
+ r_log(LOG_GENERIC, LOG_DEBUG, "UDP socket connected this=%p", (void*)this);
+ MOZ_ASSERT(state_ == NR_CONNECTED);
+
+ nsresult rv = SetAddress();
+ if (NS_FAILED(rv)) {
+ mon.NotifyAll();
+ return rv;
+ }
+
+ r_log(LOG_GENERIC, LOG_INFO, "Exit UDP socket connected");
+ mon.NotifyAll();
+
+ return NS_OK;
+}
+
+// callback while UDP socket is closed
+NS_IMETHODIMP NrUdpSocketIpc::CallListenerClosed() {
+ ASSERT_ON_THREAD(io_thread_);
+
+ ReentrantMonitorAutoEnter mon(monitor_);
+
+ r_log(LOG_GENERIC, LOG_DEBUG, "UDP socket closed this=%p", (void*)this);
+ MOZ_ASSERT(state_ == NR_CONNECTED || state_ == NR_CLOSING);
+ state_ = NR_CLOSED;
+
+ return NS_OK;
+}
+
+//
+// NrSocketBase methods.
+//
+int NrUdpSocketIpc::create(nr_transport_addr* addr) {
+ ASSERT_ON_THREAD(sts_thread_);
+
+ int r, _status;
+ nsresult rv;
+ int32_t port;
+ nsCString host;
+
+ ReentrantMonitorAutoEnter mon(monitor_);
+
+ if (state_ != NR_INIT) {
+ ABORT(R_INTERNAL);
+ }
+
+ sts_thread_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ MOZ_ASSERT(false, "Failed to get STS thread");
+ ABORT(R_INTERNAL);
+ }
+
+ if ((r = nr_transport_addr_get_addrstring_and_port(addr, &host, &port))) {
+ ABORT(r);
+ }
+
+ // wildcard address will be resolved at NrUdpSocketIpc::CallListenerVoid
+ if ((r = nr_transport_addr_copy(&my_addr_, addr))) {
+ ABORT(r);
+ }
+
+ state_ = NR_CONNECTING;
+
+ MOZ_ASSERT(io_thread_);
+ RUN_ON_THREAD(io_thread_,
+ mozilla::WrapRunnable(RefPtr<NrUdpSocketIpc>(this),
+ &NrUdpSocketIpc::create_i, host,
+ static_cast<uint16_t>(port)),
+ NS_DISPATCH_NORMAL);
+
+ // Wait until socket creation complete.
+ mon.Wait();
+
+ if (err_) {
+ close();
+ ABORT(R_INTERNAL);
+ }
+
+ state_ = NR_CONNECTED;
+
+ _status = 0;
+abort:
+ return (_status);
+}
+
+int NrUdpSocketIpc::sendto(const void* msg, size_t len, int flags,
+ const nr_transport_addr* to) {
+ ASSERT_ON_THREAD(sts_thread_);
+
+ ReentrantMonitorAutoEnter mon(monitor_);
+
+ // If send err happened before, simply return the error.
+ if (err_) {
+ return R_IO_ERROR;
+ }
+
+ if (state_ != NR_CONNECTED) {
+ return R_INTERNAL;
+ }
+
+ int r;
+ net::NetAddr addr;
+ if ((r = nr_transport_addr_to_netaddr(to, &addr))) {
+ return r;
+ }
+
+ if (nr_is_stun_request_message((UCHAR*)msg, len) && ShouldDrop(len)) {
+ return R_WOULDBLOCK;
+ }
+
+ UniquePtr<MediaPacket> buf(new MediaPacket);
+ buf->Copy(static_cast<const uint8_t*>(msg), len);
+
+ RUN_ON_THREAD(
+ io_thread_,
+ mozilla::WrapRunnable(RefPtr<NrUdpSocketIpc>(this),
+ &NrUdpSocketIpc::sendto_i, addr, std::move(buf)),
+ NS_DISPATCH_NORMAL);
+ return 0;
+}
+
+void NrUdpSocketIpc::close() {
+ r_log(LOG_GENERIC, LOG_DEBUG, "NrUdpSocketIpc::close()");
+
+ ASSERT_ON_THREAD(sts_thread_);
+
+ ReentrantMonitorAutoEnter mon(monitor_);
+ state_ = NR_CLOSING;
+
+ RUN_ON_THREAD(io_thread_,
+ mozilla::WrapRunnable(RefPtr<NrUdpSocketIpc>(this),
+ &NrUdpSocketIpc::close_i),
+ NS_DISPATCH_NORMAL);
+
+ // remove all enqueued messages
+ std::queue<RefPtr<nr_udp_message>> empty;
+ std::swap(received_msgs_, empty);
+}
+
+int NrUdpSocketIpc::recvfrom(void* buf, size_t maxlen, size_t* len, int flags,
+ nr_transport_addr* from) {
+ ASSERT_ON_THREAD(sts_thread_);
+
+ ReentrantMonitorAutoEnter mon(monitor_);
+
+ int r, _status;
+ uint32_t consumed_len;
+
+ *len = 0;
+
+ if (state_ != NR_CONNECTED) {
+ ABORT(R_INTERNAL);
+ }
+
+ if (received_msgs_.empty()) {
+ ABORT(R_WOULDBLOCK);
+ }
+
+ {
+ RefPtr<nr_udp_message> msg(received_msgs_.front());
+
+ received_msgs_.pop();
+
+ if ((r = nr_praddr_to_transport_addr(&msg->from, from, IPPROTO_UDP, 0))) {
+ err_ = true;
+ MOZ_ASSERT(false, "Get bogus address for received UDP packet");
+ ABORT(r);
+ }
+
+ consumed_len = std::min(maxlen, msg->data->len());
+ if (consumed_len < msg->data->len()) {
+ r_log(LOG_GENERIC, LOG_DEBUG,
+ "Partial received UDP packet will be discard");
+ }
+
+ memcpy(buf, msg->data->data(), consumed_len);
+ *len = consumed_len;
+ }
+
+ _status = 0;
+abort:
+ return (_status);
+}
+
+int NrUdpSocketIpc::getaddr(nr_transport_addr* addrp) {
+ ASSERT_ON_THREAD(sts_thread_);
+
+ ReentrantMonitorAutoEnter mon(monitor_);
+
+ return nr_transport_addr_copy(addrp, &my_addr_);
+}
+
+int NrUdpSocketIpc::connect(const nr_transport_addr* addr) {
+ int r, _status;
+ int32_t port;
+ nsCString host;
+
+ ReentrantMonitorAutoEnter mon(monitor_);
+ r_log(LOG_GENERIC, LOG_DEBUG, "NrUdpSocketIpc::connect(%s) this=%p",
+ addr->as_string, (void*)this);
+
+ if ((r = nr_transport_addr_get_addrstring_and_port(addr, &host, &port))) {
+ ABORT(r);
+ }
+
+ RUN_ON_THREAD(io_thread_,
+ mozilla::WrapRunnable(RefPtr<NrUdpSocketIpc>(this),
+ &NrUdpSocketIpc::connect_i, host,
+ static_cast<uint16_t>(port)),
+ NS_DISPATCH_NORMAL);
+
+ // Wait until connect() completes.
+ mon.Wait();
+
+ r_log(LOG_GENERIC, LOG_DEBUG,
+ "NrUdpSocketIpc::connect this=%p completed err_ = %s", (void*)this,
+ err_ ? "true" : "false");
+
+ if (err_) {
+ ABORT(R_INTERNAL);
+ }
+
+ _status = 0;
+abort:
+ return _status;
+}
+
+int NrUdpSocketIpc::write(const void* msg, size_t len, size_t* written) {
+ MOZ_ASSERT(false);
+ return R_INTERNAL;
+}
+
+int NrUdpSocketIpc::read(void* buf, size_t maxlen, size_t* len) {
+ MOZ_ASSERT(false);
+ return R_INTERNAL;
+}
+
+int NrUdpSocketIpc::listen(int backlog) {
+ MOZ_ASSERT(false);
+ return R_INTERNAL;
+}
+
+int NrUdpSocketIpc::accept(nr_transport_addr* addrp, nr_socket** sockp) {
+ MOZ_ASSERT(false);
+ return R_INTERNAL;
+}
+
+// IO thread executors
+void NrUdpSocketIpc::create_i(const nsACString& host, const uint16_t port) {
+ ASSERT_ON_THREAD(io_thread_);
+
+ uint32_t minBuffSize = 0;
+ RefPtr<dom::UDPSocketChild> socketChild = new dom::UDPSocketChild();
+
+ // This can spin the event loop; don't do that with the monitor held
+ socketChild->SetBackgroundSpinsEvents();
+
+ ReentrantMonitorAutoEnter mon(monitor_);
+ if (!socket_child_) {
+ socket_child_ = socketChild;
+ socket_child_->SetFilterName(
+ nsCString(NS_NETWORK_SOCKET_FILTER_HANDLER_STUN_SUFFIX));
+ } else {
+ socketChild = nullptr;
+ }
+
+ RefPtr<NrUdpSocketIpcProxy> proxy(new NrUdpSocketIpcProxy);
+ nsresult rv = proxy->Init(this);
+ if (NS_FAILED(rv)) {
+ err_ = true;
+ mon.NotifyAll();
+ return;
+ }
+
+#ifdef XP_WIN
+ if (!mozilla::IsWin8OrLater()) {
+ // Increase default receive and send buffer size on <= Win7 to be able to
+ // receive and send an unpaced HD (>= 720p = 1280x720 - I Frame ~ 21K size)
+ // stream without losing packets.
+ // Manual testing showed that 100K buffer size was not enough and the
+ // packet loss dis-appeared with 256K buffer size.
+ // See bug 1252769 for future improvements of this.
+ minBuffSize = 256 * 1024;
+ }
+#endif
+ // XXX bug 1126232 - don't use null Principal!
+ if (NS_FAILED(socket_child_->Bind(proxy, nullptr, host, port,
+ /* addressReuse = */ false,
+ /* loopback = */ false,
+ /* recv buffer size */ minBuffSize,
+ /* send buffer size */ minBuffSize))) {
+ err_ = true;
+ MOZ_ASSERT(false, "Failed to create UDP socket");
+ mon.NotifyAll();
+ return;
+ }
+}
+
+void NrUdpSocketIpc::connect_i(const nsACString& host, const uint16_t port) {
+ ASSERT_ON_THREAD(io_thread_);
+ nsresult rv;
+ ReentrantMonitorAutoEnter mon(monitor_);
+
+ RefPtr<NrUdpSocketIpcProxy> proxy(new NrUdpSocketIpcProxy);
+ rv = proxy->Init(this);
+ if (NS_FAILED(rv)) {
+ err_ = true;
+ mon.NotifyAll();
+ return;
+ }
+
+ socket_child_->Connect(proxy, host, port);
+}
+
+void NrUdpSocketIpc::sendto_i(const net::NetAddr& addr,
+ UniquePtr<MediaPacket> buf) {
+ ASSERT_ON_THREAD(io_thread_);
+
+ ReentrantMonitorAutoEnter mon(monitor_);
+
+ if (!socket_child_) {
+ MOZ_ASSERT(false);
+ err_ = true;
+ return;
+ }
+ if (NS_FAILED(
+ socket_child_->SendWithAddress(&addr, buf->data(), buf->len()))) {
+ err_ = true;
+ }
+}
+
+void NrUdpSocketIpc::close_i() {
+ ASSERT_ON_THREAD(io_thread_);
+
+ if (socket_child_) {
+ socket_child_->Close();
+ socket_child_ = nullptr;
+ }
+}
+
+#if defined(MOZILLA_INTERNAL_API)
+
+static void ReleaseIOThread_s() { sThread->ReleaseUse(); }
+
+void NrUdpSocketIpc::destroy_i() {
+ close_i();
+
+ RUN_ON_THREAD(sts_thread_, WrapRunnableNM(&ReleaseIOThread_s),
+ NS_DISPATCH_NORMAL);
+}
+#endif
+
+void NrUdpSocketIpc::recv_callback_s(RefPtr<nr_udp_message> msg) {
+ ASSERT_ON_THREAD(sts_thread_);
+
+ {
+ ReentrantMonitorAutoEnter mon(monitor_);
+ if (state_ != NR_CONNECTED) {
+ return;
+ }
+ }
+
+ // enqueue received message
+ received_msgs_.push(msg);
+
+ if ((poll_flags() & PR_POLL_READ)) {
+ fire_callback(NR_ASYNC_WAIT_READ);
+ }
+}
+
+} // namespace mozilla
+
+using namespace mozilla;
+
+// Bridge to the nr_socket interface
+static int nr_socket_local_destroy(void** objp);
+static int nr_socket_local_sendto(void* obj, const void* msg, size_t len,
+ int flags, const nr_transport_addr* to);
+static int nr_socket_local_recvfrom(void* obj, void* restrict buf,
+ size_t maxlen, size_t* len, int flags,
+ nr_transport_addr* from);
+static int nr_socket_local_getfd(void* obj, NR_SOCKET* fd);
+static int nr_socket_local_getaddr(void* obj, nr_transport_addr* addrp);
+static int nr_socket_local_close(void* obj);
+static int nr_socket_local_connect(void* obj, const nr_transport_addr* addr);
+static int nr_socket_local_write(void* obj, const void* msg, size_t len,
+ size_t* written);
+static int nr_socket_local_read(void* obj, void* restrict buf, size_t maxlen,
+ size_t* len);
+static int nr_socket_local_listen(void* obj, int backlog);
+static int nr_socket_local_accept(void* obj, nr_transport_addr* addrp,
+ nr_socket** sockp);
+
+static nr_socket_vtbl nr_socket_local_vtbl = {2,
+ nr_socket_local_destroy,
+ nr_socket_local_sendto,
+ nr_socket_local_recvfrom,
+ nr_socket_local_getfd,
+ nr_socket_local_getaddr,
+ nr_socket_local_connect,
+ nr_socket_local_write,
+ nr_socket_local_read,
+ nr_socket_local_close,
+ nr_socket_local_listen,
+ nr_socket_local_accept};
+
+/* static */
+int NrSocketBase::CreateSocket(
+ nr_transport_addr* addr, RefPtr<NrSocketBase>* sock,
+ const std::shared_ptr<NrSocketProxyConfig>& config) {
+ int r, _status;
+
+ if (IsForbiddenAddress(addr)) {
+ ABORT(R_REJECTED);
+ }
+
+ if (config && config->GetForceProxy() && addr->protocol == IPPROTO_UDP) {
+ ABORT(R_REJECTED);
+ }
+
+ // create IPC bridge for content process
+ if (XRE_IsParentProcess()) {
+ // TODO: Make NrTcpSocket work on the parent process
+ *sock = new NrSocket();
+ } else if (XRE_IsSocketProcess()) {
+ if (addr->protocol == IPPROTO_TCP) {
+ *sock = new NrTcpSocket(config);
+ } else {
+ *sock = new NrSocket();
+ }
+ } else {
+ if (addr->protocol == IPPROTO_TCP) {
+ *sock = new NrTcpSocket(config);
+ } else {
+ *sock = new NrUdpSocketIpc();
+ }
+ }
+
+ r = (*sock)->create(addr);
+ if (r) ABORT(r);
+
+ _status = 0;
+abort:
+ if (_status) {
+ *sock = nullptr;
+ }
+ return _status;
+}
+
+// static
+bool NrSocketBase::IsForbiddenAddress(nr_transport_addr* addr) {
+ int r, port;
+
+ r = nr_transport_addr_get_port(addr, &port);
+ if (r) {
+ return true;
+ }
+
+ // allow auto assigned ports
+ if (port != 0) {
+ // Don't need to check an override scheme
+ nsresult rv = NS_CheckPortSafety(port, nullptr);
+ if (NS_FAILED(rv)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static int nr_socket_local_destroy(void** objp) {
+ if (!objp || !*objp) return 0;
+
+ NrSocketBase* sock = static_cast<NrSocketBase*>(*objp);
+ *objp = nullptr;
+
+ sock->close(); // Signal STS that we want not to listen
+ sock->Release(); // Decrement the ref count
+
+ return 0;
+}
+
+static int nr_socket_local_sendto(void* obj, const void* msg, size_t len,
+ int flags, const nr_transport_addr* addr) {
+ NrSocketBase* sock = static_cast<NrSocketBase*>(obj);
+
+ return sock->sendto(msg, len, flags, addr);
+}
+
+static int nr_socket_local_recvfrom(void* obj, void* restrict buf,
+ size_t maxlen, size_t* len, int flags,
+ nr_transport_addr* addr) {
+ NrSocketBase* sock = static_cast<NrSocketBase*>(obj);
+
+ return sock->recvfrom(buf, maxlen, len, flags, addr);
+}
+
+static int nr_socket_local_getfd(void* obj, NR_SOCKET* fd) {
+ NrSocketBase* sock = static_cast<NrSocketBase*>(obj);
+
+ *fd = sock;
+
+ return 0;
+}
+
+static int nr_socket_local_getaddr(void* obj, nr_transport_addr* addrp) {
+ NrSocketBase* sock = static_cast<NrSocketBase*>(obj);
+
+ return sock->getaddr(addrp);
+}
+
+static int nr_socket_local_close(void* obj) {
+ NrSocketBase* sock = static_cast<NrSocketBase*>(obj);
+
+ sock->close();
+
+ return 0;
+}
+
+static int nr_socket_local_write(void* obj, const void* msg, size_t len,
+ size_t* written) {
+ NrSocketBase* sock = static_cast<NrSocketBase*>(obj);
+
+ return sock->write(msg, len, written);
+}
+
+static int nr_socket_local_read(void* obj, void* restrict buf, size_t maxlen,
+ size_t* len) {
+ NrSocketBase* sock = static_cast<NrSocketBase*>(obj);
+
+ return sock->read(buf, maxlen, len);
+}
+
+static int nr_socket_local_connect(void* obj, const nr_transport_addr* addr) {
+ NrSocketBase* sock = static_cast<NrSocketBase*>(obj);
+
+ return sock->connect(addr);
+}
+
+static int nr_socket_local_listen(void* obj, int backlog) {
+ NrSocketBase* sock = static_cast<NrSocketBase*>(obj);
+
+ return sock->listen(backlog);
+}
+
+static int nr_socket_local_accept(void* obj, nr_transport_addr* addrp,
+ nr_socket** sockp) {
+ NrSocketBase* sock = static_cast<NrSocketBase*>(obj);
+
+ return sock->accept(addrp, sockp);
+}
+
+// Implement async api
+int NR_async_wait(NR_SOCKET sock, int how, NR_async_cb cb, void* cb_arg,
+ char* function, int line) {
+ NrSocketBase* s = static_cast<NrSocketBase*>(sock);
+
+ return s->async_wait(how, cb, cb_arg, function, line);
+}
+
+int NR_async_cancel(NR_SOCKET sock, int how) {
+ NrSocketBase* s = static_cast<NrSocketBase*>(sock);
+
+ return s->cancel(how);
+}
+
+nr_socket_vtbl* NrSocketBase::vtbl() { return &nr_socket_local_vtbl; }
diff --git a/dom/media/webrtc/transport/nr_socket_prsock.h b/dom/media/webrtc/transport/nr_socket_prsock.h
new file mode 100644
index 0000000000..010fcb59bc
--- /dev/null
+++ b/dom/media/webrtc/transport/nr_socket_prsock.h
@@ -0,0 +1,320 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+
+/* Some source code here from nICEr. Copyright is:
+
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+*/
+
+// Implementation of nICEr/nr_socket that is tied to the Gecko
+// SocketTransportService.
+
+#ifndef nr_socket_prsock__
+#define nr_socket_prsock__
+
+#include <memory>
+#include <queue>
+
+#include "nspr.h"
+#include "prio.h"
+
+#include "nsCOMPtr.h"
+#include "nsASocketHandler.h"
+#include "nsXPCOM.h"
+#include "nsIEventTarget.h"
+#include "nsIUDPSocketChild.h"
+#include "nsProxyRelease.h"
+#include "nsThreadUtils.h"
+
+#include "mediapacket.h"
+#include "m_cpp_utils.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/ClearOnShutdown.h"
+
+// nICEr includes
+extern "C" {
+#include "transport_addr.h"
+#include "async_wait.h"
+}
+
+// Stub declaration for nICEr type
+typedef struct nr_socket_vtbl_ nr_socket_vtbl;
+typedef struct nr_socket_ nr_socket;
+
+#if defined(MOZILLA_INTERNAL_API)
+namespace mozilla {
+class NrSocketProxyConfig;
+} // namespace mozilla
+#endif
+
+namespace mozilla {
+
+namespace net {
+union NetAddr;
+}
+
+namespace dom {
+class UDPSocketChild;
+} // namespace dom
+
+class NrSocketBase {
+ public:
+ NrSocketBase() : connect_invoked_(false), poll_flags_(0) {
+ memset(cbs_, 0, sizeof(cbs_));
+ memset(cb_args_, 0, sizeof(cb_args_));
+ memset(&my_addr_, 0, sizeof(my_addr_));
+ }
+ virtual ~NrSocketBase() = default;
+
+ // Factory method; will create either an NrSocket, NrUdpSocketIpc, or
+ // NrTcpSocketIpc as appropriate.
+ static int CreateSocket(nr_transport_addr* addr, RefPtr<NrSocketBase>* sock,
+ const std::shared_ptr<NrSocketProxyConfig>& config);
+ static bool IsForbiddenAddress(nr_transport_addr* addr);
+
+ // the nr_socket APIs
+ virtual int create(nr_transport_addr* addr) = 0;
+ virtual int sendto(const void* msg, size_t len, int flags,
+ const nr_transport_addr* to) = 0;
+ virtual int recvfrom(void* buf, size_t maxlen, size_t* len, int flags,
+ nr_transport_addr* from) = 0;
+ virtual int getaddr(nr_transport_addr* addrp) = 0;
+ virtual void close() = 0;
+ virtual int connect(const nr_transport_addr* addr) = 0;
+ virtual int write(const void* msg, size_t len, size_t* written) = 0;
+ virtual int read(void* buf, size_t maxlen, size_t* len) = 0;
+ virtual int listen(int backlog) = 0;
+ virtual int accept(nr_transport_addr* addrp, nr_socket** sockp) = 0;
+
+ // Implementations of the async_event APIs
+ virtual int async_wait(int how, NR_async_cb cb, void* cb_arg, char* function,
+ int line);
+ virtual int cancel(int how);
+
+ // nsISupport reference counted interface
+ NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
+
+ uint32_t poll_flags() { return poll_flags_; }
+
+ virtual nr_socket_vtbl* vtbl(); // To access in test classes.
+
+ static TimeStamp short_term_violation_time();
+ static TimeStamp long_term_violation_time();
+ const nr_transport_addr& my_addr() const { return my_addr_; }
+
+ void fire_callback(int how);
+
+ protected:
+ bool connect_invoked_;
+ nr_transport_addr my_addr_;
+
+ private:
+ NR_async_cb cbs_[NR_ASYNC_WAIT_WRITE + 1];
+ void* cb_args_[NR_ASYNC_WAIT_WRITE + 1];
+ uint32_t poll_flags_;
+};
+
+class NrSocket : public NrSocketBase, public nsASocketHandler {
+ public:
+ NrSocket() : fd_(nullptr) {}
+
+ // Implement nsASocket
+ virtual void OnSocketReady(PRFileDesc* fd, int16_t outflags) override;
+ virtual void OnSocketDetached(PRFileDesc* fd) override;
+ virtual void IsLocal(bool* aIsLocal) override;
+ virtual uint64_t ByteCountSent() override { return 0; }
+ virtual uint64_t ByteCountReceived() override { return 0; }
+
+ // nsISupports methods
+ NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DESTROY(NrSocket, Destroy(),
+ override);
+ virtual void Destroy() { delete this; }
+
+ // Implementations of the async_event APIs
+ virtual int async_wait(int how, NR_async_cb cb, void* cb_arg, char* function,
+ int line) override;
+ virtual int cancel(int how) override;
+
+ // Implementations of the nr_socket APIs
+ virtual int create(nr_transport_addr* addr)
+ override; // (really init, but it's called create)
+ virtual int sendto(const void* msg, size_t len, int flags,
+ const nr_transport_addr* to) override;
+ virtual int recvfrom(void* buf, size_t maxlen, size_t* len, int flags,
+ nr_transport_addr* from) override;
+ virtual int getaddr(nr_transport_addr* addrp) override;
+ virtual void close() override;
+ virtual int connect(const nr_transport_addr* addr) override;
+ virtual int write(const void* msg, size_t len, size_t* written) override;
+ virtual int read(void* buf, size_t maxlen, size_t* len) override;
+ virtual int listen(int backlog) override;
+ virtual int accept(nr_transport_addr* addrp, nr_socket** sockp) override;
+
+ protected:
+ virtual ~NrSocket() {
+ if (fd_) PR_Close(fd_);
+ }
+
+ DISALLOW_COPY_ASSIGN(NrSocket);
+
+ PRFileDesc* fd_;
+ nsCOMPtr<nsIEventTarget> ststhread_;
+};
+
+struct nr_udp_message {
+ nr_udp_message(const PRNetAddr& from, UniquePtr<MediaPacket>&& data)
+ : from(from), data(std::move(data)) {}
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nr_udp_message);
+
+ PRNetAddr from;
+ UniquePtr<MediaPacket> data;
+
+ private:
+ ~nr_udp_message() = default;
+ DISALLOW_COPY_ASSIGN(nr_udp_message);
+};
+
+class NrSocketIpc : public NrSocketBase {
+ public:
+ enum NrSocketIpcState {
+ NR_INIT,
+ NR_CONNECTING,
+ NR_CONNECTED,
+ NR_CLOSING,
+ NR_CLOSED,
+ };
+
+ NrSocketIpc(nsIEventTarget* aThread);
+
+ protected:
+ nsCOMPtr<nsIEventTarget> sts_thread_;
+ // Note: for UDP PBackground, this is a thread held by SingletonThreadHolder.
+ // For TCP PNecko, this is MainThread (and TCPSocket requires MainThread
+ // currently)
+ const nsCOMPtr<nsIEventTarget> io_thread_;
+ virtual ~NrSocketIpc() = default;
+
+ private:
+ DISALLOW_COPY_ASSIGN(NrSocketIpc);
+};
+
+class NrUdpSocketIpc : public NrSocketIpc {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NrUdpSocketIpc, override)
+
+ NS_IMETHODIMP CallListenerError(const nsACString& message,
+ const nsACString& filename,
+ uint32_t line_number);
+ NS_IMETHODIMP CallListenerReceivedData(const nsACString& host, uint16_t port,
+ const nsTArray<uint8_t>& data);
+ NS_IMETHODIMP CallListenerOpened();
+ NS_IMETHODIMP CallListenerConnected();
+ NS_IMETHODIMP CallListenerClosed();
+
+ NrUdpSocketIpc();
+
+ // Implementations of the NrSocketBase APIs
+ virtual int create(nr_transport_addr* addr) override;
+ virtual int sendto(const void* msg, size_t len, int flags,
+ const nr_transport_addr* to) override;
+ virtual int recvfrom(void* buf, size_t maxlen, size_t* len, int flags,
+ nr_transport_addr* from) override;
+ virtual int getaddr(nr_transport_addr* addrp) override;
+ virtual void close() override;
+ virtual int connect(const nr_transport_addr* addr) override;
+ virtual int write(const void* msg, size_t len, size_t* written) override;
+ virtual int read(void* buf, size_t maxlen, size_t* len) override;
+ virtual int listen(int backlog) override;
+ virtual int accept(nr_transport_addr* addrp, nr_socket** sockp) override;
+
+ private:
+ virtual ~NrUdpSocketIpc();
+ virtual void Destroy();
+
+ DISALLOW_COPY_ASSIGN(NrUdpSocketIpc);
+
+ nsresult SetAddress(); // Set the local address from parent info.
+
+ // Main or private thread executors of the NrSocketBase APIs
+ void create_i(const nsACString& host, const uint16_t port);
+ void connect_i(const nsACString& host, const uint16_t port);
+ void sendto_i(const net::NetAddr& addr, UniquePtr<MediaPacket> buf);
+ void close_i();
+#if defined(MOZILLA_INTERNAL_API) && !defined(MOZILLA_XPCOMRT_API)
+ void destroy_i();
+#endif
+ // STS thread executor
+ void recv_callback_s(RefPtr<nr_udp_message> msg);
+
+ ReentrantMonitor monitor_ MOZ_UNANNOTATED; // protects err_and state_
+ bool err_;
+ NrSocketIpcState state_;
+
+ std::queue<RefPtr<nr_udp_message>> received_msgs_;
+
+ // only accessed from the io_thread
+ RefPtr<dom::UDPSocketChild> socket_child_;
+};
+
+// The socket child holds onto one of these, which just passes callbacks
+// through and makes sure the ref to the NrSocketIpc is released on STS.
+class NrUdpSocketIpcProxy : public nsIUDPSocketInternal {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIUDPSOCKETINTERNAL
+
+ nsresult Init(const RefPtr<NrUdpSocketIpc>& socket);
+
+ private:
+ virtual ~NrUdpSocketIpcProxy();
+
+ RefPtr<NrUdpSocketIpc> socket_;
+ nsCOMPtr<nsIEventTarget> sts_thread_;
+};
+
+int nr_netaddr_to_transport_addr(const net::NetAddr* netaddr,
+ nr_transport_addr* addr, int protocol);
+int nr_praddr_to_transport_addr(const PRNetAddr* praddr,
+ nr_transport_addr* addr, int protocol,
+ int keep);
+int nr_transport_addr_get_addrstring_and_port(const nr_transport_addr* addr,
+ nsACString* host, int32_t* port);
+} // namespace mozilla
+#endif
diff --git a/dom/media/webrtc/transport/nr_socket_tcp.cpp b/dom/media/webrtc/transport/nr_socket_tcp.cpp
new file mode 100644
index 0000000000..a9cc5db312
--- /dev/null
+++ b/dom/media/webrtc/transport/nr_socket_tcp.cpp
@@ -0,0 +1,310 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+Copyright (c) 2013, Mozilla
+
+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 Adobe Systems, Network Resonance, Mozilla 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
+OWNER 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.
+*/
+
+#include "nr_socket_tcp.h"
+
+#include "mozilla/ErrorNames.h"
+
+#include "WebrtcTCPSocketWrapper.h"
+
+namespace mozilla {
+using namespace net;
+
+using std::shared_ptr;
+
+class NrTcpSocketData {
+ public:
+ explicit NrTcpSocketData(nsTArray<uint8_t>&& aData)
+ : mData(std::move(aData)) {
+ MOZ_COUNT_CTOR(NrTcpSocketData);
+ }
+
+ MOZ_COUNTED_DTOR(NrTcpSocketData)
+
+ const nsTArray<uint8_t>& GetData() const { return mData; }
+
+ private:
+ nsTArray<uint8_t> mData;
+};
+
+NrTcpSocket::NrTcpSocket(const shared_ptr<NrSocketProxyConfig>& aConfig)
+ : mClosed(false),
+ mReadOffset(0),
+ mConfig(aConfig),
+ mWebrtcTCPSocket(nullptr) {
+ r_log(LOG_GENERIC, LOG_DEBUG, "NrTcpSocket::NrTcpSocket %p\n", this);
+}
+
+NrTcpSocket::~NrTcpSocket() {
+ r_log(LOG_GENERIC, LOG_DEBUG, "NrTcpSocket::~NrTcpSocket %p\n", this);
+ MOZ_ASSERT(!mWebrtcTCPSocket, "webrtc TCP socket not null");
+}
+
+int NrTcpSocket::create(nr_transport_addr* aAddr) {
+ r_log(LOG_GENERIC, LOG_DEBUG, "NrTcpSocket::create %p\n", this);
+ int32_t port;
+ nsCString host;
+
+ // Sanity check
+ if (nr_transport_addr_get_addrstring_and_port(aAddr, &host, &port)) {
+ return R_FAILED;
+ }
+
+ if (nr_transport_addr_copy(&my_addr_, aAddr)) {
+ return R_FAILED;
+ }
+
+ return 0;
+}
+
+int NrTcpSocket::connect(const nr_transport_addr* aAddr) {
+ r_log(LOG_GENERIC, LOG_DEBUG, "NrTcpSocket::connect %p\n", this);
+
+ nsCString remote_host;
+ int remote_port;
+
+ if (NS_WARN_IF(nr_transport_addr_get_addrstring_and_port(aAddr, &remote_host,
+ &remote_port))) {
+ return R_FAILED;
+ }
+
+ bool use_tls = aAddr->tls;
+
+ nsCString local_addr;
+ int local_port;
+
+ if (NS_WARN_IF(nr_transport_addr_get_addrstring_and_port(
+ &my_addr_, &local_addr, &local_port))) {
+ return R_FAILED;
+ }
+
+ mWebrtcTCPSocket = new WebrtcTCPSocketWrapper(this);
+
+ mWebrtcTCPSocket->AsyncOpen(remote_host, remote_port, local_addr, local_port,
+ use_tls, mConfig);
+
+ // trigger nr_socket_buffered to set write/read callback
+ return R_WOULDBLOCK;
+}
+
+void NrTcpSocket::close() {
+ r_log(LOG_GENERIC, LOG_DEBUG, "NrTcpSocket::close %p\n", this);
+
+ if (mClosed) {
+ return;
+ }
+
+ mClosed = true;
+
+ // We're not always open at this point.
+ if (mWebrtcTCPSocket) {
+ mWebrtcTCPSocket->Close();
+ mWebrtcTCPSocket = nullptr;
+ }
+}
+
+int NrTcpSocket::write(const void* aBuffer, size_t aCount, size_t* aWrote) {
+ r_log(LOG_GENERIC, LOG_DEBUG, "NrTcpSocket::write %p count=%zu\n", this,
+ aCount);
+
+ if (mClosed) {
+ return R_FAILED;
+ }
+
+ if (!aWrote) {
+ return R_FAILED;
+ }
+
+ if (NS_WARN_IF(!mWebrtcTCPSocket)) {
+ return R_FAILED;
+ }
+
+ *aWrote = aCount;
+
+ if (aCount > 0) {
+ nsTArray<uint8_t> writeData;
+ writeData.SetLength(aCount);
+ memcpy(writeData.Elements(), aBuffer, aCount);
+
+ mWebrtcTCPSocket->SendWrite(std::move(writeData));
+ }
+
+ return 0;
+}
+
+int NrTcpSocket::read(void* aBuffer, size_t aCount, size_t* aRead) {
+ r_log(LOG_GENERIC, LOG_DEBUG, "NrTcpSocket::read %p\n", this);
+
+ if (mClosed) {
+ return R_FAILED;
+ }
+
+ if (!aRead) {
+ return R_FAILED;
+ }
+
+ *aRead = 0;
+
+ if (mReadQueue.empty()) {
+ return R_WOULDBLOCK;
+ }
+
+ while (aCount > 0 && !mReadQueue.empty()) {
+ const NrTcpSocketData& data = mReadQueue.front();
+
+ size_t remainingCount = data.GetData().Length() - mReadOffset;
+ size_t amountToCopy = std::min(aCount, remainingCount);
+
+ char* buffer = static_cast<char*>(aBuffer) + (*aRead);
+
+ memcpy(buffer, data.GetData().Elements() + mReadOffset, amountToCopy);
+
+ mReadOffset += amountToCopy;
+ *aRead += amountToCopy;
+ aCount -= amountToCopy;
+
+ if (remainingCount == amountToCopy) {
+ mReadOffset = 0;
+ mReadQueue.pop_front();
+ }
+ }
+
+ return 0;
+}
+
+int NrTcpSocket::getaddr(nr_transport_addr* aAddr) {
+ r_log(LOG_GENERIC, LOG_DEBUG, "NrTcpSocket::getaddr %p\n", this);
+ return nr_transport_addr_copy(aAddr, &my_addr_);
+}
+
+int NrTcpSocket::sendto(const void* aBuffer, size_t aCount, int aFlags,
+ const nr_transport_addr* aAddr) {
+ // never call this
+ MOZ_ASSERT(0);
+ return R_FAILED;
+}
+
+int NrTcpSocket::recvfrom(void* aBuffer, size_t aCount, size_t* aRead,
+ int aFlags, nr_transport_addr* aAddr) {
+ // never call this
+ MOZ_ASSERT(0);
+ return R_FAILED;
+}
+
+int NrTcpSocket::listen(int aBacklog) {
+ r_log(LOG_GENERIC, LOG_DEBUG, "NrTcpSocket::listen %p\n", this);
+ return R_INTERNAL;
+}
+
+int NrTcpSocket::accept(nr_transport_addr* aAddr, nr_socket** aSocket) {
+ r_log(LOG_GENERIC, LOG_DEBUG, "NrTcpSocket::accept %p\n", this);
+ return R_INTERNAL;
+}
+
+// WebrtcTCPSocketCallback
+void NrTcpSocket::OnClose(nsresult aReason) {
+ nsCString errorName;
+ GetErrorName(aReason, errorName);
+
+ r_log(LOG_GENERIC, LOG_ERR, "NrTcpSocket::OnClose %p reason=%u name=%s\n",
+ this, static_cast<uint32_t>(aReason), errorName.get());
+
+ close();
+
+ DoCallbacks();
+}
+
+void NrTcpSocket::OnConnected(const nsACString& aProxyType) {
+ r_log(LOG_GENERIC, LOG_DEBUG, "NrTcpSocket::OnConnected %p\n", this);
+ if (aProxyType != "" && aProxyType != "direct") {
+ my_addr_.is_proxied = true;
+ }
+
+ DoCallbacks();
+}
+
+void NrTcpSocket::OnRead(nsTArray<uint8_t>&& aReadData) {
+ r_log(LOG_GENERIC, LOG_DEBUG, "NrTcpSocket::OnRead %p read=%zu\n", this,
+ aReadData.Length());
+
+ mReadQueue.emplace_back(std::move(aReadData));
+
+ DoCallbacks();
+}
+
+void NrTcpSocket::DoCallbacks() {
+ size_t lastCount = -1;
+ size_t currentCount = 0;
+ while ((poll_flags() & PR_POLL_READ) != 0 &&
+ // Make sure whatever is reading knows we're closed. This doesn't need
+ // to happen for writes since ICE doesn't like a failing writable.
+ (mClosed || (currentCount = CountUnreadBytes()) > 0) &&
+ lastCount != currentCount) {
+ fire_callback(NR_ASYNC_WAIT_READ);
+ lastCount = currentCount;
+ }
+
+ // We're always ready to write after we're connected. The parent process will
+ // buffer writes for us.
+ if (!mClosed && mWebrtcTCPSocket && (poll_flags() & PR_POLL_WRITE) != 0) {
+ fire_callback(NR_ASYNC_WAIT_WRITE);
+ }
+}
+
+size_t NrTcpSocket::CountUnreadBytes() const {
+ size_t count = 0;
+
+ for (const NrTcpSocketData& data : mReadQueue) {
+ count += data.GetData().Length();
+ }
+
+ MOZ_ASSERT(count >= mReadOffset, "offset exceeds read buffer length");
+
+ count -= mReadOffset;
+
+ return count;
+}
+
+void NrTcpSocket::AssignChannel_DoNotUse(WebrtcTCPSocketWrapper* aWrapper) {
+ mWebrtcTCPSocket = aWrapper;
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/transport/nr_socket_tcp.h b/dom/media/webrtc/transport/nr_socket_tcp.h
new file mode 100644
index 0000000000..ebf806a924
--- /dev/null
+++ b/dom/media/webrtc/transport/nr_socket_tcp.h
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+Copyright (c) 2013, Mozilla
+
+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 Adobe Systems, Network Resonance, Mozilla 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
+OWNER 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 nr_socket_tcp_h__
+#define nr_socket_tcp_h__
+
+#include <list>
+
+#include "mozilla/net/WebrtcTCPSocketCallback.h"
+
+#include "nsTArray.h"
+
+extern "C" {
+#include "nr_api.h"
+#include "nr_socket.h"
+#include "transport_addr.h"
+}
+
+#include "nr_socket_prsock.h"
+
+namespace mozilla {
+using namespace net;
+
+namespace net {
+class WebrtcTCPSocketWrapper;
+} // namespace net
+
+class NrTcpSocketData;
+class NrSocketProxyConfig;
+
+class NrTcpSocket : public NrSocketBase, public WebrtcTCPSocketCallback {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NrTcpSocket, override)
+
+ explicit NrTcpSocket(const std::shared_ptr<NrSocketProxyConfig>& aConfig);
+
+ // NrSocketBase
+ int create(nr_transport_addr* aAddr) override;
+ int connect(const nr_transport_addr* aAddr) override;
+ void close() override;
+ int write(const void* aBuffer, size_t aCount, size_t* aWrote) override;
+ int read(void* aBuffer, size_t aCount, size_t* aRead) override;
+ int getaddr(nr_transport_addr* aAddr) override;
+ int sendto(const void* aBuffer, size_t aCount, int aFlags,
+ const nr_transport_addr* aAddr) override;
+ int recvfrom(void* aBuffer, size_t aCount, size_t* aRead, int aFlags,
+ nr_transport_addr* aAddr) override;
+ int listen(int aBacklog) override;
+ int accept(nr_transport_addr* aAddr, nr_socket** aSocket) override;
+
+ // WebrtcTCPSocketCallback
+ void OnClose(nsresult aReason) override;
+ void OnConnected(const nsACString& aProxyType) override;
+ void OnRead(nsTArray<uint8_t>&& aReadData) override;
+
+ size_t CountUnreadBytes() const;
+
+ // for gtests
+ void AssignChannel_DoNotUse(WebrtcTCPSocketWrapper* aWrapper);
+
+ protected:
+ virtual ~NrTcpSocket();
+
+ private:
+ void DoCallbacks();
+
+ bool mClosed;
+
+ size_t mReadOffset;
+ std::list<NrTcpSocketData> mReadQueue;
+
+ std::shared_ptr<NrSocketProxyConfig> mConfig;
+
+ RefPtr<WebrtcTCPSocketWrapper> mWebrtcTCPSocket;
+};
+
+} // namespace mozilla
+
+#endif // nr_socket_tcp_h__
diff --git a/dom/media/webrtc/transport/nr_timer.cpp b/dom/media/webrtc/transport/nr_timer.cpp
new file mode 100644
index 0000000000..3d5671c7a4
--- /dev/null
+++ b/dom/media/webrtc/transport/nr_timer.cpp
@@ -0,0 +1,256 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original code by: ekr@rtfm.com
+
+// Implementation of the NR timer interface
+
+// Some code here copied from nrappkit. The license was.
+
+/**
+ Copyright (C) 2004, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ ekr@rtfm.com Sun Feb 22 19:35:24 2004
+ */
+
+#include <string>
+
+#include "nsCOMPtr.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIEventTarget.h"
+#include "nsINamed.h"
+#include "nsITimer.h"
+#include "nsNetCID.h"
+#include "runnable_utils.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/UniquePtr.h"
+
+extern "C" {
+#include "async_wait.h"
+#include "async_timer.h"
+#include "r_errors.h"
+#include "r_log.h"
+}
+
+namespace mozilla {
+
+class nrappkitCallback {
+ public:
+ nrappkitCallback(NR_async_cb cb, void* cb_arg, const char* function, int line)
+ : cb_(cb), cb_arg_(cb_arg), function_(function), line_(line) {}
+ virtual ~nrappkitCallback() = default;
+
+ virtual void Cancel() = 0;
+
+ protected:
+ /* additional members */
+ NR_async_cb cb_;
+ void* cb_arg_;
+ std::string function_;
+ int line_;
+};
+
+class nrappkitTimerCallback : public nrappkitCallback,
+ public nsITimerCallback,
+ public nsINamed {
+ public:
+ // We're going to release ourself in the callback, so we need to be threadsafe
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+
+ nrappkitTimerCallback(NR_async_cb cb, void* cb_arg, const char* function,
+ int line)
+ : nrappkitCallback(cb, cb_arg, function, line), timer_(nullptr) {}
+
+ void SetTimer(already_AddRefed<nsITimer>&& timer) { timer_ = timer; }
+
+ virtual void Cancel() override {
+ AddRef(); // Cancelling the timer causes the callback it holds to
+ // be released. AddRef() keeps us alive.
+ timer_->Cancel();
+ timer_ = nullptr;
+ Release(); // Will cause deletion of this object.
+ }
+
+ NS_IMETHOD
+ GetName(nsACString& aName) override {
+ aName.AssignLiteral("nrappkitTimerCallback");
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<nsITimer> timer_;
+ virtual ~nrappkitTimerCallback() = default;
+};
+
+NS_IMPL_ISUPPORTS(nrappkitTimerCallback, nsITimerCallback, nsINamed)
+
+NS_IMETHODIMP nrappkitTimerCallback::Notify(nsITimer* timer) {
+ r_log(LOG_GENERIC, LOG_DEBUG, "Timer callback fired (set in %s:%d)",
+ function_.c_str(), line_);
+ MOZ_RELEASE_ASSERT(timer == timer_);
+ cb_(nullptr, 0, cb_arg_);
+
+ // Allow the timer to go away.
+ timer_ = nullptr;
+ return NS_OK;
+}
+
+class nrappkitScheduledCallback : public nrappkitCallback {
+ public:
+ nrappkitScheduledCallback(NR_async_cb cb, void* cb_arg, const char* function,
+ int line)
+ : nrappkitCallback(cb, cb_arg, function, line) {}
+
+ void Run() {
+ if (cb_) {
+ cb_(nullptr, 0, cb_arg_);
+ }
+ }
+
+ virtual void Cancel() override { cb_ = nullptr; }
+
+ ~nrappkitScheduledCallback() = default;
+};
+
+} // namespace mozilla
+
+using namespace mozilla;
+
+static nsCOMPtr<nsIEventTarget> GetSTSThread() {
+ nsresult rv;
+
+ nsCOMPtr<nsIEventTarget> sts_thread;
+
+ sts_thread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ return sts_thread;
+}
+
+// These timers must only be used from the STS thread.
+// This function is a helper that enforces that.
+static void CheckSTSThread() {
+ DebugOnly<nsCOMPtr<nsIEventTarget>> sts_thread = GetSTSThread();
+
+ ASSERT_ON_THREAD(sts_thread.value);
+}
+
+static int nr_async_timer_set_zero(NR_async_cb cb, void* arg, char* func, int l,
+ nrappkitCallback** handle) {
+ nrappkitScheduledCallback* callback(
+ new nrappkitScheduledCallback(cb, arg, func, l));
+
+ nsresult rv = GetSTSThread()->Dispatch(
+ WrapRunnable(UniquePtr<nrappkitScheduledCallback>(callback),
+ &nrappkitScheduledCallback::Run),
+ NS_DISPATCH_NORMAL);
+ if (NS_FAILED(rv)) return R_FAILED;
+
+ *handle = callback;
+
+ // On exit to this function, the only strong reference to callback is in
+ // the Runnable. Because we are redispatching to the same thread,
+ // this is always safe.
+ return 0;
+}
+
+static int nr_async_timer_set_nonzero(int timeout, NR_async_cb cb, void* arg,
+ char* func, int l,
+ nrappkitCallback** handle) {
+ nsresult rv;
+ CheckSTSThread();
+
+ nrappkitTimerCallback* callback = new nrappkitTimerCallback(cb, arg, func, l);
+
+ nsCOMPtr<nsITimer> timer;
+ rv = NS_NewTimerWithCallback(getter_AddRefs(timer), callback, timeout,
+ nsITimer::TYPE_ONE_SHOT);
+ if (NS_FAILED(rv)) {
+ return R_FAILED;
+ }
+
+ // Move the ownership of the timer to the callback object, which holds the
+ // timer alive per spec.
+ callback->SetTimer(timer.forget());
+
+ *handle = callback;
+
+ return 0;
+}
+
+int NR_async_timer_set(int timeout, NR_async_cb cb, void* arg, char* func,
+ int l, void** handle) {
+ CheckSTSThread();
+
+ nrappkitCallback* callback;
+ int r;
+
+ if (!timeout) {
+ r = nr_async_timer_set_zero(cb, arg, func, l, &callback);
+ } else {
+ r = nr_async_timer_set_nonzero(timeout, cb, arg, func, l, &callback);
+ }
+
+ if (r) return r;
+
+ if (handle) *handle = callback;
+
+ return 0;
+}
+
+int NR_async_schedule(NR_async_cb cb, void* arg, char* func, int l) {
+ // No need to check the thread because we check it next in the
+ // timer set.
+ return NR_async_timer_set(0, cb, arg, func, l, nullptr);
+}
+
+int NR_async_timer_cancel(void* handle) {
+ // Check for the handle being nonzero because sometimes we get
+ // no-op cancels that aren't on the STS thread. This can be
+ // non-racy as long as the upper-level code is careful.
+ if (!handle) return 0;
+
+ CheckSTSThread();
+
+ nrappkitCallback* callback = static_cast<nrappkitCallback*>(handle);
+ callback->Cancel();
+
+ return 0;
+}
diff --git a/dom/media/webrtc/transport/nricectx.cpp b/dom/media/webrtc/transport/nricectx.cpp
new file mode 100644
index 0000000000..697eecdb07
--- /dev/null
+++ b/dom/media/webrtc/transport/nricectx.cpp
@@ -0,0 +1,1106 @@
+/* -*- mode: c++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+
+// Some of this code is cut-and-pasted from nICEr. Copyright is:
+
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+*/
+
+#include <string>
+#include <vector>
+
+#include "nr_socket_proxy_config.h"
+#include "nsXULAppAPI.h"
+
+#include "logging.h"
+#include "pk11pub.h"
+#include "plbase64.h"
+
+#include "nsCOMPtr.h"
+#include "nsError.h"
+#include "nsNetCID.h"
+#include "nsServiceManagerUtils.h"
+#include "ScopedNSSTypes.h"
+#include "runnable_utils.h"
+#include "nsIUUIDGenerator.h"
+
+// nICEr includes
+extern "C" {
+#include "nr_api.h"
+#include "registry.h"
+#include "async_timer.h"
+#include "r_crc32.h"
+#include "r_memory.h"
+#include "ice_reg.h"
+#include "transport_addr.h"
+#include "nr_crypto.h"
+#include "nr_socket.h"
+#include "nr_socket_local.h"
+#include "stun_reg.h"
+#include "stun_util.h"
+#include "ice_codeword.h"
+#include "ice_ctx.h"
+#include "ice_candidate.h"
+}
+
+// Local includes
+#include "nricectx.h"
+#include "nricemediastream.h"
+#include "nr_socket_prsock.h"
+#include "nrinterfaceprioritizer.h"
+#include "rlogconnector.h"
+#include "test_nr_socket.h"
+
+namespace mozilla {
+
+using std::shared_ptr;
+
+TimeStamp nr_socket_short_term_violation_time() {
+ return NrSocketBase::short_term_violation_time();
+}
+
+TimeStamp nr_socket_long_term_violation_time() {
+ return NrSocketBase::long_term_violation_time();
+}
+
+MOZ_MTLOG_MODULE("mtransport")
+
+const char kNrIceTransportUdp[] = "udp";
+const char kNrIceTransportTcp[] = "tcp";
+const char kNrIceTransportTls[] = "tls";
+
+static bool initialized = false;
+
+static int noop(void** obj) { return 0; }
+
+static nr_socket_factory_vtbl ctx_socket_factory_vtbl = {nr_socket_local_create,
+ noop};
+
+// Implement NSPR-based crypto algorithms
+static int nr_crypto_nss_random_bytes(UCHAR* buf, size_t len) {
+ UniquePK11SlotInfo slot(PK11_GetInternalSlot());
+ if (!slot) return R_INTERNAL;
+
+ SECStatus rv = PK11_GenerateRandomOnSlot(slot.get(), buf, len);
+ if (rv != SECSuccess) return R_INTERNAL;
+
+ return 0;
+}
+
+static int nr_crypto_nss_hmac(UCHAR* key, size_t keyl, UCHAR* buf, size_t bufl,
+ UCHAR* result) {
+ CK_MECHANISM_TYPE mech = CKM_SHA_1_HMAC;
+ PK11SlotInfo* slot = nullptr;
+ MOZ_ASSERT(keyl > 0);
+ SECItem keyi = {siBuffer, key, static_cast<unsigned int>(keyl)};
+ PK11SymKey* skey = nullptr;
+ PK11Context* hmac_ctx = nullptr;
+ SECStatus status;
+ unsigned int hmac_len;
+ SECItem param = {siBuffer, nullptr, 0};
+ int err = R_INTERNAL;
+
+ slot = PK11_GetInternalKeySlot();
+ if (!slot) goto abort;
+
+ skey = PK11_ImportSymKey(slot, mech, PK11_OriginUnwrap, CKA_SIGN, &keyi,
+ nullptr);
+ if (!skey) goto abort;
+
+ hmac_ctx = PK11_CreateContextBySymKey(mech, CKA_SIGN, skey, &param);
+ if (!hmac_ctx) goto abort;
+
+ status = PK11_DigestBegin(hmac_ctx);
+ if (status != SECSuccess) goto abort;
+
+ status = PK11_DigestOp(hmac_ctx, buf, bufl);
+ if (status != SECSuccess) goto abort;
+
+ status = PK11_DigestFinal(hmac_ctx, result, &hmac_len, 20);
+ if (status != SECSuccess) goto abort;
+
+ MOZ_ASSERT(hmac_len == 20);
+
+ err = 0;
+
+abort:
+ if (hmac_ctx) PK11_DestroyContext(hmac_ctx, PR_TRUE);
+ if (skey) PK11_FreeSymKey(skey);
+ if (slot) PK11_FreeSlot(slot);
+
+ return err;
+}
+
+static int nr_crypto_nss_md5(UCHAR* buf, size_t bufl, UCHAR* result) {
+ int err = R_INTERNAL;
+ SECStatus rv;
+
+ const SECHashObject* ho = HASH_GetHashObject(HASH_AlgMD5);
+ MOZ_ASSERT(ho);
+ if (!ho) goto abort;
+
+ MOZ_ASSERT(ho->length == 16);
+
+ rv = HASH_HashBuf(ho->type, result, buf, bufl);
+ if (rv != SECSuccess) goto abort;
+
+ err = 0;
+abort:
+ return err;
+}
+
+static nr_ice_crypto_vtbl nr_ice_crypto_nss_vtbl = {
+ nr_crypto_nss_random_bytes, nr_crypto_nss_hmac, nr_crypto_nss_md5};
+
+nsresult NrIceStunServer::ToNicerStunStruct(nr_ice_stun_server* server) const {
+ int r;
+
+ memset(server, 0, sizeof(nr_ice_stun_server));
+ uint8_t protocol;
+ if (transport_ == kNrIceTransportUdp) {
+ protocol = IPPROTO_UDP;
+ } else if (transport_ == kNrIceTransportTcp) {
+ protocol = IPPROTO_TCP;
+ } else if (transport_ == kNrIceTransportTls) {
+ protocol = IPPROTO_TCP;
+ } else {
+ MOZ_MTLOG(ML_ERROR, "Unsupported STUN server transport: " << transport_);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (has_addr_) {
+ if (transport_ == kNrIceTransportTls) {
+ // Refuse to try TLS without an FQDN
+ return NS_ERROR_INVALID_ARG;
+ }
+ r = nr_praddr_to_transport_addr(&addr_, &server->addr, protocol, 0);
+ if (r) {
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ MOZ_ASSERT(sizeof(server->addr.fqdn) > host_.size());
+ // Dummy information to keep nICEr happy
+ if (use_ipv6_if_fqdn_) {
+ nr_str_port_to_transport_addr("::", port_, protocol, &server->addr);
+ } else {
+ nr_str_port_to_transport_addr("0.0.0.0", port_, protocol, &server->addr);
+ }
+ PL_strncpyz(server->addr.fqdn, host_.c_str(), sizeof(server->addr.fqdn));
+ if (transport_ == kNrIceTransportTls) {
+ server->addr.tls = 1;
+ }
+ }
+
+ nr_transport_addr_fmt_addr_string(&server->addr);
+
+ return NS_OK;
+}
+
+nsresult NrIceTurnServer::ToNicerTurnStruct(nr_ice_turn_server* server) const {
+ memset(server, 0, sizeof(nr_ice_turn_server));
+
+ nsresult rv = ToNicerStunStruct(&server->turn_server);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!(server->username = r_strdup(username_.c_str())))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ // TODO(ekr@rtfm.com): handle non-ASCII passwords somehow?
+ // STUN requires they be SASLpreped, but we don't know if
+ // they are at this point.
+
+ // C++03 23.2.4, Paragraph 1 stipulates that the elements
+ // in std::vector must be contiguous, and can therefore be
+ // used as input to functions expecting C arrays.
+ const UCHAR* data = password_.empty() ? nullptr : &password_[0];
+ int r = r_data_create(&server->password, data, password_.size());
+ if (r) {
+ RFREE(server->username);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+NrIceCtx::NrIceCtx(const std::string& name)
+ : connection_state_(ICE_CTX_INIT),
+ gathering_state_(ICE_CTX_GATHER_INIT),
+ name_(name),
+ ice_controlling_set_(false),
+ streams_(),
+ ctx_(nullptr),
+ peer_(nullptr),
+ ice_handler_vtbl_(nullptr),
+ ice_handler_(nullptr),
+ trickle_(true),
+ config_(),
+ nat_(nullptr),
+ proxy_config_(nullptr) {}
+
+/* static */
+RefPtr<NrIceCtx> NrIceCtx::Create(const std::string& aName) {
+ RefPtr<NrIceCtx> ctx = new NrIceCtx(aName);
+
+ if (!ctx->Initialize()) {
+ return nullptr;
+ }
+
+ return ctx;
+}
+
+nsresult NrIceCtx::SetIceConfig(const Config& aConfig) {
+ config_ = aConfig;
+ switch (config_.mPolicy) {
+ case ICE_POLICY_RELAY:
+ MOZ_MTLOG(ML_DEBUG, "SetIceConfig: relay only");
+ nr_ice_ctx_remove_flags(ctx_, NR_ICE_CTX_FLAGS_DISABLE_HOST_CANDIDATES);
+ nr_ice_ctx_add_flags(ctx_, NR_ICE_CTX_FLAGS_RELAY_ONLY);
+ break;
+ case ICE_POLICY_NO_HOST:
+ MOZ_MTLOG(ML_DEBUG, "SetIceConfig: no host");
+ nr_ice_ctx_add_flags(ctx_, NR_ICE_CTX_FLAGS_DISABLE_HOST_CANDIDATES);
+ nr_ice_ctx_remove_flags(ctx_, NR_ICE_CTX_FLAGS_RELAY_ONLY);
+ break;
+ case ICE_POLICY_ALL:
+ MOZ_MTLOG(ML_DEBUG, "SetIceConfig: all");
+ nr_ice_ctx_remove_flags(ctx_, NR_ICE_CTX_FLAGS_DISABLE_HOST_CANDIDATES);
+ nr_ice_ctx_remove_flags(ctx_, NR_ICE_CTX_FLAGS_RELAY_ONLY);
+ break;
+ }
+
+ // TODO: Support re-configuring the test NAT someday?
+ if (!nat_ && config_.mNatSimulatorConfig.isSome()) {
+ TestNat* test_nat = new TestNat;
+ test_nat->filtering_type_ = TestNat::ToNatBehavior(
+ config_.mNatSimulatorConfig->mFilteringType.get());
+ test_nat->mapping_type_ =
+ TestNat::ToNatBehavior(config_.mNatSimulatorConfig->mMappingType.get());
+ test_nat->block_udp_ = config_.mNatSimulatorConfig->mBlockUdp;
+ test_nat->block_tcp_ = config_.mNatSimulatorConfig->mBlockTcp;
+ test_nat->block_tls_ = config_.mNatSimulatorConfig->mBlockTls;
+ test_nat->error_code_for_drop_ =
+ config_.mNatSimulatorConfig->mErrorCodeForDrop;
+ if (config_.mNatSimulatorConfig->mRedirectAddress.Length()) {
+ test_nat
+ ->stun_redirect_map_[config_.mNatSimulatorConfig->mRedirectAddress] =
+ config_.mNatSimulatorConfig->mRedirectTargets;
+ }
+ test_nat->enabled_ = true;
+ SetNat(test_nat);
+ }
+
+ return NS_OK;
+}
+
+RefPtr<NrIceMediaStream> NrIceCtx::CreateStream(const std::string& id,
+ const std::string& name,
+ int components) {
+ if (streams_.count(id)) {
+ MOZ_ASSERT(false);
+ return nullptr;
+ }
+
+ RefPtr<NrIceMediaStream> stream =
+ new NrIceMediaStream(this, id, name, components);
+ streams_[id] = stream;
+ return stream;
+}
+
+void NrIceCtx::DestroyStream(const std::string& id) {
+ auto it = streams_.find(id);
+ if (it != streams_.end()) {
+ auto preexisting_stream = it->second;
+ streams_.erase(it);
+ preexisting_stream->Close();
+ }
+
+ if (streams_.empty()) {
+ SetGatheringState(ICE_CTX_GATHER_INIT);
+ }
+}
+
+// Handler callbacks
+int NrIceCtx::select_pair(void* obj, nr_ice_media_stream* stream,
+ int component_id, nr_ice_cand_pair** potentials,
+ int potential_ct) {
+ MOZ_MTLOG(ML_DEBUG, "select pair called: potential_ct = " << potential_ct);
+ MOZ_ASSERT(stream->local_stream);
+ MOZ_ASSERT(!stream->local_stream->obsolete);
+
+ return 0;
+}
+
+int NrIceCtx::stream_ready(void* obj, nr_ice_media_stream* stream) {
+ MOZ_MTLOG(ML_DEBUG, "stream_ready called");
+ MOZ_ASSERT(!stream->local_stream);
+ MOZ_ASSERT(!stream->obsolete);
+
+ // Get the ICE ctx.
+ NrIceCtx* ctx = static_cast<NrIceCtx*>(obj);
+
+ RefPtr<NrIceMediaStream> s = ctx->FindStream(stream);
+
+ // Streams which do not exist should never be ready.
+ MOZ_ASSERT(s);
+
+ s->Ready();
+
+ return 0;
+}
+
+int NrIceCtx::stream_failed(void* obj, nr_ice_media_stream* stream) {
+ MOZ_MTLOG(ML_DEBUG, "stream_failed called");
+ MOZ_ASSERT(!stream->local_stream);
+ MOZ_ASSERT(!stream->obsolete);
+
+ // Get the ICE ctx
+ NrIceCtx* ctx = static_cast<NrIceCtx*>(obj);
+ RefPtr<NrIceMediaStream> s = ctx->FindStream(stream);
+
+ // Streams which do not exist should never fail.
+ MOZ_ASSERT(s);
+
+ ctx->SetConnectionState(ICE_CTX_FAILED);
+ s->Failed();
+ return 0;
+}
+
+int NrIceCtx::ice_checking(void* obj, nr_ice_peer_ctx* pctx) {
+ MOZ_MTLOG(ML_DEBUG, "ice_checking called");
+
+ // Get the ICE ctx
+ NrIceCtx* ctx = static_cast<NrIceCtx*>(obj);
+
+ ctx->SetConnectionState(ICE_CTX_CHECKING);
+
+ return 0;
+}
+
+int NrIceCtx::ice_connected(void* obj, nr_ice_peer_ctx* pctx) {
+ MOZ_MTLOG(ML_DEBUG, "ice_connected called");
+
+ // Get the ICE ctx
+ NrIceCtx* ctx = static_cast<NrIceCtx*>(obj);
+
+ // This is called even on failed contexts.
+ if (ctx->connection_state() != ICE_CTX_FAILED) {
+ ctx->SetConnectionState(ICE_CTX_CONNECTED);
+ }
+
+ return 0;
+}
+
+int NrIceCtx::ice_disconnected(void* obj, nr_ice_peer_ctx* pctx) {
+ MOZ_MTLOG(ML_DEBUG, "ice_disconnected called");
+
+ // Get the ICE ctx
+ NrIceCtx* ctx = static_cast<NrIceCtx*>(obj);
+
+ ctx->SetConnectionState(ICE_CTX_DISCONNECTED);
+
+ return 0;
+}
+
+int NrIceCtx::msg_recvd(void* obj, nr_ice_peer_ctx* pctx,
+ nr_ice_media_stream* stream, int component_id,
+ UCHAR* msg, int len) {
+ // Get the ICE ctx
+ NrIceCtx* ctx = static_cast<NrIceCtx*>(obj);
+ RefPtr<NrIceMediaStream> s = ctx->FindStream(stream);
+
+ // Streams which do not exist should never have packets.
+ MOZ_ASSERT(s);
+
+ s->SignalPacketReceived(s, component_id, msg, len);
+
+ return 0;
+}
+
+void NrIceCtx::trickle_cb(void* arg, nr_ice_ctx* ice_ctx,
+ nr_ice_media_stream* stream, int component_id,
+ nr_ice_candidate* candidate) {
+ if (stream->obsolete) {
+ // Stream was probably just marked obsolete, resulting in this callback
+ return;
+ }
+ // Get the ICE ctx
+ NrIceCtx* ctx = static_cast<NrIceCtx*>(arg);
+ RefPtr<NrIceMediaStream> s = ctx->FindStream(stream);
+
+ if (!s) {
+ // This stream has been removed because it is inactive
+ return;
+ }
+
+ if (!candidate) {
+ s->SignalCandidate(s, "", stream->ufrag, "", "");
+ return;
+ }
+
+ std::string actual_addr;
+ std::string mdns_addr;
+ ctx->GenerateObfuscatedAddress(candidate, &mdns_addr, &actual_addr);
+
+ // Format the candidate.
+ char candidate_str[NR_ICE_MAX_ATTRIBUTE_SIZE];
+ int r = nr_ice_format_candidate_attribute(
+ candidate, candidate_str, sizeof(candidate_str),
+ (ctx->ctx()->flags & NR_ICE_CTX_FLAGS_OBFUSCATE_HOST_ADDRESSES) ? 1 : 0);
+ MOZ_ASSERT(!r);
+ if (r) return;
+
+ MOZ_MTLOG(ML_INFO, "NrIceCtx(" << ctx->name_ << "): trickling candidate "
+ << candidate_str);
+
+ s->SignalCandidate(s, candidate_str, stream->ufrag, mdns_addr, actual_addr);
+}
+
+void NrIceCtx::InitializeGlobals(const GlobalConfig& aConfig) {
+ RLogConnector::CreateInstance();
+ // Initialize the crypto callbacks and logging stuff
+ if (!initialized) {
+ NR_reg_init(NR_REG_MODE_LOCAL);
+ nr_crypto_vtbl = &nr_ice_crypto_nss_vtbl;
+ initialized = true;
+
+ // Set the priorites for candidate type preferences.
+ // These numbers come from RFC 5245 S. 4.1.2.2
+ NR_reg_set_uchar((char*)NR_ICE_REG_PREF_TYPE_SRV_RFLX, 100);
+ NR_reg_set_uchar((char*)NR_ICE_REG_PREF_TYPE_PEER_RFLX, 110);
+ NR_reg_set_uchar((char*)NR_ICE_REG_PREF_TYPE_HOST, 126);
+ NR_reg_set_uchar((char*)NR_ICE_REG_PREF_TYPE_RELAYED, 5);
+ NR_reg_set_uchar((char*)NR_ICE_REG_PREF_TYPE_SRV_RFLX_TCP, 99);
+ NR_reg_set_uchar((char*)NR_ICE_REG_PREF_TYPE_PEER_RFLX_TCP, 109);
+ NR_reg_set_uchar((char*)NR_ICE_REG_PREF_TYPE_HOST_TCP, 125);
+ NR_reg_set_uchar((char*)NR_ICE_REG_PREF_TYPE_RELAYED_TCP, 0);
+ NR_reg_set_uint4((char*)"stun.client.maximum_transmits",
+ aConfig.mStunClientMaxTransmits);
+ NR_reg_set_uint4((char*)NR_ICE_REG_TRICKLE_GRACE_PERIOD,
+ aConfig.mTrickleIceGracePeriod);
+ NR_reg_set_int4((char*)NR_ICE_REG_ICE_TCP_SO_SOCK_COUNT,
+ aConfig.mIceTcpSoSockCount);
+ NR_reg_set_int4((char*)NR_ICE_REG_ICE_TCP_LISTEN_BACKLOG,
+ aConfig.mIceTcpListenBacklog);
+
+ NR_reg_set_char((char*)NR_ICE_REG_ICE_TCP_DISABLE, !aConfig.mTcpEnabled);
+
+ if (aConfig.mAllowLoopback) {
+ NR_reg_set_char((char*)NR_STUN_REG_PREF_ALLOW_LOOPBACK_ADDRS, 1);
+ }
+
+ if (aConfig.mAllowLinkLocal) {
+ NR_reg_set_char((char*)NR_STUN_REG_PREF_ALLOW_LINK_LOCAL_ADDRS, 1);
+ }
+ if (!aConfig.mForceNetInterface.Length()) {
+ NR_reg_set_string((char*)NR_ICE_REG_PREF_FORCE_INTERFACE_NAME,
+ const_cast<char*>(aConfig.mForceNetInterface.get()));
+ }
+
+ // For now, always use nr_resolver for UDP.
+ NR_reg_set_char((char*)NR_ICE_REG_USE_NR_RESOLVER_FOR_UDP, 1);
+
+ // Use nr_resolver for TCP only when not in e10s mode (for unit-tests)
+ if (XRE_IsParentProcess()) {
+ NR_reg_set_char((char*)NR_ICE_REG_USE_NR_RESOLVER_FOR_TCP, 1);
+ }
+ }
+}
+
+void NrIceCtx::SetTargetForDefaultLocalAddressLookup(
+ const std::string& target_ip, uint16_t target_port) {
+ nr_ice_set_target_for_default_local_address_lookup(ctx_, target_ip.c_str(),
+ target_port);
+}
+
+#define MAXADDRS 100 // mirrors setting in ice_ctx.c
+
+/* static */
+nsTArray<NrIceStunAddr> NrIceCtx::GetStunAddrs() {
+ nsTArray<NrIceStunAddr> addrs;
+
+ nr_local_addr local_addrs[MAXADDRS];
+ int addr_ct = 0;
+
+ // most likely running on parent process and need crypto vtbl
+ // initialized on Windows (Linux and OSX don't seem to care)
+ if (!initialized) {
+ nr_crypto_vtbl = &nr_ice_crypto_nss_vtbl;
+ }
+
+ MOZ_MTLOG(ML_INFO, "NrIceCtx static call to find local stun addresses");
+ if (nr_stun_find_local_addresses(local_addrs, MAXADDRS, &addr_ct)) {
+ MOZ_MTLOG(ML_INFO, "Error finding local stun addresses");
+ } else {
+ for (int i = 0; i < addr_ct; ++i) {
+ NrIceStunAddr addr(&local_addrs[i]);
+ addrs.AppendElement(addr);
+ }
+ }
+
+ return addrs;
+}
+
+void NrIceCtx::SetStunAddrs(const nsTArray<NrIceStunAddr>& addrs) {
+ nr_local_addr* local_addrs;
+ local_addrs = new nr_local_addr[addrs.Length()];
+
+ for (size_t i = 0; i < addrs.Length(); ++i) {
+ nr_local_addr_copy(&local_addrs[i],
+ const_cast<nr_local_addr*>(&addrs[i].localAddr()));
+ }
+ nr_ice_set_local_addresses(ctx_, local_addrs, addrs.Length());
+
+ delete[] local_addrs;
+}
+
+bool NrIceCtx::Initialize() {
+ // Create the ICE context
+ int r;
+
+ UINT4 flags = NR_ICE_CTX_FLAGS_AGGRESSIVE_NOMINATION;
+ r = nr_ice_ctx_create(const_cast<char*>(name_.c_str()), flags, &ctx_);
+
+ if (r) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't create ICE ctx for '" << name_ << "'");
+ return false;
+ }
+
+ // override default factory to capture optional proxy config when creating
+ // sockets.
+ nr_socket_factory* factory;
+ r = nr_socket_factory_create_int(this, &ctx_socket_factory_vtbl, &factory);
+
+ if (r) {
+ MOZ_MTLOG(LogLevel::Error, "Couldn't create ctx socket factory.");
+ return false;
+ }
+ nr_ice_ctx_set_socket_factory(ctx_, factory);
+
+ nr_interface_prioritizer* prioritizer = CreateInterfacePrioritizer();
+ if (!prioritizer) {
+ MOZ_MTLOG(LogLevel::Error, "Couldn't create interface prioritizer.");
+ return false;
+ }
+
+ r = nr_ice_ctx_set_interface_prioritizer(ctx_, prioritizer);
+ if (r) {
+ MOZ_MTLOG(LogLevel::Error, "Couldn't set interface prioritizer.");
+ return false;
+ }
+
+ if (generating_trickle()) {
+ r = nr_ice_ctx_set_trickle_cb(ctx_, &NrIceCtx::trickle_cb, this);
+ if (r) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't set trickle cb for '" << name_ << "'");
+ return false;
+ }
+ }
+
+ // Create the handler objects
+ ice_handler_vtbl_ = new nr_ice_handler_vtbl();
+ ice_handler_vtbl_->select_pair = &NrIceCtx::select_pair;
+ ice_handler_vtbl_->stream_ready = &NrIceCtx::stream_ready;
+ ice_handler_vtbl_->stream_failed = &NrIceCtx::stream_failed;
+ ice_handler_vtbl_->ice_connected = &NrIceCtx::ice_connected;
+ ice_handler_vtbl_->msg_recvd = &NrIceCtx::msg_recvd;
+ ice_handler_vtbl_->ice_checking = &NrIceCtx::ice_checking;
+ ice_handler_vtbl_->ice_disconnected = &NrIceCtx::ice_disconnected;
+
+ ice_handler_ = new nr_ice_handler();
+ ice_handler_->vtbl = ice_handler_vtbl_;
+ ice_handler_->obj = this;
+
+ // Create the peer ctx. Because we do not support parallel forking, we
+ // only have one peer ctx.
+ std::string peer_name = name_ + ":default";
+ r = nr_ice_peer_ctx_create(ctx_, ice_handler_,
+ const_cast<char*>(peer_name.c_str()), &peer_);
+ if (r) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't create ICE peer ctx for '" << name_ << "'");
+ return false;
+ }
+
+ nsresult rv;
+ sts_target_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+
+ if (!NS_SUCCEEDED(rv)) return false;
+
+ return true;
+}
+
+int NrIceCtx::SetNat(const RefPtr<TestNat>& aNat) {
+ nat_ = aNat;
+ nr_socket_factory* fac;
+ int r = nat_->create_socket_factory(&fac);
+ if (r) {
+ return r;
+ }
+ nr_ice_ctx_set_socket_factory(ctx_, fac);
+ return 0;
+}
+
+// ONLY USE THIS FOR TESTING. Will cause totally unpredictable and possibly very
+// bad effects if ICE is still live.
+void NrIceCtx::internal_DeinitializeGlobal() {
+ NR_reg_del((char*)"stun");
+ NR_reg_del((char*)"ice");
+ RLogConnector::DestroyInstance();
+ nr_crypto_vtbl = nullptr;
+ initialized = false;
+}
+
+void NrIceCtx::internal_SetTimerAccelarator(int divider) {
+ ctx_->test_timer_divider = divider;
+}
+
+void NrIceCtx::AccumulateStats(const NrIceStats& stats) {
+ nr_accumulate_count(&(ctx_->stats.stun_retransmits), stats.stun_retransmits);
+ nr_accumulate_count(&(ctx_->stats.turn_401s), stats.turn_401s);
+ nr_accumulate_count(&(ctx_->stats.turn_403s), stats.turn_403s);
+ nr_accumulate_count(&(ctx_->stats.turn_438s), stats.turn_438s);
+}
+
+NrIceStats NrIceCtx::Destroy() {
+ // designed to be called more than once so if stats are desired, this can be
+ // called just prior to the destructor
+ MOZ_MTLOG(ML_NOTICE, "NrIceCtx(" << name_ << "): " << __func__);
+
+ for (auto& idAndStream : streams_) {
+ idAndStream.second->Close();
+ }
+
+ NrIceStats stats;
+ if (ctx_) {
+ stats.stun_retransmits = ctx_->stats.stun_retransmits;
+ stats.turn_401s = ctx_->stats.turn_401s;
+ stats.turn_403s = ctx_->stats.turn_403s;
+ stats.turn_438s = ctx_->stats.turn_438s;
+ }
+
+ if (peer_) {
+ nr_ice_peer_ctx_destroy(&peer_);
+ }
+ if (ctx_) {
+ nr_ice_ctx_destroy(&ctx_);
+ }
+
+ delete ice_handler_vtbl_;
+ delete ice_handler_;
+
+ ice_handler_vtbl_ = nullptr;
+ ice_handler_ = nullptr;
+ proxy_config_ = nullptr;
+ streams_.clear();
+
+ return stats;
+}
+
+NrIceCtx::~NrIceCtx() = default;
+
+void NrIceCtx::destroy_peer_ctx() { nr_ice_peer_ctx_destroy(&peer_); }
+
+nsresult NrIceCtx::SetControlling(Controlling controlling) {
+ if (!ice_controlling_set_) {
+ peer_->controlling = (controlling == ICE_CONTROLLING) ? 1 : 0;
+ ice_controlling_set_ = true;
+
+ MOZ_MTLOG(ML_DEBUG,
+ "ICE ctx " << name_ << " setting controlling to" << controlling);
+ }
+ return NS_OK;
+}
+
+NrIceCtx::Controlling NrIceCtx::GetControlling() {
+ return (peer_->controlling) ? ICE_CONTROLLING : ICE_CONTROLLED;
+}
+
+nsresult NrIceCtx::SetStunServers(
+ const std::vector<NrIceStunServer>& stun_servers) {
+ MOZ_MTLOG(ML_NOTICE, "NrIceCtx(" << name_ << "): " << __func__);
+ // We assume nr_ice_stun_server is memmoveable. That's true right now.
+ std::vector<nr_ice_stun_server> servers;
+
+ for (size_t i = 0; i < stun_servers.size(); ++i) {
+ nr_ice_stun_server server;
+ nsresult rv = stun_servers[i].ToNicerStunStruct(&server);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't convert STUN server for '" << name_ << "'");
+ } else {
+ servers.push_back(server);
+ }
+ }
+
+ int r = nr_ice_ctx_set_stun_servers(ctx_, servers.data(),
+ static_cast<int>(servers.size()));
+ if (r) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't set STUN servers for '" << name_ << "'");
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+// TODO(ekr@rtfm.com): This is just SetStunServers with s/Stun/Turn
+// Could we do a template or something?
+nsresult NrIceCtx::SetTurnServers(
+ const std::vector<NrIceTurnServer>& turn_servers) {
+ MOZ_MTLOG(ML_NOTICE, "NrIceCtx(" << name_ << "): " << __func__);
+ // We assume nr_ice_turn_server is memmoveable. That's true right now.
+ std::vector<nr_ice_turn_server> servers;
+
+ for (size_t i = 0; i < turn_servers.size(); ++i) {
+ nr_ice_turn_server server;
+ nsresult rv = turn_servers[i].ToNicerTurnStruct(&server);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't convert TURN server for '" << name_ << "'");
+ } else {
+ servers.push_back(server);
+ }
+ }
+
+ int r = nr_ice_ctx_set_turn_servers(ctx_, servers.data(),
+ static_cast<int>(servers.size()));
+ if (r) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't set TURN servers for '" << name_ << "'");
+ // TODO(ekr@rtfm.com): This leaks the username/password. Need to free that.
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult NrIceCtx::SetResolver(nr_resolver* resolver) {
+ int r = nr_ice_ctx_set_resolver(ctx_, resolver);
+
+ if (r) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't set resolver for '" << name_ << "'");
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult NrIceCtx::SetProxyConfig(NrSocketProxyConfig&& config) {
+ proxy_config_.reset(new NrSocketProxyConfig(std::move(config)));
+ if (nat_) {
+ nat_->set_proxy_config(proxy_config_);
+ }
+
+ if (proxy_config_->GetForceProxy()) {
+ nr_ice_ctx_add_flags(ctx_, NR_ICE_CTX_FLAGS_ONLY_PROXY);
+ } else {
+ nr_ice_ctx_remove_flags(ctx_, NR_ICE_CTX_FLAGS_ONLY_PROXY);
+ }
+
+ return NS_OK;
+}
+
+void NrIceCtx::SetCtxFlags(bool default_route_only) {
+ ASSERT_ON_THREAD(sts_target_);
+
+ if (default_route_only) {
+ nr_ice_ctx_add_flags(ctx_, NR_ICE_CTX_FLAGS_ONLY_DEFAULT_ADDRS);
+ } else {
+ nr_ice_ctx_remove_flags(ctx_, NR_ICE_CTX_FLAGS_ONLY_DEFAULT_ADDRS);
+ }
+}
+
+nsresult NrIceCtx::StartGathering(bool default_route_only,
+ bool obfuscate_host_addresses) {
+ ASSERT_ON_THREAD(sts_target_);
+ MOZ_MTLOG(ML_NOTICE, "NrIceCtx(" << name_ << "): " << __func__);
+
+ if (obfuscate_host_addresses) {
+ nr_ice_ctx_add_flags(ctx_, NR_ICE_CTX_FLAGS_OBFUSCATE_HOST_ADDRESSES);
+ }
+
+ SetCtxFlags(default_route_only);
+
+ // This might start gathering for the first time, or again after
+ // renegotiation, or might do nothing at all if gathering has already
+ // finished.
+ int r = nr_ice_gather(ctx_, &NrIceCtx::gather_cb, this);
+
+ if (!r) {
+ SetGatheringState(ICE_CTX_GATHER_COMPLETE);
+ } else if (r == R_WOULDBLOCK) {
+ SetGatheringState(ICE_CTX_GATHER_STARTED);
+ } else {
+ SetGatheringState(ICE_CTX_GATHER_COMPLETE);
+ MOZ_MTLOG(ML_ERROR, "ICE FAILED: Couldn't gather ICE candidates for '"
+ << name_ << "', error=" << r);
+ SetConnectionState(ICE_CTX_FAILED);
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+RefPtr<NrIceMediaStream> NrIceCtx::FindStream(nr_ice_media_stream* stream) {
+ for (auto& idAndStream : streams_) {
+ if (idAndStream.second->HasStream(stream)) {
+ return idAndStream.second;
+ }
+ }
+
+ return nullptr;
+}
+
+std::vector<std::string> NrIceCtx::GetGlobalAttributes() {
+ char** attrs = nullptr;
+ int attrct;
+ int r;
+ std::vector<std::string> ret;
+
+ r = nr_ice_get_global_attributes(ctx_, &attrs, &attrct);
+ if (r) {
+ MOZ_MTLOG(ML_ERROR,
+ "Couldn't get ufrag and password for '" << name_ << "'");
+ return ret;
+ }
+
+ for (int i = 0; i < attrct; i++) {
+ ret.push_back(std::string(attrs[i]));
+ RFREE(attrs[i]);
+ }
+ RFREE(attrs);
+
+ return ret;
+}
+
+nsresult NrIceCtx::ParseGlobalAttributes(std::vector<std::string> attrs) {
+ std::vector<char*> attrs_in;
+ attrs_in.reserve(attrs.size());
+ for (auto& attr : attrs) {
+ attrs_in.push_back(const_cast<char*>(attr.c_str()));
+ }
+
+ int r = nr_ice_peer_ctx_parse_global_attributes(
+ peer_, attrs_in.empty() ? nullptr : &attrs_in[0], attrs_in.size());
+ if (r) {
+ MOZ_MTLOG(ML_ERROR,
+ "Couldn't parse global attributes for " << name_ << "'");
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+bool NrIceCtx::HasStreamsToConnect() const {
+ for (auto& idAndStream : streams_) {
+ if (idAndStream.second->state() != NrIceMediaStream::ICE_CLOSED) {
+ return true;
+ }
+ }
+ return false;
+}
+
+nsresult NrIceCtx::StartChecks() {
+ int r;
+ MOZ_MTLOG(ML_NOTICE, "NrIceCtx(" << name_ << "): " << __func__);
+
+ if (!HasStreamsToConnect()) {
+ MOZ_MTLOG(ML_NOTICE, "In StartChecks, nothing to do on " << name_);
+ return NS_OK;
+ }
+
+ r = nr_ice_peer_ctx_pair_candidates(peer_);
+ if (r) {
+ MOZ_MTLOG(ML_ERROR, "ICE FAILED: Couldn't pair candidates on " << name_);
+ SetConnectionState(ICE_CTX_FAILED);
+ return NS_ERROR_FAILURE;
+ }
+
+ r = nr_ice_peer_ctx_start_checks2(peer_, 1);
+ if (r) {
+ if (r == R_NOT_FOUND) {
+ MOZ_MTLOG(ML_INFO, "Couldn't start peer checks on "
+ << name_ << ", assuming trickle ICE");
+ } else {
+ MOZ_MTLOG(ML_ERROR,
+ "ICE FAILED: Couldn't start peer checks on " << name_);
+ SetConnectionState(ICE_CTX_FAILED);
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return NS_OK;
+}
+
+void NrIceCtx::gather_cb(NR_SOCKET s, int h, void* arg) {
+ NrIceCtx* ctx = static_cast<NrIceCtx*>(arg);
+
+ ctx->SetGatheringState(ICE_CTX_GATHER_COMPLETE);
+}
+
+void NrIceCtx::UpdateNetworkState(bool online) {
+ MOZ_MTLOG(ML_NOTICE, "NrIceCtx(" << name_ << "): updating network state to "
+ << (online ? "online" : "offline"));
+ if (connection_state_ == ICE_CTX_CLOSED) {
+ return;
+ }
+
+ if (online) {
+ nr_ice_peer_ctx_refresh_consent_all_streams(peer_);
+ } else {
+ nr_ice_peer_ctx_disconnect_all_streams(peer_);
+ }
+}
+
+void NrIceCtx::SetConnectionState(ConnectionState state) {
+ if (state == connection_state_) return;
+
+ MOZ_MTLOG(ML_INFO, "NrIceCtx(" << name_ << "): state " << connection_state_
+ << "->" << state);
+ connection_state_ = state;
+
+ if (connection_state_ == ICE_CTX_FAILED) {
+ MOZ_MTLOG(ML_INFO,
+ "NrIceCtx(" << name_ << "): dumping r_log ringbuffer... ");
+ std::deque<std::string> logs;
+ RLogConnector::GetInstance()->GetAny(0, &logs);
+ for (auto& log : logs) {
+ MOZ_MTLOG(ML_INFO, log);
+ }
+ }
+
+ SignalConnectionStateChange(this, state);
+}
+
+void NrIceCtx::SetGatheringState(GatheringState state) {
+ if (state == gathering_state_) return;
+
+ MOZ_MTLOG(ML_DEBUG, "NrIceCtx(" << name_ << "): gathering state "
+ << gathering_state_ << "->" << state);
+ gathering_state_ = state;
+
+ SignalGatheringStateChange(this, state);
+}
+
+void NrIceCtx::GenerateObfuscatedAddress(nr_ice_candidate* candidate,
+ std::string* mdns_address,
+ std::string* actual_address) {
+ if (candidate->type == HOST &&
+ (ctx_->flags & NR_ICE_CTX_FLAGS_OBFUSCATE_HOST_ADDRESSES)) {
+ char addr[64];
+ if (nr_transport_addr_get_addrstring(&candidate->addr, addr,
+ sizeof(addr))) {
+ return;
+ }
+
+ *actual_address = addr;
+
+ const auto& iter = obfuscated_host_addresses_.find(*actual_address);
+ if (iter != obfuscated_host_addresses_.end()) {
+ *mdns_address = iter->second;
+ } else {
+ nsresult rv;
+ nsCOMPtr<nsIUUIDGenerator> uuidgen =
+ do_GetService("@mozilla.org/uuid-generator;1", &rv);
+ // If this fails, we'll return a zero UUID rather than something
+ // unexpected.
+ nsID id = {};
+ id.Clear();
+ if (NS_SUCCEEDED(rv)) {
+ rv = uuidgen->GenerateUUIDInPlace(&id);
+ if (NS_FAILED(rv)) {
+ id.Clear();
+ }
+ }
+
+ char chars[NSID_LENGTH];
+ id.ToProvidedString(chars);
+ // The string will look like {64888863-a253-424a-9b30-1ed285d20142},
+ // we want to trim off the braces.
+ const char* ptr_to_id = chars;
+ ++ptr_to_id;
+ chars[NSID_LENGTH - 2] = 0;
+
+ std::ostringstream o;
+ o << ptr_to_id << ".local";
+ *mdns_address = o.str();
+
+ obfuscated_host_addresses_[*actual_address] = *mdns_address;
+ }
+ candidate->mdns_addr = r_strdup(mdns_address->c_str());
+ }
+}
+
+} // namespace mozilla
+
+// Reimplement nr_ice_compute_codeword to avoid copyright issues
+void nr_ice_compute_codeword(char* buf, int len, char* codeword) {
+ UINT4 c;
+
+ r_crc32(buf, len, &c);
+
+ PL_Base64Encode(reinterpret_cast<char*>(&c), 3, codeword);
+ codeword[4] = 0;
+}
+
+int nr_socket_local_create(void* obj, nr_transport_addr* addr,
+ nr_socket** sockp) {
+ using namespace mozilla;
+
+ RefPtr<NrSocketBase> sock;
+ int r, _status;
+ shared_ptr<NrSocketProxyConfig> config = nullptr;
+
+ if (obj) {
+ config = static_cast<NrIceCtx*>(obj)->GetProxyConfig();
+ }
+
+ r = NrSocketBase::CreateSocket(addr, &sock, config);
+ if (r) {
+ ABORT(r);
+ }
+
+ r = nr_socket_create_int(static_cast<void*>(sock), sock->vtbl(), sockp);
+ if (r) ABORT(r);
+
+ _status = 0;
+
+ {
+ // We will release this reference in destroy(), not exactly the normal
+ // ownership model, but it is what it is.
+ NrSocketBase* dummy = sock.forget().take();
+ (void)dummy;
+ }
+
+abort:
+ return _status;
+}
diff --git a/dom/media/webrtc/transport/nricectx.h b/dom/media/webrtc/transport/nricectx.h
new file mode 100644
index 0000000000..a0a0b5b772
--- /dev/null
+++ b/dom/media/webrtc/transport/nricectx.h
@@ -0,0 +1,421 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+
+// Some of this code is cut-and-pasted from nICEr. Copyright is:
+
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+*/
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+
+// This is a wrapper around the nICEr ICE stack
+#ifndef nricectx_h__
+#define nricectx_h__
+
+#include <memory>
+#include <string>
+#include <vector>
+#include <map>
+
+#include "sigslot.h"
+
+#include "prnetdb.h"
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "nsIEventTarget.h"
+#include "nsTArray.h"
+#include "mozilla/Maybe.h"
+
+#include "m_cpp_utils.h"
+#include "nricestunaddr.h"
+#include "nricemediastream.h"
+
+typedef struct nr_ice_ctx_ nr_ice_ctx;
+typedef struct nr_ice_peer_ctx_ nr_ice_peer_ctx;
+typedef struct nr_ice_media_stream_ nr_ice_media_stream;
+typedef struct nr_ice_handler_ nr_ice_handler;
+typedef struct nr_ice_handler_vtbl_ nr_ice_handler_vtbl;
+typedef struct nr_ice_candidate_ nr_ice_candidate;
+typedef struct nr_ice_cand_pair_ nr_ice_cand_pair;
+typedef struct nr_ice_stun_server_ nr_ice_stun_server;
+typedef struct nr_ice_turn_server_ nr_ice_turn_server;
+typedef struct nr_resolver_ nr_resolver;
+typedef struct nr_proxy_tunnel_config_ nr_proxy_tunnel_config;
+
+typedef void* NR_SOCKET;
+
+namespace mozilla {
+
+class NrSocketProxyConfig;
+
+class NrIceMediaStream;
+
+extern const char kNrIceTransportUdp[];
+extern const char kNrIceTransportTcp[];
+extern const char kNrIceTransportTls[];
+
+class NrIceStunServer {
+ public:
+ explicit NrIceStunServer(const PRNetAddr& addr) : has_addr_(true) {
+ memcpy(&addr_, &addr, sizeof(addr));
+ }
+
+ // The main function to use. Will take either an address or a hostname.
+ static UniquePtr<NrIceStunServer> Create(
+ const std::string& addr, uint16_t port,
+ const char* transport = kNrIceTransportUdp) {
+ UniquePtr<NrIceStunServer> server(new NrIceStunServer(transport));
+
+ nsresult rv = server->Init(addr, port);
+ if (NS_FAILED(rv)) return nullptr;
+
+ return server;
+ }
+
+ nsresult ToNicerStunStruct(nr_ice_stun_server* server) const;
+
+ bool HasFqdn() const { return !has_addr_; }
+
+ void SetUseIPv6IfFqdn() {
+ MOZ_ASSERT(HasFqdn());
+ use_ipv6_if_fqdn_ = true;
+ }
+
+ protected:
+ explicit NrIceStunServer(const char* transport)
+ : addr_(), transport_(transport) {}
+
+ nsresult Init(const std::string& addr, uint16_t port) {
+ PRStatus status = PR_StringToNetAddr(addr.c_str(), &addr_);
+ if (status == PR_SUCCESS) {
+ // Parseable as an address
+ addr_.inet.port = PR_htons(port);
+ port_ = port;
+ has_addr_ = true;
+ return NS_OK;
+ } else if (addr.size() < 256) {
+ // Apparently this is a hostname.
+ host_ = addr;
+ port_ = port;
+ has_addr_ = false;
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+ }
+
+ bool has_addr_;
+ std::string host_;
+ uint16_t port_;
+ PRNetAddr addr_;
+ std::string transport_;
+ bool use_ipv6_if_fqdn_ = false;
+};
+
+class NrIceTurnServer : public NrIceStunServer {
+ public:
+ static UniquePtr<NrIceTurnServer> Create(
+ const std::string& addr, uint16_t port, const std::string& username,
+ const std::vector<unsigned char>& password,
+ const char* transport = kNrIceTransportUdp) {
+ UniquePtr<NrIceTurnServer> server(
+ new NrIceTurnServer(username, password, transport));
+
+ nsresult rv = server->Init(addr, port);
+ if (NS_FAILED(rv)) return nullptr;
+
+ return server;
+ }
+
+ nsresult ToNicerTurnStruct(nr_ice_turn_server* server) const;
+
+ private:
+ NrIceTurnServer(const std::string& username,
+ const std::vector<unsigned char>& password,
+ const char* transport)
+ : NrIceStunServer(transport), username_(username), password_(password) {}
+
+ std::string username_;
+ std::vector<unsigned char> password_;
+};
+
+class TestNat;
+
+class NrIceStats {
+ public:
+ uint16_t stun_retransmits = 0;
+ uint16_t turn_401s = 0;
+ uint16_t turn_403s = 0;
+ uint16_t turn_438s = 0;
+};
+
+class NrIceCtx {
+ public:
+ enum ConnectionState {
+ ICE_CTX_INIT,
+ ICE_CTX_CHECKING,
+ ICE_CTX_CONNECTED,
+ ICE_CTX_COMPLETED,
+ ICE_CTX_FAILED,
+ ICE_CTX_DISCONNECTED,
+ ICE_CTX_CLOSED
+ };
+
+ enum GatheringState {
+ ICE_CTX_GATHER_INIT,
+ ICE_CTX_GATHER_STARTED,
+ ICE_CTX_GATHER_COMPLETE
+ };
+
+ enum Controlling { ICE_CONTROLLING, ICE_CONTROLLED };
+
+ enum Policy { ICE_POLICY_RELAY, ICE_POLICY_NO_HOST, ICE_POLICY_ALL };
+
+ struct NatSimulatorConfig {
+ bool mBlockTcp = false;
+ bool mBlockUdp = false;
+ bool mBlockTls = false;
+ int mErrorCodeForDrop = 0;
+ nsCString mMappingType = "ENDPOINT_INDEPENDENT"_ns;
+ nsCString mFilteringType = "ENDPOINT_INDEPENDENT"_ns;
+ nsCString mRedirectAddress;
+ CopyableTArray<nsCString> mRedirectTargets;
+ };
+
+ struct Config {
+ NrIceCtx::Policy mPolicy = NrIceCtx::ICE_POLICY_ALL;
+ Maybe<NatSimulatorConfig> mNatSimulatorConfig;
+ };
+
+ static RefPtr<NrIceCtx> Create(const std::string& aName);
+
+ nsresult SetIceConfig(const Config& aConfig);
+
+ RefPtr<NrIceMediaStream> CreateStream(const std::string& id,
+ const std::string& name,
+ int components);
+ void DestroyStream(const std::string& id);
+
+ struct GlobalConfig {
+ bool mAllowLinkLocal = false;
+ bool mAllowLoopback = false;
+ bool mTcpEnabled = true;
+ int mStunClientMaxTransmits = 7;
+ int mTrickleIceGracePeriod = 5000;
+ int mIceTcpSoSockCount = 3;
+ int mIceTcpListenBacklog = 10;
+ nsCString mForceNetInterface;
+ };
+
+ // initialize ICE globals, crypto, and logging
+ static void InitializeGlobals(const GlobalConfig& aConfig);
+
+ void SetTargetForDefaultLocalAddressLookup(const std::string& target_ip,
+ uint16_t target_port);
+
+ // static GetStunAddrs for use in parent process to support
+ // sandboxing restrictions
+ static nsTArray<NrIceStunAddr> GetStunAddrs();
+ void SetStunAddrs(const nsTArray<NrIceStunAddr>& addrs);
+
+ bool Initialize();
+
+ int SetNat(const RefPtr<TestNat>& aNat);
+
+ // Deinitialize all ICE global state. Used only for testing.
+ static void internal_DeinitializeGlobal();
+
+ // Divide some timers to faster testing. Used only for testing.
+ void internal_SetTimerAccelarator(int divider);
+
+ nr_ice_ctx* ctx() { return ctx_; }
+ nr_ice_peer_ctx* peer() { return peer_; }
+
+ // Testing only.
+ void destroy_peer_ctx();
+
+ RefPtr<NrIceMediaStream> GetStream(const std::string& id) {
+ auto it = streams_.find(id);
+ if (it != streams_.end()) {
+ return it->second;
+ }
+ return nullptr;
+ }
+
+ std::vector<RefPtr<NrIceMediaStream>> GetStreams() const {
+ std::vector<RefPtr<NrIceMediaStream>> result;
+ for (auto& idAndStream : streams_) {
+ result.push_back(idAndStream.second);
+ }
+ return result;
+ }
+
+ bool HasStreamsToConnect() const;
+
+ // The name of the ctx
+ const std::string& name() const { return name_; }
+
+ // Current state
+ ConnectionState connection_state() const { return connection_state_; }
+
+ // Current state
+ GatheringState gathering_state() const { return gathering_state_; }
+
+ // Get the global attributes
+ std::vector<std::string> GetGlobalAttributes();
+
+ // Set the other side's global attributes
+ nsresult ParseGlobalAttributes(std::vector<std::string> attrs);
+
+ // Set whether we are controlling or not.
+ nsresult SetControlling(Controlling controlling);
+
+ Controlling GetControlling();
+
+ // Set the STUN servers. Must be called before StartGathering
+ // (if at all).
+ nsresult SetStunServers(const std::vector<NrIceStunServer>& stun_servers);
+
+ // Set the TURN servers. Must be called before StartGathering
+ // (if at all).
+ nsresult SetTurnServers(const std::vector<NrIceTurnServer>& turn_servers);
+
+ // Provide the resolution provider. Must be called before
+ // StartGathering.
+ nsresult SetResolver(nr_resolver* resolver);
+
+ // Provide the proxy address. Must be called before
+ // StartGathering.
+ nsresult SetProxyConfig(NrSocketProxyConfig&& config);
+
+ const std::shared_ptr<NrSocketProxyConfig>& GetProxyConfig() {
+ return proxy_config_;
+ }
+
+ void SetCtxFlags(bool default_route_only);
+
+ // Start ICE gathering
+ nsresult StartGathering(bool default_route_only,
+ bool obfuscate_host_addresses);
+
+ // Start checking
+ nsresult StartChecks();
+
+ // Notify that the network has gone online/offline
+ void UpdateNetworkState(bool online);
+
+ void AccumulateStats(const NrIceStats& stats);
+ NrIceStats Destroy();
+
+ // Are we trickling?
+ bool generating_trickle() const { return trickle_; }
+
+ // Signals to indicate events. API users can (and should)
+ // register for these.
+ sigslot::signal2<NrIceCtx*, NrIceCtx::GatheringState>
+ SignalGatheringStateChange;
+ sigslot::signal2<NrIceCtx*, NrIceCtx::ConnectionState>
+ SignalConnectionStateChange;
+
+ // The thread to direct method calls to
+ nsCOMPtr<nsIEventTarget> thread() { return sts_target_; }
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NrIceCtx)
+
+ private:
+ explicit NrIceCtx(const std::string& name);
+
+ virtual ~NrIceCtx();
+
+ DISALLOW_COPY_ASSIGN(NrIceCtx);
+
+ // Callbacks for nICEr
+ static void gather_cb(NR_SOCKET s, int h, void* arg); // ICE gather complete
+
+ // Handler implementation
+ static int select_pair(void* obj, nr_ice_media_stream* stream,
+ int component_id, nr_ice_cand_pair** potentials,
+ int potential_ct);
+ static int stream_ready(void* obj, nr_ice_media_stream* stream);
+ static int stream_failed(void* obj, nr_ice_media_stream* stream);
+ static int ice_checking(void* obj, nr_ice_peer_ctx* pctx);
+ static int ice_connected(void* obj, nr_ice_peer_ctx* pctx);
+ static int ice_disconnected(void* obj, nr_ice_peer_ctx* pctx);
+ static int msg_recvd(void* obj, nr_ice_peer_ctx* pctx,
+ nr_ice_media_stream* stream, int component_id,
+ unsigned char* msg, int len);
+ static void trickle_cb(void* arg, nr_ice_ctx* ctx,
+ nr_ice_media_stream* stream, int component_id,
+ nr_ice_candidate* candidate);
+
+ // Find a media stream by stream ptr. Gross
+ RefPtr<NrIceMediaStream> FindStream(nr_ice_media_stream* stream);
+
+ // Set the state
+ void SetConnectionState(ConnectionState state);
+
+ // Set the state
+ void SetGatheringState(GatheringState state);
+
+ void GenerateObfuscatedAddress(nr_ice_candidate* candidate,
+ std::string* mdns_address,
+ std::string* actual_address);
+
+ ConnectionState connection_state_;
+ GatheringState gathering_state_;
+ const std::string name_;
+ bool ice_controlling_set_;
+ std::map<std::string, RefPtr<NrIceMediaStream>> streams_;
+ nr_ice_ctx* ctx_;
+ nr_ice_peer_ctx* peer_;
+ nr_ice_handler_vtbl* ice_handler_vtbl_; // Must be pointer
+ nr_ice_handler* ice_handler_; // Must be pointer
+ bool trickle_;
+ nsCOMPtr<nsIEventTarget> sts_target_; // The thread to run on
+ Config config_;
+ RefPtr<TestNat> nat_;
+ std::shared_ptr<NrSocketProxyConfig> proxy_config_;
+ std::map<std::string, std::string> obfuscated_host_addresses_;
+};
+
+} // namespace mozilla
+#endif
diff --git a/dom/media/webrtc/transport/nricemediastream.cpp b/dom/media/webrtc/transport/nricemediastream.cpp
new file mode 100644
index 0000000000..ef7980dcf3
--- /dev/null
+++ b/dom/media/webrtc/transport/nricemediastream.cpp
@@ -0,0 +1,709 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+
+// Some of this code is cut-and-pasted from nICEr. Copyright is:
+
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+*/
+
+#include <string>
+#include <vector>
+
+#include "logging.h"
+#include "nsError.h"
+#include "nsThreadUtils.h"
+
+// nICEr includes
+extern "C" {
+#include "nr_api.h"
+#include "transport_addr.h"
+#include "nr_socket.h"
+#include "ice_ctx.h"
+#include "ice_candidate.h"
+#include "ice_handler.h"
+}
+
+// Local includes
+#include "nricectx.h"
+#include "nricemediastream.h"
+
+namespace mozilla {
+
+MOZ_MTLOG_MODULE("mtransport")
+
+static bool ToNrIceAddr(nr_transport_addr& addr, NrIceAddr* out) {
+ int r;
+ char addrstring[INET6_ADDRSTRLEN + 1];
+
+ r = nr_transport_addr_get_addrstring(&addr, addrstring, sizeof(addrstring));
+ if (r) return false;
+ out->host = addrstring;
+
+ int port;
+ r = nr_transport_addr_get_port(&addr, &port);
+ if (r) return false;
+
+ out->port = port;
+
+ switch (addr.protocol) {
+ case IPPROTO_TCP:
+ if (addr.tls) {
+ out->transport = kNrIceTransportTls;
+ } else {
+ out->transport = kNrIceTransportTcp;
+ }
+ break;
+ case IPPROTO_UDP:
+ out->transport = kNrIceTransportUdp;
+ break;
+ default:
+ MOZ_CRASH();
+ return false;
+ }
+
+ return true;
+}
+
+static bool ToNrIceCandidate(const nr_ice_candidate& candc,
+ NrIceCandidate* out) {
+ MOZ_ASSERT(out);
+ int r;
+ // Const-cast because the internal nICEr code isn't const-correct.
+ nr_ice_candidate* cand = const_cast<nr_ice_candidate*>(&candc);
+
+ if (!ToNrIceAddr(cand->addr, &out->cand_addr)) return false;
+
+ if (cand->mdns_addr) {
+ out->mdns_addr = cand->mdns_addr;
+ }
+
+ if (cand->isock) {
+ nr_transport_addr addr;
+ r = nr_socket_getaddr(cand->isock->sock, &addr);
+ if (r) return false;
+
+ out->is_proxied = addr.is_proxied;
+
+ if (!ToNrIceAddr(addr, &out->local_addr)) return false;
+ }
+
+ NrIceCandidate::Type type;
+
+ switch (cand->type) {
+ case HOST:
+ type = NrIceCandidate::ICE_HOST;
+ break;
+ case SERVER_REFLEXIVE:
+ type = NrIceCandidate::ICE_SERVER_REFLEXIVE;
+ break;
+ case PEER_REFLEXIVE:
+ type = NrIceCandidate::ICE_PEER_REFLEXIVE;
+ break;
+ case RELAYED:
+ type = NrIceCandidate::ICE_RELAYED;
+ break;
+ default:
+ return false;
+ }
+
+ NrIceCandidate::TcpType tcp_type;
+ switch (cand->tcp_type) {
+ case TCP_TYPE_ACTIVE:
+ tcp_type = NrIceCandidate::ICE_ACTIVE;
+ break;
+ case TCP_TYPE_PASSIVE:
+ tcp_type = NrIceCandidate::ICE_PASSIVE;
+ break;
+ case TCP_TYPE_SO:
+ tcp_type = NrIceCandidate::ICE_SO;
+ break;
+ default:
+ tcp_type = NrIceCandidate::ICE_NONE;
+ break;
+ }
+
+ out->type = type;
+ out->tcp_type = tcp_type;
+ out->codeword = candc.codeword;
+ out->label = candc.label;
+ out->trickled = candc.trickled;
+ out->priority = candc.priority;
+ return true;
+}
+
+// Make an NrIceCandidate from the candidate |cand|.
+// This is not a member fxn because we want to hide the
+// defn of nr_ice_candidate but we pass by reference.
+static UniquePtr<NrIceCandidate> MakeNrIceCandidate(
+ const nr_ice_candidate& candc) {
+ UniquePtr<NrIceCandidate> out(new NrIceCandidate());
+
+ if (!ToNrIceCandidate(candc, out.get())) {
+ return nullptr;
+ }
+ return out;
+}
+
+static bool Matches(const nr_ice_media_stream* stream, const std::string& ufrag,
+ const std::string& pwd) {
+ return stream && (stream->ufrag == ufrag) && (stream->pwd == pwd);
+}
+
+NrIceMediaStream::NrIceMediaStream(NrIceCtx* ctx, const std::string& id,
+ const std::string& name, size_t components)
+ : state_(ICE_CONNECTING),
+ ctx_(ctx),
+ name_(name),
+ components_(components),
+ stream_(nullptr),
+ old_stream_(nullptr),
+ id_(id) {}
+
+NrIceMediaStream::~NrIceMediaStream() {
+ // We do not need to destroy anything. All major resources
+ // are attached to the ice ctx.
+}
+
+nsresult NrIceMediaStream::ConnectToPeer(
+ const std::string& ufrag, const std::string& pwd,
+ const std::vector<std::string>& attributes) {
+ MOZ_ASSERT(stream_);
+
+ if (Matches(old_stream_, ufrag, pwd)) {
+ // (We swap before we close so we never have stream_ == nullptr)
+ MOZ_MTLOG(ML_DEBUG,
+ "Rolling back to old stream ufrag=" << ufrag << " " << name_);
+ std::swap(stream_, old_stream_);
+ CloseStream(&old_stream_);
+ } else if (old_stream_) {
+ // Right now we wait for ICE to complete before closing the old stream.
+ // It might be worth it to close it sooner, but we don't want to close it
+ // right away.
+ MOZ_MTLOG(ML_DEBUG,
+ "ICE restart committed, marking old stream as obsolete, "
+ "beginning switchover to ufrag="
+ << ufrag << " " << name_);
+ nr_ice_media_stream_set_obsolete(old_stream_);
+ }
+
+ nr_ice_media_stream* peer_stream;
+ if (nr_ice_peer_ctx_find_pstream(ctx_->peer(), stream_, &peer_stream)) {
+ // No peer yet
+ std::vector<char*> attributes_in;
+ attributes_in.reserve(attributes.size());
+ for (auto& attribute : attributes) {
+ MOZ_MTLOG(ML_DEBUG, "Setting " << attribute << " on stream " << name_);
+ attributes_in.push_back(const_cast<char*>(attribute.c_str()));
+ }
+
+ // Still need to call nr_ice_ctx_parse_stream_attributes.
+ int r = nr_ice_peer_ctx_parse_stream_attributes(
+ ctx_->peer(), stream_,
+ attributes_in.empty() ? nullptr : &attributes_in[0],
+ attributes_in.size());
+ if (r) {
+ MOZ_MTLOG(ML_ERROR,
+ "Couldn't parse attributes for stream " << name_ << "'");
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult NrIceMediaStream::SetIceCredentials(const std::string& ufrag,
+ const std::string& pwd) {
+ if (Matches(stream_, ufrag, pwd)) {
+ return NS_OK;
+ }
+
+ if (Matches(old_stream_, ufrag, pwd)) {
+ return NS_OK;
+ }
+
+ MOZ_MTLOG(ML_DEBUG, "Setting ICE credentials for " << name_ << " - " << ufrag
+ << ":" << pwd);
+ CloseStream(&old_stream_);
+ old_stream_ = stream_;
+
+ std::string name(name_ + " - " + ufrag + ":" + pwd);
+
+ int r = nr_ice_add_media_stream(ctx_->ctx(), name.c_str(), ufrag.c_str(),
+ pwd.c_str(), components_, &stream_);
+ if (r) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't create ICE media stream for '"
+ << name_ << "': error=" << r);
+ stream_ = old_stream_;
+ old_stream_ = nullptr;
+ return NS_ERROR_FAILURE;
+ }
+
+ state_ = ICE_CONNECTING;
+ return NS_OK;
+}
+
+// Parse trickle ICE candidate
+nsresult NrIceMediaStream::ParseTrickleCandidate(const std::string& candidate,
+ const std::string& ufrag,
+ const std::string& mdns_addr) {
+ nr_ice_media_stream* stream = GetStreamForRemoteUfrag(ufrag);
+ if (!stream) {
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_MTLOG(ML_NOTICE, "NrIceCtx(" << ctx_->ctx()->label << ")/STREAM("
+ << name() << ") : parsing trickle candidate "
+ << candidate);
+
+ int r = nr_ice_peer_ctx_parse_trickle_candidate(
+ ctx_->peer(), stream, const_cast<char*>(candidate.c_str()),
+ mdns_addr.c_str());
+
+ if (r) {
+ if (r == R_ALREADY) {
+ MOZ_MTLOG(ML_INFO, "Trickle candidate is redundant for stream '"
+ << name_
+ << "' because it is completed: " << candidate);
+ } else if (r == R_REJECTED) {
+ MOZ_MTLOG(ML_INFO,
+ "Trickle candidate is ignored for stream '"
+ << name_
+ << "', probably because it is for an unused component"
+ << ": " << candidate);
+ } else {
+ MOZ_MTLOG(ML_ERROR, "Couldn't parse trickle candidate for stream '"
+ << name_ << "': " << candidate);
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ return NS_OK;
+}
+
+// Returns NS_ERROR_NOT_AVAILABLE if component is unpaired or disabled.
+nsresult NrIceMediaStream::GetActivePair(int component,
+ UniquePtr<NrIceCandidate>* localp,
+ UniquePtr<NrIceCandidate>* remotep) {
+ int r;
+ nr_ice_candidate* local_int;
+ nr_ice_candidate* remote_int;
+
+ if (!stream_) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ r = nr_ice_media_stream_get_active(ctx_->peer(), stream_, component,
+ &local_int, &remote_int);
+ // If result is R_REJECTED then component is unpaired or disabled.
+ if (r == R_REJECTED) return NS_ERROR_NOT_AVAILABLE;
+
+ if (r) return NS_ERROR_FAILURE;
+
+ UniquePtr<NrIceCandidate> local(MakeNrIceCandidate(*local_int));
+ if (!local) return NS_ERROR_FAILURE;
+
+ UniquePtr<NrIceCandidate> remote(MakeNrIceCandidate(*remote_int));
+ if (!remote) return NS_ERROR_FAILURE;
+
+ if (localp) *localp = std::move(local);
+ if (remotep) *remotep = std::move(remote);
+
+ return NS_OK;
+}
+
+nsresult NrIceMediaStream::GetCandidatePairs(
+ std::vector<NrIceCandidatePair>* out_pairs) const {
+ MOZ_ASSERT(out_pairs);
+ if (!stream_) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // If we haven't at least started checking then there is nothing to report
+ if (ctx_->peer()->state != NR_ICE_PEER_STATE_PAIRED) {
+ return NS_OK;
+ }
+
+ // Get the check_list on the peer stream (this is where the check_list
+ // actually lives, not in stream_)
+ nr_ice_media_stream* peer_stream;
+ int r = nr_ice_peer_ctx_find_pstream(ctx_->peer(), stream_, &peer_stream);
+ if (r != 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nr_ice_cand_pair *p1, *p2;
+ out_pairs->clear();
+
+ TAILQ_FOREACH(p1, &peer_stream->check_list, check_queue_entry) {
+ MOZ_ASSERT(p1);
+ MOZ_ASSERT(p1->local);
+ MOZ_ASSERT(p1->remote);
+ NrIceCandidatePair pair;
+
+ p2 = TAILQ_FIRST(&peer_stream->check_list);
+ while (p2) {
+ if (p1 == p2) {
+ /* Don't compare with our self. */
+ p2 = TAILQ_NEXT(p2, check_queue_entry);
+ continue;
+ }
+ if (strncmp(p1->codeword, p2->codeword, sizeof(p1->codeword)) == 0) {
+ /* In case of duplicate pairs we only report the one winning pair */
+ if (((p2->remote->component && (p2->remote->component->active == p2)) &&
+ !(p1->remote->component &&
+ (p1->remote->component->active == p1))) ||
+ ((p2->peer_nominated || p2->nominated) &&
+ !(p1->peer_nominated || p1->nominated)) ||
+ (p2->priority > p1->priority) ||
+ ((p2->state == NR_ICE_PAIR_STATE_SUCCEEDED) &&
+ (p1->state != NR_ICE_PAIR_STATE_SUCCEEDED)) ||
+ ((p2->state != NR_ICE_PAIR_STATE_CANCELLED) &&
+ (p1->state == NR_ICE_PAIR_STATE_CANCELLED))) {
+ /* p2 is a better pair. */
+ break;
+ }
+ }
+ p2 = TAILQ_NEXT(p2, check_queue_entry);
+ }
+ if (p2) {
+ /* p2 points to a duplicate but better pair so skip this one */
+ continue;
+ }
+
+ switch (p1->state) {
+ case NR_ICE_PAIR_STATE_FROZEN:
+ pair.state = NrIceCandidatePair::State::STATE_FROZEN;
+ break;
+ case NR_ICE_PAIR_STATE_WAITING:
+ pair.state = NrIceCandidatePair::State::STATE_WAITING;
+ break;
+ case NR_ICE_PAIR_STATE_IN_PROGRESS:
+ pair.state = NrIceCandidatePair::State::STATE_IN_PROGRESS;
+ break;
+ case NR_ICE_PAIR_STATE_FAILED:
+ pair.state = NrIceCandidatePair::State::STATE_FAILED;
+ break;
+ case NR_ICE_PAIR_STATE_SUCCEEDED:
+ pair.state = NrIceCandidatePair::State::STATE_SUCCEEDED;
+ break;
+ case NR_ICE_PAIR_STATE_CANCELLED:
+ pair.state = NrIceCandidatePair::State::STATE_CANCELLED;
+ break;
+ default:
+ MOZ_ASSERT(0);
+ }
+
+ pair.priority = p1->priority;
+ pair.nominated = p1->peer_nominated || p1->nominated;
+ pair.component_id = p1->remote->component->component_id;
+
+ // As discussed with drno: a component's can_send field (set to true
+ // by ICE consent) is a very close approximation for writable and
+ // readable. Note: the component for the local candidate never has
+ // the can_send member set to true, remote for both readable and
+ // writable. (mjf)
+ pair.writable = p1->remote->component->can_send;
+ pair.readable = p1->remote->component->can_send;
+ pair.selected =
+ p1->remote->component && p1->remote->component->active == p1;
+ pair.codeword = p1->codeword;
+ pair.bytes_sent = p1->bytes_sent;
+ pair.bytes_recvd = p1->bytes_recvd;
+ pair.ms_since_last_send =
+ p1->last_sent.tv_sec * 1000 + p1->last_sent.tv_usec / 1000;
+ pair.ms_since_last_recv =
+ p1->last_recvd.tv_sec * 1000 + p1->last_recvd.tv_usec / 1000;
+
+ if (!ToNrIceCandidate(*(p1->local), &pair.local) ||
+ !ToNrIceCandidate(*(p1->remote), &pair.remote)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ out_pairs->push_back(pair);
+ }
+
+ return NS_OK;
+}
+
+nsresult NrIceMediaStream::GetDefaultCandidate(
+ int component, NrIceCandidate* candidate) const {
+ if (!stream_) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nr_ice_candidate* cand;
+
+ int r = nr_ice_media_stream_get_default_candidate(stream_, component, &cand);
+ if (r) {
+ if (r == R_NOT_FOUND) {
+ MOZ_MTLOG(ML_INFO, "Couldn't get default ICE candidate for '"
+ << name_ << "', no candidates.");
+ } else {
+ MOZ_MTLOG(ML_ERROR, "Couldn't get default ICE candidate for '"
+ << name_ << "', " << r);
+ }
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!ToNrIceCandidate(*cand, candidate)) {
+ MOZ_MTLOG(ML_ERROR,
+ "Failed to convert default ICE candidate for '" << name_ << "'");
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+std::vector<std::string> NrIceMediaStream::GetAttributes() const {
+ char** attrs = nullptr;
+ int attrct;
+ int r;
+ std::vector<std::string> ret;
+
+ if (!stream_) {
+ return ret;
+ }
+
+ r = nr_ice_media_stream_get_attributes(stream_, &attrs, &attrct);
+ if (r) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't get ICE candidates for '" << name_ << "'");
+ return ret;
+ }
+
+ for (int i = 0; i < attrct; i++) {
+ ret.push_back(attrs[i]);
+ RFREE(attrs[i]);
+ }
+
+ RFREE(attrs);
+
+ return ret;
+}
+
+static nsresult GetCandidatesFromStream(
+ nr_ice_media_stream* stream, std::vector<NrIceCandidate>* candidates) {
+ MOZ_ASSERT(candidates);
+ nr_ice_component* comp = STAILQ_FIRST(&stream->components);
+ while (comp) {
+ if (comp->state != NR_ICE_COMPONENT_DISABLED) {
+ nr_ice_candidate* cand = TAILQ_FIRST(&comp->candidates);
+ while (cand) {
+ NrIceCandidate new_cand;
+ // This can fail if the candidate is server reflexive or relayed, and
+ // has not yet received a response (ie; it doesn't know its address
+ // yet). For the purposes of this code, this isn't a candidate we're
+ // interested in, since it is not fully baked yet.
+ if (ToNrIceCandidate(*cand, &new_cand)) {
+ candidates->push_back(new_cand);
+ }
+ cand = TAILQ_NEXT(cand, entry_comp);
+ }
+ }
+ comp = STAILQ_NEXT(comp, entry);
+ }
+
+ return NS_OK;
+}
+
+nsresult NrIceMediaStream::GetLocalCandidates(
+ std::vector<NrIceCandidate>* candidates) const {
+ if (!stream_) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return GetCandidatesFromStream(stream_, candidates);
+}
+
+nsresult NrIceMediaStream::GetRemoteCandidates(
+ std::vector<NrIceCandidate>* candidates) const {
+ if (!stream_) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // If we haven't at least started checking then there is nothing to report
+ if (ctx_->peer()->state != NR_ICE_PEER_STATE_PAIRED) {
+ return NS_OK;
+ }
+
+ nr_ice_media_stream* peer_stream;
+ int r = nr_ice_peer_ctx_find_pstream(ctx_->peer(), stream_, &peer_stream);
+ if (r != 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return GetCandidatesFromStream(peer_stream, candidates);
+}
+
+nsresult NrIceMediaStream::DisableComponent(int component_id) {
+ if (!stream_) return NS_ERROR_FAILURE;
+
+ int r = nr_ice_media_stream_disable_component(stream_, component_id);
+ if (r) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't disable '" << name_ << "':" << component_id);
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult NrIceMediaStream::GetConsentStatus(int component_id, bool* can_send,
+ struct timeval* ts) {
+ if (!stream_) return NS_ERROR_FAILURE;
+
+ nr_ice_media_stream* peer_stream;
+ int r = nr_ice_peer_ctx_find_pstream(ctx_->peer(), stream_, &peer_stream);
+ if (r) {
+ MOZ_MTLOG(ML_ERROR, "Failed to find peer stream for '"
+ << name_ << "':" << component_id);
+ return NS_ERROR_FAILURE;
+ }
+
+ int send = 0;
+ r = nr_ice_media_stream_get_consent_status(peer_stream, component_id, &send,
+ ts);
+ if (r) {
+ MOZ_MTLOG(ML_ERROR, "Failed to get consent status for '"
+ << name_ << "':" << component_id);
+ return NS_ERROR_FAILURE;
+ }
+ *can_send = !!send;
+
+ return NS_OK;
+}
+
+bool NrIceMediaStream::HasStream(nr_ice_media_stream* stream) const {
+ return (stream == stream_) || (stream == old_stream_);
+}
+
+nsresult NrIceMediaStream::SendPacket(int component_id,
+ const unsigned char* data, size_t len) {
+ nr_ice_media_stream* stream = old_stream_ ? old_stream_ : stream_;
+ if (!stream) {
+ return NS_ERROR_FAILURE;
+ }
+
+ int r = nr_ice_media_stream_send(ctx_->peer(), stream, component_id,
+ const_cast<unsigned char*>(data), len);
+ if (r) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't send media on '" << name_ << "'");
+ if (r == R_WOULDBLOCK) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ return NS_BASE_STREAM_OSERROR;
+ }
+
+ return NS_OK;
+}
+
+void NrIceMediaStream::Ready() {
+ // This function is called whenever a stream becomes ready, but it
+ // gets fired multiple times when a stream gets nominated repeatedly.
+ if (state_ != ICE_OPEN) {
+ MOZ_MTLOG(ML_DEBUG, "Marking stream ready '" << name_ << "'");
+ state_ = ICE_OPEN;
+ NS_DispatchToCurrentThread(NewRunnableMethod<nr_ice_media_stream*>(
+ "NrIceMediaStream::DeferredCloseOldStream", this,
+ &NrIceMediaStream::DeferredCloseOldStream, old_stream_));
+ SignalReady(this);
+ } else {
+ MOZ_MTLOG(ML_DEBUG,
+ "Stream ready callback fired again for '" << name_ << "'");
+ }
+}
+
+void NrIceMediaStream::Failed() {
+ if (state_ != ICE_CLOSED) {
+ MOZ_MTLOG(ML_DEBUG, "Marking stream failed '" << name_ << "'");
+ state_ = ICE_CLOSED;
+ // We don't need the old stream anymore.
+ NS_DispatchToCurrentThread(NewRunnableMethod<nr_ice_media_stream*>(
+ "NrIceMediaStream::DeferredCloseOldStream", this,
+ &NrIceMediaStream::DeferredCloseOldStream, old_stream_));
+ SignalFailed(this);
+ }
+}
+
+void NrIceMediaStream::Close() {
+ MOZ_MTLOG(ML_DEBUG, "Marking stream closed '" << name_ << "'");
+ state_ = ICE_CLOSED;
+
+ CloseStream(&old_stream_);
+ CloseStream(&stream_);
+ ctx_ = nullptr;
+}
+
+void NrIceMediaStream::CloseStream(nr_ice_media_stream** stream) {
+ if (*stream) {
+ int r = nr_ice_remove_media_stream(ctx_->ctx(), stream);
+ if (r) {
+ MOZ_ASSERT(false, "Failed to remove stream");
+ MOZ_MTLOG(ML_ERROR, "Failed to remove stream, error=" << r);
+ }
+ *stream = nullptr;
+ }
+}
+
+void NrIceMediaStream::DeferredCloseOldStream(const nr_ice_media_stream* old) {
+ if (old == old_stream_) {
+ CloseStream(&old_stream_);
+ }
+}
+
+nr_ice_media_stream* NrIceMediaStream::GetStreamForRemoteUfrag(
+ const std::string& aUfrag) {
+ if (aUfrag.empty()) {
+ return stream_;
+ }
+
+ nr_ice_media_stream* peer_stream = nullptr;
+
+ if (!nr_ice_peer_ctx_find_pstream(ctx_->peer(), stream_, &peer_stream) &&
+ aUfrag == peer_stream->ufrag) {
+ return stream_;
+ }
+
+ if (old_stream_ &&
+ !nr_ice_peer_ctx_find_pstream(ctx_->peer(), old_stream_, &peer_stream) &&
+ aUfrag == peer_stream->ufrag) {
+ return old_stream_;
+ }
+
+ return nullptr;
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/transport/nricemediastream.h b/dom/media/webrtc/transport/nricemediastream.h
new file mode 100644
index 0000000000..f18b09c47f
--- /dev/null
+++ b/dom/media/webrtc/transport/nricemediastream.h
@@ -0,0 +1,225 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+
+// Some of this code is cut-and-pasted from nICEr. Copyright is:
+
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+*/
+
+// This is a wrapper around the nICEr ICE stack
+#ifndef nricemediastream_h__
+#define nricemediastream_h__
+
+#include <string>
+#include <vector>
+
+#include "sigslot.h"
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "nscore.h"
+
+#include "m_cpp_utils.h"
+
+namespace mozilla {
+
+typedef struct nr_ice_ctx_ nr_ice_ctx;
+typedef struct nr_ice_peer_ctx_ nr_ice_peer_ctx;
+typedef struct nr_ice_media_stream_ nr_ice_media_stream;
+
+class NrIceCtx;
+
+struct NrIceAddr {
+ std::string host;
+ uint16_t port;
+ std::string transport;
+};
+
+/* A summary of a candidate, for use in asking which candidate
+ pair is active */
+struct NrIceCandidate {
+ enum Type { ICE_HOST, ICE_SERVER_REFLEXIVE, ICE_PEER_REFLEXIVE, ICE_RELAYED };
+
+ enum TcpType { ICE_NONE, ICE_ACTIVE, ICE_PASSIVE, ICE_SO };
+
+ NrIceAddr cand_addr;
+ NrIceAddr local_addr;
+ std::string mdns_addr;
+ Type type;
+ TcpType tcp_type;
+ std::string codeword;
+ std::string label;
+ bool trickled;
+ uint32_t priority;
+ bool is_proxied = false;
+};
+
+struct NrIceCandidatePair {
+ enum State {
+ STATE_FROZEN,
+ STATE_WAITING,
+ STATE_IN_PROGRESS,
+ STATE_FAILED,
+ STATE_SUCCEEDED,
+ STATE_CANCELLED
+ };
+
+ State state;
+ uint64_t priority;
+ // Set regardless of who nominated it. Does not necessarily mean that it is
+ // ready to be selected (ie; nominated by peer, but our check has not
+ // succeeded yet.) Note: since this implementation uses aggressive nomination,
+ // when we are the controlling agent, this will always be set if the pair is
+ // in STATE_SUCCEEDED.
+ bool nominated;
+ bool writable;
+ bool readable;
+ // Set if this candidate pair has been selected. Note: Since we are using
+ // aggressive nomination, this could change frequently as ICE runs.
+ bool selected;
+ NrIceCandidate local;
+ NrIceCandidate remote;
+ // TODO(bcampen@mozilla.com): Is it important to put the foundation in here?
+ std::string codeword;
+ uint64_t component_id;
+
+ // for RTCIceCandidatePairStats
+ uint64_t bytes_sent;
+ uint64_t bytes_recvd;
+ uint64_t ms_since_last_send;
+ uint64_t ms_since_last_recv;
+};
+
+class NrIceMediaStream {
+ public:
+ NrIceMediaStream(NrIceCtx* ctx, const std::string& id,
+ const std::string& name, size_t components);
+
+ nsresult SetIceCredentials(const std::string& ufrag, const std::string& pwd);
+ nsresult ConnectToPeer(const std::string& ufrag, const std::string& pwd,
+ const std::vector<std::string>& peer_attrs);
+ enum State { ICE_CONNECTING, ICE_OPEN, ICE_CLOSED };
+
+ State state() const { return state_; }
+
+ // The name of the stream
+ const std::string& name() const { return name_; }
+
+ // Get all the ICE attributes; used for testing
+ std::vector<std::string> GetAttributes() const;
+
+ nsresult GetLocalCandidates(std::vector<NrIceCandidate>* candidates) const;
+ nsresult GetRemoteCandidates(std::vector<NrIceCandidate>* candidates) const;
+
+ // Get all candidate pairs, whether in the check list or triggered check
+ // queue, in priority order. |out_pairs| is cleared before being filled.
+ nsresult GetCandidatePairs(std::vector<NrIceCandidatePair>* out_pairs) const;
+
+ nsresult GetDefaultCandidate(int component, NrIceCandidate* candidate) const;
+
+ // Parse trickle ICE candidate
+ nsresult ParseTrickleCandidate(const std::string& candidate,
+ const std::string& ufrag,
+ const std::string& mdns_addr);
+
+ // Disable a component
+ nsresult DisableComponent(int component);
+
+ // Get the candidate pair currently active. It's the
+ // caller's responsibility to free these.
+ nsresult GetActivePair(int component, UniquePtr<NrIceCandidate>* local,
+ UniquePtr<NrIceCandidate>* remote);
+
+ // Get the current ICE consent send status plus the timeval of the last
+ // consent update time.
+ nsresult GetConsentStatus(int component, bool* can_send, struct timeval* ts);
+
+ // The number of components
+ size_t components() const { return components_; }
+
+ bool HasStream(nr_ice_media_stream* stream) const;
+ // Signals to indicate events. API users can (and should)
+ // register for these.
+
+ // Send a packet
+ nsresult SendPacket(int component_id, const unsigned char* data, size_t len);
+
+ // Set your state to ready. Called by the NrIceCtx;
+ void Ready();
+ void Failed();
+
+ // Close the stream. Called by the NrIceCtx.
+ // Different from the destructor because other people
+ // might be holding RefPtrs but we want those writes to fail once
+ // the context has been destroyed.
+ void Close();
+
+ // So the receiver of SignalCandidate can determine which transport
+ // the candidate belongs to.
+ const std::string& GetId() const { return id_; }
+
+ sigslot::signal5<NrIceMediaStream*, const std::string&, const std::string&,
+ const std::string&, const std::string&>
+ SignalCandidate; // A new ICE candidate:
+
+ sigslot::signal1<NrIceMediaStream*> SignalReady; // Candidate pair ready.
+ sigslot::signal1<NrIceMediaStream*> SignalFailed; // Candidate pair failed.
+ sigslot::signal4<NrIceMediaStream*, int, const unsigned char*, int>
+ SignalPacketReceived; // Incoming packet
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NrIceMediaStream)
+
+ private:
+ ~NrIceMediaStream();
+
+ DISALLOW_COPY_ASSIGN(NrIceMediaStream);
+
+ void CloseStream(nr_ice_media_stream** stream);
+ void DeferredCloseOldStream(const nr_ice_media_stream* old);
+ nr_ice_media_stream* GetStreamForRemoteUfrag(const std::string& ufrag);
+
+ State state_;
+ RefPtr<NrIceCtx> ctx_;
+ const std::string name_;
+ const size_t components_;
+ nr_ice_media_stream* stream_;
+ nr_ice_media_stream* old_stream_;
+ const std::string id_;
+};
+
+} // namespace mozilla
+#endif
diff --git a/dom/media/webrtc/transport/nriceresolver.cpp b/dom/media/webrtc/transport/nriceresolver.cpp
new file mode 100644
index 0000000000..e1737e0e65
--- /dev/null
+++ b/dom/media/webrtc/transport/nriceresolver.cpp
@@ -0,0 +1,234 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original authors: jib@mozilla.com, ekr@rtfm.com
+
+// Some of this code is cut-and-pasted from nICEr. Copyright is:
+
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+*/
+
+#include "logging.h"
+#include "nspr.h"
+#include "prnetdb.h"
+
+#include "mozilla/Assertions.h"
+
+extern "C" {
+#include "nr_api.h"
+#include "async_timer.h"
+#include "nr_resolver.h"
+#include "transport_addr.h"
+}
+
+#include "mozilla/net/DNS.h" // TODO(jib@mozilla.com) down here because bug 848578
+#include "nsThreadUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIDNSService.h"
+#include "nsIDNSListener.h"
+#include "nsIDNSRecord.h"
+#include "nsNetCID.h"
+#include "nsCOMPtr.h"
+#include "nriceresolver.h"
+#include "nr_socket_prsock.h"
+#include "transport/runnable_utils.h"
+
+namespace mozilla {
+
+MOZ_MTLOG_MODULE("mtransport")
+
+NrIceResolver::NrIceResolver()
+ : vtbl_(new nr_resolver_vtbl())
+#ifdef DEBUG
+ ,
+ allocated_resolvers_(0)
+#endif
+{
+ vtbl_->destroy = &NrIceResolver::destroy;
+ vtbl_->resolve = &NrIceResolver::resolve;
+ vtbl_->cancel = &NrIceResolver::cancel;
+}
+
+NrIceResolver::~NrIceResolver() {
+ MOZ_ASSERT(!allocated_resolvers_);
+ delete vtbl_;
+}
+
+nsresult NrIceResolver::Init() {
+ nsresult rv;
+
+ sts_thread_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ dns_ = do_GetService(NS_DNSSERVICE_CONTRACTID, &rv);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_MTLOG(ML_ERROR, "Could not acquire DNS service");
+ }
+ return rv;
+}
+
+nr_resolver* NrIceResolver::AllocateResolver() {
+ nr_resolver* resolver;
+
+ int r = nr_resolver_create_int((void*)this, vtbl_, &resolver);
+ MOZ_ASSERT(!r);
+ if (r) {
+ MOZ_MTLOG(ML_ERROR, "nr_resolver_create_int failed");
+ return nullptr;
+ }
+ // We must be available to allocators until they all call DestroyResolver,
+ // because allocators may (and do) outlive the originator of NrIceResolver.
+ AddRef();
+#ifdef DEBUG
+ ++allocated_resolvers_;
+#endif
+ return resolver;
+}
+
+void NrIceResolver::DestroyResolver() {
+#ifdef DEBUG
+ --allocated_resolvers_;
+#endif
+ // Undoes Addref in AllocateResolver so the NrIceResolver can be freed.
+ Release();
+}
+
+int NrIceResolver::destroy(void** objp) {
+ if (!objp || !*objp) return 0;
+ NrIceResolver* resolver = static_cast<NrIceResolver*>(*objp);
+ *objp = nullptr;
+ resolver->DestroyResolver();
+ return 0;
+}
+
+int NrIceResolver::resolve(void* obj, nr_resolver_resource* resource,
+ int (*cb)(void* cb_arg, nr_transport_addr* addr),
+ void* cb_arg, void** handle) {
+ MOZ_ASSERT(obj);
+ return static_cast<NrIceResolver*>(obj)->resolve(resource, cb, cb_arg,
+ handle);
+}
+
+int NrIceResolver::resolve(nr_resolver_resource* resource,
+ int (*cb)(void* cb_arg, nr_transport_addr* addr),
+ void* cb_arg, void** handle) {
+ int _status;
+ MOZ_ASSERT(allocated_resolvers_ > 0);
+ ASSERT_ON_THREAD(sts_thread_);
+ RefPtr<PendingResolution> pr;
+ nsIDNSService::DNSFlags resolve_flags = nsIDNSService::RESOLVE_DEFAULT_FLAGS;
+ OriginAttributes attrs;
+
+ if (resource->transport_protocol != IPPROTO_UDP &&
+ resource->transport_protocol != IPPROTO_TCP) {
+ MOZ_MTLOG(ML_ERROR, "Only UDP and TCP are supported.");
+ ABORT(R_NOT_FOUND);
+ }
+ pr = new PendingResolution(
+ sts_thread_, resource->port ? resource->port : 3478,
+ resource->transport_protocol ? resource->transport_protocol : IPPROTO_UDP,
+ cb, cb_arg);
+
+ switch (resource->address_family) {
+ case AF_INET:
+ resolve_flags = nsIDNSService::RESOLVE_DISABLE_IPV6;
+ break;
+ case AF_INET6:
+ resolve_flags = nsIDNSService::RESOLVE_DISABLE_IPV4;
+ break;
+ default:
+ ABORT(R_BAD_ARGS);
+ }
+
+ if (NS_FAILED(dns_->AsyncResolveNative(
+ nsAutoCString(resource->domain_name),
+ nsIDNSService::RESOLVE_TYPE_DEFAULT, resolve_flags, nullptr, pr,
+ sts_thread_, attrs, getter_AddRefs(pr->request_)))) {
+ MOZ_MTLOG(ML_ERROR, "AsyncResolve failed.");
+ ABORT(R_NOT_FOUND);
+ }
+ // Because the C API offers no "finished" method to release the handle we
+ // return, we cannot return the request we got from AsyncResolve directly.
+ //
+ // Instead, we return an addref'ed reference to PendingResolution itself,
+ // which in turn holds the request and coordinates between cancel and
+ // OnLookupComplete to release it only once.
+ pr.forget(handle);
+
+ _status = 0;
+abort:
+ return _status;
+}
+
+nsresult NrIceResolver::PendingResolution::OnLookupComplete(
+ nsICancelable* request, nsIDNSRecord* aRecord, nsresult status) {
+ ASSERT_ON_THREAD(thread_);
+ // First check if we've been canceled. This is single-threaded on the STS
+ // thread, but cancel() cannot guarantee this event isn't on the queue.
+ if (request_) {
+ nr_transport_addr* cb_addr = nullptr;
+ nr_transport_addr ta;
+ // TODO(jib@mozilla.com): Revisit when we do TURN.
+ if (NS_SUCCEEDED(status)) {
+ net::NetAddr na;
+ nsCOMPtr<nsIDNSAddrRecord> record = do_QueryInterface(aRecord);
+ if (record && NS_SUCCEEDED(record->GetNextAddr(port_, &na))) {
+ MOZ_ALWAYS_TRUE(nr_netaddr_to_transport_addr(&na, &ta, transport_) ==
+ 0);
+ cb_addr = &ta;
+ }
+ }
+ cb_(cb_arg_, cb_addr);
+ request_ = nullptr;
+ Release();
+ }
+ return NS_OK;
+}
+
+int NrIceResolver::cancel(void* obj, void* handle) {
+ MOZ_ALWAYS_TRUE(obj);
+ MOZ_ASSERT(handle);
+ ASSERT_ON_THREAD(static_cast<NrIceResolver*>(obj)->sts_thread_);
+ return static_cast<PendingResolution*>(handle)->cancel();
+}
+
+int NrIceResolver::PendingResolution::cancel() {
+ request_->Cancel(NS_ERROR_ABORT);
+ request_ = nullptr;
+ Release();
+ return 0;
+}
+
+NS_IMPL_ISUPPORTS(NrIceResolver::PendingResolution, nsIDNSListener);
+} // End of namespace mozilla
diff --git a/dom/media/webrtc/transport/nriceresolver.h b/dom/media/webrtc/transport/nriceresolver.h
new file mode 100644
index 0000000000..7399c78050
--- /dev/null
+++ b/dom/media/webrtc/transport/nriceresolver.h
@@ -0,0 +1,119 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original authors: jib@mozilla.com, ekr@rtfm.com
+
+// Some of this code is cut-and-pasted from nICEr. Copyright is:
+
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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 nriceresolver_h__
+#define nriceresolver_h__
+
+#include "nsIDNSService.h"
+#include "nsIDNSListener.h"
+#include "nsICancelable.h"
+#include "nricectx.h"
+
+typedef struct nr_resolver_ nr_resolver;
+typedef struct nr_resolver_vtbl_ nr_resolver_vtbl;
+typedef struct nr_transport_addr_ nr_transport_addr;
+typedef struct nr_resolver_resource_ nr_resolver_resource;
+
+namespace mozilla {
+
+class NrIceResolver {
+ private:
+ ~NrIceResolver();
+
+ public:
+ NrIceResolver();
+
+ nsresult Init();
+ nr_resolver* AllocateResolver();
+ void DestroyResolver();
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NrIceResolver)
+
+ int resolve(nr_resolver_resource* resource,
+ int (*cb)(void* cb_arg, nr_transport_addr* addr), void* cb_arg,
+ void** handle);
+
+ private:
+ // Implementations of vtbl functions
+ static int destroy(void** objp);
+ static int resolve(void* obj, nr_resolver_resource* resource,
+ int (*cb)(void* cb_arg, nr_transport_addr* addr),
+ void* cb_arg, void** handle);
+ static void resolve_cb(NR_SOCKET s, int how, void* cb_arg);
+ static int cancel(void* obj, void* handle);
+
+ class PendingResolution : public nsIDNSListener {
+ public:
+ PendingResolution(nsIEventTarget* thread, uint16_t port, int transport,
+ int (*cb)(void* cb_arg, nr_transport_addr* addr),
+ void* cb_arg)
+ : thread_(thread),
+ port_(port),
+ transport_(transport),
+ cb_(cb),
+ cb_arg_(cb_arg) {}
+ NS_IMETHOD OnLookupComplete(nsICancelable* request, nsIDNSRecord* record,
+ nsresult status) override;
+
+ int cancel();
+ nsCOMPtr<nsICancelable> request_;
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ private:
+ virtual ~PendingResolution() = default;
+
+ nsCOMPtr<nsIEventTarget> thread_;
+ uint16_t port_;
+ int transport_;
+ int (*cb_)(void* cb_arg, nr_transport_addr* addr);
+ void* cb_arg_;
+ };
+
+ nr_resolver_vtbl* vtbl_;
+ nsCOMPtr<nsIEventTarget> sts_thread_;
+ nsCOMPtr<nsIDNSService> dns_;
+#ifdef DEBUG
+ int allocated_resolvers_;
+#endif
+};
+
+} // End of namespace mozilla
+#endif
diff --git a/dom/media/webrtc/transport/nriceresolverfake.cpp b/dom/media/webrtc/transport/nriceresolverfake.cpp
new file mode 100644
index 0000000000..93ef37efc1
--- /dev/null
+++ b/dom/media/webrtc/transport/nriceresolverfake.cpp
@@ -0,0 +1,174 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+
+// Some of this code is cut-and-pasted from nICEr. Copyright is:
+
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+*/
+
+#include "prio.h"
+#include "mozilla/Assertions.h"
+
+extern "C" {
+#include "async_wait.h"
+#include "async_timer.h"
+#include "nr_resolver.h"
+#include "r_macros.h"
+#include "transport_addr.h"
+}
+
+#include "nriceresolverfake.h"
+#include "nr_socket_prsock.h"
+
+namespace mozilla {
+
+NrIceResolverFake::NrIceResolverFake()
+ : vtbl_(new nr_resolver_vtbl),
+ addrs_(),
+ delay_ms_(100),
+ allocated_resolvers_(0) {
+ vtbl_->destroy = &NrIceResolverFake::destroy;
+ vtbl_->resolve = &NrIceResolverFake::resolve;
+ vtbl_->cancel = &NrIceResolverFake::cancel;
+}
+
+NrIceResolverFake::~NrIceResolverFake() {
+ MOZ_ASSERT(allocated_resolvers_ == 0);
+ delete vtbl_;
+}
+
+nr_resolver* NrIceResolverFake::AllocateResolver() {
+ nr_resolver* resolver;
+
+ int r = nr_resolver_create_int((void*)this, vtbl_, &resolver);
+ MOZ_ASSERT(!r);
+ if (r) return nullptr;
+
+ ++allocated_resolvers_;
+
+ return resolver;
+}
+
+void NrIceResolverFake::DestroyResolver() { --allocated_resolvers_; }
+
+int NrIceResolverFake::destroy(void** objp) {
+ if (!objp || !*objp) return 0;
+
+ NrIceResolverFake* fake = static_cast<NrIceResolverFake*>(*objp);
+ *objp = nullptr;
+
+ fake->DestroyResolver();
+
+ return 0;
+}
+
+int NrIceResolverFake::resolve(void* obj, nr_resolver_resource* resource,
+ int (*cb)(void* cb_arg, nr_transport_addr* addr),
+ void* cb_arg, void** handle) {
+ int r, _status;
+
+ MOZ_ASSERT(obj);
+ NrIceResolverFake* fake = static_cast<NrIceResolverFake*>(obj);
+
+ MOZ_ASSERT(fake->allocated_resolvers_ > 0);
+
+ PendingResolution* pending = new PendingResolution(
+ fake, resource->domain_name, resource->port ? resource->port : 3478,
+ resource->transport_protocol ? resource->transport_protocol : IPPROTO_UDP,
+ resource->address_family, cb, cb_arg);
+
+ if ((r = NR_ASYNC_TIMER_SET(fake->delay_ms_, NrIceResolverFake::resolve_cb,
+ (void*)pending, &pending->timer_handle_))) {
+ delete pending;
+ ABORT(r);
+ }
+ *handle = pending;
+
+ _status = 0;
+abort:
+ return (_status);
+}
+
+void NrIceResolverFake::resolve_cb(NR_SOCKET s, int how, void* cb_arg) {
+ MOZ_ASSERT(cb_arg);
+ PendingResolution* pending = static_cast<PendingResolution*>(cb_arg);
+
+ const PRNetAddr* addr =
+ pending->resolver_->Resolve(pending->hostname_, pending->address_family_);
+
+ if (addr) {
+ nr_transport_addr transport_addr;
+
+ int r = nr_praddr_to_transport_addr(addr, &transport_addr,
+ pending->transport_, 0);
+ MOZ_ASSERT(!r);
+ if (r) goto abort;
+
+ r = nr_transport_addr_set_port(&transport_addr, pending->port_);
+ MOZ_ASSERT(!r);
+ if (r) goto abort;
+
+ /* Fill in the address string */
+ r = nr_transport_addr_fmt_addr_string(&transport_addr);
+ MOZ_ASSERT(!r);
+ if (r) goto abort;
+
+ pending->cb_(pending->cb_arg_, &transport_addr);
+ delete pending;
+ return;
+ }
+
+abort:
+ // Resolution failed.
+ pending->cb_(pending->cb_arg_, nullptr);
+
+ delete pending;
+}
+
+int NrIceResolverFake::cancel(void* obj, void* handle) {
+ MOZ_ASSERT(obj);
+ MOZ_ASSERT(static_cast<NrIceResolverFake*>(obj)->allocated_resolvers_ > 0);
+
+ PendingResolution* pending = static_cast<PendingResolution*>(handle);
+
+ NR_async_timer_cancel(pending->timer_handle_);
+ delete pending;
+
+ return (0);
+}
+
+} // End of namespace mozilla
diff --git a/dom/media/webrtc/transport/nriceresolverfake.h b/dom/media/webrtc/transport/nriceresolverfake.h
new file mode 100644
index 0000000000..e46ce603de
--- /dev/null
+++ b/dom/media/webrtc/transport/nriceresolverfake.h
@@ -0,0 +1,137 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+
+// Some of this code is cut-and-pasted from nICEr. Copyright is:
+
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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 nriceresolverfake_h__
+#define nriceresolverfake_h__
+
+#include <map>
+#include <string>
+
+#include "csi_platform.h"
+
+typedef struct nr_resolver_ nr_resolver;
+typedef struct nr_resolver_vtbl_ nr_resolver_vtbl;
+typedef struct nr_transport_addr_ nr_transport_addr;
+typedef struct nr_resolver_resource_ nr_resolver_resource;
+
+namespace mozilla {
+
+class NrIceResolverFake {
+ public:
+ NrIceResolverFake();
+ ~NrIceResolverFake();
+
+ void SetAddr(const std::string& hostname, const PRNetAddr& addr) {
+ switch (addr.raw.family) {
+ case AF_INET:
+ addrs_[hostname] = addr;
+ break;
+ case AF_INET6:
+ addrs6_[hostname] = addr;
+ break;
+ default:
+ MOZ_CRASH();
+ }
+ }
+
+ nr_resolver* AllocateResolver();
+
+ void DestroyResolver();
+
+ private:
+ // Implementations of vtbl functions
+ static int destroy(void** objp);
+ static int resolve(void* obj, nr_resolver_resource* resource,
+ int (*cb)(void* cb_arg, nr_transport_addr* addr),
+ void* cb_arg, void** handle);
+ static void resolve_cb(NR_SOCKET s, int how, void* cb_arg);
+ static int cancel(void* obj, void* handle);
+
+ // Get an address.
+ const PRNetAddr* Resolve(const std::string& hostname, int address_family) {
+ switch (address_family) {
+ case AF_INET:
+ if (!addrs_.count(hostname)) return nullptr;
+
+ return &addrs_[hostname];
+ case AF_INET6:
+ if (!addrs6_.count(hostname)) return nullptr;
+
+ return &addrs6_[hostname];
+ default:
+ MOZ_CRASH();
+ }
+ }
+
+ struct PendingResolution {
+ PendingResolution(NrIceResolverFake* resolver, const std::string& hostname,
+ uint16_t port, int transport, int address_family,
+ int (*cb)(void* cb_arg, nr_transport_addr* addr),
+ void* cb_arg)
+ : resolver_(resolver),
+ hostname_(hostname),
+ port_(port),
+ transport_(transport),
+ address_family_(address_family),
+ cb_(cb),
+ cb_arg_(cb_arg) {}
+
+ NrIceResolverFake* resolver_;
+ std::string hostname_;
+ uint16_t port_;
+ int transport_;
+ int address_family_;
+ int (*cb_)(void* cb_arg, nr_transport_addr* addr);
+ void* cb_arg_;
+ void* timer_handle_;
+ };
+
+ nr_resolver_vtbl* vtbl_;
+ std::map<std::string, PRNetAddr> addrs_;
+ std::map<std::string, PRNetAddr> addrs6_;
+ uint32_t delay_ms_;
+ int allocated_resolvers_;
+};
+
+} // End of namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/transport/nricestunaddr.cpp b/dom/media/webrtc/transport/nricestunaddr.cpp
new file mode 100644
index 0000000000..d1e8d3ac52
--- /dev/null
+++ b/dom/media/webrtc/transport/nricestunaddr.cpp
@@ -0,0 +1,93 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "logging.h"
+
+// nICEr includes
+extern "C" {
+#include "nr_api.h"
+#include "local_addr.h"
+}
+
+// Local includes
+#include "nricestunaddr.h"
+
+namespace mozilla {
+
+MOZ_MTLOG_MODULE("mtransport")
+
+NrIceStunAddr::NrIceStunAddr() : localAddr_(new nr_local_addr) {
+ memset(localAddr_, 0, sizeof(nr_local_addr));
+}
+
+NrIceStunAddr::NrIceStunAddr(const nr_local_addr* addr)
+ : localAddr_(new nr_local_addr) {
+ nr_local_addr_copy(localAddr_, const_cast<nr_local_addr*>(addr));
+}
+
+NrIceStunAddr::NrIceStunAddr(const NrIceStunAddr& rhs)
+ : localAddr_(new nr_local_addr) {
+ nr_local_addr_copy(localAddr_, const_cast<nr_local_addr*>(rhs.localAddr_));
+}
+
+NrIceStunAddr::~NrIceStunAddr() { delete localAddr_; }
+
+size_t NrIceStunAddr::SerializationBufferSize() const {
+ return sizeof(nr_local_addr);
+}
+
+nsresult NrIceStunAddr::Serialize(char* buffer, size_t buffer_size) const {
+ if (buffer_size != sizeof(nr_local_addr)) {
+ MOZ_MTLOG(ML_ERROR,
+ "Failed trying to serialize NrIceStunAddr, "
+ "input buffer length ("
+ << buffer_size << ") does not match required length ("
+ << sizeof(nr_local_addr) << ")");
+ MOZ_ASSERT(false, "Failed to serialize NrIceStunAddr, bad buffer size");
+ return NS_ERROR_FAILURE;
+ }
+
+ nr_local_addr* toAddr = (nr_local_addr*)buffer;
+ if (nr_local_addr_copy(toAddr, localAddr_)) {
+ MOZ_MTLOG(ML_ERROR,
+ "Failed trying to serialize NrIceStunAddr, "
+ "could not copy nr_local_addr.");
+ MOZ_ASSERT(false,
+ "Failed to serialize NrIceStunAddr, nr_local_addr_copy failed");
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult NrIceStunAddr::Deserialize(const char* buffer, size_t buffer_size) {
+ if (buffer_size != sizeof(nr_local_addr)) {
+ MOZ_MTLOG(ML_ERROR,
+ "Failed trying to deserialize NrIceStunAddr, "
+ "input buffer length ("
+ << buffer_size << ") does not match required length ("
+ << sizeof(nr_local_addr) << ")");
+ MOZ_ASSERT(false, "Failed to deserialize NrIceStunAddr, bad buffer size");
+ return NS_ERROR_FAILURE;
+ }
+
+ nr_local_addr* from_addr =
+ const_cast<nr_local_addr*>((const nr_local_addr*)buffer);
+
+ // At this point, from_addr->addr.addr is invalid (null), but will
+ // be fixed by nr_local_addr_copy.
+ if (nr_local_addr_copy(localAddr_, from_addr)) {
+ MOZ_MTLOG(ML_ERROR,
+ "Failed trying to deserialize NrIceStunAddr, "
+ "could not copy nr_local_addr.");
+ MOZ_ASSERT(
+ false,
+ "Failed to deserialize NrIceStunAddr, nr_local_addr_copy failed");
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/transport/nricestunaddr.h b/dom/media/webrtc/transport/nricestunaddr.h
new file mode 100644
index 0000000000..8ebcdd6fe7
--- /dev/null
+++ b/dom/media/webrtc/transport/nricestunaddr.h
@@ -0,0 +1,36 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nricestunaddr_h__
+#define nricestunaddr_h__
+
+#include "nsError.h" // for nsresult
+
+typedef struct nr_local_addr_ nr_local_addr;
+
+namespace mozilla {
+
+class NrIceStunAddr {
+ public:
+ NrIceStunAddr(); // needed for IPC deserialization
+ explicit NrIceStunAddr(const nr_local_addr* addr);
+ NrIceStunAddr(const NrIceStunAddr& rhs);
+
+ ~NrIceStunAddr();
+
+ const nr_local_addr& localAddr() const { return *localAddr_; }
+
+ // serialization/deserialization helper functions for use
+ // in dom/media/webrtc/transport/ipc/NrIceStunAddrMessagUtils.h
+ size_t SerializationBufferSize() const;
+ nsresult Serialize(char* buffer, size_t buffer_size) const;
+ nsresult Deserialize(const char* buffer, size_t buffer_size);
+
+ private:
+ nr_local_addr* localAddr_;
+};
+
+} // namespace mozilla
+
+#endif // nricestunaddr_h__
diff --git a/dom/media/webrtc/transport/nrinterfaceprioritizer.cpp b/dom/media/webrtc/transport/nrinterfaceprioritizer.cpp
new file mode 100644
index 0000000000..f022f8c29a
--- /dev/null
+++ b/dom/media/webrtc/transport/nrinterfaceprioritizer.cpp
@@ -0,0 +1,258 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include <algorithm>
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+#include "logging.h"
+#include "nr_api.h"
+#include "nrinterfaceprioritizer.h"
+
+MOZ_MTLOG_MODULE("mtransport")
+
+namespace {
+
+class LocalAddress {
+ public:
+ LocalAddress()
+ : ifname_(),
+ addr_(),
+ key_(),
+ is_vpn_(-1),
+ estimated_speed_(-1),
+ type_preference_(-1),
+ ip_version_(-1) {}
+
+ bool Init(const nr_local_addr& local_addr) {
+ ifname_ = local_addr.addr.ifname;
+
+ char buf[MAXIFNAME + 47];
+ int r = nr_transport_addr_fmt_ifname_addr_string(&local_addr.addr, buf,
+ sizeof(buf));
+ if (r) {
+ MOZ_MTLOG(ML_ERROR, "Error formatting interface key.");
+ return false;
+ }
+ key_ = buf;
+
+ r = nr_transport_addr_get_addrstring(&local_addr.addr, buf, sizeof(buf));
+ if (r) {
+ MOZ_MTLOG(ML_ERROR, "Error formatting address string.");
+ return false;
+ }
+ addr_ = buf;
+
+ is_vpn_ = (local_addr.interface.type & NR_INTERFACE_TYPE_VPN) != 0 ? 1 : 0;
+ estimated_speed_ = local_addr.interface.estimated_speed;
+ type_preference_ = GetNetworkTypePreference(local_addr.interface.type);
+ ip_version_ = local_addr.addr.ip_version;
+ return true;
+ }
+
+ bool operator<(const LocalAddress& rhs) const {
+ // Interface that is "less" here is preferred.
+ // If type preferences are different, we should simply sort by
+ // |type_preference_|.
+ if (type_preference_ != rhs.type_preference_) {
+ return type_preference_ < rhs.type_preference_;
+ }
+
+ // If type preferences are the same, the next thing we use to sort is vpn.
+ // If two LocalAddress are different in |is_vpn_|, the LocalAddress that is
+ // not in vpn gets priority.
+ if (is_vpn_ != rhs.is_vpn_) {
+ return is_vpn_ < rhs.is_vpn_;
+ }
+
+ // Compare estimated speed.
+ if (estimated_speed_ != rhs.estimated_speed_) {
+ return estimated_speed_ > rhs.estimated_speed_;
+ }
+
+ // See if our hard-coded pref list helps us.
+ auto thisindex = std::find(interface_preference_list().begin(),
+ interface_preference_list().end(), ifname_);
+ auto rhsindex = std::find(interface_preference_list().begin(),
+ interface_preference_list().end(), rhs.ifname_);
+ if (thisindex != rhsindex) {
+ return thisindex < rhsindex;
+ }
+
+ // Prefer IPV6 over IPV4
+ if (ip_version_ != rhs.ip_version_) {
+ return ip_version_ > rhs.ip_version_;
+ }
+
+ // Now we start getting into arbitrary stuff
+ if (ifname_ != rhs.ifname_) {
+ return ifname_ < rhs.ifname_;
+ }
+
+ return addr_ < rhs.addr_;
+ }
+
+ const std::string& GetKey() const { return key_; }
+
+ private:
+ // Getting the preference corresponding to a type. Getting lower number here
+ // means the type of network is preferred.
+ static inline int GetNetworkTypePreference(int type) {
+ if (type & NR_INTERFACE_TYPE_WIRED) {
+ return 1;
+ }
+ if (type & NR_INTERFACE_TYPE_WIFI) {
+ return 2;
+ }
+ if (type & NR_INTERFACE_TYPE_MOBILE) {
+ return 3;
+ }
+ if (type & NR_INTERFACE_TYPE_TEREDO) {
+ // Teredo gets penalty because it's IP relayed
+ return 5;
+ }
+ return 4;
+ }
+
+ // TODO(bug 895790): Once we can get useful interface properties on Darwin,
+ // we should remove this stuff.
+ static const std::vector<std::string>& interface_preference_list() {
+ static std::vector<std::string> list(build_interface_preference_list());
+ return list;
+ }
+
+ static std::vector<std::string> build_interface_preference_list() {
+ std::vector<std::string> result;
+ result.push_back("rl0");
+ result.push_back("wi0");
+ result.push_back("en0");
+ result.push_back("enp2s0");
+ result.push_back("enp3s0");
+ result.push_back("en1");
+ result.push_back("en2");
+ result.push_back("en3");
+ result.push_back("eth0");
+ result.push_back("eth1");
+ result.push_back("eth2");
+ result.push_back("em1");
+ result.push_back("em0");
+ result.push_back("ppp");
+ result.push_back("ppp0");
+ result.push_back("vmnet1");
+ result.push_back("vmnet0");
+ result.push_back("vmnet3");
+ result.push_back("vmnet4");
+ result.push_back("vmnet5");
+ result.push_back("vmnet6");
+ result.push_back("vmnet7");
+ result.push_back("vmnet8");
+ result.push_back("virbr0");
+ result.push_back("wlan0");
+ result.push_back("lo0");
+ return result;
+ }
+
+ std::string ifname_;
+ std::string addr_;
+ std::string key_;
+ int is_vpn_;
+ int estimated_speed_;
+ int type_preference_;
+ int ip_version_;
+};
+
+class InterfacePrioritizer {
+ public:
+ InterfacePrioritizer() : local_addrs_(), preference_map_(), sorted_(false) {}
+
+ int add(const nr_local_addr* iface) {
+ LocalAddress addr;
+ if (!addr.Init(*iface)) {
+ return R_FAILED;
+ }
+ std::pair<std::set<LocalAddress>::iterator, bool> r =
+ local_addrs_.insert(addr);
+ if (!r.second) {
+ return R_ALREADY; // This address is already in the set.
+ }
+ sorted_ = false;
+ return 0;
+ }
+
+ int sort() {
+ UCHAR tmp_pref = 127;
+ preference_map_.clear();
+ for (const auto& local_addr : local_addrs_) {
+ if (tmp_pref == 0) {
+ return R_FAILED;
+ }
+ preference_map_.insert(make_pair(local_addr.GetKey(), tmp_pref--));
+ }
+ sorted_ = true;
+ return 0;
+ }
+
+ int getPreference(const char* key, UCHAR* pref) {
+ if (!sorted_) {
+ return R_FAILED;
+ }
+ std::map<std::string, UCHAR>::iterator i = preference_map_.find(key);
+ if (i == preference_map_.end()) {
+ return R_NOT_FOUND;
+ }
+ *pref = i->second;
+ return 0;
+ }
+
+ private:
+ std::set<LocalAddress> local_addrs_;
+ std::map<std::string, UCHAR> preference_map_;
+ bool sorted_;
+};
+
+} // anonymous namespace
+
+static int add_interface(void* obj, nr_local_addr* iface) {
+ InterfacePrioritizer* ip = static_cast<InterfacePrioritizer*>(obj);
+ return ip->add(iface);
+}
+
+static int get_priority(void* obj, const char* key, UCHAR* pref) {
+ InterfacePrioritizer* ip = static_cast<InterfacePrioritizer*>(obj);
+ return ip->getPreference(key, pref);
+}
+
+static int sort_preference(void* obj) {
+ InterfacePrioritizer* ip = static_cast<InterfacePrioritizer*>(obj);
+ return ip->sort();
+}
+
+static int destroy(void** objp) {
+ if (!objp || !*objp) {
+ return 0;
+ }
+
+ InterfacePrioritizer* ip = static_cast<InterfacePrioritizer*>(*objp);
+ *objp = nullptr;
+ delete ip;
+
+ return 0;
+}
+
+static nr_interface_prioritizer_vtbl priorizer_vtbl = {
+ add_interface, get_priority, sort_preference, destroy};
+
+namespace mozilla {
+
+nr_interface_prioritizer* CreateInterfacePrioritizer() {
+ nr_interface_prioritizer* ip;
+ int r = nr_interface_prioritizer_create_int(new InterfacePrioritizer(),
+ &priorizer_vtbl, &ip);
+ if (r != 0) {
+ return nullptr;
+ }
+ return ip;
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/transport/nrinterfaceprioritizer.h b/dom/media/webrtc/transport/nrinterfaceprioritizer.h
new file mode 100644
index 0000000000..051b586445
--- /dev/null
+++ b/dom/media/webrtc/transport/nrinterfaceprioritizer.h
@@ -0,0 +1,17 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef nrinterfacepriority_h__
+#define nrinterfacepriority_h__
+
+extern "C" {
+#include "nr_interface_prioritizer.h"
+}
+
+namespace mozilla {
+
+nr_interface_prioritizer* CreateInterfacePrioritizer();
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/transport/rlogconnector.cpp b/dom/media/webrtc/transport/rlogconnector.cpp
new file mode 100644
index 0000000000..bdb58aac56
--- /dev/null
+++ b/dom/media/webrtc/transport/rlogconnector.cpp
@@ -0,0 +1,186 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Original author: bcampen@mozilla.com */
+
+#include "rlogconnector.h"
+
+#include <cstdarg>
+#include <deque>
+#include <string>
+#include <utility> // Pinch hitting for <utility> and std::move
+#include <vector>
+
+#include "logging.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/Sprintf.h"
+
+extern "C" {
+#include <csi_platform.h>
+#include "r_log.h"
+#include "registry.h"
+}
+
+/* Matches r_dest_vlog type defined in r_log.h */
+static int ringbuffer_vlog(int facility, int level, const char* format,
+ va_list ap) {
+ if (mozilla::RLogConnector::GetInstance()->ShouldLog(level)) {
+ // I could be evil and printf right into a std::string, but unless this
+ // shows up in profiling, it is not worth doing.
+ char temp[4096];
+ VsprintfLiteral(temp, format, ap);
+
+ mozilla::RLogConnector::GetInstance()->Log(level, std::string(temp));
+ }
+ return 0;
+}
+
+static mozilla::LogLevel rLogLvlToMozLogLvl(int level) {
+ switch (level) {
+ case LOG_EMERG:
+ case LOG_ALERT:
+ case LOG_CRIT:
+ case LOG_ERR:
+ return mozilla::LogLevel::Error;
+ case LOG_WARNING:
+ return mozilla::LogLevel::Warning;
+ case LOG_NOTICE:
+ return mozilla::LogLevel::Info;
+ case LOG_INFO:
+ return mozilla::LogLevel::Debug;
+ case LOG_DEBUG:
+ default:
+ return mozilla::LogLevel::Verbose;
+ }
+}
+
+MOZ_MTLOG_MODULE("nicer");
+
+namespace mozilla {
+
+RLogConnector* RLogConnector::instance;
+
+RLogConnector::RLogConnector()
+ : log_limit_(4096), mutex_("RLogConnector::mutex_"), disableCount_(0) {}
+
+RLogConnector::~RLogConnector() = default;
+
+void RLogConnector::SetLogLimit(uint32_t new_limit) {
+ OffTheBooksMutexAutoLock lock(mutex_);
+ log_limit_ = new_limit;
+ RemoveOld();
+}
+
+bool RLogConnector::ShouldLog(int level) const {
+ return level <= LOG_INFO ||
+ MOZ_LOG_TEST(getLogModule(), rLogLvlToMozLogLvl(level));
+}
+
+void RLogConnector::Log(int level, std::string&& log) {
+ MOZ_MTLOG(rLogLvlToMozLogLvl(level), log);
+ OffTheBooksMutexAutoLock lock(mutex_);
+ if (disableCount_ == 0) {
+ AddMsg(std::move(log));
+ }
+}
+
+void RLogConnector::AddMsg(std::string&& msg) {
+ log_messages_.push_front(std::move(msg));
+ RemoveOld();
+}
+
+inline void RLogConnector::RemoveOld() {
+ if (log_messages_.size() > log_limit_) {
+ log_messages_.resize(log_limit_);
+ }
+}
+
+RLogConnector* RLogConnector::CreateInstance() {
+ if (!instance) {
+ instance = new RLogConnector;
+ NR_reg_init(NR_REG_MODE_LOCAL);
+ r_log_set_extra_destination(LOG_DEBUG, &ringbuffer_vlog);
+ }
+ return instance;
+}
+
+RLogConnector* RLogConnector::GetInstance() { return instance; }
+
+void RLogConnector::DestroyInstance() {
+ // First param is ignored when passing null
+ r_log_set_extra_destination(LOG_DEBUG, nullptr);
+ delete instance;
+ instance = nullptr;
+}
+
+// As long as at least one PeerConnection exists in a Private Window rlog
+// messages will not be saved in the RLogConnector. This is necessary because
+// the log_messages buffer is shared across all instances of
+// PeerConnectionImpls. There is no way with the current structure of r_log to
+// run separate logs.
+
+void RLogConnector::EnterPrivateMode() {
+ OffTheBooksMutexAutoLock lock(mutex_);
+ ++disableCount_;
+ MOZ_ASSERT(disableCount_ != 0);
+
+ if (disableCount_ == 1) {
+ AddMsg("LOGGING SUSPENDED: a connection is active in a Private Window ***");
+ }
+}
+
+void RLogConnector::ExitPrivateMode() {
+ OffTheBooksMutexAutoLock lock(mutex_);
+ MOZ_ASSERT(disableCount_ != 0);
+
+ if (--disableCount_ == 0) {
+ AddMsg(
+ "LOGGING RESUMED: no connections are active in a Private Window ***");
+ }
+}
+
+void RLogConnector::Clear() {
+ OffTheBooksMutexAutoLock lock(mutex_);
+ log_messages_.clear();
+}
+
+void RLogConnector::Filter(const std::string& substring, uint32_t limit,
+ std::deque<std::string>* matching_logs) {
+ std::vector<std::string> substrings;
+ substrings.push_back(substring);
+ FilterAny(substrings, limit, matching_logs);
+}
+
+inline bool AnySubstringMatches(const std::vector<std::string>& substrings,
+ const std::string& string) {
+ for (auto sub = substrings.begin(); sub != substrings.end(); ++sub) {
+ if (string.find(*sub) != std::string::npos) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void RLogConnector::FilterAny(const std::vector<std::string>& substrings,
+ uint32_t limit,
+ std::deque<std::string>* matching_logs) {
+ OffTheBooksMutexAutoLock lock(mutex_);
+ if (limit == 0) {
+ // At a max, all of the log messages.
+ limit = log_limit_;
+ }
+
+ for (auto log = log_messages_.begin();
+ log != log_messages_.end() && matching_logs->size() < limit; ++log) {
+ if (AnySubstringMatches(substrings, *log)) {
+ matching_logs->push_front(*log);
+ }
+ }
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/transport/rlogconnector.h b/dom/media/webrtc/transport/rlogconnector.h
new file mode 100644
index 0000000000..8236eb2ab3
--- /dev/null
+++ b/dom/media/webrtc/transport/rlogconnector.h
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Some of this code is cut-and-pasted from nICEr. Copyright is:
+
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+*/
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Original author: bcampen@mozilla.com */
+
+/*
+ This file defines an r_dest_vlog that can be used to accumulate log messages
+ for later inspection/filtering. The intent is to use this for interactive
+ debug purposes on an about:webrtc page or similar.
+*/
+
+#ifndef rlogconnector_h__
+#define rlogconnector_h__
+
+#include <stdint.h>
+
+#include <deque>
+#include <string>
+#include <vector>
+
+#include "mozilla/Mutex.h"
+
+#include "m_cpp_utils.h"
+
+namespace mozilla {
+
+class RLogConnector {
+ public:
+ /*
+ NB: These are not threadsafe, nor are they safe to call during static
+ init/deinit.
+ */
+ static RLogConnector* CreateInstance();
+ static RLogConnector* GetInstance();
+ static void DestroyInstance();
+
+ /*
+ Retrieves log statements that match a given substring, subject to a
+ limit. |matching_logs| will be filled in chronological order (front()
+ is oldest, back() is newest). |limit| == 0 will be interpreted as no
+ limit.
+ */
+ void Filter(const std::string& substring, uint32_t limit,
+ std::deque<std::string>* matching_logs);
+
+ void FilterAny(const std::vector<std::string>& substrings, uint32_t limit,
+ std::deque<std::string>* matching_logs);
+
+ inline void GetAny(uint32_t limit, std::deque<std::string>* matching_logs) {
+ Filter("", limit, matching_logs);
+ }
+
+ void SetLogLimit(uint32_t new_limit);
+ bool ShouldLog(int level) const;
+ void Log(int level, std::string&& log);
+ void Clear();
+
+ // Methods to signal when a PeerConnection exists in a Private Window.
+ void EnterPrivateMode();
+ void ExitPrivateMode();
+
+ private:
+ RLogConnector();
+ ~RLogConnector();
+ void RemoveOld();
+ void AddMsg(std::string&& msg);
+
+ static RLogConnector* instance;
+
+ /*
+ * Might be worthwhile making this a circular buffer, but I think it is
+ * preferable to take up as little space as possible if no logging is
+ * happening/the ringbuffer is not being used.
+ */
+ std::deque<std::string> log_messages_;
+ /* Max size of log buffer (should we use time-depth instead/also?) */
+ uint32_t log_limit_;
+ OffTheBooksMutex mutex_;
+ uint32_t disableCount_;
+
+ DISALLOW_COPY_ASSIGN(RLogConnector);
+}; // class RLogConnector
+
+} // namespace mozilla
+
+#endif // rlogconnector_h__
diff --git a/dom/media/webrtc/transport/runnable_utils.h b/dom/media/webrtc/transport/runnable_utils.h
new file mode 100644
index 0000000000..c7248f4f55
--- /dev/null
+++ b/dom/media/webrtc/transport/runnable_utils.h
@@ -0,0 +1,222 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+
+#ifndef runnable_utils_h__
+#define runnable_utils_h__
+
+#include <utility>
+
+#include "mozilla/RefPtr.h"
+#include "nsThreadUtils.h"
+#include <functional>
+#include <tuple>
+#include <type_traits>
+
+// Abstract base class for all of our templates
+namespace mozilla {
+
+namespace detail {
+
+enum RunnableResult { NoResult, ReturnsResult };
+
+static inline nsresult RunOnThreadInternal(nsIEventTarget* thread,
+ nsIRunnable* runnable,
+ uint32_t flags) {
+ return thread->Dispatch(runnable, flags);
+}
+
+template <RunnableResult result>
+class runnable_args_base : public Runnable {
+ public:
+ runnable_args_base() : Runnable("media-runnable_args_base") {}
+
+ NS_IMETHOD Run() final {
+ MOZ_ASSERT(!mHasRun, "Can only be run once");
+
+ RunInternal();
+#ifdef DEBUG
+ mHasRun = true;
+#endif
+
+ return NS_OK;
+ }
+
+ protected:
+ virtual void RunInternal() = 0;
+#ifdef DEBUG
+ bool mHasRun = false;
+#endif
+};
+
+} // namespace detail
+
+template <typename FunType, typename... Args>
+class runnable_args_func : public detail::runnable_args_base<detail::NoResult> {
+ public:
+ // |explicit| to pacify static analysis when there are no |args|.
+ template <typename... Arguments>
+ explicit runnable_args_func(FunType f, Arguments&&... args)
+ : mFunc(f), mArgs(std::forward<Arguments>(args)...) {}
+
+ protected:
+ void RunInternal() override {
+ std::apply(std::move(mFunc), std::move(mArgs));
+ }
+
+ private:
+ FunType mFunc;
+ std::tuple<Args...> mArgs;
+};
+
+template <typename FunType, typename... Args>
+runnable_args_func<FunType, std::decay_t<Args>...>* WrapRunnableNM(
+ FunType f, Args&&... args) {
+ return new runnable_args_func<FunType, std::decay_t<Args>...>(
+ f, std::forward<Args>(args)...);
+}
+
+template <typename Ret, typename FunType, typename... Args>
+class runnable_args_func_ret
+ : public detail::runnable_args_base<detail::ReturnsResult> {
+ public:
+ template <typename... Arguments>
+ runnable_args_func_ret(Ret* ret, FunType f, Arguments&&... args)
+ : mReturn(ret), mFunc(f), mArgs(std::forward<Arguments>(args)...) {}
+
+ protected:
+ void RunInternal() override {
+ *mReturn = std::apply(std::move(mFunc), std::move(mArgs));
+ }
+
+ private:
+ Ret* mReturn;
+ FunType mFunc;
+ std::tuple<Args...> mArgs;
+};
+
+template <typename R, typename FunType, typename... Args>
+runnable_args_func_ret<R, FunType, std::decay_t<Args>...>* WrapRunnableNMRet(
+ R* ret, FunType f, Args&&... args) {
+ return new runnable_args_func_ret<R, FunType, std::decay_t<Args>...>(
+ ret, f, std::forward<Args>(args)...);
+}
+
+template <typename Class, typename M, typename... Args>
+class runnable_args_memfn
+ : public detail::runnable_args_base<detail::NoResult> {
+ public:
+ template <typename... Arguments>
+ runnable_args_memfn(Class&& obj, M method, Arguments&&... args)
+ : mObj(std::forward<Class>(obj)),
+ mMethod(method),
+ mArgs(std::forward<Arguments>(args)...) {}
+
+ protected:
+ void RunInternal() override {
+ std::apply(std::mem_fn(mMethod),
+ std::tuple_cat(std::tie(mObj), std::move(mArgs)));
+ }
+
+ private:
+ // For holders such as RefPtr and UniquePtr make sure concrete copy is held
+ // rather than a potential dangling reference.
+ std::decay_t<Class> mObj;
+ M mMethod;
+ std::tuple<Args...> mArgs;
+};
+
+template <typename Class, typename M, typename... Args>
+runnable_args_memfn<Class, M, std::decay_t<Args>...>* WrapRunnable(
+ Class&& obj, M method, Args&&... args) {
+ return new runnable_args_memfn<Class, M, std::decay_t<Args>...>(
+ std::forward<Class>(obj), method, std::forward<Args>(args)...);
+}
+
+template <typename Ret, typename Class, typename M, typename... Args>
+class runnable_args_memfn_ret
+ : public detail::runnable_args_base<detail::ReturnsResult> {
+ public:
+ template <typename... Arguments>
+ runnable_args_memfn_ret(Ret* ret, Class&& obj, M method, Arguments... args)
+ : mReturn(ret),
+ mObj(std::forward<Class>(obj)),
+ mMethod(method),
+ mArgs(std::forward<Arguments>(args)...) {}
+
+ protected:
+ void RunInternal() override {
+ *mReturn = std::apply(std::mem_fn(mMethod),
+ std::tuple_cat(std::tie(mObj), std::move(mArgs)));
+ }
+
+ private:
+ Ret* mReturn;
+ // For holders such as RefPtr and UniquePtr make sure concrete copy is held
+ // rather than a potential dangling reference.
+ std::decay_t<Class> mObj;
+ M mMethod;
+ std::tuple<Args...> mArgs;
+};
+
+template <typename R, typename Class, typename M, typename... Args>
+runnable_args_memfn_ret<R, Class, M, std::decay_t<Args>...>* WrapRunnableRet(
+ R* ret, Class&& obj, M method, Args&&... args) {
+ return new runnable_args_memfn_ret<R, Class, M, std::decay_t<Args>...>(
+ ret, std::forward<Class>(obj), method, std::forward<Args>(args)...);
+}
+
+static inline nsresult RUN_ON_THREAD(
+ nsIEventTarget* thread,
+ detail::runnable_args_base<detail::NoResult>* runnable, uint32_t flags) {
+ return detail::RunOnThreadInternal(
+ thread, static_cast<nsIRunnable*>(runnable), flags);
+}
+
+static inline nsresult RUN_ON_THREAD(
+ nsIEventTarget* thread,
+ detail::runnable_args_base<detail::ReturnsResult>* runnable) {
+ return NS_DispatchAndSpinEventLoopUntilComplete(
+ "webrtc RUN_ON_THREAD"_ns, thread,
+ do_AddRef(static_cast<nsIRunnable*>(runnable)));
+}
+
+#ifdef DEBUG
+# define ASSERT_ON_THREAD(t) \
+ do { \
+ if (t) { \
+ bool on; \
+ nsresult rv; \
+ rv = t->IsOnCurrentThread(&on); \
+ MOZ_ASSERT(NS_SUCCEEDED(rv)); \
+ MOZ_ASSERT(on); \
+ } \
+ } while (0)
+#else
+# define ASSERT_ON_THREAD(t)
+#endif
+
+template <class T>
+class DispatchedRelease : public detail::runnable_args_base<detail::NoResult> {
+ public:
+ explicit DispatchedRelease(already_AddRefed<T>& ref) : ref_(ref) {}
+
+ protected:
+ void RunInternal() override { ref_ = nullptr; }
+
+ private:
+ RefPtr<T> ref_;
+};
+
+template <typename T>
+DispatchedRelease<T>* WrapRelease(already_AddRefed<T>&& ref) {
+ return new DispatchedRelease<T>(ref);
+}
+
+} /* namespace mozilla */
+
+#endif
diff --git a/dom/media/webrtc/transport/sigslot.h b/dom/media/webrtc/transport/sigslot.h
new file mode 100644
index 0000000000..448e8137fe
--- /dev/null
+++ b/dom/media/webrtc/transport/sigslot.h
@@ -0,0 +1,619 @@
+// sigslot.h: Signal/Slot classes
+//
+// Written by Sarah Thompson (sarah@telergy.com) 2002.
+//
+// License: Public domain. You are free to use this code however you like, with
+// the proviso that the author takes on no responsibility or liability for any
+// use.
+//
+// QUICK DOCUMENTATION
+//
+// (see also the full documentation at http://sigslot.sourceforge.net/)
+//
+// #define switches
+// SIGSLOT_PURE_ISO:
+// Define this to force ISO C++ compliance. This also disables all of
+// the thread safety support on platforms where it is available.
+//
+// SIGSLOT_USE_POSIX_THREADS:
+// Force use of Posix threads when using a C++ compiler other than gcc
+// on a platform that supports Posix threads. (When using gcc, this is
+// the default - use SIGSLOT_PURE_ISO to disable this if necessary)
+//
+// SIGSLOT_DEFAULT_MT_POLICY:
+// Where thread support is enabled, this defaults to
+// multi_threaded_global. Otherwise, the default is single_threaded.
+// #define this yourself to override the default. In pure ISO mode,
+// anything other than single_threaded will cause a compiler error.
+//
+// PLATFORM NOTES
+//
+// Win32:
+// On Win32, the WEBRTC_WIN symbol must be #defined. Most mainstream
+// compilers do this by default, but you may need to define it yourself
+// if your build environment is less standard. This causes the Win32
+// thread support to be compiled in and used automatically.
+//
+// Unix/Linux/BSD, etc.:
+// If you're using gcc, it is assumed that you have Posix threads
+// available, so they are used automatically. You can override this (as
+// under Windows) with the SIGSLOT_PURE_ISO switch. If you're using
+// something other than gcc but still want to use Posix threads, you
+// need to #define SIGSLOT_USE_POSIX_THREADS.
+//
+// ISO C++:
+// If none of the supported platforms are detected, or if
+// SIGSLOT_PURE_ISO is defined, all multithreading support is turned
+// off, along with any code that might cause a pure ISO C++ environment
+// to complain. Before you ask, gcc -ansi -pedantic won't compile this
+// library, but gcc -ansi is fine. Pedantic mode seems to throw a lot of
+// errors that aren't really there. If you feel like investigating this,
+// please contact the author.
+//
+//
+// THREADING MODES
+//
+// single_threaded:
+// Your program is assumed to be single threaded from the point of view
+// of signal/slot usage (i.e. all objects using signals and slots are
+// created and destroyed from a single thread). Behaviour if objects are
+// destroyed concurrently is undefined (i.e. you'll get the occasional
+// segmentation fault/memory exception).
+//
+// multi_threaded_global:
+// Your program is assumed to be multi threaded. Objects using signals
+// and slots can be safely created and destroyed from any thread, even
+// when connections exist. In multi_threaded_global mode, this is
+// achieved by a single global mutex (actually a critical section on
+// Windows because they are faster). This option uses less OS resources,
+// but results in more opportunities for contention, possibly resulting
+// in more context switches than are strictly necessary.
+//
+// multi_threaded_local:
+// Behaviour in this mode is essentially the same as
+// multi_threaded_global, except that each signal, and each object that
+// inherits has_slots, all have their own mutex/critical section. In
+// practice, this means that mutex collisions (and hence context
+// switches) only happen if they are absolutely essential. However, on
+// some platforms, creating a lot of mutexes can slow down the whole OS,
+// so use this option with care.
+//
+// USING THE LIBRARY
+//
+// See the full documentation at http://sigslot.sourceforge.net/
+//
+// Libjingle specific:
+//
+// This file has been modified such that has_slots and signalx do not have to be
+// using the same threading requirements. E.g. it is possible to connect a
+// has_slots<single_threaded> and signal0<multi_threaded_local> or
+// has_slots<multi_threaded_local> and signal0<single_threaded>.
+// If has_slots is single threaded the user must ensure that it is not trying
+// to connect or disconnect to signalx concurrently or data race may occur.
+// If signalx is single threaded the user must ensure that disconnect, connect
+// or signal is not happening concurrently or data race may occur.
+
+#ifndef RTC_BASE_SIGSLOT_H_
+#define RTC_BASE_SIGSLOT_H_
+
+#include <stdlib.h>
+#include <cstring>
+#include <list>
+#include <set>
+
+// On our copy of sigslot.h, we set single threading as default.
+#define SIGSLOT_DEFAULT_MT_POLICY single_threaded
+
+#if defined(SIGSLOT_PURE_ISO) || \
+ (!defined(WEBRTC_WIN) && !defined(__GNUG__) && \
+ !defined(SIGSLOT_USE_POSIX_THREADS))
+# define _SIGSLOT_SINGLE_THREADED
+#elif defined(WEBRTC_WIN)
+# define _SIGSLOT_HAS_WIN32_THREADS
+# if !defined(WIN32_LEAN_AND_MEAN)
+# define WIN32_LEAN_AND_MEAN
+# endif
+# include "rtc_base/win32.h"
+#elif defined(__GNUG__) || defined(SIGSLOT_USE_POSIX_THREADS)
+# define _SIGSLOT_HAS_POSIX_THREADS
+# include <pthread.h>
+#else
+# define _SIGSLOT_SINGLE_THREADED
+#endif
+
+#ifndef SIGSLOT_DEFAULT_MT_POLICY
+# ifdef _SIGSLOT_SINGLE_THREADED
+# define SIGSLOT_DEFAULT_MT_POLICY single_threaded
+# else
+# define SIGSLOT_DEFAULT_MT_POLICY multi_threaded_local
+# endif
+#endif
+
+// TODO: change this namespace to rtc?
+namespace sigslot {
+
+class single_threaded {
+ public:
+ void lock() {}
+ void unlock() {}
+};
+
+#ifdef _SIGSLOT_HAS_WIN32_THREADS
+// The multi threading policies only get compiled in if they are enabled.
+class multi_threaded_global {
+ public:
+ multi_threaded_global() {
+ static bool isinitialised = false;
+
+ if (!isinitialised) {
+ InitializeCriticalSection(get_critsec());
+ isinitialised = true;
+ }
+ }
+
+ void lock() { EnterCriticalSection(get_critsec()); }
+
+ void unlock() { LeaveCriticalSection(get_critsec()); }
+
+ private:
+ CRITICAL_SECTION* get_critsec() {
+ static CRITICAL_SECTION g_critsec;
+ return &g_critsec;
+ }
+};
+
+class multi_threaded_local {
+ public:
+ multi_threaded_local() { InitializeCriticalSection(&m_critsec); }
+
+ multi_threaded_local(const multi_threaded_local&) {
+ InitializeCriticalSection(&m_critsec);
+ }
+
+ ~multi_threaded_local() { DeleteCriticalSection(&m_critsec); }
+
+ void lock() { EnterCriticalSection(&m_critsec); }
+
+ void unlock() { LeaveCriticalSection(&m_critsec); }
+
+ private:
+ CRITICAL_SECTION m_critsec;
+};
+#endif // _SIGSLOT_HAS_WIN32_THREADS
+
+#ifdef _SIGSLOT_HAS_POSIX_THREADS
+// The multi threading policies only get compiled in if they are enabled.
+class multi_threaded_global {
+ public:
+ void lock() { pthread_mutex_lock(get_mutex()); }
+ void unlock() { pthread_mutex_unlock(get_mutex()); }
+
+ private:
+ static pthread_mutex_t* get_mutex();
+};
+
+class multi_threaded_local {
+ public:
+ multi_threaded_local() { pthread_mutex_init(&m_mutex, nullptr); }
+ multi_threaded_local(const multi_threaded_local&) {
+ pthread_mutex_init(&m_mutex, nullptr);
+ }
+ ~multi_threaded_local() { pthread_mutex_destroy(&m_mutex); }
+ void lock() { pthread_mutex_lock(&m_mutex); }
+ void unlock() { pthread_mutex_unlock(&m_mutex); }
+
+ private:
+ pthread_mutex_t m_mutex;
+};
+#endif // _SIGSLOT_HAS_POSIX_THREADS
+
+template <class mt_policy>
+class lock_block {
+ public:
+ mt_policy* m_mutex;
+
+ explicit lock_block(mt_policy* mtx) : m_mutex(mtx) { m_mutex->lock(); }
+
+ ~lock_block() { m_mutex->unlock(); }
+};
+
+class _signal_base_interface;
+
+class has_slots_interface {
+ private:
+ typedef void (*signal_connect_t)(has_slots_interface* self,
+ _signal_base_interface* sender);
+ typedef void (*signal_disconnect_t)(has_slots_interface* self,
+ _signal_base_interface* sender);
+ typedef void (*disconnect_all_t)(has_slots_interface* self);
+
+ const signal_connect_t m_signal_connect;
+ const signal_disconnect_t m_signal_disconnect;
+ const disconnect_all_t m_disconnect_all;
+
+ protected:
+ has_slots_interface(signal_connect_t conn, signal_disconnect_t disc,
+ disconnect_all_t disc_all)
+ : m_signal_connect(conn),
+ m_signal_disconnect(disc),
+ m_disconnect_all(disc_all) {}
+
+ // Doesn't really need to be virtual, but is for backwards compatibility
+ // (it was virtual in a previous version of sigslot).
+ virtual ~has_slots_interface() = default;
+
+ public:
+ void signal_connect(_signal_base_interface* sender) {
+ m_signal_connect(this, sender);
+ }
+
+ void signal_disconnect(_signal_base_interface* sender) {
+ m_signal_disconnect(this, sender);
+ }
+
+ void disconnect_all() { m_disconnect_all(this); }
+};
+
+class _signal_base_interface {
+ private:
+ typedef void (*slot_disconnect_t)(_signal_base_interface* self,
+ has_slots_interface* pslot);
+ typedef void (*slot_duplicate_t)(_signal_base_interface* self,
+ const has_slots_interface* poldslot,
+ has_slots_interface* pnewslot);
+
+ const slot_disconnect_t m_slot_disconnect;
+ const slot_duplicate_t m_slot_duplicate;
+
+ protected:
+ _signal_base_interface(slot_disconnect_t disc, slot_duplicate_t dupl)
+ : m_slot_disconnect(disc), m_slot_duplicate(dupl) {}
+
+ ~_signal_base_interface() = default;
+
+ public:
+ void slot_disconnect(has_slots_interface* pslot) {
+ m_slot_disconnect(this, pslot);
+ }
+
+ void slot_duplicate(const has_slots_interface* poldslot,
+ has_slots_interface* pnewslot) {
+ m_slot_duplicate(this, poldslot, pnewslot);
+ }
+};
+
+class _opaque_connection {
+ private:
+ typedef void (*emit_t)(const _opaque_connection*);
+ template <typename FromT, typename ToT>
+ union union_caster {
+ FromT from;
+ ToT to;
+ };
+
+ emit_t pemit;
+ has_slots_interface* pdest;
+ // Pointers to member functions may be up to 16 bytes for virtual classes,
+ // so make sure we have enough space to store it.
+ unsigned char pmethod[16];
+
+ public:
+ template <typename DestT, typename... Args>
+ _opaque_connection(DestT* pd, void (DestT::*pm)(Args...)) : pdest(pd) {
+ typedef void (DestT::*pm_t)(Args...);
+ static_assert(sizeof(pm_t) <= sizeof(pmethod),
+ "Size of slot function pointer too large.");
+
+ std::memcpy(pmethod, &pm, sizeof(pm_t));
+
+ typedef void (*em_t)(const _opaque_connection* self, Args...);
+ union_caster<em_t, emit_t> caster2;
+ caster2.from = &_opaque_connection::emitter<DestT, Args...>;
+ pemit = caster2.to;
+ }
+
+ has_slots_interface* getdest() const { return pdest; }
+
+ _opaque_connection duplicate(has_slots_interface* newtarget) const {
+ _opaque_connection res = *this;
+ res.pdest = newtarget;
+ return res;
+ }
+
+ // Just calls the stored "emitter" function pointer stored at construction
+ // time.
+ template <typename... Args>
+ void emit(Args... args) const {
+ typedef void (*em_t)(const _opaque_connection*, Args...);
+ union_caster<emit_t, em_t> caster;
+ caster.from = pemit;
+ (caster.to)(this, args...);
+ }
+
+ private:
+ template <typename DestT, typename... Args>
+ static void emitter(const _opaque_connection* self, Args... args) {
+ typedef void (DestT::*pm_t)(Args...);
+ pm_t pm;
+ std::memcpy(&pm, self->pmethod, sizeof(pm_t));
+ (static_cast<DestT*>(self->pdest)->*(pm))(args...);
+ }
+};
+
+template <class mt_policy>
+class _signal_base : public _signal_base_interface, public mt_policy {
+ protected:
+ typedef std::list<_opaque_connection> connections_list;
+
+ _signal_base()
+ : _signal_base_interface(&_signal_base::do_slot_disconnect,
+ &_signal_base::do_slot_duplicate),
+ m_current_iterator(m_connected_slots.end()) {}
+
+ ~_signal_base() { disconnect_all(); }
+
+ private:
+ _signal_base& operator=(_signal_base const& that);
+
+ public:
+ _signal_base(const _signal_base& o)
+ : _signal_base_interface(&_signal_base::do_slot_disconnect,
+ &_signal_base::do_slot_duplicate),
+ m_current_iterator(m_connected_slots.end()) {
+ lock_block<mt_policy> lock(this);
+ for (const auto& connection : o.m_connected_slots) {
+ connection.getdest()->signal_connect(this);
+ m_connected_slots.push_back(connection);
+ }
+ }
+
+ bool is_empty() {
+ lock_block<mt_policy> lock(this);
+ return m_connected_slots.empty();
+ }
+
+ void disconnect_all() {
+ lock_block<mt_policy> lock(this);
+
+ while (!m_connected_slots.empty()) {
+ has_slots_interface* pdest = m_connected_slots.front().getdest();
+ m_connected_slots.pop_front();
+ pdest->signal_disconnect(static_cast<_signal_base_interface*>(this));
+ }
+ // If disconnect_all is called while the signal is firing, advance the
+ // current slot iterator to the end to avoid an invalidated iterator from
+ // being dereferenced.
+ m_current_iterator = m_connected_slots.end();
+ }
+
+#if !defined(NDEBUG)
+ bool connected(has_slots_interface* pclass) {
+ lock_block<mt_policy> lock(this);
+ connections_list::const_iterator it = m_connected_slots.begin();
+ connections_list::const_iterator itEnd = m_connected_slots.end();
+ while (it != itEnd) {
+ if (it->getdest() == pclass) return true;
+ ++it;
+ }
+ return false;
+ }
+#endif
+
+ void disconnect(has_slots_interface* pclass) {
+ lock_block<mt_policy> lock(this);
+ connections_list::iterator it = m_connected_slots.begin();
+ connections_list::iterator itEnd = m_connected_slots.end();
+
+ while (it != itEnd) {
+ if (it->getdest() == pclass) {
+ // If we're currently using this iterator because the signal is firing,
+ // advance it to avoid it being invalidated.
+ if (m_current_iterator == it) {
+ m_current_iterator = m_connected_slots.erase(it);
+ } else {
+ m_connected_slots.erase(it);
+ }
+ pclass->signal_disconnect(static_cast<_signal_base_interface*>(this));
+ return;
+ }
+ ++it;
+ }
+ }
+
+ private:
+ static void do_slot_disconnect(_signal_base_interface* p,
+ has_slots_interface* pslot) {
+ _signal_base* const self = static_cast<_signal_base*>(p);
+ lock_block<mt_policy> lock(self);
+ connections_list::iterator it = self->m_connected_slots.begin();
+ connections_list::iterator itEnd = self->m_connected_slots.end();
+
+ while (it != itEnd) {
+ connections_list::iterator itNext = it;
+ ++itNext;
+
+ if (it->getdest() == pslot) {
+ // If we're currently using this iterator because the signal is firing,
+ // advance it to avoid it being invalidated.
+ if (self->m_current_iterator == it) {
+ self->m_current_iterator = self->m_connected_slots.erase(it);
+ } else {
+ self->m_connected_slots.erase(it);
+ }
+ }
+
+ it = itNext;
+ }
+ }
+
+ static void do_slot_duplicate(_signal_base_interface* p,
+ const has_slots_interface* oldtarget,
+ has_slots_interface* newtarget) {
+ _signal_base* const self = static_cast<_signal_base*>(p);
+ lock_block<mt_policy> lock(self);
+ connections_list::iterator it = self->m_connected_slots.begin();
+ connections_list::iterator itEnd = self->m_connected_slots.end();
+
+ while (it != itEnd) {
+ if (it->getdest() == oldtarget) {
+ self->m_connected_slots.push_back(it->duplicate(newtarget));
+ }
+
+ ++it;
+ }
+ }
+
+ protected:
+ connections_list m_connected_slots;
+
+ // Used to handle a slot being disconnected while a signal is
+ // firing (iterating m_connected_slots).
+ connections_list::iterator m_current_iterator;
+ bool m_erase_current_iterator = false;
+};
+
+template <class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+class has_slots : public has_slots_interface, public mt_policy {
+ private:
+ typedef std::set<_signal_base_interface*> sender_set;
+ typedef sender_set::const_iterator const_iterator;
+
+ public:
+ has_slots()
+ : has_slots_interface(&has_slots::do_signal_connect,
+ &has_slots::do_signal_disconnect,
+ &has_slots::do_disconnect_all) {}
+
+ has_slots(has_slots const& o)
+ : has_slots_interface(&has_slots::do_signal_connect,
+ &has_slots::do_signal_disconnect,
+ &has_slots::do_disconnect_all) {
+ lock_block<mt_policy> lock(this);
+ for (auto* sender : o.m_senders) {
+ sender->slot_duplicate(&o, this);
+ m_senders.insert(sender);
+ }
+ }
+
+ ~has_slots() { this->disconnect_all(); }
+
+ private:
+ has_slots& operator=(has_slots const&);
+
+ static void do_signal_connect(has_slots_interface* p,
+ _signal_base_interface* sender) {
+ has_slots* const self = static_cast<has_slots*>(p);
+ lock_block<mt_policy> lock(self);
+ self->m_senders.insert(sender);
+ }
+
+ static void do_signal_disconnect(has_slots_interface* p,
+ _signal_base_interface* sender) {
+ has_slots* const self = static_cast<has_slots*>(p);
+ lock_block<mt_policy> lock(self);
+ self->m_senders.erase(sender);
+ }
+
+ static void do_disconnect_all(has_slots_interface* p) {
+ has_slots* const self = static_cast<has_slots*>(p);
+ lock_block<mt_policy> lock(self);
+ while (!self->m_senders.empty()) {
+ std::set<_signal_base_interface*> senders;
+ senders.swap(self->m_senders);
+ const_iterator it = senders.begin();
+ const_iterator itEnd = senders.end();
+
+ while (it != itEnd) {
+ _signal_base_interface* s = *it;
+ ++it;
+ s->slot_disconnect(p);
+ }
+ }
+ }
+
+ private:
+ sender_set m_senders;
+};
+
+template <class mt_policy, typename... Args>
+class signal_with_thread_policy : public _signal_base<mt_policy> {
+ private:
+ typedef _signal_base<mt_policy> base;
+
+ protected:
+ typedef typename base::connections_list connections_list;
+
+ public:
+ signal_with_thread_policy() = default;
+
+ template <class desttype>
+ void connect(desttype* pclass, void (desttype::*pmemfun)(Args...)) {
+ lock_block<mt_policy> lock(this);
+ this->m_connected_slots.push_back(_opaque_connection(pclass, pmemfun));
+ pclass->signal_connect(static_cast<_signal_base_interface*>(this));
+ }
+
+ void emit(Args... args) {
+ lock_block<mt_policy> lock(this);
+ this->m_current_iterator = this->m_connected_slots.begin();
+ while (this->m_current_iterator != this->m_connected_slots.end()) {
+ _opaque_connection const& conn = *this->m_current_iterator;
+ ++(this->m_current_iterator);
+ conn.emit<Args...>(args...);
+ }
+ }
+
+ void operator()(Args... args) { emit(args...); }
+};
+
+// Alias with default thread policy. Needed because both default arguments
+// and variadic template arguments must go at the end of the list, so we
+// can't have both at once.
+template <typename... Args>
+using signal = signal_with_thread_policy<SIGSLOT_DEFAULT_MT_POLICY, Args...>;
+
+// The previous verion of sigslot didn't use variadic templates, so you would
+// need to write "sigslot::signal2<Arg1, Arg2>", for example.
+// Now you can just write "sigslot::signal<Arg1, Arg2>", but these aliases
+// exist for backwards compatibility.
+template <typename mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+using signal0 = signal_with_thread_policy<mt_policy>;
+
+template <typename A1, typename mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+using signal1 = signal_with_thread_policy<mt_policy, A1>;
+
+template <typename A1, typename A2,
+ typename mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+using signal2 = signal_with_thread_policy<mt_policy, A1, A2>;
+
+template <typename A1, typename A2, typename A3,
+ typename mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+using signal3 = signal_with_thread_policy<mt_policy, A1, A2, A3>;
+
+template <typename A1, typename A2, typename A3, typename A4,
+ typename mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+using signal4 = signal_with_thread_policy<mt_policy, A1, A2, A3, A4>;
+
+template <typename A1, typename A2, typename A3, typename A4, typename A5,
+ typename mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+using signal5 = signal_with_thread_policy<mt_policy, A1, A2, A3, A4, A5>;
+
+template <typename A1, typename A2, typename A3, typename A4, typename A5,
+ typename A6, typename mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+using signal6 = signal_with_thread_policy<mt_policy, A1, A2, A3, A4, A5, A6>;
+
+template <typename A1, typename A2, typename A3, typename A4, typename A5,
+ typename A6, typename A7,
+ typename mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+using signal7 =
+ signal_with_thread_policy<mt_policy, A1, A2, A3, A4, A5, A6, A7>;
+
+template <typename A1, typename A2, typename A3, typename A4, typename A5,
+ typename A6, typename A7, typename A8,
+ typename mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+using signal8 =
+ signal_with_thread_policy<mt_policy, A1, A2, A3, A4, A5, A6, A7, A8>;
+
+} // namespace sigslot
+
+#endif // RTC_BASE_SIGSLOT_H_
diff --git a/dom/media/webrtc/transport/simpletokenbucket.cpp b/dom/media/webrtc/transport/simpletokenbucket.cpp
new file mode 100644
index 0000000000..0509697c7b
--- /dev/null
+++ b/dom/media/webrtc/transport/simpletokenbucket.cpp
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Original author: bcampen@mozilla.com */
+
+#include "simpletokenbucket.h"
+
+#include <stdint.h>
+
+#include "prinrval.h"
+
+namespace mozilla {
+
+SimpleTokenBucket::SimpleTokenBucket(size_t bucket_size,
+ size_t tokens_per_second)
+ : max_tokens_(bucket_size),
+ num_tokens_(bucket_size),
+ tokens_per_second_(tokens_per_second),
+ last_time_tokens_added_(PR_IntervalNow()) {}
+
+size_t SimpleTokenBucket::getTokens(size_t num_requested_tokens) {
+ // Only fill if there isn't enough to satisfy the request.
+ // If we get tokens so seldomly that we are able to roll the timer all
+ // the way around its range, then we lose that entire range of time
+ // for token accumulation. Probably not the end of the world.
+ if (num_requested_tokens > num_tokens_) {
+ PRIntervalTime now = PR_IntervalNow();
+
+ // If we roll over the max, since everything in this calculation is the same
+ // unsigned type, this will still yield the elapsed time (unless we've
+ // wrapped more than once).
+ PRIntervalTime elapsed_ticks = now - last_time_tokens_added_;
+
+ uint32_t elapsed_milli_sec = PR_IntervalToMilliseconds(elapsed_ticks);
+ size_t tokens_to_add = (elapsed_milli_sec * tokens_per_second_) / 1000;
+
+ // Only update our timestamp if we added some tokens
+ // TODO:(bcampen@mozilla.com) Should we attempt to "save" leftover time?
+ if (tokens_to_add) {
+ num_tokens_ += tokens_to_add;
+ if (num_tokens_ > max_tokens_) {
+ num_tokens_ = max_tokens_;
+ }
+
+ last_time_tokens_added_ = now;
+ }
+
+ if (num_requested_tokens > num_tokens_) {
+ return num_tokens_;
+ }
+ }
+
+ num_tokens_ -= num_requested_tokens;
+ return num_requested_tokens;
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/transport/simpletokenbucket.h b/dom/media/webrtc/transport/simpletokenbucket.h
new file mode 100644
index 0000000000..7e809535b1
--- /dev/null
+++ b/dom/media/webrtc/transport/simpletokenbucket.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Original author: bcampen@mozilla.com */
+
+/*
+ * This file defines a dirt-simple token bucket class.
+ */
+
+#ifndef simpletokenbucket_h__
+#define simpletokenbucket_h__
+
+#include <stdint.h>
+
+#include "prinrval.h"
+
+#include "m_cpp_utils.h"
+
+namespace mozilla {
+
+class SimpleTokenBucket {
+ public:
+ /*
+ * Create a SimpleTokenBucket with a given maximum size and
+ * token replenishment rate.
+ * (eg; if you want a maximum rate of 5 per second over a 7 second
+ * period, call SimpleTokenBucket b(5*7, 5);)
+ */
+ SimpleTokenBucket(size_t bucket_size, size_t tokens_per_second);
+
+ /*
+ * Attempt to acquire a number of tokens. If successful, returns
+ * |num_tokens|, otherwise returns the number of tokens currently
+ * in the bucket.
+ * Note: To get the number of tokens in the bucket, pass something
+ * like UINT32_MAX.
+ */
+ size_t getTokens(size_t num_tokens);
+
+ protected: // Allow testing to touch these.
+ uint64_t max_tokens_;
+ uint64_t num_tokens_;
+ size_t tokens_per_second_;
+ PRIntervalTime last_time_tokens_added_;
+
+ DISALLOW_COPY_ASSIGN(SimpleTokenBucket);
+};
+
+} // namespace mozilla
+
+#endif // simpletokenbucket_h__
diff --git a/dom/media/webrtc/transport/srtp/README_MOZILLA b/dom/media/webrtc/transport/srtp/README_MOZILLA
new file mode 100644
index 0000000000..8533b67c53
--- /dev/null
+++ b/dom/media/webrtc/transport/srtp/README_MOZILLA
@@ -0,0 +1,7 @@
+This directory contains build files for libsrtp. The actual library
+source is in $TOPSRCDIR/third_party/libsrtp/
+
+The upstream git repository is https://github.com/cisco/libsrtp
+
+TBD add code and instructions how to do a clean update import without manual
+intervention.
diff --git a/dom/media/webrtc/transport/srtp/moz.build b/dom/media/webrtc/transport/srtp/moz.build
new file mode 100644
index 0000000000..d3d4971a6d
--- /dev/null
+++ b/dom/media/webrtc/transport/srtp/moz.build
@@ -0,0 +1,8 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+if CONFIG["MOZ_SRTP"]:
+ DIRS += ["/third_party/libsrtp/src"]
diff --git a/dom/media/webrtc/transport/stun_socket_filter.cpp b/dom/media/webrtc/transport/stun_socket_filter.cpp
new file mode 100644
index 0000000000..b568f97a40
--- /dev/null
+++ b/dom/media/webrtc/transport/stun_socket_filter.cpp
@@ -0,0 +1,432 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include <string>
+#include <set>
+#include <iomanip>
+
+extern "C" {
+#include "nr_api.h"
+#include "transport_addr.h"
+#include "stun.h"
+}
+
+#include "logging.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/net/DNS.h"
+#include "stun_socket_filter.h"
+#include "nr_socket_prsock.h"
+
+namespace {
+
+MOZ_MTLOG_MODULE("mtransport")
+
+class NetAddrCompare {
+ public:
+ bool operator()(const mozilla::net::NetAddr& lhs,
+ const mozilla::net::NetAddr& rhs) const {
+ if (lhs.raw.family != rhs.raw.family) {
+ return lhs.raw.family < rhs.raw.family;
+ }
+
+ switch (lhs.raw.family) {
+ case AF_INET:
+ if (lhs.inet.port != rhs.inet.port) {
+ return lhs.inet.port < rhs.inet.port;
+ }
+ return lhs.inet.ip < rhs.inet.ip;
+ case AF_INET6:
+ if (lhs.inet6.port != rhs.inet6.port) {
+ return lhs.inet6.port < rhs.inet6.port;
+ }
+ return memcmp(&lhs.inet6.ip, &rhs.inet6.ip, sizeof(lhs.inet6.ip)) < 0;
+ default:
+ MOZ_ASSERT(false);
+ }
+ return false;
+ }
+};
+
+class PendingSTUNRequest {
+ public:
+ PendingSTUNRequest(const mozilla::net::NetAddr& netaddr, const UINT12& id)
+ : id_(id), net_addr_(netaddr), is_id_set_(true) {}
+
+ MOZ_IMPLICIT PendingSTUNRequest(const mozilla::net::NetAddr& netaddr)
+ : id_(), net_addr_(netaddr), is_id_set_(false) {}
+
+ bool operator<(const PendingSTUNRequest& rhs) const {
+ if (NetAddrCompare()(net_addr_, rhs.net_addr_)) {
+ return true;
+ }
+
+ if (NetAddrCompare()(rhs.net_addr_, net_addr_)) {
+ return false;
+ }
+
+ if (!is_id_set_ && !rhs.is_id_set_) {
+ // PendingSTUNRequest can be stored to set only when it has id,
+ // so comparing two PendingSTUNRequst without id is not going
+ // to happen.
+ MOZ_CRASH();
+ }
+
+ if (!(is_id_set_ && rhs.is_id_set_)) {
+ // one of operands doesn't have id, ignore the difference.
+ return false;
+ }
+
+ return memcmp(id_.octet, rhs.id_.octet, sizeof(id_.octet)) < 0;
+ }
+
+ private:
+ const UINT12 id_;
+ const mozilla::net::NetAddr net_addr_;
+ const bool is_id_set_;
+};
+
+static uint16_t GetPortInfallible(const mozilla::net::NetAddr& aAddr) {
+ uint16_t result = 0;
+ (void)aAddr.GetPort(&result);
+ return result;
+}
+
+static std::ostream& operator<<(std::ostream& aStream, UINT12 aId) {
+ for (int octet : aId.octet) {
+ aStream << std::hex << std::setfill('0') << std::setw(2) << octet;
+ }
+ return aStream;
+}
+
+class STUNUDPSocketFilter : public nsISocketFilter {
+ public:
+ STUNUDPSocketFilter() : white_list_(), pending_requests_() {}
+
+ // Allocated/freed and used on the PBackground IPC thread
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISOCKETFILTER
+
+ private:
+ virtual ~STUNUDPSocketFilter() = default;
+
+ bool filter_incoming_packet(const mozilla::net::NetAddr* remote_addr,
+ const uint8_t* data, uint32_t len);
+
+ bool filter_outgoing_packet(const mozilla::net::NetAddr* remote_addr,
+ const uint8_t* data, uint32_t len);
+
+ std::set<mozilla::net::NetAddr, NetAddrCompare> white_list_;
+ std::set<PendingSTUNRequest> pending_requests_;
+ std::set<PendingSTUNRequest> response_allowed_;
+};
+
+NS_IMPL_ISUPPORTS(STUNUDPSocketFilter, nsISocketFilter)
+
+NS_IMETHODIMP
+STUNUDPSocketFilter::FilterPacket(const mozilla::net::NetAddr* remote_addr,
+ const uint8_t* data, uint32_t len,
+ int32_t direction, bool* result) {
+ switch (direction) {
+ case nsISocketFilter::SF_INCOMING:
+ *result = filter_incoming_packet(remote_addr, data, len);
+ break;
+ case nsISocketFilter::SF_OUTGOING:
+ *result = filter_outgoing_packet(remote_addr, data, len);
+ break;
+ default:
+ MOZ_CRASH("Unknown packet direction");
+ }
+ return NS_OK;
+}
+
+bool STUNUDPSocketFilter::filter_incoming_packet(
+ const mozilla::net::NetAddr* remote_addr, const uint8_t* data,
+ uint32_t len) {
+ // Check white list
+ if (white_list_.find(*remote_addr) != white_list_.end()) {
+ MOZ_MTLOG(ML_DEBUG, __func__ << this << " Address in whitelist: "
+ << remote_addr->ToString() << ":"
+ << GetPortInfallible(*remote_addr));
+ return true;
+ }
+
+ // If it is a STUN response message and we can match its id with one of the
+ // pending requests, we can add this address into whitelist.
+ if (nr_is_stun_response_message(
+ reinterpret_cast<UCHAR*>(const_cast<uint8_t*>(data)), len)) {
+ const nr_stun_message_header* msg =
+ reinterpret_cast<const nr_stun_message_header*>(data);
+ PendingSTUNRequest pending_req(*remote_addr, msg->id);
+ std::set<PendingSTUNRequest>::iterator it =
+ pending_requests_.find(pending_req);
+ if (it != pending_requests_.end()) {
+ pending_requests_.erase(it);
+ response_allowed_.erase(pending_req);
+ white_list_.insert(*remote_addr);
+ MOZ_MTLOG(ML_DEBUG, __func__ << this
+ << " Allowing known STUN response, "
+ "remembering address in whitelist: "
+ << remote_addr->ToString() << ":"
+ << GetPortInfallible(*remote_addr)
+ << " id=" << msg->id);
+ return true;
+ }
+ }
+ // If it's an incoming STUN request we let it pass and add it to the list of
+ // pending response for white listing once we answer.
+ if (nr_is_stun_request_message(
+ reinterpret_cast<UCHAR*>(const_cast<uint8_t*>(data)), len)) {
+ const nr_stun_message_header* msg =
+ reinterpret_cast<const nr_stun_message_header*>(data);
+ response_allowed_.insert(PendingSTUNRequest(*remote_addr, msg->id));
+ MOZ_MTLOG(
+ ML_DEBUG,
+ __func__ << this
+ << " Allowing STUN request, will allow packets in return: "
+ << remote_addr->ToString() << ":"
+ << GetPortInfallible(*remote_addr) << " id=" << msg->id);
+ return true;
+ }
+ // Lastly if we have send a STUN request to the destination of this
+ // packet we allow it to send us anything back in case it's for example a
+ // DTLS message (but we don't white list).
+ std::set<PendingSTUNRequest>::iterator it =
+ pending_requests_.find(PendingSTUNRequest(*remote_addr));
+ if (it != pending_requests_.end()) {
+ MOZ_MTLOG(
+ ML_DEBUG,
+ __func__
+ << this
+ << " Allowing packet from source while waiting for a response: "
+ << remote_addr->ToString() << ":"
+ << GetPortInfallible(*remote_addr));
+ return true;
+ }
+
+ MOZ_MTLOG(
+ ML_DEBUG,
+ __func__
+ << " Disallowing packet that is neither a STUN request or response: "
+ << remote_addr->ToString() << ":" << GetPortInfallible(*remote_addr));
+ return false;
+}
+
+bool STUNUDPSocketFilter::filter_outgoing_packet(
+ const mozilla::net::NetAddr* remote_addr, const uint8_t* data,
+ uint32_t len) {
+ // Check white list
+ if (white_list_.find(*remote_addr) != white_list_.end()) {
+ MOZ_MTLOG(ML_DEBUG, __func__ << this << " Address in whitelist: "
+ << remote_addr->ToString() << ":"
+ << GetPortInfallible(*remote_addr));
+ return true;
+ }
+
+ // Check if it is a stun packet. If yes, we put it into a pending list and
+ // wait for response packet.
+ if (nr_is_stun_request_message(
+ reinterpret_cast<UCHAR*>(const_cast<uint8_t*>(data)), len)) {
+ const nr_stun_message_header* msg =
+ reinterpret_cast<const nr_stun_message_header*>(data);
+ pending_requests_.insert(PendingSTUNRequest(*remote_addr, msg->id));
+ MOZ_MTLOG(
+ ML_DEBUG,
+ __func__ << this
+ << " Allowing STUN request, will allow packets in return: "
+ << remote_addr->ToString() << ":"
+ << GetPortInfallible(*remote_addr) << " id=" << msg->id);
+ return true;
+ }
+
+ // If it is a stun response packet, and we had received the request before, we
+ // can allow it packet to pass filter.
+ if (nr_is_stun_response_message(
+ reinterpret_cast<UCHAR*>(const_cast<uint8_t*>(data)), len)) {
+ const nr_stun_message_header* msg =
+ reinterpret_cast<const nr_stun_message_header*>(data);
+ std::set<PendingSTUNRequest>::iterator it =
+ response_allowed_.find(PendingSTUNRequest(*remote_addr, msg->id));
+ if (it != response_allowed_.end()) {
+ white_list_.insert(*remote_addr);
+ response_allowed_.erase(it);
+ MOZ_MTLOG(ML_DEBUG, __func__ << this
+ << " Allowing known STUN response, "
+ "remembering address in whitelist: "
+ << remote_addr->ToString() << ":"
+ << GetPortInfallible(*remote_addr)
+ << " id=" << msg->id);
+ return true;
+ }
+
+ MOZ_MTLOG(ML_DEBUG,
+ __func__ << this << " Disallowing unknown STUN response: "
+ << remote_addr->ToString() << ":"
+ << GetPortInfallible(*remote_addr) << " id=" << msg->id);
+ return false;
+ }
+
+ MOZ_MTLOG(
+ ML_DEBUG,
+ __func__
+ << " Disallowing packet that is neither a STUN request or response: "
+ << remote_addr->ToString() << ":" << GetPortInfallible(*remote_addr));
+ return false;
+}
+
+class PendingSTUNId {
+ public:
+ explicit PendingSTUNId(const UINT12& id) : id_(id) {}
+
+ bool operator<(const PendingSTUNId& rhs) const {
+ return memcmp(id_.octet, rhs.id_.octet, sizeof(id_.octet)) < 0;
+ }
+
+ private:
+ const UINT12 id_;
+};
+
+class STUNTCPSocketFilter : public nsISocketFilter {
+ public:
+ STUNTCPSocketFilter()
+ : white_listed_(false), pending_request_ids_(), response_allowed_ids_() {}
+
+ // Allocated/freed and used on the PBackground IPC thread
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISOCKETFILTER
+
+ private:
+ virtual ~STUNTCPSocketFilter() = default;
+
+ bool filter_incoming_packet(const uint8_t* data, uint32_t len);
+
+ bool filter_outgoing_packet(const uint8_t* data, uint32_t len);
+
+ bool white_listed_;
+ std::set<PendingSTUNId> pending_request_ids_;
+ std::set<PendingSTUNId> response_allowed_ids_;
+};
+
+NS_IMPL_ISUPPORTS(STUNTCPSocketFilter, nsISocketFilter)
+
+NS_IMETHODIMP
+STUNTCPSocketFilter::FilterPacket(const mozilla::net::NetAddr* remote_addr,
+ const uint8_t* data, uint32_t len,
+ int32_t direction, bool* result) {
+ switch (direction) {
+ case nsISocketFilter::SF_INCOMING:
+ *result = filter_incoming_packet(data, len);
+ break;
+ case nsISocketFilter::SF_OUTGOING:
+ *result = filter_outgoing_packet(data, len);
+ break;
+ default:
+ MOZ_CRASH("Unknown packet direction");
+ }
+ return NS_OK;
+}
+
+bool STUNTCPSocketFilter::filter_incoming_packet(const uint8_t* data,
+ uint32_t len) {
+ // check if white listed already
+ if (white_listed_) {
+ return true;
+ }
+
+ UCHAR* stun = const_cast<uint8_t*>(data);
+ uint32_t length = len;
+ if (!nr_is_stun_message(stun, length)) {
+ stun += 2;
+ length -= 2;
+ if (!nr_is_stun_message(stun, length)) {
+ // Note: the UDP filter lets incoming packets pass, because order of
+ // packets is not guaranteed and the next packet is likely an important
+ // packet for DTLS (which is costly in terms of timing to wait for a
+ // retransmit). This does not apply to TCP with its guaranteed order. But
+ // we still let it pass, because otherwise we would have to buffer bytes
+ // here until the minimum STUN request size of bytes has been received.
+ return true;
+ }
+ }
+
+ const nr_stun_message_header* msg =
+ reinterpret_cast<const nr_stun_message_header*>(stun);
+
+ // If it is a STUN response message and we can match its id with one of the
+ // pending requests, we can add this address into whitelist.
+ if (nr_is_stun_response_message(stun, length)) {
+ std::set<PendingSTUNId>::iterator it =
+ pending_request_ids_.find(PendingSTUNId(msg->id));
+ if (it != pending_request_ids_.end()) {
+ pending_request_ids_.erase(it);
+ white_listed_ = true;
+ }
+ } else {
+ // If it is a STUN message, but not a response message, we add it into
+ // response allowed list and allow outgoing filter to send a response back.
+ response_allowed_ids_.insert(PendingSTUNId(msg->id));
+ }
+
+ return true;
+}
+
+bool STUNTCPSocketFilter::filter_outgoing_packet(const uint8_t* data,
+ uint32_t len) {
+ // check if white listed already
+ if (white_listed_) {
+ return true;
+ }
+
+ UCHAR* stun = const_cast<uint8_t*>(data);
+ uint32_t length = len;
+ if (!nr_is_stun_message(stun, length)) {
+ stun += 2;
+ length -= 2;
+ if (!nr_is_stun_message(stun, length)) {
+ return false;
+ }
+ }
+
+ const nr_stun_message_header* msg =
+ reinterpret_cast<const nr_stun_message_header*>(stun);
+
+ // Check if it is a stun request. If yes, we put it into a pending list and
+ // wait for response packet.
+ if (nr_is_stun_request_message(stun, length)) {
+ pending_request_ids_.insert(PendingSTUNId(msg->id));
+ return true;
+ }
+
+ // If it is a stun response packet, and we had received the request before, we
+ // can allow it packet to pass filter.
+ if (nr_is_stun_response_message(stun, length)) {
+ std::set<PendingSTUNId>::iterator it =
+ response_allowed_ids_.find(PendingSTUNId(msg->id));
+ if (it != response_allowed_ids_.end()) {
+ response_allowed_ids_.erase(it);
+ white_listed_ = true;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+} // anonymous namespace
+
+NS_IMPL_ISUPPORTS(nsStunUDPSocketFilterHandler, nsISocketFilterHandler)
+
+NS_IMETHODIMP nsStunUDPSocketFilterHandler::NewFilter(
+ nsISocketFilter** result) {
+ nsISocketFilter* ret = new STUNUDPSocketFilter();
+ NS_ADDREF(*result = ret);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsStunTCPSocketFilterHandler, nsISocketFilterHandler)
+
+NS_IMETHODIMP nsStunTCPSocketFilterHandler::NewFilter(
+ nsISocketFilter** result) {
+ nsISocketFilter* ret = new STUNTCPSocketFilter();
+ NS_ADDREF(*result = ret);
+ return NS_OK;
+}
diff --git a/dom/media/webrtc/transport/stun_socket_filter.h b/dom/media/webrtc/transport/stun_socket_filter.h
new file mode 100644
index 0000000000..393257577d
--- /dev/null
+++ b/dom/media/webrtc/transport/stun_socket_filter.h
@@ -0,0 +1,41 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef stun_socket_filter_h__
+#define stun_socket_filter_h__
+
+#include "nsISocketFilter.h"
+
+#define NS_STUN_UDP_SOCKET_FILTER_HANDLER_CID \
+ {0x3e43ee93, \
+ 0x829e, \
+ 0x4ea6, \
+ {0xa3, 0x4e, 0x62, 0xd9, 0xe4, 0xc9, 0xf9, 0x93}};
+
+class nsStunUDPSocketFilterHandler : public nsISocketFilterHandler {
+ public:
+ // Threadsafe because we create off-main-thread, but destroy on MainThread
+ // via FreeFactoryEntries()
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISOCKETFILTERHANDLER
+ private:
+ virtual ~nsStunUDPSocketFilterHandler() = default;
+};
+
+#define NS_STUN_TCP_SOCKET_FILTER_HANDLER_CID \
+ {0x9fea635a, \
+ 0x2fc2, \
+ 0x4d08, \
+ {0x97, 0x21, 0xd2, 0x38, 0xd3, 0xf5, 0x2f, 0x92}};
+
+class nsStunTCPSocketFilterHandler : public nsISocketFilterHandler {
+ public:
+ // Threadsafe because we create off-main-thread, but destroy on MainThread
+ // via FreeFactoryEntries()
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISOCKETFILTERHANDLER
+ private:
+ virtual ~nsStunTCPSocketFilterHandler() = default;
+};
+
+#endif // stun_socket_filter_h__
diff --git a/dom/media/webrtc/transport/test/TestSyncRunnable.cpp b/dom/media/webrtc/transport/test/TestSyncRunnable.cpp
new file mode 100644
index 0000000000..ca671b4e79
--- /dev/null
+++ b/dom/media/webrtc/transport/test/TestSyncRunnable.cpp
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 12; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIThread.h"
+#include "nsThreadUtils.h"
+#include "mozilla/SyncRunnable.h"
+
+#include "gtest/gtest.h"
+
+using namespace mozilla;
+
+nsIThread* gThread = nullptr;
+
+class TestRunnable : public Runnable {
+ public:
+ TestRunnable() : Runnable("TestRunnable"), ran_(false) {}
+
+ NS_IMETHOD Run() override {
+ ran_ = true;
+
+ return NS_OK;
+ }
+
+ bool ran() const { return ran_; }
+
+ private:
+ bool ran_;
+};
+
+class TestSyncRunnable : public ::testing::Test {
+ public:
+ static void SetUpTestCase() {
+ nsresult rv = NS_NewNamedThread("thread", &gThread);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ }
+
+ static void TearDownTestCase() {
+ if (gThread) gThread->Shutdown();
+ }
+};
+
+TEST_F(TestSyncRunnable, TestDispatch) {
+ RefPtr<TestRunnable> r(new TestRunnable());
+ RefPtr<SyncRunnable> s(new SyncRunnable(r));
+ s->DispatchToThread(gThread);
+
+ ASSERT_TRUE(r->ran());
+}
+
+TEST_F(TestSyncRunnable, TestDispatchStatic) {
+ RefPtr<TestRunnable> r(new TestRunnable());
+ SyncRunnable::DispatchToThread(gThread, r);
+ ASSERT_TRUE(r->ran());
+}
diff --git a/dom/media/webrtc/transport/test/buffered_stun_socket_unittest.cpp b/dom/media/webrtc/transport/test/buffered_stun_socket_unittest.cpp
new file mode 100644
index 0000000000..e6a9cd38a2
--- /dev/null
+++ b/dom/media/webrtc/transport/test/buffered_stun_socket_unittest.cpp
@@ -0,0 +1,245 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+
+extern "C" {
+#include "nr_api.h"
+#include "nr_socket.h"
+#include "nr_socket_buffered_stun.h"
+#include "transport_addr.h"
+}
+
+#include "stun_msg.h"
+
+#include "dummysocket.h"
+
+#include "nr_socket_prsock.h"
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+#include "gtest_utils.h"
+
+using namespace mozilla;
+
+static uint8_t kStunMessage[] = {0x00, 0x01, 0x00, 0x08, 0x21, 0x12, 0xa4,
+ 0x42, 0x9b, 0x90, 0xbe, 0x2c, 0xae, 0x1a,
+ 0x0c, 0xa8, 0xa0, 0xd6, 0x8b, 0x08, 0x80,
+ 0x28, 0x00, 0x04, 0xdb, 0x35, 0x5f, 0xaa};
+static size_t kStunMessageLen = sizeof(kStunMessage);
+
+class BufferedStunSocketTest : public MtransportTest {
+ public:
+ BufferedStunSocketTest()
+ : MtransportTest(), dummy_(nullptr), test_socket_(nullptr) {}
+
+ ~BufferedStunSocketTest() { nr_socket_destroy(&test_socket_); }
+
+ void SetUp() override {
+ MtransportTest::SetUp();
+
+ RefPtr<DummySocket> dummy(new DummySocket());
+
+ int r =
+ nr_socket_buffered_stun_create(dummy->get_nr_socket(), kStunMessageLen,
+ TURN_TCP_FRAMING, &test_socket_);
+ ASSERT_EQ(0, r);
+ dummy_ = std::move(dummy); // Now owned by test_socket_.
+
+ r = nr_str_port_to_transport_addr((char*)"192.0.2.133", 3333, IPPROTO_TCP,
+ &remote_addr_);
+ ASSERT_EQ(0, r);
+
+ r = nr_socket_connect(test_socket_, &remote_addr_);
+ ASSERT_EQ(0, r);
+ }
+
+ nr_socket* socket() { return test_socket_; }
+
+ protected:
+ RefPtr<DummySocket> dummy_;
+ nr_socket* test_socket_;
+ nr_transport_addr remote_addr_;
+};
+
+TEST_F(BufferedStunSocketTest, TestCreate) {}
+
+TEST_F(BufferedStunSocketTest, TestSendTo) {
+ int r = nr_socket_sendto(test_socket_, kStunMessage, kStunMessageLen, 0,
+ &remote_addr_);
+ ASSERT_EQ(0, r);
+
+ dummy_->CheckWriteBuffer(kStunMessage, kStunMessageLen);
+}
+
+TEST_F(BufferedStunSocketTest, TestSendToBuffered) {
+ dummy_->SetWritable(0);
+
+ int r = nr_socket_sendto(test_socket_, kStunMessage, kStunMessageLen, 0,
+ &remote_addr_);
+ ASSERT_EQ(0, r);
+
+ dummy_->CheckWriteBuffer(nullptr, 0);
+
+ dummy_->SetWritable(kStunMessageLen);
+ dummy_->FireWritableCb();
+ dummy_->CheckWriteBuffer(kStunMessage, kStunMessageLen);
+}
+
+TEST_F(BufferedStunSocketTest, TestSendFullThenDrain) {
+ dummy_->SetWritable(0);
+
+ for (;;) {
+ int r = nr_socket_sendto(test_socket_, kStunMessage, kStunMessageLen, 0,
+ &remote_addr_);
+ if (r == R_WOULDBLOCK) break;
+
+ ASSERT_EQ(0, r);
+ }
+
+ // Nothing was written.
+ dummy_->CheckWriteBuffer(nullptr, 0);
+
+ // Now flush.
+ dummy_->SetWritable(kStunMessageLen);
+ dummy_->FireWritableCb();
+ dummy_->ClearWriteBuffer();
+
+ // Verify we can write something.
+ int r = nr_socket_sendto(test_socket_, kStunMessage, kStunMessageLen, 0,
+ &remote_addr_);
+ ASSERT_EQ(0, r);
+
+ // And that it appears.
+ dummy_->CheckWriteBuffer(kStunMessage, kStunMessageLen);
+}
+
+TEST_F(BufferedStunSocketTest, TestSendToPartialBuffered) {
+ dummy_->SetWritable(10);
+
+ int r = nr_socket_sendto(test_socket_, kStunMessage, kStunMessageLen, 0,
+ &remote_addr_);
+ ASSERT_EQ(0, r);
+
+ dummy_->CheckWriteBuffer(kStunMessage, 10);
+ dummy_->ClearWriteBuffer();
+
+ dummy_->SetWritable(kStunMessageLen);
+ dummy_->FireWritableCb();
+ dummy_->CheckWriteBuffer(kStunMessage + 10, kStunMessageLen - 10);
+}
+
+TEST_F(BufferedStunSocketTest, TestSendToReject) {
+ dummy_->SetWritable(0);
+
+ int r = nr_socket_sendto(test_socket_, kStunMessage, kStunMessageLen, 0,
+ &remote_addr_);
+ ASSERT_EQ(0, r);
+
+ dummy_->CheckWriteBuffer(nullptr, 0);
+
+ r = nr_socket_sendto(test_socket_, kStunMessage, kStunMessageLen, 0,
+ &remote_addr_);
+ ASSERT_EQ(R_WOULDBLOCK, r);
+
+ dummy_->CheckWriteBuffer(nullptr, 0);
+}
+
+TEST_F(BufferedStunSocketTest, TestSendToWrongAddr) {
+ nr_transport_addr addr;
+
+ int r = nr_str_port_to_transport_addr((char*)"192.0.2.134", 3333, IPPROTO_TCP,
+ &addr);
+ ASSERT_EQ(0, r);
+
+ r = nr_socket_sendto(test_socket_, kStunMessage, kStunMessageLen, 0, &addr);
+ ASSERT_EQ(R_BAD_DATA, r);
+}
+
+TEST_F(BufferedStunSocketTest, TestReceiveRecvFrom) {
+ dummy_->SetReadBuffer(kStunMessage, kStunMessageLen);
+
+ unsigned char tmp[2048];
+ size_t len;
+ nr_transport_addr addr;
+
+ int r = nr_socket_recvfrom(test_socket_, tmp, sizeof(tmp), &len, 0, &addr);
+ ASSERT_EQ(0, r);
+ ASSERT_EQ(kStunMessageLen, len);
+ ASSERT_EQ(0, memcmp(kStunMessage, tmp, kStunMessageLen));
+ ASSERT_EQ(0, nr_transport_addr_cmp(&addr, &remote_addr_,
+ NR_TRANSPORT_ADDR_CMP_MODE_ALL));
+}
+
+TEST_F(BufferedStunSocketTest, TestReceiveRecvFromPartial) {
+ dummy_->SetReadBuffer(kStunMessage, 15);
+
+ unsigned char tmp[2048];
+ size_t len;
+ nr_transport_addr addr;
+
+ int r = nr_socket_recvfrom(test_socket_, tmp, sizeof(tmp), &len, 0, &addr);
+ ASSERT_EQ(R_WOULDBLOCK, r);
+
+ dummy_->SetReadBuffer(kStunMessage + 15, kStunMessageLen - 15);
+
+ r = nr_socket_recvfrom(test_socket_, tmp, sizeof(tmp), &len, 0, &addr);
+ ASSERT_EQ(0, r);
+ ASSERT_EQ(kStunMessageLen, len);
+ ASSERT_EQ(0, memcmp(kStunMessage, tmp, kStunMessageLen));
+ ASSERT_EQ(0, nr_transport_addr_cmp(&addr, &remote_addr_,
+ NR_TRANSPORT_ADDR_CMP_MODE_ALL));
+
+ r = nr_socket_recvfrom(test_socket_, tmp, sizeof(tmp), &len, 0, &addr);
+ ASSERT_EQ(R_WOULDBLOCK, r);
+}
+
+TEST_F(BufferedStunSocketTest, TestReceiveRecvFromGarbage) {
+ uint8_t garbage[50];
+ memset(garbage, 0xff, sizeof(garbage));
+
+ dummy_->SetReadBuffer(garbage, sizeof(garbage));
+
+ unsigned char tmp[2048];
+ size_t len;
+ nr_transport_addr addr;
+ int r = nr_socket_recvfrom(test_socket_, tmp, sizeof(tmp), &len, 0, &addr);
+ ASSERT_EQ(R_BAD_DATA, r);
+
+ r = nr_socket_recvfrom(test_socket_, tmp, sizeof(tmp), &len, 0, &addr);
+ ASSERT_EQ(R_FAILED, r);
+}
+
+TEST_F(BufferedStunSocketTest, TestReceiveRecvFromTooShort) {
+ dummy_->SetReadBuffer(kStunMessage, kStunMessageLen);
+
+ unsigned char tmp[2048];
+ size_t len;
+ nr_transport_addr addr;
+
+ int r = nr_socket_recvfrom(test_socket_, tmp, kStunMessageLen - 1, &len, 0,
+ &addr);
+ ASSERT_EQ(R_BAD_ARGS, r);
+}
+
+TEST_F(BufferedStunSocketTest, TestReceiveRecvFromReallyLong) {
+ uint8_t garbage[4096];
+ memset(garbage, 0xff, sizeof(garbage));
+ memcpy(garbage, kStunMessage, kStunMessageLen);
+ nr_stun_message_header* hdr =
+ reinterpret_cast<nr_stun_message_header*>(garbage);
+ hdr->length = htons(3000);
+
+ dummy_->SetReadBuffer(garbage, sizeof(garbage));
+
+ unsigned char tmp[4096];
+ size_t len;
+ nr_transport_addr addr;
+
+ int r = nr_socket_recvfrom(test_socket_, tmp, kStunMessageLen - 1, &len, 0,
+ &addr);
+ ASSERT_EQ(R_BAD_DATA, r);
+}
diff --git a/dom/media/webrtc/transport/test/dummysocket.h b/dom/media/webrtc/transport/test/dummysocket.h
new file mode 100644
index 0000000000..6e20a1f7e7
--- /dev/null
+++ b/dom/media/webrtc/transport/test/dummysocket.h
@@ -0,0 +1,217 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original authors: ekr@rtfm.com; ryan@tokbox.com
+
+#ifndef MTRANSPORT_DUMMY_SOCKET_H_
+#define MTRANSPORT_DUMMY_SOCKET_H_
+
+#include "nr_socket_prsock.h"
+
+extern "C" {
+#include "transport_addr.h"
+}
+
+#include "mediapacket.h"
+#include "mozilla/UniquePtr.h"
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+#include "gtest_utils.h"
+
+namespace mozilla {
+
+static UniquePtr<MediaPacket> merge(UniquePtr<MediaPacket> a,
+ UniquePtr<MediaPacket> b) {
+ if (a && a->len() && b && b->len()) {
+ UniquePtr<uint8_t[]> data(new uint8_t[a->len() + b->len()]);
+ memcpy(data.get(), a->data(), a->len());
+ memcpy(data.get() + a->len(), b->data(), b->len());
+
+ UniquePtr<MediaPacket> merged(new MediaPacket);
+ merged->Take(std::move(data), a->len() + b->len());
+ return merged;
+ }
+
+ if (a && a->len()) {
+ return a;
+ }
+
+ if (b && b->len()) {
+ return b;
+ }
+
+ return nullptr;
+}
+
+class DummySocket : public NrSocketBase {
+ public:
+ DummySocket()
+ : writable_(UINT_MAX),
+ write_buffer_(nullptr),
+ readable_(UINT_MAX),
+ read_buffer_(nullptr),
+ cb_(nullptr),
+ cb_arg_(nullptr),
+ self_(nullptr) {}
+
+ // the nr_socket APIs
+ virtual int create(nr_transport_addr* addr) override { return 0; }
+
+ virtual int sendto(const void* msg, size_t len, int flags,
+ const nr_transport_addr* to) override {
+ MOZ_CRASH();
+ return 0;
+ }
+
+ virtual int recvfrom(void* buf, size_t maxlen, size_t* len, int flags,
+ nr_transport_addr* from) override {
+ MOZ_CRASH();
+ return 0;
+ }
+
+ virtual int getaddr(nr_transport_addr* addrp) override {
+ MOZ_CRASH();
+ return 0;
+ }
+
+ virtual void close() override {}
+
+ virtual int connect(const nr_transport_addr* addr) override {
+ nr_transport_addr_copy(&connect_addr_, addr);
+ return 0;
+ }
+
+ virtual int listen(int backlog) override { return 0; }
+
+ virtual int accept(nr_transport_addr* addrp, nr_socket** sockp) override {
+ return 0;
+ }
+
+ virtual int write(const void* msg, size_t len, size_t* written) override {
+ size_t to_write = std::min(len, writable_);
+
+ if (to_write) {
+ UniquePtr<MediaPacket> msgbuf(new MediaPacket);
+ msgbuf->Copy(static_cast<const uint8_t*>(msg), to_write);
+ write_buffer_ = merge(std::move(write_buffer_), std::move(msgbuf));
+ }
+
+ *written = to_write;
+
+ return 0;
+ }
+
+ virtual int read(void* buf, size_t maxlen, size_t* len) override {
+ if (!read_buffer_.get()) {
+ return R_WOULDBLOCK;
+ }
+
+ size_t to_read = std::min(read_buffer_->len(), std::min(maxlen, readable_));
+
+ memcpy(buf, read_buffer_->data(), to_read);
+ *len = to_read;
+
+ if (to_read < read_buffer_->len()) {
+ MediaPacket* newPacket = new MediaPacket;
+ newPacket->Copy(read_buffer_->data() + to_read,
+ read_buffer_->len() - to_read);
+ read_buffer_.reset(newPacket);
+ } else {
+ read_buffer_.reset();
+ }
+
+ return 0;
+ }
+
+ // Implementations of the async_event APIs.
+ // These are no-ops because we handle scheduling manually
+ // for test purposes.
+ virtual int async_wait(int how, NR_async_cb cb, void* cb_arg, char* function,
+ int line) override {
+ EXPECT_EQ(nullptr, cb_);
+ cb_ = cb;
+ cb_arg_ = cb_arg;
+
+ return 0;
+ }
+
+ virtual int cancel(int how) override {
+ cb_ = nullptr;
+ cb_arg_ = nullptr;
+
+ return 0;
+ }
+
+ // Read/Manipulate the current state.
+ void CheckWriteBuffer(const uint8_t* data, size_t len) {
+ if (!len) {
+ EXPECT_EQ(nullptr, write_buffer_.get());
+ } else {
+ EXPECT_NE(nullptr, write_buffer_.get());
+ ASSERT_EQ(len, write_buffer_->len());
+ ASSERT_EQ(0, memcmp(data, write_buffer_->data(), len));
+ }
+ }
+
+ void ClearWriteBuffer() { write_buffer_.reset(); }
+
+ void SetWritable(size_t val) { writable_ = val; }
+
+ void FireWritableCb() {
+ NR_async_cb cb = cb_;
+ void* cb_arg = cb_arg_;
+
+ cb_ = nullptr;
+ cb_arg_ = nullptr;
+
+ cb(this, NR_ASYNC_WAIT_WRITE, cb_arg);
+ }
+
+ void SetReadBuffer(const uint8_t* data, size_t len) {
+ EXPECT_EQ(nullptr, write_buffer_.get());
+ read_buffer_.reset(new MediaPacket);
+ read_buffer_->Copy(data, len);
+ }
+
+ void ClearReadBuffer() { read_buffer_.reset(); }
+
+ void SetReadable(size_t val) { readable_ = val; }
+
+ nr_socket* get_nr_socket() {
+ if (!self_) {
+ int r = nr_socket_create_int(this, vtbl(), &self_);
+ AddRef();
+ if (r) return nullptr;
+ }
+
+ return self_;
+ }
+
+ nr_transport_addr* get_connect_addr() { return &connect_addr_; }
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DummySocket, override);
+
+ private:
+ ~DummySocket() = default;
+
+ DISALLOW_COPY_ASSIGN(DummySocket);
+
+ size_t writable_; // Amount we allow someone to write.
+ UniquePtr<MediaPacket> write_buffer_;
+ size_t readable_; // Amount we allow someone to read.
+ UniquePtr<MediaPacket> read_buffer_;
+
+ NR_async_cb cb_;
+ void* cb_arg_;
+ nr_socket* self_;
+
+ nr_transport_addr connect_addr_;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webrtc/transport/test/gtest_ringbuffer_dumper.h b/dom/media/webrtc/transport/test/gtest_ringbuffer_dumper.h
new file mode 100644
index 0000000000..25e85c2155
--- /dev/null
+++ b/dom/media/webrtc/transport/test/gtest_ringbuffer_dumper.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: bcampen@mozilla.com
+
+#ifndef gtest_ringbuffer_dumper_h__
+#define gtest_ringbuffer_dumper_h__
+
+#include "mozilla/SyncRunnable.h"
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+
+#include "mtransport_test_utils.h"
+#include "runnable_utils.h"
+#include "rlogconnector.h"
+
+using mozilla::RLogConnector;
+using mozilla::WrapRunnable;
+
+namespace test {
+class RingbufferDumper : public ::testing::EmptyTestEventListener {
+ public:
+ explicit RingbufferDumper(MtransportTestUtils* test_utils)
+ : test_utils_(test_utils) {}
+
+ void ClearRingBuffer_s() {
+ RLogConnector::CreateInstance();
+ // Set limit to zero to clear the ringbuffer
+ RLogConnector::GetInstance()->SetLogLimit(0);
+ RLogConnector::GetInstance()->SetLogLimit(UINT32_MAX);
+ }
+
+ void DestroyRingBuffer_s() { RLogConnector::DestroyInstance(); }
+
+ void DumpRingBuffer_s() {
+ std::deque<std::string> logs;
+ // Get an unlimited number of log lines, with no filter
+ RLogConnector::GetInstance()->GetAny(0, &logs);
+ for (auto l = logs.begin(); l != logs.end(); ++l) {
+ std::cout << *l << std::endl;
+ }
+ ClearRingBuffer_s();
+ }
+
+ virtual void OnTestStart(const ::testing::TestInfo& testInfo) override {
+ mozilla::SyncRunnable::DispatchToThread(
+ test_utils_->sts_target(),
+ WrapRunnable(this, &RingbufferDumper::ClearRingBuffer_s));
+ }
+
+ virtual void OnTestEnd(const ::testing::TestInfo& testInfo) override {
+ mozilla::SyncRunnable::DispatchToThread(
+ test_utils_->sts_target(),
+ WrapRunnable(this, &RingbufferDumper::DestroyRingBuffer_s));
+ }
+
+ // Called after a failed assertion or a SUCCEED() invocation.
+ virtual void OnTestPartResult(
+ const ::testing::TestPartResult& testResult) override {
+ if (testResult.failed()) {
+ // Dump (and empty) the RLogConnector
+ mozilla::SyncRunnable::DispatchToThread(
+ test_utils_->sts_target(),
+ WrapRunnable(this, &RingbufferDumper::DumpRingBuffer_s));
+ }
+ }
+
+ private:
+ MtransportTestUtils* test_utils_;
+};
+
+} // namespace test
+
+#endif // gtest_ringbuffer_dumper_h__
diff --git a/dom/media/webrtc/transport/test/gtest_utils.h b/dom/media/webrtc/transport/test/gtest_utils.h
new file mode 100644
index 0000000000..40c2570ea1
--- /dev/null
+++ b/dom/media/webrtc/transport/test/gtest_utils.h
@@ -0,0 +1,201 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Utilities to wrap gtest, based on libjingle's gunit
+
+// Some sections of this code are under the following license:
+
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
+ */
+
+// Original author: ekr@rtfm.com
+#ifndef gtest_utils__h__
+#define gtest_utils__h__
+
+#include <iostream>
+
+#include "nspr.h"
+#include "prinrval.h"
+#include "prthread.h"
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+
+#include "gtest_ringbuffer_dumper.h"
+#include "mtransport_test_utils.h"
+#include "nss.h"
+#include "ssl.h"
+
+extern "C" {
+#include "registry.h"
+#include "transport_addr.h"
+}
+
+// Wait up to timeout seconds for expression to be true
+#define WAIT(expression, timeout) \
+ do { \
+ for (PRIntervalTime start = PR_IntervalNow(); \
+ !(expression) && !((PR_IntervalNow() - start) > \
+ PR_MillisecondsToInterval(timeout));) { \
+ PR_Sleep(10); \
+ } \
+ } while (0)
+
+// Same as GTEST_WAIT, but stores the result in res. Used when
+// you also want the result of expression but wish to avoid
+// double evaluation.
+#define WAIT_(expression, timeout, res) \
+ do { \
+ for (PRIntervalTime start = PR_IntervalNow(); \
+ !(res = (expression)) && !((PR_IntervalNow() - start) > \
+ PR_MillisecondsToInterval(timeout));) { \
+ PR_Sleep(10); \
+ } \
+ } while (0)
+
+#define ASSERT_TRUE_WAIT(expression, timeout) \
+ do { \
+ bool res; \
+ WAIT_(expression, timeout, res); \
+ ASSERT_TRUE(res); \
+ } while (0)
+
+#define EXPECT_TRUE_WAIT(expression, timeout) \
+ do { \
+ bool res; \
+ WAIT_(expression, timeout, res); \
+ EXPECT_TRUE(res); \
+ } while (0)
+
+#define ASSERT_EQ_WAIT(expected, actual, timeout) \
+ do { \
+ WAIT(expected == actual, timeout); \
+ ASSERT_EQ(expected, actual); \
+ } while (0)
+
+using test::RingbufferDumper;
+
+class MtransportTest : public ::testing::Test {
+ public:
+ MtransportTest() : test_utils_(nullptr), dumper_(nullptr) {}
+
+ void SetUp() override {
+ test_utils_ = new MtransportTestUtils();
+ NSS_NoDB_Init(nullptr);
+ NSS_SetDomesticPolicy();
+
+ NR_reg_init(NR_REG_MODE_LOCAL);
+
+ // Attempt to load env vars used by tests.
+ GetEnvironment("TURN_SERVER_ADDRESS", turn_server_);
+ GetEnvironment("TURN_SERVER_USER", turn_user_);
+ GetEnvironment("TURN_SERVER_PASSWORD", turn_password_);
+ GetEnvironment("STUN_SERVER_ADDRESS", stun_server_address_);
+ GetEnvironment("STUN_SERVER_HOSTNAME", stun_server_hostname_);
+
+ std::string disable_non_local;
+ GetEnvironment("MOZ_DISABLE_NONLOCAL_CONNECTIONS", disable_non_local);
+ std::string upload_dir;
+ GetEnvironment("MOZ_UPLOAD_DIR", upload_dir);
+
+ if ((!disable_non_local.empty() && disable_non_local != "0") ||
+ !upload_dir.empty()) {
+ // We're assuming that MOZ_UPLOAD_DIR is only set on tbpl;
+ // MOZ_DISABLE_NONLOCAL_CONNECTIONS probably should be set when running
+ // the cpp unit-tests, but is not presently.
+ stun_server_address_ = "";
+ stun_server_hostname_ = "";
+ turn_server_ = "";
+ }
+
+ // Some tests are flaky and need to check if they're supposed to run.
+ webrtc_enabled_ = CheckEnvironmentFlag("MOZ_WEBRTC_TESTS");
+
+ ::testing::TestEventListeners& listeners =
+ ::testing::UnitTest::GetInstance()->listeners();
+
+ dumper_ = new RingbufferDumper(test_utils_);
+ listeners.Append(dumper_);
+ }
+
+ void TearDown() override {
+ ::testing::UnitTest::GetInstance()->listeners().Release(dumper_);
+ delete dumper_;
+ delete test_utils_;
+ }
+
+ void GetEnvironment(const char* aVar, std::string& out) {
+ char* value = getenv(aVar);
+ if (value) {
+ out = value;
+ }
+ }
+
+ bool CheckEnvironmentFlag(const char* aVar) {
+ std::string value;
+ GetEnvironment(aVar, value);
+ return value == "1";
+ }
+
+ bool WarnIfTurnNotConfigured() const {
+ bool configured =
+ !turn_server_.empty() && !turn_user_.empty() && !turn_password_.empty();
+
+ if (configured) {
+ nr_transport_addr addr;
+ if (nr_str_port_to_transport_addr(turn_server_.c_str(), 3478, IPPROTO_UDP,
+ &addr)) {
+ printf(
+ "Invalid TURN_SERVER_ADDRESS \"%s\". Only IP numbers supported.\n",
+ turn_server_.c_str());
+ configured = false;
+ }
+ } else {
+ printf(
+ "Set TURN_SERVER_ADDRESS, TURN_SERVER_USER, and "
+ "TURN_SERVER_PASSWORD\n"
+ "environment variables to run this test\n");
+ }
+
+ return !configured;
+ }
+
+ MtransportTestUtils* test_utils_;
+ RingbufferDumper* dumper_;
+
+ std::string turn_server_;
+ std::string turn_user_;
+ std::string turn_password_;
+ std::string stun_server_address_;
+ std::string stun_server_hostname_;
+
+ bool webrtc_enabled_;
+};
+#endif
diff --git a/dom/media/webrtc/transport/test/ice_unittest.cpp b/dom/media/webrtc/transport/test/ice_unittest.cpp
new file mode 100644
index 0000000000..d87fa0b0da
--- /dev/null
+++ b/dom/media/webrtc/transport/test/ice_unittest.cpp
@@ -0,0 +1,4400 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+
+#include <algorithm>
+#include <deque>
+#include <iostream>
+#include <limits>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "sigslot.h"
+
+#include "logging.h"
+#include "ssl.h"
+
+#include "mozilla/Preferences.h"
+#include "nsThreadUtils.h"
+#include "nsXPCOM.h"
+
+extern "C" {
+#include "r_types.h"
+#include "async_wait.h"
+#include "async_timer.h"
+#include "r_data.h"
+#include "util.h"
+#include "r_time.h"
+}
+
+#include "ice_ctx.h"
+#include "ice_peer_ctx.h"
+#include "ice_media_stream.h"
+
+#include "nricectx.h"
+#include "nricemediastream.h"
+#include "nriceresolverfake.h"
+#include "nriceresolver.h"
+#include "nrinterfaceprioritizer.h"
+#include "gtest_ringbuffer_dumper.h"
+#include "rlogconnector.h"
+#include "runnable_utils.h"
+#include "stunserver.h"
+#include "nr_socket_prsock.h"
+#include "test_nr_socket.h"
+#include "nsISocketFilter.h"
+#include "mozilla/net/DNS.h"
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+#include "gtest_utils.h"
+
+using namespace mozilla;
+
+static unsigned int kDefaultTimeout = 7000;
+
+// TODO(nils@mozilla.com): This should get replaced with some non-external
+// solution like discussed in bug 860775.
+const std::string kDefaultStunServerHostname((char*)"stun.l.google.com");
+const std::string kBogusStunServerHostname(
+ (char*)"stun-server-nonexistent.invalid");
+const uint16_t kDefaultStunServerPort = 19305;
+const std::string kBogusIceCandidate(
+ (char*)"candidate:0 2 UDP 2113601790 192.168.178.20 50769 typ");
+
+const std::string kUnreachableHostIceCandidate(
+ (char*)"candidate:0 1 UDP 2113601790 192.168.178.20 50769 typ host");
+
+namespace {
+
+// DNS resolution helper code
+static std::string Resolve(const std::string& fqdn, int address_family) {
+ struct addrinfo hints;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = address_family;
+ hints.ai_protocol = IPPROTO_UDP;
+ struct addrinfo* res;
+ int err = getaddrinfo(fqdn.c_str(), nullptr, &hints, &res);
+ if (err) {
+ std::cerr << "Error in getaddrinfo: " << err << std::endl;
+ return "";
+ }
+
+ char str_addr[64] = {0};
+ switch (res->ai_family) {
+ case AF_INET:
+ inet_ntop(AF_INET,
+ &reinterpret_cast<struct sockaddr_in*>(res->ai_addr)->sin_addr,
+ str_addr, sizeof(str_addr));
+ break;
+ case AF_INET6:
+ inet_ntop(
+ AF_INET6,
+ &reinterpret_cast<struct sockaddr_in6*>(res->ai_addr)->sin6_addr,
+ str_addr, sizeof(str_addr));
+ break;
+ default:
+ std::cerr << "Got unexpected address family in DNS lookup: "
+ << res->ai_family << std::endl;
+ freeaddrinfo(res);
+ return "";
+ }
+
+ if (!strlen(str_addr)) {
+ std::cerr << "inet_ntop failed" << std::endl;
+ }
+
+ freeaddrinfo(res);
+ return str_addr;
+}
+
+class StunTest : public MtransportTest {
+ public:
+ StunTest() : MtransportTest() {}
+
+ void SetUp() override {
+ MtransportTest::SetUp();
+
+ stun_server_hostname_ = kDefaultStunServerHostname;
+ // If only a STUN server FQDN was provided, look up its IP address for the
+ // address-only tests.
+ if (stun_server_address_.empty() && !stun_server_hostname_.empty()) {
+ stun_server_address_ = Resolve(stun_server_hostname_, AF_INET);
+ ASSERT_TRUE(!stun_server_address_.empty());
+ }
+
+ // Make sure NrIceCtx is in a testable state.
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableNM(&NrIceCtx::internal_DeinitializeGlobal));
+
+ // NB: NrIceCtx::internal_DeinitializeGlobal destroys the RLogConnector
+ // singleton.
+ RLogConnector::CreateInstance();
+
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableNM(&TestStunServer::GetInstance, AF_INET));
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableNM(&TestStunServer::GetInstance, AF_INET6));
+
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableNM(&TestStunTcpServer::GetInstance, AF_INET));
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableNM(&TestStunTcpServer::GetInstance, AF_INET6));
+ }
+
+ void TearDown() override {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableNM(&NrIceCtx::internal_DeinitializeGlobal));
+
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableNM(&TestStunServer::ShutdownInstance));
+
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableNM(&TestStunTcpServer::ShutdownInstance));
+
+ RLogConnector::DestroyInstance();
+
+ MtransportTest::TearDown();
+ }
+};
+
+enum TrickleMode { TRICKLE_NONE, TRICKLE_SIMULATE, TRICKLE_REAL };
+
+enum ConsentStatus { CONSENT_FRESH, CONSENT_STALE, CONSENT_EXPIRED };
+
+typedef std::string (*CandidateFilter)(const std::string& candidate);
+
+std::vector<std::string> split(const std::string& s, char delim) {
+ std::vector<std::string> elems;
+ std::stringstream ss(s);
+ std::string item;
+ while (std::getline(ss, item, delim)) {
+ elems.push_back(item);
+ }
+ return elems;
+}
+
+static std::string IsSrflxCandidate(const std::string& candidate) {
+ std::vector<std::string> tokens = split(candidate, ' ');
+ if ((tokens.at(6) == "typ") && (tokens.at(7) == "srflx")) {
+ return candidate;
+ }
+ return std::string();
+}
+
+static std::string IsRelayCandidate(const std::string& candidate) {
+ if (candidate.find("typ relay") != std::string::npos) {
+ return candidate;
+ }
+ return std::string();
+}
+
+static std::string IsTcpCandidate(const std::string& candidate) {
+ if (candidate.find("TCP") != std::string::npos) {
+ return candidate;
+ }
+ return std::string();
+}
+
+static std::string IsTcpSoCandidate(const std::string& candidate) {
+ if (candidate.find("tcptype so") != std::string::npos) {
+ return candidate;
+ }
+ return std::string();
+}
+
+static std::string IsLoopbackCandidate(const std::string& candidate) {
+ if (candidate.find("127.0.0.") != std::string::npos) {
+ return candidate;
+ }
+ return std::string();
+}
+
+static std::string IsIpv4Candidate(const std::string& candidate) {
+ std::vector<std::string> tokens = split(candidate, ' ');
+ if (tokens.at(4).find(':') == std::string::npos) {
+ return candidate;
+ }
+ return std::string();
+}
+
+static std::string SabotageHostCandidateAndDropReflexive(
+ const std::string& candidate) {
+ if (candidate.find("typ srflx") != std::string::npos) {
+ return std::string();
+ }
+
+ if (candidate.find("typ host") != std::string::npos) {
+ return kUnreachableHostIceCandidate;
+ }
+
+ return candidate;
+}
+
+bool ContainsSucceededPair(const std::vector<NrIceCandidatePair>& pairs) {
+ for (const auto& pair : pairs) {
+ if (pair.state == NrIceCandidatePair::STATE_SUCCEEDED) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// Note: Does not correspond to any notion of prioritization; this is just
+// so we can use stl containers/algorithms that need a comparator
+bool operator<(const NrIceCandidate& lhs, const NrIceCandidate& rhs) {
+ if (lhs.cand_addr.host == rhs.cand_addr.host) {
+ if (lhs.cand_addr.port == rhs.cand_addr.port) {
+ if (lhs.cand_addr.transport == rhs.cand_addr.transport) {
+ if (lhs.type == rhs.type) {
+ return lhs.tcp_type < rhs.tcp_type;
+ }
+ return lhs.type < rhs.type;
+ }
+ return lhs.cand_addr.transport < rhs.cand_addr.transport;
+ }
+ return lhs.cand_addr.port < rhs.cand_addr.port;
+ }
+ return lhs.cand_addr.host < rhs.cand_addr.host;
+}
+
+bool operator==(const NrIceCandidate& lhs, const NrIceCandidate& rhs) {
+ return !((lhs < rhs) || (rhs < lhs));
+}
+
+class IceCandidatePairCompare {
+ public:
+ bool operator()(const NrIceCandidatePair& lhs,
+ const NrIceCandidatePair& rhs) const {
+ if (lhs.priority == rhs.priority) {
+ if (lhs.local == rhs.local) {
+ if (lhs.remote == rhs.remote) {
+ return lhs.codeword < rhs.codeword;
+ }
+ return lhs.remote < rhs.remote;
+ }
+ return lhs.local < rhs.local;
+ }
+ return lhs.priority < rhs.priority;
+ }
+};
+
+class IceTestPeer;
+
+class SchedulableTrickleCandidate {
+ public:
+ SchedulableTrickleCandidate(IceTestPeer* peer, size_t stream,
+ const std::string& candidate,
+ const std::string& ufrag,
+ MtransportTestUtils* utils)
+ : peer_(peer),
+ stream_(stream),
+ candidate_(candidate),
+ ufrag_(ufrag),
+ timer_handle_(nullptr),
+ test_utils_(utils) {}
+
+ ~SchedulableTrickleCandidate() {
+ if (timer_handle_) NR_async_timer_cancel(timer_handle_);
+ }
+
+ void Schedule(unsigned int ms) {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &SchedulableTrickleCandidate::Schedule_s, ms));
+ }
+
+ void Schedule_s(unsigned int ms) {
+ MOZ_ASSERT(!timer_handle_);
+ NR_ASYNC_TIMER_SET(ms, Trickle_cb, this, &timer_handle_);
+ }
+
+ static void Trickle_cb(NR_SOCKET s, int how, void* cb_arg) {
+ static_cast<SchedulableTrickleCandidate*>(cb_arg)->Trickle();
+ }
+
+ void Trickle();
+
+ std::string& Candidate() { return candidate_; }
+
+ const std::string& Candidate() const { return candidate_; }
+
+ bool IsHost() const {
+ return candidate_.find("typ host") != std::string::npos;
+ }
+
+ bool IsReflexive() const {
+ return candidate_.find("typ srflx") != std::string::npos;
+ }
+
+ bool IsRelay() const {
+ return candidate_.find("typ relay") != std::string::npos;
+ }
+
+ private:
+ IceTestPeer* peer_;
+ size_t stream_;
+ std::string candidate_;
+ std::string ufrag_;
+ void* timer_handle_;
+ MtransportTestUtils* test_utils_;
+
+ DISALLOW_COPY_ASSIGN(SchedulableTrickleCandidate);
+};
+
+class IceTestPeer : public sigslot::has_slots<> {
+ public:
+ IceTestPeer(const std::string& name, MtransportTestUtils* utils, bool offerer,
+ const NrIceCtx::Config& config)
+ : name_(name),
+ ice_ctx_(NrIceCtx::Create(name)),
+ offerer_(offerer),
+ candidates_(),
+ stream_counter_(0),
+ shutting_down_(false),
+ gathering_complete_(false),
+ ready_ct_(0),
+ ice_connected_(false),
+ ice_failed_(false),
+ ice_reached_checking_(false),
+ received_(0),
+ sent_(0),
+ fake_resolver_(),
+ dns_resolver_(new NrIceResolver()),
+ remote_(nullptr),
+ candidate_filter_(nullptr),
+ expected_local_type_(NrIceCandidate::ICE_HOST),
+ expected_local_transport_(kNrIceTransportUdp),
+ expected_remote_type_(NrIceCandidate::ICE_HOST),
+ trickle_mode_(TRICKLE_NONE),
+ simulate_ice_lite_(false),
+ nat_(new TestNat),
+ test_utils_(utils) {
+ ice_ctx_->SignalGatheringStateChange.connect(
+ this, &IceTestPeer::GatheringStateChange);
+ ice_ctx_->SignalConnectionStateChange.connect(
+ this, &IceTestPeer::ConnectionStateChange);
+
+ ice_ctx_->SetIceConfig(config);
+
+ consent_timestamp_.tv_sec = 0;
+ consent_timestamp_.tv_usec = 0;
+ int r = ice_ctx_->SetNat(nat_);
+ (void)r;
+ MOZ_ASSERT(!r);
+ }
+
+ ~IceTestPeer() {
+ test_utils_->SyncDispatchToSTS(WrapRunnable(this, &IceTestPeer::Shutdown));
+
+ // Give the ICE destruction callback time to fire before
+ // we destroy the resolver.
+ PR_Sleep(1000);
+ }
+
+ std::string MakeTransportId(size_t index) const {
+ char id[100];
+ snprintf(id, sizeof(id), "%s:stream%d", name_.c_str(), (int)index);
+ return id;
+ }
+
+ void SetIceCredentials_s(NrIceMediaStream& stream) {
+ static size_t counter = 0;
+ std::ostringstream prefix;
+ prefix << name_ << "-" << counter++;
+ std::string ufrag = prefix.str() + "-ufrag";
+ std::string pwd = prefix.str() + "-pwd";
+ if (mIceCredentials.count(stream.GetId())) {
+ mOldIceCredentials[stream.GetId()] = mIceCredentials[stream.GetId()];
+ }
+ mIceCredentials[stream.GetId()] = std::make_pair(ufrag, pwd);
+ stream.SetIceCredentials(ufrag, pwd);
+ }
+
+ void AddStream_s(int components) {
+ std::string id = MakeTransportId(stream_counter_++);
+
+ RefPtr<NrIceMediaStream> stream =
+ ice_ctx_->CreateStream(id, id, components);
+
+ ASSERT_TRUE(stream);
+ SetIceCredentials_s(*stream);
+
+ stream->SignalCandidate.connect(this, &IceTestPeer::CandidateInitialized);
+ stream->SignalReady.connect(this, &IceTestPeer::StreamReady);
+ stream->SignalFailed.connect(this, &IceTestPeer::StreamFailed);
+ stream->SignalPacketReceived.connect(this, &IceTestPeer::PacketReceived);
+ }
+
+ void AddStream(int components) {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &IceTestPeer::AddStream_s, components));
+ }
+
+ void RemoveStream_s(size_t index) {
+ ice_ctx_->DestroyStream(MakeTransportId(index));
+ }
+
+ void RemoveStream(size_t index) {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &IceTestPeer::RemoveStream_s, index));
+ }
+
+ RefPtr<NrIceMediaStream> GetStream_s(size_t index) {
+ std::string id = MakeTransportId(index);
+ return ice_ctx_->GetStream(id);
+ }
+
+ void SetStunServer(const std::string addr, uint16_t port,
+ const char* transport = kNrIceTransportUdp) {
+ if (addr.empty()) {
+ // Happens when MOZ_DISABLE_NONLOCAL_CONNECTIONS is set
+ return;
+ }
+
+ std::vector<NrIceStunServer> stun_servers;
+ UniquePtr<NrIceStunServer> server(
+ NrIceStunServer::Create(addr, port, transport));
+ stun_servers.push_back(*server);
+ SetStunServers(stun_servers);
+ }
+
+ void SetStunServers(const std::vector<NrIceStunServer>& servers) {
+ ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->SetStunServers(servers)));
+ }
+
+ void UseTestStunServer() {
+ SetStunServer(TestStunServer::GetInstance(AF_INET)->addr(),
+ TestStunServer::GetInstance(AF_INET)->port());
+ }
+
+ void SetTurnServer(const std::string addr, uint16_t port,
+ const std::string username, const std::string password,
+ const char* transport) {
+ std::vector<unsigned char> password_vec(password.begin(), password.end());
+ SetTurnServer(addr, port, username, password_vec, transport);
+ }
+
+ void SetTurnServer(const std::string addr, uint16_t port,
+ const std::string username,
+ const std::vector<unsigned char> password,
+ const char* transport) {
+ std::vector<NrIceTurnServer> turn_servers;
+ UniquePtr<NrIceTurnServer> server(
+ NrIceTurnServer::Create(addr, port, username, password, transport));
+ turn_servers.push_back(*server);
+ ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->SetTurnServers(turn_servers)));
+ }
+
+ void SetTurnServers(const std::vector<NrIceTurnServer> servers) {
+ ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->SetTurnServers(servers)));
+ }
+
+ void SetFakeResolver(const std::string& ip, const std::string& fqdn) {
+ ASSERT_TRUE(NS_SUCCEEDED(dns_resolver_->Init()));
+ if (!ip.empty() && !fqdn.empty()) {
+ PRNetAddr addr;
+ PRStatus status = PR_StringToNetAddr(ip.c_str(), &addr);
+ addr.inet.port = kDefaultStunServerPort;
+ ASSERT_EQ(PR_SUCCESS, status);
+ fake_resolver_.SetAddr(fqdn, addr);
+ }
+ ASSERT_TRUE(
+ NS_SUCCEEDED(ice_ctx_->SetResolver(fake_resolver_.AllocateResolver())));
+ }
+
+ void SetDNSResolver() {
+ ASSERT_TRUE(NS_SUCCEEDED(dns_resolver_->Init()));
+ ASSERT_TRUE(
+ NS_SUCCEEDED(ice_ctx_->SetResolver(dns_resolver_->AllocateResolver())));
+ }
+
+ void Gather(bool default_route_only = false,
+ bool obfuscate_host_addresses = false) {
+ nsresult res;
+
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableRet(&res, ice_ctx_, &NrIceCtx::StartGathering,
+ default_route_only, obfuscate_host_addresses));
+
+ ASSERT_TRUE(NS_SUCCEEDED(res));
+ }
+
+ void SetCtxFlags(bool default_route_only) {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(ice_ctx_, &NrIceCtx::SetCtxFlags, default_route_only));
+ }
+
+ nsTArray<NrIceStunAddr> GetStunAddrs() { return ice_ctx_->GetStunAddrs(); }
+
+ void SetStunAddrs(const nsTArray<NrIceStunAddr>& addrs) {
+ ice_ctx_->SetStunAddrs(addrs);
+ }
+
+ void UseNat() { nat_->enabled_ = true; }
+
+ void SetTimerDivider(int div) { ice_ctx_->internal_SetTimerAccelarator(div); }
+
+ void SetStunResponseDelay(uint32_t delay) {
+ nat_->delay_stun_resp_ms_ = delay;
+ }
+
+ void SetFilteringType(TestNat::NatBehavior type) {
+ MOZ_ASSERT(!nat_->has_port_mappings());
+ nat_->filtering_type_ = type;
+ }
+
+ void SetMappingType(TestNat::NatBehavior type) {
+ MOZ_ASSERT(!nat_->has_port_mappings());
+ nat_->mapping_type_ = type;
+ }
+
+ void SetBlockUdp(bool block) {
+ MOZ_ASSERT(!nat_->has_port_mappings());
+ nat_->block_udp_ = block;
+ }
+
+ void SetBlockStun(bool block) { nat_->block_stun_ = block; }
+
+ // Get various pieces of state
+ std::vector<std::string> GetGlobalAttributes() {
+ std::vector<std::string> attrs(ice_ctx_->GetGlobalAttributes());
+ if (simulate_ice_lite_) {
+ attrs.push_back("ice-lite");
+ }
+ return attrs;
+ }
+
+ std::vector<std::string> GetAttributes(size_t stream) {
+ std::vector<std::string> v;
+
+ RUN_ON_THREAD(
+ test_utils_->sts_target(),
+ WrapRunnableRet(&v, this, &IceTestPeer::GetAttributes_s, stream));
+
+ return v;
+ }
+
+ std::string FilterCandidate(const std::string& candidate) {
+ if (candidate_filter_) {
+ return candidate_filter_(candidate);
+ }
+ return candidate;
+ }
+
+ std::vector<std::string> GetAttributes_s(size_t index) {
+ std::vector<std::string> attributes;
+
+ auto stream = GetStream_s(index);
+ if (!stream) {
+ EXPECT_TRUE(false) << "No such stream " << index;
+ return attributes;
+ }
+
+ std::vector<std::string> attributes_in = stream->GetAttributes();
+
+ for (const auto& attribute : attributes_in) {
+ if (attribute.find("candidate:") != std::string::npos) {
+ std::string candidate(FilterCandidate(attribute));
+ if (!candidate.empty()) {
+ std::cerr << name_ << " Returning candidate: " << candidate
+ << std::endl;
+ attributes.push_back(candidate);
+ }
+ } else {
+ attributes.push_back(attribute);
+ }
+ }
+
+ return attributes;
+ }
+
+ void SetExpectedTypes(NrIceCandidate::Type local, NrIceCandidate::Type remote,
+ std::string local_transport = kNrIceTransportUdp) {
+ expected_local_type_ = local;
+ expected_local_transport_ = local_transport;
+ expected_remote_type_ = remote;
+ }
+
+ void SetExpectedRemoteCandidateAddr(const std::string& addr) {
+ expected_remote_addr_ = addr;
+ }
+
+ int GetCandidatesPrivateIpv4Range(size_t stream) {
+ std::vector<std::string> attributes = GetAttributes(stream);
+
+ int host_net = 0;
+ for (const auto& a : attributes) {
+ if (a.find("typ host") != std::string::npos) {
+ nr_transport_addr addr;
+ std::vector<std::string> tokens = split(a, ' ');
+ int r = nr_str_port_to_transport_addr(tokens.at(4).c_str(), 0,
+ IPPROTO_UDP, &addr);
+ MOZ_ASSERT(!r);
+ if (!r && (addr.ip_version == NR_IPV4)) {
+ int n = nr_transport_addr_get_private_addr_range(&addr);
+ if (n) {
+ if (host_net) {
+ // TODO: add support for multiple private interfaces
+ std::cerr
+ << "This test doesn't support multiple private interfaces";
+ return -1;
+ }
+ host_net = n;
+ }
+ }
+ }
+ }
+ return host_net;
+ }
+
+ bool gathering_complete() { return gathering_complete_; }
+ int ready_ct() { return ready_ct_; }
+ bool is_ready_s(size_t index) {
+ auto media_stream = GetStream_s(index);
+ if (!media_stream) {
+ EXPECT_TRUE(false) << "No such stream " << index;
+ return false;
+ }
+ return media_stream->state() == NrIceMediaStream::ICE_OPEN;
+ }
+ bool is_ready(size_t stream) {
+ bool result;
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableRet(&result, this, &IceTestPeer::is_ready_s, stream));
+ return result;
+ }
+ bool ice_connected() { return ice_connected_; }
+ bool ice_failed() { return ice_failed_; }
+ bool ice_reached_checking() { return ice_reached_checking_; }
+ size_t received() { return received_; }
+ size_t sent() { return sent_; }
+
+ void RestartIce() {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &IceTestPeer::RestartIce_s));
+ }
+
+ void RestartIce_s() {
+ for (auto& stream : ice_ctx_->GetStreams()) {
+ SetIceCredentials_s(*stream);
+ }
+ // take care of some local bookkeeping
+ ready_ct_ = 0;
+ gathering_complete_ = false;
+ ice_connected_ = false;
+ ice_failed_ = false;
+ ice_reached_checking_ = false;
+ remote_ = nullptr;
+ }
+
+ void RollbackIceRestart() {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &IceTestPeer::RollbackIceRestart_s));
+ }
+
+ void RollbackIceRestart_s() {
+ for (auto& stream : ice_ctx_->GetStreams()) {
+ mIceCredentials[stream->GetId()] = mOldIceCredentials[stream->GetId()];
+ }
+ }
+
+ // Start connecting to another peer
+ void Connect_s(IceTestPeer* remote, TrickleMode trickle_mode,
+ bool start = true) {
+ nsresult res;
+
+ remote_ = remote;
+
+ trickle_mode_ = trickle_mode;
+ ice_connected_ = false;
+ ice_failed_ = false;
+ ice_reached_checking_ = false;
+ res = ice_ctx_->ParseGlobalAttributes(remote->GetGlobalAttributes());
+ ASSERT_FALSE(remote->simulate_ice_lite_ &&
+ (ice_ctx_->GetControlling() == NrIceCtx::ICE_CONTROLLED));
+ ASSERT_TRUE(NS_SUCCEEDED(res));
+
+ for (size_t i = 0; i < stream_counter_; ++i) {
+ auto aStream = GetStream_s(i);
+ if (aStream) {
+ std::vector<std::string> attributes = remote->GetAttributes(i);
+
+ for (auto it = attributes.begin(); it != attributes.end();) {
+ if (trickle_mode == TRICKLE_SIMULATE &&
+ it->find("candidate:") != std::string::npos) {
+ std::cerr << name_ << " Deferring remote candidate: " << *it
+ << std::endl;
+ attributes.erase(it);
+ } else {
+ std::cerr << name_ << " Adding remote attribute: " + *it
+ << std::endl;
+ ++it;
+ }
+ }
+ auto credentials = mIceCredentials[aStream->GetId()];
+ res = aStream->ConnectToPeer(credentials.first, credentials.second,
+ attributes);
+ ASSERT_TRUE(NS_SUCCEEDED(res));
+ }
+ }
+
+ if (start) {
+ ice_ctx_->SetControlling(offerer_ ? NrIceCtx::ICE_CONTROLLING
+ : NrIceCtx::ICE_CONTROLLED);
+ // Now start checks
+ res = ice_ctx_->StartChecks();
+ ASSERT_TRUE(NS_SUCCEEDED(res));
+ }
+ }
+
+ void Connect(IceTestPeer* remote, TrickleMode trickle_mode,
+ bool start = true) {
+ test_utils_->SyncDispatchToSTS(WrapRunnable(this, &IceTestPeer::Connect_s,
+ remote, trickle_mode, start));
+ }
+
+ void SimulateTrickle(size_t stream) {
+ std::cerr << name_ << " Doing trickle for stream " << stream << std::endl;
+ // If we are in trickle deferred mode, now trickle in the candidates
+ // for |stream|
+
+ std::vector<SchedulableTrickleCandidate*>& candidates =
+ ControlTrickle(stream);
+
+ for (auto& candidate : candidates) {
+ candidate->Schedule(0);
+ }
+ }
+
+ // Allows test case to completely control when/if candidates are trickled
+ // (test could also do things like insert extra trickle candidates, or
+ // change existing ones, or insert duplicates, really anything is fair game)
+ std::vector<SchedulableTrickleCandidate*>& ControlTrickle(size_t stream) {
+ std::cerr << "Doing controlled trickle for stream " << stream << std::endl;
+
+ std::vector<std::string> attributes = remote_->GetAttributes(stream);
+
+ for (const auto& attribute : attributes) {
+ if (attribute.find("candidate:") != std::string::npos) {
+ controlled_trickle_candidates_[stream].push_back(
+ new SchedulableTrickleCandidate(this, stream, attribute, "",
+ test_utils_));
+ }
+ }
+
+ return controlled_trickle_candidates_[stream];
+ }
+
+ nsresult TrickleCandidate_s(const std::string& candidate,
+ const std::string& ufrag, size_t index) {
+ auto stream = GetStream_s(index);
+ if (!stream) {
+ // stream might have gone away before the trickle timer popped
+ return NS_OK;
+ }
+ return stream->ParseTrickleCandidate(candidate, ufrag, "");
+ }
+
+ void DumpCandidate(std::string which, const NrIceCandidate& cand) {
+ std::string type;
+ std::string tcp_type;
+
+ std::string addr;
+ int port;
+
+ if (which.find("Remote") != std::string::npos) {
+ addr = cand.cand_addr.host;
+ port = cand.cand_addr.port;
+ } else {
+ addr = cand.local_addr.host;
+ port = cand.local_addr.port;
+ }
+ switch (cand.type) {
+ case NrIceCandidate::ICE_HOST:
+ type = "host";
+ break;
+ case NrIceCandidate::ICE_SERVER_REFLEXIVE:
+ type = "srflx";
+ break;
+ case NrIceCandidate::ICE_PEER_REFLEXIVE:
+ type = "prflx";
+ break;
+ case NrIceCandidate::ICE_RELAYED:
+ type = "relay";
+ if (which.find("Local") != std::string::npos) {
+ type += "(" + cand.local_addr.transport + ")";
+ }
+ break;
+ default:
+ FAIL();
+ };
+
+ switch (cand.tcp_type) {
+ case NrIceCandidate::ICE_NONE:
+ break;
+ case NrIceCandidate::ICE_ACTIVE:
+ tcp_type = " tcptype=active";
+ break;
+ case NrIceCandidate::ICE_PASSIVE:
+ tcp_type = " tcptype=passive";
+ break;
+ case NrIceCandidate::ICE_SO:
+ tcp_type = " tcptype=so";
+ break;
+ default:
+ FAIL();
+ };
+
+ std::cerr << which << " --> " << type << " " << addr << ":" << port << "/"
+ << cand.cand_addr.transport << tcp_type
+ << " codeword=" << cand.codeword << std::endl;
+ }
+
+ void DumpAndCheckActiveCandidates_s() {
+ std::cerr << name_ << " Active candidates:" << std::endl;
+ for (const auto& stream : ice_ctx_->GetStreams()) {
+ for (size_t j = 0; j < stream->components(); ++j) {
+ std::cerr << name_ << " Stream " << stream->GetId() << " component "
+ << j + 1 << std::endl;
+
+ UniquePtr<NrIceCandidate> local;
+ UniquePtr<NrIceCandidate> remote;
+
+ nsresult res = stream->GetActivePair(j + 1, &local, &remote);
+ if (res == NS_ERROR_NOT_AVAILABLE) {
+ std::cerr << "Component unpaired or disabled." << std::endl;
+ } else {
+ ASSERT_TRUE(NS_SUCCEEDED(res));
+ DumpCandidate("Local ", *local);
+ /* Depending on timing, and the whims of the network
+ * stack/configuration we're running on top of, prflx is always a
+ * possibility. */
+ if (expected_local_type_ == NrIceCandidate::ICE_HOST) {
+ ASSERT_NE(NrIceCandidate::ICE_SERVER_REFLEXIVE, local->type);
+ ASSERT_NE(NrIceCandidate::ICE_RELAYED, local->type);
+ } else {
+ ASSERT_EQ(expected_local_type_, local->type);
+ }
+ ASSERT_EQ(expected_local_transport_, local->local_addr.transport);
+ DumpCandidate("Remote ", *remote);
+ /* Depending on timing, and the whims of the network
+ * stack/configuration we're running on top of, prflx is always a
+ * possibility. */
+ if (expected_remote_type_ == NrIceCandidate::ICE_HOST) {
+ ASSERT_NE(NrIceCandidate::ICE_SERVER_REFLEXIVE, remote->type);
+ ASSERT_NE(NrIceCandidate::ICE_RELAYED, remote->type);
+ } else {
+ ASSERT_EQ(expected_remote_type_, remote->type);
+ }
+ if (!expected_remote_addr_.empty()) {
+ ASSERT_EQ(expected_remote_addr_, remote->cand_addr.host);
+ }
+ }
+ }
+ }
+ }
+
+ void DumpAndCheckActiveCandidates() {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &IceTestPeer::DumpAndCheckActiveCandidates_s));
+ }
+
+ void Close() {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(ice_ctx_, &NrIceCtx::destroy_peer_ctx));
+ }
+
+ void Shutdown() {
+ std::cerr << name_ << " Shutdown" << std::endl;
+ shutting_down_ = true;
+ for (auto& controlled_trickle_candidate : controlled_trickle_candidates_) {
+ for (auto& cand : controlled_trickle_candidate.second) {
+ delete cand;
+ }
+ }
+
+ ice_ctx_->Destroy();
+ ice_ctx_ = nullptr;
+
+ if (remote_) {
+ remote_->UnsetRemote();
+ remote_ = nullptr;
+ }
+ }
+
+ void UnsetRemote() { remote_ = nullptr; }
+
+ void StartChecks() {
+ nsresult res;
+
+ test_utils_->SyncDispatchToSTS(WrapRunnableRet(
+ &res, ice_ctx_, &NrIceCtx::SetControlling,
+ offerer_ ? NrIceCtx::ICE_CONTROLLING : NrIceCtx::ICE_CONTROLLED));
+ // Now start checks
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableRet(&res, ice_ctx_, &NrIceCtx::StartChecks));
+ ASSERT_TRUE(NS_SUCCEEDED(res));
+ }
+
+ // Handle events
+ void GatheringStateChange(NrIceCtx* ctx, NrIceCtx::GatheringState state) {
+ if (shutting_down_) {
+ return;
+ }
+ if (state != NrIceCtx::ICE_CTX_GATHER_COMPLETE) {
+ return;
+ }
+
+ std::cerr << name_ << " Gathering complete" << std::endl;
+ gathering_complete_ = true;
+
+ std::cerr << name_ << " ATTRIBUTES:" << std::endl;
+ for (const auto& stream : ice_ctx_->GetStreams()) {
+ std::cerr << "Stream " << stream->GetId() << std::endl;
+
+ std::vector<std::string> attributes = stream->GetAttributes();
+
+ for (const auto& attribute : attributes) {
+ std::cerr << attribute << std::endl;
+ }
+ }
+ std::cerr << std::endl;
+ }
+
+ void CandidateInitialized(NrIceMediaStream* stream,
+ const std::string& raw_candidate,
+ const std::string& ufrag,
+ const std::string& mdns_addr,
+ const std::string& actual_addr) {
+ std::string candidate(FilterCandidate(raw_candidate));
+ if (candidate.empty()) {
+ return;
+ }
+ std::cerr << "Candidate for stream " << stream->name()
+ << " initialized: " << candidate << std::endl;
+ candidates_[stream->name()].push_back(candidate);
+
+ // If we are connected, then try to trickle to the other side.
+ if (remote_ && remote_->remote_ && (trickle_mode_ != TRICKLE_SIMULATE)) {
+ // first, find the index of the stream we've been given so
+ // we can get the corresponding stream on the remote side
+ for (size_t i = 0; i < stream_counter_; ++i) {
+ if (GetStream_s(i) == stream) {
+ ASSERT_GT(remote_->stream_counter_, i);
+ nsresult res = remote_->GetStream_s(i)->ParseTrickleCandidate(
+ candidate, ufrag, "");
+ ASSERT_TRUE(NS_SUCCEEDED(res));
+ return;
+ }
+ }
+ ADD_FAILURE() << "No matching stream found for " << stream;
+ }
+ }
+
+ nsresult GetCandidatePairs_s(size_t stream_index,
+ std::vector<NrIceCandidatePair>* pairs) {
+ MOZ_ASSERT(pairs);
+ auto stream = GetStream_s(stream_index);
+ if (!stream) {
+ // Is there a better error for "no such index"?
+ ADD_FAILURE() << "No such media stream index: " << stream_index;
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return stream->GetCandidatePairs(pairs);
+ }
+
+ nsresult GetCandidatePairs(size_t stream_index,
+ std::vector<NrIceCandidatePair>* pairs) {
+ nsresult v;
+ test_utils_->SyncDispatchToSTS(WrapRunnableRet(
+ &v, this, &IceTestPeer::GetCandidatePairs_s, stream_index, pairs));
+ return v;
+ }
+
+ void DumpCandidatePair(const NrIceCandidatePair& pair) {
+ std::cerr << std::endl;
+ DumpCandidate("Local", pair.local);
+ DumpCandidate("Remote", pair.remote);
+ std::cerr << "state = " << pair.state << " priority = " << pair.priority
+ << " nominated = " << pair.nominated
+ << " selected = " << pair.selected
+ << " codeword = " << pair.codeword << std::endl;
+ }
+
+ void DumpCandidatePairs_s(NrIceMediaStream* stream) {
+ std::vector<NrIceCandidatePair> pairs;
+ nsresult res = stream->GetCandidatePairs(&pairs);
+ ASSERT_TRUE(NS_SUCCEEDED(res));
+
+ std::cerr << "Begin list of candidate pairs [" << std::endl;
+
+ for (auto& pair : pairs) {
+ DumpCandidatePair(pair);
+ }
+ std::cerr << "]" << std::endl;
+ }
+
+ void DumpCandidatePairs_s() {
+ std::cerr << "Dumping candidate pairs for all streams [" << std::endl;
+ for (const auto& stream : ice_ctx_->GetStreams()) {
+ DumpCandidatePairs_s(stream.get());
+ }
+ std::cerr << "]" << std::endl;
+ }
+
+ bool CandidatePairsPriorityDescending(
+ const std::vector<NrIceCandidatePair>& pairs) {
+ // Verify that priority is descending
+ uint64_t priority = std::numeric_limits<uint64_t>::max();
+
+ for (size_t p = 0; p < pairs.size(); ++p) {
+ if (priority < pairs[p].priority) {
+ std::cerr << "Priority increased in subsequent pairs:" << std::endl;
+ DumpCandidatePair(pairs[p - 1]);
+ DumpCandidatePair(pairs[p]);
+ return false;
+ }
+ if (priority == pairs[p].priority) {
+ if (!IceCandidatePairCompare()(pairs[p], pairs[p - 1]) &&
+ !IceCandidatePairCompare()(pairs[p - 1], pairs[p])) {
+ std::cerr << "Ignoring identical pair from trigger check"
+ << std::endl;
+ } else {
+ std::cerr << "Duplicate priority in subseqent pairs:" << std::endl;
+ DumpCandidatePair(pairs[p - 1]);
+ DumpCandidatePair(pairs[p]);
+ return false;
+ }
+ }
+ priority = pairs[p].priority;
+ }
+ return true;
+ }
+
+ void UpdateAndValidateCandidatePairs(
+ size_t stream_index, std::vector<NrIceCandidatePair>* new_pairs) {
+ std::vector<NrIceCandidatePair> old_pairs = *new_pairs;
+ GetCandidatePairs(stream_index, new_pairs);
+ ASSERT_TRUE(CandidatePairsPriorityDescending(*new_pairs))
+ << "New list of "
+ "candidate pairs is either not sorted in priority order, or has "
+ "duplicate priorities.";
+ ASSERT_TRUE(CandidatePairsPriorityDescending(old_pairs))
+ << "Old list of "
+ "candidate pairs is either not sorted in priority order, or has "
+ "duplicate priorities. This indicates some bug in the test case.";
+ std::vector<NrIceCandidatePair> added_pairs;
+ std::vector<NrIceCandidatePair> removed_pairs;
+
+ // set_difference computes the set of elements that are present in the
+ // first set, but not the second
+ // NrIceCandidatePair::operator< compares based on the priority, local
+ // candidate, and remote candidate in that order. This means this will
+ // catch cases where the priority has remained the same, but one of the
+ // candidates has changed.
+ std::set_difference((*new_pairs).begin(), (*new_pairs).end(),
+ old_pairs.begin(), old_pairs.end(),
+ std::inserter(added_pairs, added_pairs.begin()),
+ IceCandidatePairCompare());
+
+ std::set_difference(old_pairs.begin(), old_pairs.end(),
+ (*new_pairs).begin(), (*new_pairs).end(),
+ std::inserter(removed_pairs, removed_pairs.begin()),
+ IceCandidatePairCompare());
+
+ for (auto& added_pair : added_pairs) {
+ std::cerr << "Found new candidate pair." << std::endl;
+ DumpCandidatePair(added_pair);
+ }
+
+ for (auto& removed_pair : removed_pairs) {
+ std::cerr << "Pre-existing candidate pair is now missing:" << std::endl;
+ DumpCandidatePair(removed_pair);
+ }
+
+ ASSERT_TRUE(removed_pairs.empty())
+ << "At least one candidate pair has "
+ "gone missing.";
+ }
+
+ void StreamReady(NrIceMediaStream* stream) {
+ ++ready_ct_;
+ std::cerr << name_ << " Stream ready for " << stream->name()
+ << " ct=" << ready_ct_ << std::endl;
+ DumpCandidatePairs_s(stream);
+ }
+ void StreamFailed(NrIceMediaStream* stream) {
+ std::cerr << name_ << " Stream failed for " << stream->name()
+ << " ct=" << ready_ct_ << std::endl;
+ DumpCandidatePairs_s(stream);
+ }
+
+ void ConnectionStateChange(NrIceCtx* ctx, NrIceCtx::ConnectionState state) {
+ (void)ctx;
+ switch (state) {
+ case NrIceCtx::ICE_CTX_INIT:
+ break;
+ case NrIceCtx::ICE_CTX_CHECKING:
+ std::cerr << name_ << " ICE reached checking" << std::endl;
+ ice_reached_checking_ = true;
+ break;
+ case NrIceCtx::ICE_CTX_CONNECTED:
+ std::cerr << name_ << " ICE connected" << std::endl;
+ ice_connected_ = true;
+ break;
+ case NrIceCtx::ICE_CTX_COMPLETED:
+ std::cerr << name_ << " ICE completed" << std::endl;
+ break;
+ case NrIceCtx::ICE_CTX_FAILED:
+ std::cerr << name_ << " ICE failed" << std::endl;
+ ice_failed_ = true;
+ break;
+ case NrIceCtx::ICE_CTX_DISCONNECTED:
+ std::cerr << name_ << " ICE disconnected" << std::endl;
+ ice_connected_ = false;
+ break;
+ default:
+ MOZ_CRASH();
+ }
+ }
+
+ void PacketReceived(NrIceMediaStream* stream, int component,
+ const unsigned char* data, int len) {
+ std::cerr << name_ << ": received " << len << " bytes" << std::endl;
+ ++received_;
+ }
+
+ void SendPacket(int stream, int component, const unsigned char* data,
+ int len) {
+ auto media_stream = GetStream_s(stream);
+ if (!media_stream) {
+ ADD_FAILURE() << "No such stream " << stream;
+ return;
+ }
+
+ ASSERT_TRUE(NS_SUCCEEDED(media_stream->SendPacket(component, data, len)));
+
+ ++sent_;
+ std::cerr << name_ << ": sent " << len << " bytes" << std::endl;
+ }
+
+ void SendFailure(int stream, int component) {
+ auto media_stream = GetStream_s(stream);
+ if (!media_stream) {
+ ADD_FAILURE() << "No such stream " << stream;
+ return;
+ }
+
+ const std::string d("FAIL");
+ ASSERT_TRUE(NS_FAILED(media_stream->SendPacket(
+ component, reinterpret_cast<const unsigned char*>(d.c_str()),
+ d.length())));
+
+ std::cerr << name_ << ": send failed as expected" << std::endl;
+ }
+
+ void SetCandidateFilter(CandidateFilter filter) {
+ candidate_filter_ = filter;
+ }
+
+ void ParseCandidate_s(size_t i, const std::string& candidate,
+ const std::string& mdns_addr) {
+ auto media_stream = GetStream_s(i);
+ ASSERT_TRUE(media_stream.get())
+ << "No such stream " << i;
+ media_stream->ParseTrickleCandidate(candidate, "", mdns_addr);
+ }
+
+ void ParseCandidate(size_t i, const std::string& candidate,
+ const std::string& mdns_addr) {
+ test_utils_->SyncDispatchToSTS(WrapRunnable(
+ this, &IceTestPeer::ParseCandidate_s, i, candidate, mdns_addr));
+ }
+
+ void DisableComponent_s(size_t index, int component_id) {
+ ASSERT_LT(index, stream_counter_);
+ auto stream = GetStream_s(index);
+ ASSERT_TRUE(stream.get())
+ << "No such stream " << index;
+ nsresult res = stream->DisableComponent(component_id);
+ ASSERT_TRUE(NS_SUCCEEDED(res));
+ }
+
+ void DisableComponent(size_t stream, int component_id) {
+ test_utils_->SyncDispatchToSTS(WrapRunnable(
+ this, &IceTestPeer::DisableComponent_s, stream, component_id));
+ }
+
+ void AssertConsentRefresh_s(size_t index, int component_id,
+ ConsentStatus status) {
+ ASSERT_LT(index, stream_counter_);
+ auto stream = GetStream_s(index);
+ ASSERT_TRUE(stream.get())
+ << "No such stream " << index;
+ bool can_send;
+ struct timeval timestamp;
+ nsresult res =
+ stream->GetConsentStatus(component_id, &can_send, &timestamp);
+ ASSERT_TRUE(NS_SUCCEEDED(res));
+ if (status == CONSENT_EXPIRED) {
+ ASSERT_EQ(can_send, 0);
+ } else {
+ ASSERT_EQ(can_send, 1);
+ }
+ if (consent_timestamp_.tv_sec) {
+ if (status == CONSENT_FRESH) {
+ ASSERT_EQ(r_timeval_cmp(&timestamp, &consent_timestamp_), 1);
+ } else {
+ ASSERT_EQ(r_timeval_cmp(&timestamp, &consent_timestamp_), 0);
+ }
+ }
+ consent_timestamp_.tv_sec = timestamp.tv_sec;
+ consent_timestamp_.tv_usec = timestamp.tv_usec;
+ std::cerr << name_
+ << ": new consent timestamp = " << consent_timestamp_.tv_sec
+ << "." << consent_timestamp_.tv_usec << std::endl;
+ }
+
+ void AssertConsentRefresh(ConsentStatus status) {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &IceTestPeer::AssertConsentRefresh_s, 0, 1, status));
+ }
+
+ void ChangeNetworkState_s(bool online) {
+ ice_ctx_->UpdateNetworkState(online);
+ }
+
+ void ChangeNetworkStateToOffline() {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &IceTestPeer::ChangeNetworkState_s, false));
+ }
+
+ void ChangeNetworkStateToOnline() {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &IceTestPeer::ChangeNetworkState_s, true));
+ }
+
+ void SetControlling(NrIceCtx::Controlling controlling) {
+ nsresult res;
+ test_utils_->SyncDispatchToSTS(WrapRunnableRet(
+ &res, ice_ctx_, &NrIceCtx::SetControlling, controlling));
+ ASSERT_TRUE(NS_SUCCEEDED(res));
+ }
+
+ NrIceCtx::Controlling GetControlling() { return ice_ctx_->GetControlling(); }
+
+ void SetTiebreaker(uint64_t tiebreaker) {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &IceTestPeer::SetTiebreaker_s, tiebreaker));
+ }
+
+ void SetTiebreaker_s(uint64_t tiebreaker) {
+ ice_ctx_->peer()->tiebreaker = tiebreaker;
+ }
+
+ void SimulateIceLite() {
+ simulate_ice_lite_ = true;
+ SetControlling(NrIceCtx::ICE_CONTROLLED);
+ }
+
+ nsresult GetDefaultCandidate(unsigned int stream, NrIceCandidate* cand) {
+ nsresult rv;
+
+ test_utils_->SyncDispatchToSTS(WrapRunnableRet(
+ &rv, this, &IceTestPeer::GetDefaultCandidate_s, stream, cand));
+
+ return rv;
+ }
+
+ nsresult GetDefaultCandidate_s(unsigned int index, NrIceCandidate* cand) {
+ return GetStream_s(index)->GetDefaultCandidate(1, cand);
+ }
+
+ private:
+ std::string name_;
+ RefPtr<NrIceCtx> ice_ctx_;
+ bool offerer_;
+ std::map<std::string, std::vector<std::string>> candidates_;
+ // Maps from stream id to list of remote trickle candidates
+ std::map<size_t, std::vector<SchedulableTrickleCandidate*>>
+ controlled_trickle_candidates_;
+ std::map<std::string, std::pair<std::string, std::string>> mIceCredentials;
+ std::map<std::string, std::pair<std::string, std::string>> mOldIceCredentials;
+ size_t stream_counter_;
+ bool shutting_down_;
+ bool gathering_complete_;
+ int ready_ct_;
+ bool ice_connected_;
+ bool ice_failed_;
+ bool ice_reached_checking_;
+ size_t received_;
+ size_t sent_;
+ struct timeval consent_timestamp_;
+ NrIceResolverFake fake_resolver_;
+ RefPtr<NrIceResolver> dns_resolver_;
+ IceTestPeer* remote_;
+ CandidateFilter candidate_filter_;
+ NrIceCandidate::Type expected_local_type_;
+ std::string expected_local_transport_;
+ NrIceCandidate::Type expected_remote_type_;
+ std::string expected_remote_addr_;
+ TrickleMode trickle_mode_;
+ bool simulate_ice_lite_;
+ RefPtr<mozilla::TestNat> nat_;
+ MtransportTestUtils* test_utils_;
+};
+
+void SchedulableTrickleCandidate::Trickle() {
+ timer_handle_ = nullptr;
+ nsresult res = peer_->TrickleCandidate_s(candidate_, ufrag_, stream_);
+ ASSERT_TRUE(NS_SUCCEEDED(res));
+}
+
+class WebRtcIceGatherTest : public StunTest {
+ public:
+ void SetUp() override {
+ StunTest::SetUp();
+
+ Preferences::SetInt("media.peerconnection.ice.tcp_so_sock_count", 3);
+
+ test_utils_->SyncDispatchToSTS(WrapRunnable(
+ TestStunServer::GetInstance(AF_INET), &TestStunServer::Reset));
+ if (TestStunServer::GetInstance(AF_INET6)) {
+ test_utils_->SyncDispatchToSTS(WrapRunnable(
+ TestStunServer::GetInstance(AF_INET6), &TestStunServer::Reset));
+ }
+ }
+
+ void TearDown() override {
+ peer_ = nullptr;
+ StunTest::TearDown();
+ }
+
+ void EnsurePeer() {
+ if (!peer_) {
+ peer_ =
+ MakeUnique<IceTestPeer>("P1", test_utils_, true, NrIceCtx::Config());
+ }
+ }
+
+ void Gather(unsigned int waitTime = kDefaultTimeout,
+ bool default_route_only = false,
+ bool obfuscate_host_addresses = false) {
+ EnsurePeer();
+ peer_->Gather(default_route_only, obfuscate_host_addresses);
+
+ if (waitTime) {
+ WaitForGather(waitTime);
+ }
+ }
+
+ void WaitForGather(unsigned int waitTime = kDefaultTimeout) {
+ ASSERT_TRUE_WAIT(peer_->gathering_complete(), waitTime);
+ }
+
+ void AddStunServerWithResponse(const std::string& fake_addr,
+ uint16_t fake_port, const std::string& fqdn,
+ const std::string& proto,
+ std::vector<NrIceStunServer>* stun_servers) {
+ int family;
+ if (fake_addr.find(':') != std::string::npos) {
+ family = AF_INET6;
+ } else {
+ family = AF_INET;
+ }
+
+ std::string stun_addr;
+ uint16_t stun_port;
+ if (proto == kNrIceTransportUdp) {
+ TestStunServer::GetInstance(family)->SetResponseAddr(fake_addr,
+ fake_port);
+ stun_addr = TestStunServer::GetInstance(family)->addr();
+ stun_port = TestStunServer::GetInstance(family)->port();
+ } else if (proto == kNrIceTransportTcp) {
+ TestStunTcpServer::GetInstance(family)->SetResponseAddr(fake_addr,
+ fake_port);
+ stun_addr = TestStunTcpServer::GetInstance(family)->addr();
+ stun_port = TestStunTcpServer::GetInstance(family)->port();
+ } else {
+ MOZ_CRASH();
+ }
+
+ if (!fqdn.empty()) {
+ peer_->SetFakeResolver(stun_addr, fqdn);
+ stun_addr = fqdn;
+ }
+
+ stun_servers->push_back(
+ *NrIceStunServer::Create(stun_addr, stun_port, proto.c_str()));
+
+ if (family == AF_INET6 && !fqdn.empty()) {
+ stun_servers->back().SetUseIPv6IfFqdn();
+ }
+ }
+
+ void UseFakeStunUdpServerWithResponse(
+ const std::string& fake_addr, uint16_t fake_port,
+ const std::string& fqdn = std::string()) {
+ EnsurePeer();
+ std::vector<NrIceStunServer> stun_servers;
+ AddStunServerWithResponse(fake_addr, fake_port, fqdn, "udp", &stun_servers);
+ peer_->SetStunServers(stun_servers);
+ }
+
+ void UseFakeStunTcpServerWithResponse(
+ const std::string& fake_addr, uint16_t fake_port,
+ const std::string& fqdn = std::string()) {
+ EnsurePeer();
+ std::vector<NrIceStunServer> stun_servers;
+ AddStunServerWithResponse(fake_addr, fake_port, fqdn, "tcp", &stun_servers);
+ peer_->SetStunServers(stun_servers);
+ }
+
+ void UseFakeStunUdpTcpServersWithResponse(const std::string& fake_udp_addr,
+ uint16_t fake_udp_port,
+ const std::string& fake_tcp_addr,
+ uint16_t fake_tcp_port) {
+ EnsurePeer();
+ std::vector<NrIceStunServer> stun_servers;
+ AddStunServerWithResponse(fake_udp_addr, fake_udp_port,
+ "", // no fqdn
+ "udp", &stun_servers);
+ AddStunServerWithResponse(fake_tcp_addr, fake_tcp_port,
+ "", // no fqdn
+ "tcp", &stun_servers);
+
+ peer_->SetStunServers(stun_servers);
+ }
+
+ void UseTestStunServer() {
+ TestStunServer::GetInstance(AF_INET)->Reset();
+ peer_->SetStunServer(TestStunServer::GetInstance(AF_INET)->addr(),
+ TestStunServer::GetInstance(AF_INET)->port());
+ }
+
+ // NB: Only does substring matching, watch out for stuff like "1.2.3.4"
+ // matching "21.2.3.47". " 1.2.3.4 " should not have false positives.
+ bool StreamHasMatchingCandidate(unsigned int stream, const std::string& match,
+ const std::string& match2 = "") {
+ std::vector<std::string> attributes = peer_->GetAttributes(stream);
+ for (auto& attribute : attributes) {
+ if (std::string::npos != attribute.find(match)) {
+ if (!match2.length() || std::string::npos != attribute.find(match2)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ void DumpAttributes(unsigned int stream) {
+ std::vector<std::string> attributes = peer_->GetAttributes(stream);
+
+ std::cerr << "Attributes for stream " << stream << "->" << attributes.size()
+ << std::endl;
+
+ for (const auto& a : attributes) {
+ std::cerr << "Attribute: " << a << std::endl;
+ }
+ }
+
+ protected:
+ mozilla::UniquePtr<IceTestPeer> peer_;
+};
+
+class WebRtcIceConnectTest : public StunTest {
+ public:
+ WebRtcIceConnectTest()
+ : initted_(false),
+ test_stun_server_inited_(false),
+ use_nat_(false),
+ filtering_type_(TestNat::ENDPOINT_INDEPENDENT),
+ mapping_type_(TestNat::ENDPOINT_INDEPENDENT),
+ block_udp_(false) {}
+
+ void SetUp() override {
+ StunTest::SetUp();
+
+ nsresult rv;
+ target_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ }
+
+ void TearDown() override {
+ p1_ = nullptr;
+ p2_ = nullptr;
+
+ StunTest::TearDown();
+ }
+
+ void AddStream(int components) {
+ Init();
+ p1_->AddStream(components);
+ p2_->AddStream(components);
+ }
+
+ void RemoveStream(size_t index) {
+ p1_->RemoveStream(index);
+ p2_->RemoveStream(index);
+ }
+
+ void Init(bool setup_stun_servers = true,
+ NrIceCtx::Policy ice_policy = NrIceCtx::ICE_POLICY_ALL) {
+ if (initted_) {
+ return;
+ }
+
+ NrIceCtx::Config config;
+ config.mPolicy = ice_policy;
+
+ p1_ = MakeUnique<IceTestPeer>("P1", test_utils_, true, config);
+ p2_ = MakeUnique<IceTestPeer>("P2", test_utils_, false, config);
+ InitPeer(p1_.get(), setup_stun_servers);
+ InitPeer(p2_.get(), setup_stun_servers);
+
+ initted_ = true;
+ }
+
+ void InitPeer(IceTestPeer* peer, bool setup_stun_servers = true) {
+ if (use_nat_) {
+ // If we enable nat simulation, but still use a real STUN server somewhere
+ // on the internet, we will see failures if there is a real NAT in
+ // addition to our simulated one, particularly if it disallows
+ // hairpinning.
+ if (setup_stun_servers) {
+ InitTestStunServer();
+ peer->UseTestStunServer();
+ }
+ peer->UseNat();
+ peer->SetFilteringType(filtering_type_);
+ peer->SetMappingType(mapping_type_);
+ peer->SetBlockUdp(block_udp_);
+ } else if (setup_stun_servers) {
+ std::vector<NrIceStunServer> stun_servers;
+
+ stun_servers.push_back(*NrIceStunServer::Create(
+ stun_server_address_, kDefaultStunServerPort, kNrIceTransportUdp));
+
+ peer->SetStunServers(stun_servers);
+ }
+ }
+
+ bool Gather(unsigned int waitTime = kDefaultTimeout,
+ bool default_route_only = false) {
+ Init();
+
+ return GatherCallerAndCallee(p1_.get(), p2_.get(), waitTime,
+ default_route_only);
+ }
+
+ bool GatherCallerAndCallee(IceTestPeer* caller, IceTestPeer* callee,
+ unsigned int waitTime = kDefaultTimeout,
+ bool default_route_only = false) {
+ caller->Gather(default_route_only);
+ callee->Gather(default_route_only);
+
+ if (waitTime) {
+ EXPECT_TRUE_WAIT(caller->gathering_complete(), waitTime);
+ if (!caller->gathering_complete()) return false;
+ EXPECT_TRUE_WAIT(callee->gathering_complete(), waitTime);
+ if (!callee->gathering_complete()) return false;
+ }
+ return true;
+ }
+
+ void UseNat() {
+ // to be useful, this method should be called before Init
+ ASSERT_FALSE(initted_);
+ use_nat_ = true;
+ }
+
+ void SetFilteringType(TestNat::NatBehavior type) {
+ // to be useful, this method should be called before Init
+ ASSERT_FALSE(initted_);
+ filtering_type_ = type;
+ }
+
+ void SetMappingType(TestNat::NatBehavior type) {
+ // to be useful, this method should be called before Init
+ ASSERT_FALSE(initted_);
+ mapping_type_ = type;
+ }
+
+ void BlockUdp() {
+ // note: |block_udp_| is used only in InitPeer.
+ // Use IceTestPeer::SetBlockUdp to act on the peer directly.
+ block_udp_ = true;
+ }
+
+ void SetupAndCheckConsent() {
+ p1_->SetTimerDivider(10);
+ p2_->SetTimerDivider(10);
+ ASSERT_TRUE(Gather());
+ Connect();
+ p1_->AssertConsentRefresh(CONSENT_FRESH);
+ p2_->AssertConsentRefresh(CONSENT_FRESH);
+ SendReceive();
+ }
+
+ void AssertConsentRefresh(ConsentStatus status = CONSENT_FRESH) {
+ p1_->AssertConsentRefresh(status);
+ p2_->AssertConsentRefresh(status);
+ }
+
+ void InitTestStunServer() {
+ if (test_stun_server_inited_) {
+ return;
+ }
+
+ std::cerr << "Resetting TestStunServer" << std::endl;
+ TestStunServer::GetInstance(AF_INET)->Reset();
+ test_stun_server_inited_ = true;
+ }
+
+ void UseTestStunServer() {
+ InitTestStunServer();
+ p1_->UseTestStunServer();
+ p2_->UseTestStunServer();
+ }
+
+ void SetTurnServer(const std::string addr, uint16_t port,
+ const std::string username, const std::string password,
+ const char* transport = kNrIceTransportUdp) {
+ p1_->SetTurnServer(addr, port, username, password, transport);
+ p2_->SetTurnServer(addr, port, username, password, transport);
+ }
+
+ void SetTurnServers(const std::vector<NrIceTurnServer>& servers) {
+ p1_->SetTurnServers(servers);
+ p2_->SetTurnServers(servers);
+ }
+
+ void SetCandidateFilter(CandidateFilter filter, bool both = true) {
+ p1_->SetCandidateFilter(filter);
+ if (both) {
+ p2_->SetCandidateFilter(filter);
+ }
+ }
+
+ void Connect() { ConnectCallerAndCallee(p1_.get(), p2_.get()); }
+
+ void ConnectCallerAndCallee(IceTestPeer* caller, IceTestPeer* callee,
+ TrickleMode mode = TRICKLE_NONE) {
+ ASSERT_TRUE(caller->ready_ct() == 0);
+ ASSERT_TRUE(caller->ice_connected() == 0);
+ ASSERT_TRUE(caller->ice_reached_checking() == 0);
+ ASSERT_TRUE(callee->ready_ct() == 0);
+ ASSERT_TRUE(callee->ice_connected() == 0);
+ ASSERT_TRUE(callee->ice_reached_checking() == 0);
+
+ // IceTestPeer::Connect grabs attributes from the first arg, and
+ // gives them to |this|, meaning that callee->Connect(caller, ...)
+ // simulates caller sending an offer to callee. Order matters here
+ // because it determines which peer is controlling.
+ callee->Connect(caller, mode);
+ caller->Connect(callee, mode);
+
+ if (mode != TRICKLE_SIMULATE) {
+ ASSERT_TRUE_WAIT(caller->ice_connected() && callee->ice_connected(),
+ kDefaultTimeout);
+ ASSERT_TRUE(caller->ready_ct() >= 1 && callee->ready_ct() >= 1);
+ ASSERT_TRUE(caller->ice_reached_checking());
+ ASSERT_TRUE(callee->ice_reached_checking());
+
+ caller->DumpAndCheckActiveCandidates();
+ callee->DumpAndCheckActiveCandidates();
+ }
+ }
+
+ void SetExpectedTypes(NrIceCandidate::Type local, NrIceCandidate::Type remote,
+ std::string transport = kNrIceTransportUdp) {
+ p1_->SetExpectedTypes(local, remote, transport);
+ p2_->SetExpectedTypes(local, remote, transport);
+ }
+
+ void SetExpectedRemoteCandidateAddr(const std::string& addr) {
+ p1_->SetExpectedRemoteCandidateAddr(addr);
+ p2_->SetExpectedRemoteCandidateAddr(addr);
+ }
+
+ void ConnectP1(TrickleMode mode = TRICKLE_NONE) {
+ p1_->Connect(p2_.get(), mode);
+ }
+
+ void ConnectP2(TrickleMode mode = TRICKLE_NONE) {
+ p2_->Connect(p1_.get(), mode);
+ }
+
+ void WaitForConnectedStreams(int expected_streams = 1) {
+ ASSERT_TRUE_WAIT(p1_->ready_ct() == expected_streams &&
+ p2_->ready_ct() == expected_streams,
+ kDefaultTimeout);
+ ASSERT_TRUE_WAIT(p1_->ice_connected() && p2_->ice_connected(),
+ kDefaultTimeout);
+ }
+
+ void AssertCheckingReached() {
+ ASSERT_TRUE(p1_->ice_reached_checking());
+ ASSERT_TRUE(p2_->ice_reached_checking());
+ }
+
+ void WaitForConnected(unsigned int timeout = kDefaultTimeout) {
+ ASSERT_TRUE_WAIT(p1_->ice_connected(), timeout);
+ ASSERT_TRUE_WAIT(p2_->ice_connected(), timeout);
+ }
+
+ void WaitForGather() {
+ ASSERT_TRUE_WAIT(p1_->gathering_complete(), kDefaultTimeout);
+ ASSERT_TRUE_WAIT(p2_->gathering_complete(), kDefaultTimeout);
+ }
+
+ void WaitForDisconnected(unsigned int timeout = kDefaultTimeout) {
+ ASSERT_TRUE(p1_->ice_connected());
+ ASSERT_TRUE(p2_->ice_connected());
+ ASSERT_TRUE_WAIT(p1_->ice_connected() == 0 && p2_->ice_connected() == 0,
+ timeout);
+ }
+
+ void WaitForFailed(unsigned int timeout = kDefaultTimeout) {
+ ASSERT_TRUE_WAIT(p1_->ice_failed() && p2_->ice_failed(), timeout);
+ }
+
+ void ConnectTrickle(TrickleMode trickle = TRICKLE_SIMULATE) {
+ p2_->Connect(p1_.get(), trickle);
+ p1_->Connect(p2_.get(), trickle);
+ }
+
+ void SimulateTrickle(size_t stream) {
+ p1_->SimulateTrickle(stream);
+ p2_->SimulateTrickle(stream);
+ ASSERT_TRUE_WAIT(p1_->is_ready(stream), kDefaultTimeout);
+ ASSERT_TRUE_WAIT(p2_->is_ready(stream), kDefaultTimeout);
+ }
+
+ void SimulateTrickleP1(size_t stream) { p1_->SimulateTrickle(stream); }
+
+ void SimulateTrickleP2(size_t stream) { p2_->SimulateTrickle(stream); }
+
+ void CloseP1() { p1_->Close(); }
+
+ void ConnectThenDelete() {
+ p2_->Connect(p1_.get(), TRICKLE_NONE, false);
+ p1_->Connect(p2_.get(), TRICKLE_NONE, true);
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &WebRtcIceConnectTest::CloseP1));
+ p2_->StartChecks();
+
+ // Wait to see if we crash
+ PR_Sleep(PR_MillisecondsToInterval(kDefaultTimeout));
+ }
+
+ // default is p1_ sending to p2_
+ void SendReceive() { SendReceive(p1_.get(), p2_.get()); }
+
+ void SendReceive(IceTestPeer* p1, IceTestPeer* p2,
+ bool expect_tx_failure = false,
+ bool expect_rx_failure = false) {
+ size_t previousSent = p1->sent();
+ size_t previousReceived = p2->received();
+
+ if (expect_tx_failure) {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(p1, &IceTestPeer::SendFailure, 0, 1));
+ ASSERT_EQ(previousSent, p1->sent());
+ } else {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(p1, &IceTestPeer::SendPacket, 0, 1,
+ reinterpret_cast<const unsigned char*>("TEST"), 4));
+ ASSERT_EQ(previousSent + 1, p1->sent());
+ }
+ if (expect_rx_failure) {
+ usleep(1000);
+ ASSERT_EQ(previousReceived, p2->received());
+ } else {
+ ASSERT_TRUE_WAIT(p2->received() == previousReceived + 1, 1000);
+ }
+ }
+
+ void SendFailure() {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(p1_.get(), &IceTestPeer::SendFailure, 0, 1));
+ }
+
+ protected:
+ bool initted_;
+ bool test_stun_server_inited_;
+ nsCOMPtr<nsIEventTarget> target_;
+ mozilla::UniquePtr<IceTestPeer> p1_;
+ mozilla::UniquePtr<IceTestPeer> p2_;
+ bool use_nat_;
+ TestNat::NatBehavior filtering_type_;
+ TestNat::NatBehavior mapping_type_;
+ bool block_udp_;
+};
+
+class WebRtcIcePrioritizerTest : public StunTest {
+ public:
+ WebRtcIcePrioritizerTest() : prioritizer_(nullptr) {}
+
+ ~WebRtcIcePrioritizerTest() {
+ if (prioritizer_) {
+ nr_interface_prioritizer_destroy(&prioritizer_);
+ }
+ }
+
+ void SetPriorizer(nr_interface_prioritizer* prioritizer) {
+ prioritizer_ = prioritizer;
+ }
+
+ void AddInterface(const std::string& num, int type, int estimated_speed) {
+ std::string str_addr = "10.0.0." + num;
+ std::string ifname = "eth" + num;
+ nr_local_addr local_addr;
+ local_addr.interface.type = type;
+ local_addr.interface.estimated_speed = estimated_speed;
+
+ int r = nr_str_port_to_transport_addr(str_addr.c_str(), 0, IPPROTO_UDP,
+ &(local_addr.addr));
+ ASSERT_EQ(0, r);
+ strncpy(local_addr.addr.ifname, ifname.c_str(), MAXIFNAME - 1);
+ local_addr.addr.ifname[MAXIFNAME - 1] = '\0';
+
+ r = nr_interface_prioritizer_add_interface(prioritizer_, &local_addr);
+ ASSERT_EQ(0, r);
+ r = nr_interface_prioritizer_sort_preference(prioritizer_);
+ ASSERT_EQ(0, r);
+ }
+
+ void HasLowerPreference(const std::string& num1, const std::string& num2) {
+ std::string key1 = "eth" + num1 + ":10.0.0." + num1;
+ std::string key2 = "eth" + num2 + ":10.0.0." + num2;
+ UCHAR pref1, pref2;
+ int r = nr_interface_prioritizer_get_priority(prioritizer_, key1.c_str(),
+ &pref1);
+ ASSERT_EQ(0, r);
+ r = nr_interface_prioritizer_get_priority(prioritizer_, key2.c_str(),
+ &pref2);
+ ASSERT_EQ(0, r);
+ ASSERT_LE(pref1, pref2);
+ }
+
+ private:
+ nr_interface_prioritizer* prioritizer_;
+};
+
+class WebRtcIcePacketFilterTest : public StunTest {
+ public:
+ WebRtcIcePacketFilterTest() : udp_filter_(nullptr), tcp_filter_(nullptr) {}
+
+ void SetUp() {
+ StunTest::SetUp();
+
+ NrIceCtx::InitializeGlobals(NrIceCtx::GlobalConfig());
+
+ // Set up enough of the ICE ctx to allow the packet filter to work
+ ice_ctx_ = NrIceCtx::Create("test");
+
+ nsCOMPtr<nsISocketFilterHandler> udp_handler =
+ do_GetService(NS_STUN_UDP_SOCKET_FILTER_HANDLER_CONTRACTID);
+ ASSERT_TRUE(udp_handler);
+ udp_handler->NewFilter(getter_AddRefs(udp_filter_));
+
+ nsCOMPtr<nsISocketFilterHandler> tcp_handler =
+ do_GetService(NS_STUN_TCP_SOCKET_FILTER_HANDLER_CONTRACTID);
+ ASSERT_TRUE(tcp_handler);
+ tcp_handler->NewFilter(getter_AddRefs(tcp_filter_));
+ }
+
+ void TearDown() {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &WebRtcIcePacketFilterTest::TearDown_s));
+ StunTest::TearDown();
+ }
+
+ void TearDown_s() { ice_ctx_ = nullptr; }
+
+ void TestIncoming(const uint8_t* data, uint32_t len, uint8_t from_addr,
+ int from_port, bool expected_result) {
+ mozilla::net::NetAddr addr;
+ MakeNetAddr(&addr, from_addr, from_port);
+ bool result;
+ nsresult rv = udp_filter_->FilterPacket(
+ &addr, data, len, nsISocketFilter::SF_INCOMING, &result);
+ ASSERT_EQ(NS_OK, rv);
+ ASSERT_EQ(expected_result, result);
+ }
+
+ void TestIncomingTcp(const uint8_t* data, uint32_t len,
+ bool expected_result) {
+ mozilla::net::NetAddr addr;
+ bool result;
+ nsresult rv = tcp_filter_->FilterPacket(
+ &addr, data, len, nsISocketFilter::SF_INCOMING, &result);
+ ASSERT_EQ(NS_OK, rv);
+ ASSERT_EQ(expected_result, result);
+ }
+
+ void TestIncomingTcpFramed(const uint8_t* data, uint32_t len,
+ bool expected_result) {
+ mozilla::net::NetAddr addr;
+ bool result;
+ uint8_t* framed_data = new uint8_t[len + 2];
+ framed_data[0] = htons(len);
+ memcpy(&framed_data[2], data, len);
+ nsresult rv = tcp_filter_->FilterPacket(
+ &addr, framed_data, len + 2, nsISocketFilter::SF_INCOMING, &result);
+ ASSERT_EQ(NS_OK, rv);
+ ASSERT_EQ(expected_result, result);
+ delete[] framed_data;
+ }
+
+ void TestOutgoing(const uint8_t* data, uint32_t len, uint8_t to_addr,
+ int to_port, bool expected_result) {
+ mozilla::net::NetAddr addr;
+ MakeNetAddr(&addr, to_addr, to_port);
+ bool result;
+ nsresult rv = udp_filter_->FilterPacket(
+ &addr, data, len, nsISocketFilter::SF_OUTGOING, &result);
+ ASSERT_EQ(NS_OK, rv);
+ ASSERT_EQ(expected_result, result);
+ }
+
+ void TestOutgoingTcp(const uint8_t* data, uint32_t len,
+ bool expected_result) {
+ mozilla::net::NetAddr addr;
+ bool result;
+ nsresult rv = tcp_filter_->FilterPacket(
+ &addr, data, len, nsISocketFilter::SF_OUTGOING, &result);
+ ASSERT_EQ(NS_OK, rv);
+ ASSERT_EQ(expected_result, result);
+ }
+
+ void TestOutgoingTcpFramed(const uint8_t* data, uint32_t len,
+ bool expected_result) {
+ mozilla::net::NetAddr addr;
+ bool result;
+ uint8_t* framed_data = new uint8_t[len + 2];
+ framed_data[0] = htons(len);
+ memcpy(&framed_data[2], data, len);
+ nsresult rv = tcp_filter_->FilterPacket(
+ &addr, framed_data, len + 2, nsISocketFilter::SF_OUTGOING, &result);
+ ASSERT_EQ(NS_OK, rv);
+ ASSERT_EQ(expected_result, result);
+ delete[] framed_data;
+ }
+
+ private:
+ void MakeNetAddr(mozilla::net::NetAddr* net_addr, uint8_t last_digit,
+ uint16_t port) {
+ net_addr->inet.family = AF_INET;
+ net_addr->inet.ip = 192 << 24 | 168 << 16 | 1 << 8 | last_digit;
+ net_addr->inet.port = port;
+ }
+
+ nsCOMPtr<nsISocketFilter> udp_filter_;
+ nsCOMPtr<nsISocketFilter> tcp_filter_;
+ RefPtr<NrIceCtx> ice_ctx_;
+};
+} // end namespace
+
+TEST_F(WebRtcIceGatherTest, TestGatherFakeStunServerHostnameNoResolver) {
+ if (stun_server_hostname_.empty()) {
+ return;
+ }
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ peer_->SetStunServer(stun_server_hostname_, kDefaultStunServerPort);
+ peer_->AddStream(1);
+ Gather();
+}
+
+// Disabled because google isn't running any TCP stun servers right now
+TEST_F(WebRtcIceGatherTest,
+ DISABLED_TestGatherFakeStunServerTcpHostnameNoResolver) {
+ if (stun_server_hostname_.empty()) {
+ return;
+ }
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = true;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ peer_->SetStunServer(stun_server_hostname_, kDefaultStunServerPort,
+ kNrIceTransportTcp);
+ peer_->AddStream(1);
+ Gather();
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, " TCP "));
+}
+
+TEST_F(WebRtcIceGatherTest, TestGatherFakeStunServerIpAddress) {
+ if (stun_server_address_.empty()) {
+ return;
+ }
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ peer_->SetStunServer(stun_server_address_, kDefaultStunServerPort);
+ peer_->SetFakeResolver(stun_server_address_, stun_server_hostname_);
+ peer_->AddStream(1);
+ Gather();
+}
+
+TEST_F(WebRtcIceGatherTest, TestGatherStunServerIpAddressNoHost) {
+ if (stun_server_address_.empty()) {
+ return;
+ }
+
+ {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ }
+
+ NrIceCtx::Config config;
+ config.mPolicy = NrIceCtx::ICE_POLICY_NO_HOST;
+ peer_ = MakeUnique<IceTestPeer>("P1", test_utils_, true, config);
+ peer_->AddStream(1);
+ peer_->SetStunServer(stun_server_address_, kDefaultStunServerPort);
+ peer_->SetFakeResolver(stun_server_address_, stun_server_hostname_);
+ Gather();
+ ASSERT_FALSE(StreamHasMatchingCandidate(0, " host "));
+}
+
+TEST_F(WebRtcIceGatherTest, TestGatherFakeStunServerHostname) {
+ if (stun_server_hostname_.empty()) {
+ return;
+ }
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ peer_->SetStunServer(stun_server_hostname_, kDefaultStunServerPort);
+ peer_->SetFakeResolver(stun_server_address_, stun_server_hostname_);
+ peer_->AddStream(1);
+ Gather();
+}
+
+TEST_F(WebRtcIceGatherTest, TestGatherFakeStunBogusHostname) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ peer_->SetStunServer(kBogusStunServerHostname, kDefaultStunServerPort);
+ peer_->SetFakeResolver(stun_server_address_, stun_server_hostname_);
+ peer_->AddStream(1);
+ Gather();
+}
+
+TEST_F(WebRtcIceGatherTest, TestGatherDNSStunServerIpAddress) {
+ if (stun_server_address_.empty()) {
+ return;
+ }
+
+ {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ }
+
+ // A srflx candidate is considered redundant and discarded if its address
+ // equals that of a host candidate. (Frequently, a srflx candidate and a host
+ // candidate have equal addresses when the agent is not behind a NAT.) So set
+ // ICE_POLICY_NO_HOST here to ensure that a srflx candidate is not falsely
+ // discarded in this test.
+ NrIceCtx::Config config;
+ config.mPolicy = NrIceCtx::ICE_POLICY_NO_HOST;
+ peer_ = MakeUnique<IceTestPeer>("P1", test_utils_, true, config);
+
+ peer_->SetStunServer(stun_server_address_, kDefaultStunServerPort);
+ peer_->SetDNSResolver();
+ peer_->AddStream(1);
+ Gather();
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, " UDP "));
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, "typ srflx raddr"));
+}
+
+// Disabled because google isn't running any TCP stun servers right now
+TEST_F(WebRtcIceGatherTest, DISABLED_TestGatherDNSStunServerIpAddressTcp) {
+ if (stun_server_address_.empty()) {
+ return;
+ }
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = true;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ peer_->SetStunServer(stun_server_address_, kDefaultStunServerPort,
+ kNrIceTransportTcp);
+ peer_->SetDNSResolver();
+ peer_->AddStream(1);
+ Gather();
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, "tcptype passive"));
+ ASSERT_FALSE(StreamHasMatchingCandidate(0, "tcptype passive", " 9 "));
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, "tcptype so"));
+ ASSERT_FALSE(StreamHasMatchingCandidate(0, "tcptype so", " 9 "));
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, "tcptype active", " 9 "));
+}
+
+TEST_F(WebRtcIceGatherTest, TestGatherDNSStunServerHostname) {
+ if (stun_server_hostname_.empty()) {
+ return;
+ }
+
+ {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ }
+
+ // A srflx candidate is considered redundant and discarded if its address
+ // equals that of a host candidate. (Frequently, a srflx candidate and a host
+ // candidate have equal addresses when the agent is not behind a NAT.) So set
+ // ICE_POLICY_NO_HOST here to ensure that a srflx candidate is not falsely
+ // discarded in this test.
+ NrIceCtx::Config config;
+ config.mPolicy = NrIceCtx::ICE_POLICY_NO_HOST;
+ peer_ = MakeUnique<IceTestPeer>("P1", test_utils_, true, config);
+
+ peer_->SetStunServer(stun_server_hostname_, kDefaultStunServerPort);
+ peer_->SetDNSResolver();
+ peer_->AddStream(1);
+ Gather();
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, " UDP "));
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, "typ srflx raddr"));
+}
+
+// Disabled because google isn't running any TCP stun servers right now
+TEST_F(WebRtcIceGatherTest, DISABLED_TestGatherDNSStunServerHostnameTcp) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = true;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ peer_->SetStunServer(stun_server_hostname_, kDefaultStunServerPort,
+ kNrIceTransportTcp);
+ peer_->SetDNSResolver();
+ peer_->AddStream(1);
+ Gather();
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, "tcptype passive"));
+ ASSERT_FALSE(StreamHasMatchingCandidate(0, "tcptype passive", " 9 "));
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, "tcptype so"));
+ ASSERT_FALSE(StreamHasMatchingCandidate(0, "tcptype so", " 9 "));
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, "tcptype active", " 9 "));
+}
+
+// Disabled because google isn't running any TCP stun servers right now
+TEST_F(WebRtcIceGatherTest,
+ DISABLED_TestGatherDNSStunServerHostnameBothUdpTcp) {
+ if (stun_server_hostname_.empty()) {
+ return;
+ }
+
+ std::vector<NrIceStunServer> stun_servers;
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = true;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ stun_servers.push_back(*NrIceStunServer::Create(
+ stun_server_hostname_, kDefaultStunServerPort, kNrIceTransportUdp));
+ stun_servers.push_back(*NrIceStunServer::Create(
+ stun_server_hostname_, kDefaultStunServerPort, kNrIceTransportTcp));
+ peer_->SetStunServers(stun_servers);
+ peer_->SetDNSResolver();
+ peer_->AddStream(1);
+ Gather();
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, " UDP "));
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, " TCP "));
+}
+
+// Disabled because google isn't running any TCP stun servers right now
+TEST_F(WebRtcIceGatherTest,
+ DISABLED_TestGatherDNSStunServerIpAddressBothUdpTcp) {
+ if (stun_server_address_.empty()) {
+ return;
+ }
+
+ std::vector<NrIceStunServer> stun_servers;
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = true;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ stun_servers.push_back(*NrIceStunServer::Create(
+ stun_server_address_, kDefaultStunServerPort, kNrIceTransportUdp));
+ stun_servers.push_back(*NrIceStunServer::Create(
+ stun_server_address_, kDefaultStunServerPort, kNrIceTransportTcp));
+ peer_->SetStunServers(stun_servers);
+ peer_->SetDNSResolver();
+ peer_->AddStream(1);
+ Gather();
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, " UDP "));
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, " TCP "));
+}
+
+TEST_F(WebRtcIceGatherTest, TestGatherDNSStunBogusHostname) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ peer_->SetStunServer(kBogusStunServerHostname, kDefaultStunServerPort);
+ peer_->SetDNSResolver();
+ peer_->AddStream(1);
+ Gather();
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, " UDP "));
+}
+
+// Disabled because google isn't running any TCP stun servers right now
+TEST_F(WebRtcIceGatherTest, DISABLED_TestGatherDNSStunBogusHostnameTcp) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = true;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ peer_->SetStunServer(kBogusStunServerHostname, kDefaultStunServerPort,
+ kNrIceTransportTcp);
+ peer_->SetDNSResolver();
+ peer_->AddStream(1);
+ Gather();
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, " TCP "));
+}
+
+TEST_F(WebRtcIceGatherTest, TestDefaultCandidate) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ peer_->SetStunServer(stun_server_hostname_, kDefaultStunServerPort);
+ peer_->AddStream(1);
+ Gather();
+ NrIceCandidate default_candidate;
+ ASSERT_TRUE(NS_SUCCEEDED(peer_->GetDefaultCandidate(0, &default_candidate)));
+}
+
+TEST_F(WebRtcIceGatherTest, TestGatherTurn) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ if (turn_server_.empty()) return;
+ peer_->SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_,
+ turn_password_, kNrIceTransportUdp);
+ peer_->AddStream(1);
+ Gather();
+}
+
+TEST_F(WebRtcIceGatherTest, TestGatherTurnTcp) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ if (turn_server_.empty()) return;
+ peer_->SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_,
+ turn_password_, kNrIceTransportTcp);
+ peer_->AddStream(1);
+ Gather();
+}
+
+TEST_F(WebRtcIceGatherTest, TestGatherDisableComponent) {
+ if (stun_server_hostname_.empty()) {
+ return;
+ }
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ peer_->SetStunServer(stun_server_hostname_, kDefaultStunServerPort);
+ peer_->AddStream(1);
+ peer_->AddStream(2);
+ peer_->DisableComponent(1, 2);
+ Gather();
+ std::vector<std::string> attributes = peer_->GetAttributes(1);
+
+ for (auto& attribute : attributes) {
+ if (attribute.find("candidate:") != std::string::npos) {
+ size_t sp1 = attribute.find(' ');
+ ASSERT_EQ(0, attribute.compare(sp1 + 1, 1, "1", 1));
+ }
+ }
+}
+
+TEST_F(WebRtcIceGatherTest, TestGatherVerifyNoLoopback) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ peer_->AddStream(1);
+ Gather();
+ ASSERT_FALSE(StreamHasMatchingCandidate(0, "127.0.0.1"));
+}
+
+TEST_F(WebRtcIceGatherTest, TestGatherAllowLoopback) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ config.mAllowLoopback = true;
+ NrIceCtx::InitializeGlobals(config);
+
+ // Set up peer with loopback allowed.
+ peer_ = MakeUnique<IceTestPeer>("P1", test_utils_, true, NrIceCtx::Config());
+ peer_->AddStream(1);
+ Gather();
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, "127.0.0.1"));
+}
+
+TEST_F(WebRtcIceGatherTest, TestGatherTcpDisabledNoStun) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ peer_->AddStream(1);
+ Gather();
+ ASSERT_FALSE(StreamHasMatchingCandidate(0, " TCP "));
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, " UDP "));
+}
+
+TEST_F(WebRtcIceGatherTest, VerifyTestStunServer) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseFakeStunUdpServerWithResponse("192.0.2.133", 3333);
+ peer_->AddStream(1);
+ Gather();
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, " 192.0.2.133 3333 "));
+}
+
+TEST_F(WebRtcIceGatherTest, VerifyTestStunTcpServer) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = true;
+ NrIceCtx::InitializeGlobals(config);
+ UseFakeStunTcpServerWithResponse("192.0.2.233", 3333);
+ peer_->AddStream(1);
+ Gather();
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, " 192.0.2.233 3333 typ srflx",
+ " tcptype "));
+}
+
+TEST_F(WebRtcIceGatherTest, VerifyTestStunServerV6) {
+ if (!TestStunServer::GetInstance(AF_INET6)) {
+ // No V6 addresses
+ return;
+ }
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseFakeStunUdpServerWithResponse("beef::", 3333);
+ peer_->AddStream(1);
+ Gather();
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, " beef:: 3333 "));
+}
+
+TEST_F(WebRtcIceGatherTest, VerifyTestStunServerFQDN) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseFakeStunUdpServerWithResponse("192.0.2.133", 3333, "stun.example.com");
+ peer_->AddStream(1);
+ Gather();
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, " 192.0.2.133 3333 "));
+}
+
+TEST_F(WebRtcIceGatherTest, VerifyTestStunServerV6FQDN) {
+ if (!TestStunServer::GetInstance(AF_INET6)) {
+ // No V6 addresses
+ return;
+ }
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseFakeStunUdpServerWithResponse("beef::", 3333, "stun.example.com");
+ peer_->AddStream(1);
+ Gather();
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, " beef:: 3333 "));
+}
+
+TEST_F(WebRtcIceGatherTest, TestStunServerReturnsWildcardAddr) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseFakeStunUdpServerWithResponse("0.0.0.0", 3333);
+ peer_->AddStream(1);
+ Gather(kDefaultTimeout * 3);
+ ASSERT_FALSE(StreamHasMatchingCandidate(0, " 0.0.0.0 "));
+}
+
+TEST_F(WebRtcIceGatherTest, TestStunServerReturnsWildcardAddrV6) {
+ if (!TestStunServer::GetInstance(AF_INET6)) {
+ // No V6 addresses
+ return;
+ }
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseFakeStunUdpServerWithResponse("::", 3333);
+ peer_->AddStream(1);
+ Gather(kDefaultTimeout * 3);
+ ASSERT_FALSE(StreamHasMatchingCandidate(0, " :: "));
+}
+
+TEST_F(WebRtcIceGatherTest, TestStunServerReturnsPort0) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseFakeStunUdpServerWithResponse("192.0.2.133", 0);
+ peer_->AddStream(1);
+ Gather(kDefaultTimeout * 3);
+ ASSERT_FALSE(StreamHasMatchingCandidate(0, " 192.0.2.133 0 "));
+}
+
+TEST_F(WebRtcIceGatherTest, TestStunServerReturnsLoopbackAddr) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseFakeStunUdpServerWithResponse("127.0.0.133", 3333);
+ peer_->AddStream(1);
+ Gather(kDefaultTimeout * 3);
+ ASSERT_FALSE(StreamHasMatchingCandidate(0, " 127.0.0.133 "));
+}
+
+TEST_F(WebRtcIceGatherTest, TestStunServerReturnsLoopbackAddrV6) {
+ if (!TestStunServer::GetInstance(AF_INET6)) {
+ // No V6 addresses
+ return;
+ }
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseFakeStunUdpServerWithResponse("::1", 3333);
+ peer_->AddStream(1);
+ Gather(kDefaultTimeout * 3);
+ ASSERT_FALSE(StreamHasMatchingCandidate(0, " ::1 "));
+}
+
+TEST_F(WebRtcIceGatherTest, TestStunServerTrickle) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseFakeStunUdpServerWithResponse("192.0.2.1", 3333);
+ peer_->AddStream(1);
+ TestStunServer::GetInstance(AF_INET)->SetDropInitialPackets(3);
+ Gather(0);
+ ASSERT_FALSE(StreamHasMatchingCandidate(0, "192.0.2.1"));
+ WaitForGather();
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, "192.0.2.1"));
+}
+
+// Test no host with our fake STUN server and apparently NATted.
+TEST_F(WebRtcIceGatherTest, TestFakeStunServerNatedNoHost) {
+ {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ }
+
+ NrIceCtx::Config config;
+ config.mPolicy = NrIceCtx::ICE_POLICY_NO_HOST;
+ peer_ = MakeUnique<IceTestPeer>("P1", test_utils_, true, config);
+ UseFakeStunUdpServerWithResponse("192.0.2.1", 3333);
+ peer_->AddStream(1);
+ Gather(0);
+ WaitForGather();
+ DumpAttributes(0);
+ ASSERT_FALSE(StreamHasMatchingCandidate(0, "host"));
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, "srflx"));
+ NrIceCandidate default_candidate;
+ nsresult rv = peer_->GetDefaultCandidate(0, &default_candidate);
+ if (NS_SUCCEEDED(rv)) {
+ ASSERT_NE(NrIceCandidate::ICE_HOST, default_candidate.type);
+ }
+}
+
+// Test no host with our fake STUN server and apparently non-NATted.
+TEST_F(WebRtcIceGatherTest, TestFakeStunServerNoNatNoHost) {
+ {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ }
+
+ NrIceCtx::Config config;
+ config.mPolicy = NrIceCtx::ICE_POLICY_NO_HOST;
+ peer_ = MakeUnique<IceTestPeer>("P1", test_utils_, true, config);
+ UseTestStunServer();
+ peer_->AddStream(1);
+ Gather(0);
+ WaitForGather();
+ DumpAttributes(0);
+ ASSERT_FALSE(StreamHasMatchingCandidate(0, "host"));
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, "srflx"));
+}
+
+// Test that srflx candidate is discarded in non-NATted environment if host
+// address obfuscation is not enabled.
+TEST_F(WebRtcIceGatherTest,
+ TestSrflxCandidateDiscardedWithObfuscateHostAddressesNotEnabled) {
+ {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ }
+
+ NrIceCtx::Config config;
+ peer_ = MakeUnique<IceTestPeer>("P1", test_utils_, true, config);
+ UseTestStunServer();
+ peer_->AddStream(1);
+ Gather(0, false, false);
+ WaitForGather();
+ DumpAttributes(0);
+ EXPECT_TRUE(StreamHasMatchingCandidate(0, "host"));
+ EXPECT_FALSE(StreamHasMatchingCandidate(0, "srflx"));
+}
+
+// Test that srflx candidate is generated in non-NATted environment if host
+// address obfuscation is enabled.
+TEST_F(WebRtcIceGatherTest,
+ TestSrflxCandidateGeneratedWithObfuscateHostAddressesEnabled) {
+ {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ }
+
+ NrIceCtx::Config config;
+ peer_ = MakeUnique<IceTestPeer>("P1", test_utils_, true, config);
+ UseTestStunServer();
+ peer_->AddStream(1);
+ Gather(0, false, true);
+ WaitForGather();
+ DumpAttributes(0);
+ EXPECT_TRUE(StreamHasMatchingCandidate(0, "host"));
+ EXPECT_TRUE(StreamHasMatchingCandidate(0, "srflx"));
+}
+
+TEST_F(WebRtcIceGatherTest, TestStunTcpServerTrickle) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = true;
+ NrIceCtx::InitializeGlobals(config);
+ UseFakeStunTcpServerWithResponse("192.0.3.1", 3333);
+ TestStunTcpServer::GetInstance(AF_INET)->SetDelay(500);
+ peer_->AddStream(1);
+ Gather(0);
+ ASSERT_FALSE(StreamHasMatchingCandidate(0, " 192.0.3.1 ", " tcptype "));
+ WaitForGather();
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, " 192.0.3.1 ", " tcptype "));
+}
+
+TEST_F(WebRtcIceGatherTest, TestStunTcpAndUdpServerTrickle) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = true;
+ NrIceCtx::InitializeGlobals(config);
+ UseFakeStunUdpTcpServersWithResponse("192.0.2.1", 3333, "192.0.3.1", 3333);
+ TestStunServer::GetInstance(AF_INET)->SetDropInitialPackets(3);
+ TestStunTcpServer::GetInstance(AF_INET)->SetDelay(500);
+ peer_->AddStream(1);
+ Gather(0);
+ ASSERT_FALSE(StreamHasMatchingCandidate(0, "192.0.2.1", "UDP"));
+ ASSERT_FALSE(StreamHasMatchingCandidate(0, " 192.0.3.1 ", " tcptype "));
+ WaitForGather();
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, "192.0.2.1", "UDP"));
+ ASSERT_TRUE(StreamHasMatchingCandidate(0, " 192.0.3.1 ", " tcptype "));
+}
+
+TEST_F(WebRtcIceGatherTest, TestSetIceControlling) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ peer_->AddStream(1);
+ peer_->SetControlling(NrIceCtx::ICE_CONTROLLING);
+ NrIceCtx::Controlling controlling = peer_->GetControlling();
+ ASSERT_EQ(NrIceCtx::ICE_CONTROLLING, controlling);
+ // SetControlling should only allow setting this once
+ peer_->SetControlling(NrIceCtx::ICE_CONTROLLED);
+ controlling = peer_->GetControlling();
+ ASSERT_EQ(NrIceCtx::ICE_CONTROLLING, controlling);
+}
+
+TEST_F(WebRtcIceGatherTest, TestSetIceControlled) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ EnsurePeer();
+ peer_->AddStream(1);
+ peer_->SetControlling(NrIceCtx::ICE_CONTROLLED);
+ NrIceCtx::Controlling controlling = peer_->GetControlling();
+ ASSERT_EQ(NrIceCtx::ICE_CONTROLLED, controlling);
+ // SetControlling should only allow setting this once
+ peer_->SetControlling(NrIceCtx::ICE_CONTROLLING);
+ controlling = peer_->GetControlling();
+ ASSERT_EQ(NrIceCtx::ICE_CONTROLLED, controlling);
+}
+
+TEST_F(WebRtcIceConnectTest, TestGather) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+}
+
+TEST_F(WebRtcIceConnectTest, TestGatherTcp) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = true;
+ NrIceCtx::InitializeGlobals(config);
+ Init();
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+}
+
+TEST_F(WebRtcIceConnectTest, TestGatherAutoPrioritize) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ Init();
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnect) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ Connect();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectRestartIce) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ Connect();
+ SendReceive(p1_.get(), p2_.get());
+
+ p2_->RestartIce();
+ ASSERT_FALSE(p2_->gathering_complete());
+
+ // verify p1 and p2 streams are still connected after restarting ice on p2
+ SendReceive(p1_.get(), p2_.get());
+
+ mozilla::UniquePtr<IceTestPeer> p3_;
+ p3_ = MakeUnique<IceTestPeer>("P3", test_utils_, true, NrIceCtx::Config());
+ InitPeer(p3_.get());
+ p3_->AddStream(1);
+
+ ASSERT_TRUE(GatherCallerAndCallee(p2_.get(), p3_.get()));
+ std::cout << "-------------------------------------------------" << std::endl;
+ ConnectCallerAndCallee(p3_.get(), p2_.get(), TRICKLE_SIMULATE);
+ SendReceive(p1_.get(), p2_.get()); // p1 and p2 are still connected
+ SendReceive(p3_.get(), p2_.get(), true, true); // p3 and p2 not yet connected
+ p2_->SimulateTrickle(0);
+ p3_->SimulateTrickle(0);
+ ASSERT_TRUE_WAIT(p3_->is_ready(0), kDefaultTimeout);
+ ASSERT_TRUE_WAIT(p2_->is_ready(0), kDefaultTimeout);
+ SendReceive(p1_.get(), p2_.get(), false, true); // p1 and p2 not connected
+ SendReceive(p3_.get(), p2_.get()); // p3 and p2 are now connected
+
+ p3_ = nullptr;
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectRestartIceThenAbort) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ Connect();
+ SendReceive(p1_.get(), p2_.get());
+
+ p2_->RestartIce();
+ ASSERT_FALSE(p2_->gathering_complete());
+
+ // verify p1 and p2 streams are still connected after restarting ice on p2
+ SendReceive(p1_.get(), p2_.get());
+
+ mozilla::UniquePtr<IceTestPeer> p3_;
+ p3_ = MakeUnique<IceTestPeer>("P3", test_utils_, true, NrIceCtx::Config());
+ InitPeer(p3_.get());
+ p3_->AddStream(1);
+
+ ASSERT_TRUE(GatherCallerAndCallee(p2_.get(), p3_.get()));
+ std::cout << "-------------------------------------------------" << std::endl;
+ p2_->RollbackIceRestart();
+ p2_->Connect(p1_.get(), TRICKLE_NONE);
+ SendReceive(p1_.get(), p2_.get());
+ p3_ = nullptr;
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectIceRestartRoleConflict) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ // Just for fun lets do this with switched rolls
+ p1_->SetControlling(NrIceCtx::ICE_CONTROLLED);
+ p2_->SetControlling(NrIceCtx::ICE_CONTROLLING);
+ Connect();
+ SendReceive(p1_.get(), p2_.get());
+ // Set rolls should not switch by connecting
+ ASSERT_EQ(NrIceCtx::ICE_CONTROLLED, p1_->GetControlling());
+ ASSERT_EQ(NrIceCtx::ICE_CONTROLLING, p2_->GetControlling());
+
+ p2_->RestartIce();
+ ASSERT_FALSE(p2_->gathering_complete());
+ p2_->SetControlling(NrIceCtx::ICE_CONTROLLED);
+ ASSERT_EQ(NrIceCtx::ICE_CONTROLLING, p2_->GetControlling())
+ << "ICE restart should not allow role to change, unless ice-lite happens";
+
+ mozilla::UniquePtr<IceTestPeer> p3_;
+ p3_ = MakeUnique<IceTestPeer>("P3", test_utils_, true, NrIceCtx::Config());
+ InitPeer(p3_.get());
+ p3_->AddStream(1);
+ // Set control role for p3 accordingly (with role conflict)
+ p3_->SetControlling(NrIceCtx::ICE_CONTROLLING);
+ ASSERT_EQ(NrIceCtx::ICE_CONTROLLING, p3_->GetControlling());
+
+ ASSERT_TRUE(GatherCallerAndCallee(p2_.get(), p3_.get()));
+ std::cout << "-------------------------------------------------" << std::endl;
+ ConnectCallerAndCallee(p3_.get(), p2_.get());
+ auto p2role = p2_->GetControlling();
+ ASSERT_NE(p2role, p3_->GetControlling()) << "Conflict should be resolved";
+ ASSERT_EQ(NrIceCtx::ICE_CONTROLLED, p1_->GetControlling())
+ << "P1 should be unaffected by role conflict";
+
+ // And again we are not allowed to switch roles at this point any more
+ p1_->SetControlling(NrIceCtx::ICE_CONTROLLING);
+ ASSERT_EQ(NrIceCtx::ICE_CONTROLLED, p1_->GetControlling());
+ p3_->SetControlling(p2role);
+ ASSERT_NE(p2role, p3_->GetControlling());
+
+ p3_ = nullptr;
+}
+
+TEST_F(WebRtcIceConnectTest,
+ TestIceRestartWithMultipleInterfacesAndUserStartingScreenSharing) {
+ const char* FAKE_WIFI_ADDR = "10.0.0.1";
+ const char* FAKE_WIFI_IF_NAME = "wlan9";
+
+ // prepare a fake wifi interface
+ nr_local_addr wifi_addr;
+ wifi_addr.interface.type = NR_INTERFACE_TYPE_WIFI;
+ wifi_addr.interface.estimated_speed = 1000;
+
+ int r = nr_str_port_to_transport_addr(FAKE_WIFI_ADDR, 0, IPPROTO_UDP,
+ &(wifi_addr.addr));
+ ASSERT_EQ(0, r);
+ strncpy(wifi_addr.addr.ifname, FAKE_WIFI_IF_NAME, MAXIFNAME);
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ // setup initial ICE connection between p1_ and p2_
+ UseNat();
+ AddStream(1);
+ SetExpectedTypes(NrIceCandidate::Type::ICE_SERVER_REFLEXIVE,
+ NrIceCandidate::Type::ICE_SERVER_REFLEXIVE);
+ ASSERT_TRUE(Gather(kDefaultTimeout, true));
+ Connect();
+
+ // verify the connection is working
+ SendReceive(p1_.get(), p2_.get());
+
+ // simulate user accepting permissions for screen sharing
+ p2_->SetCtxFlags(false);
+
+ // and having an additional non-default interface
+ nsTArray<NrIceStunAddr> stunAddr = p2_->GetStunAddrs();
+ stunAddr.InsertElementAt(0, NrIceStunAddr(&wifi_addr));
+ p2_->SetStunAddrs(stunAddr);
+
+ std::cout << "-------------------------------------------------" << std::endl;
+
+ // now restart ICE
+ p2_->RestartIce();
+ ASSERT_FALSE(p2_->gathering_complete());
+
+ // verify that we can successfully gather candidates
+ p2_->Gather();
+ EXPECT_TRUE_WAIT(p2_->gathering_complete(), kDefaultTimeout);
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectTcp) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = true;
+ NrIceCtx::InitializeGlobals(config);
+ Init();
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ SetCandidateFilter(IsTcpCandidate);
+ SetExpectedTypes(NrIceCandidate::Type::ICE_HOST,
+ NrIceCandidate::Type::ICE_HOST, kNrIceTransportTcp);
+ Connect();
+}
+
+// TCP SO tests works on localhost only with delay applied:
+// tc qdisc add dev lo root netem delay 10ms
+TEST_F(WebRtcIceConnectTest, DISABLED_TestConnectTcpSo) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = true;
+ NrIceCtx::InitializeGlobals(config);
+ Init();
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ SetCandidateFilter(IsTcpSoCandidate);
+ SetExpectedTypes(NrIceCandidate::Type::ICE_HOST,
+ NrIceCandidate::Type::ICE_HOST, kNrIceTransportTcp);
+ Connect();
+}
+
+// Disabled because this breaks with hairpinning.
+TEST_F(WebRtcIceConnectTest, DISABLED_TestConnectNoHost) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ Init(false, NrIceCtx::ICE_POLICY_NO_HOST);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ SetExpectedTypes(NrIceCandidate::Type::ICE_SERVER_REFLEXIVE,
+ NrIceCandidate::Type::ICE_SERVER_REFLEXIVE,
+ kNrIceTransportTcp);
+ Connect();
+}
+
+TEST_F(WebRtcIceConnectTest, TestLoopbackOnlySortOf) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ config.mAllowLoopback = true;
+ NrIceCtx::InitializeGlobals(config);
+ Init(false);
+ AddStream(1);
+ SetCandidateFilter(IsLoopbackCandidate);
+ ASSERT_TRUE(Gather());
+ SetExpectedRemoteCandidateAddr("127.0.0.1");
+ Connect();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectBothControllingP1Wins) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ p1_->SetTiebreaker(1);
+ p2_->SetTiebreaker(0);
+ ASSERT_TRUE(Gather());
+ p1_->SetControlling(NrIceCtx::ICE_CONTROLLING);
+ p2_->SetControlling(NrIceCtx::ICE_CONTROLLING);
+ Connect();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectBothControllingP2Wins) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ p1_->SetTiebreaker(0);
+ p2_->SetTiebreaker(1);
+ ASSERT_TRUE(Gather());
+ p1_->SetControlling(NrIceCtx::ICE_CONTROLLING);
+ p2_->SetControlling(NrIceCtx::ICE_CONTROLLING);
+ Connect();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectIceLiteOfferer) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ p1_->SimulateIceLite();
+ Connect();
+}
+
+TEST_F(WebRtcIceConnectTest, TestTrickleBothControllingP1Wins) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ p1_->SetTiebreaker(1);
+ p2_->SetTiebreaker(0);
+ ASSERT_TRUE(Gather());
+ p1_->SetControlling(NrIceCtx::ICE_CONTROLLING);
+ p2_->SetControlling(NrIceCtx::ICE_CONTROLLING);
+ ConnectTrickle();
+ SimulateTrickle(0);
+ WaitForConnected(1000);
+ AssertCheckingReached();
+}
+
+TEST_F(WebRtcIceConnectTest, TestTrickleBothControllingP2Wins) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ p1_->SetTiebreaker(0);
+ p2_->SetTiebreaker(1);
+ ASSERT_TRUE(Gather());
+ p1_->SetControlling(NrIceCtx::ICE_CONTROLLING);
+ p2_->SetControlling(NrIceCtx::ICE_CONTROLLING);
+ ConnectTrickle();
+ SimulateTrickle(0);
+ WaitForConnected(1000);
+ AssertCheckingReached();
+}
+
+TEST_F(WebRtcIceConnectTest, TestTrickleIceLiteOfferer) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ p1_->SimulateIceLite();
+ ConnectTrickle();
+ SimulateTrickle(0);
+ WaitForConnected(1000);
+ AssertCheckingReached();
+}
+
+TEST_F(WebRtcIceConnectTest, TestGatherFullCone) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseNat();
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+}
+
+TEST_F(WebRtcIceConnectTest, TestGatherFullConeAutoPrioritize) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseNat();
+ Init();
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectFullCone) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseNat();
+ AddStream(1);
+ SetExpectedTypes(NrIceCandidate::Type::ICE_SERVER_REFLEXIVE,
+ NrIceCandidate::Type::ICE_SERVER_REFLEXIVE);
+ ASSERT_TRUE(Gather());
+ Connect();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectNoNatNoHost) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ Init(false, NrIceCtx::ICE_POLICY_NO_HOST);
+ UseTestStunServer();
+ // Because we are connecting from our host candidate to the
+ // other side's apparent srflx (which is also their host)
+ // we see a host/srflx pair.
+ SetExpectedTypes(NrIceCandidate::Type::ICE_HOST,
+ NrIceCandidate::Type::ICE_SERVER_REFLEXIVE);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ Connect();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectFullConeNoHost) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseNat();
+ Init(false, NrIceCtx::ICE_POLICY_NO_HOST);
+ UseTestStunServer();
+ SetExpectedTypes(NrIceCandidate::Type::ICE_SERVER_REFLEXIVE,
+ NrIceCandidate::Type::ICE_SERVER_REFLEXIVE);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ Connect();
+}
+
+TEST_F(WebRtcIceConnectTest, TestGatherAddressRestrictedCone) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseNat();
+ SetFilteringType(TestNat::ADDRESS_DEPENDENT);
+ SetMappingType(TestNat::ENDPOINT_INDEPENDENT);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectAddressRestrictedCone) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseNat();
+ SetFilteringType(TestNat::ADDRESS_DEPENDENT);
+ SetMappingType(TestNat::ENDPOINT_INDEPENDENT);
+ AddStream(1);
+ SetExpectedTypes(NrIceCandidate::Type::ICE_SERVER_REFLEXIVE,
+ NrIceCandidate::Type::ICE_SERVER_REFLEXIVE);
+ ASSERT_TRUE(Gather());
+ Connect();
+}
+
+TEST_F(WebRtcIceConnectTest, TestGatherPortRestrictedCone) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseNat();
+ SetFilteringType(TestNat::PORT_DEPENDENT);
+ SetMappingType(TestNat::ENDPOINT_INDEPENDENT);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectPortRestrictedCone) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseNat();
+ SetFilteringType(TestNat::PORT_DEPENDENT);
+ SetMappingType(TestNat::ENDPOINT_INDEPENDENT);
+ AddStream(1);
+ SetExpectedTypes(NrIceCandidate::Type::ICE_SERVER_REFLEXIVE,
+ NrIceCandidate::Type::ICE_SERVER_REFLEXIVE);
+ ASSERT_TRUE(Gather());
+ Connect();
+}
+
+TEST_F(WebRtcIceConnectTest, TestGatherSymmetricNat) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseNat();
+ SetFilteringType(TestNat::PORT_DEPENDENT);
+ SetMappingType(TestNat::PORT_DEPENDENT);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectSymmetricNat) {
+ if (turn_server_.empty()) return;
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseNat();
+ SetFilteringType(TestNat::PORT_DEPENDENT);
+ SetMappingType(TestNat::PORT_DEPENDENT);
+ p1_->SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
+ NrIceCandidate::Type::ICE_RELAYED);
+ p2_->SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
+ NrIceCandidate::Type::ICE_RELAYED);
+ SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_,
+ turn_password_);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ Connect();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectSymmetricNatAndNoNat) {
+ {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = true;
+ NrIceCtx::InitializeGlobals(config);
+ }
+
+ NrIceCtx::Config config;
+ p1_ = MakeUnique<IceTestPeer>("P1", test_utils_, true, config);
+ p1_->UseNat();
+ p1_->SetFilteringType(TestNat::PORT_DEPENDENT);
+ p1_->SetMappingType(TestNat::PORT_DEPENDENT);
+
+ p2_ = MakeUnique<IceTestPeer>("P2", test_utils_, false, config);
+ initted_ = true;
+
+ AddStream(1);
+ p1_->SetExpectedTypes(NrIceCandidate::Type::ICE_PEER_REFLEXIVE,
+ NrIceCandidate::Type::ICE_HOST);
+ p2_->SetExpectedTypes(NrIceCandidate::Type::ICE_HOST,
+ NrIceCandidate::Type::ICE_PEER_REFLEXIVE);
+ ASSERT_TRUE(Gather());
+ Connect();
+}
+
+TEST_F(WebRtcIceConnectTest, TestGatherNatBlocksUDP) {
+ if (turn_server_.empty()) return;
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseNat();
+ BlockUdp();
+ std::vector<NrIceTurnServer> turn_servers;
+ std::vector<unsigned char> password_vec(turn_password_.begin(),
+ turn_password_.end());
+ turn_servers.push_back(
+ *NrIceTurnServer::Create(turn_server_, kDefaultStunServerPort, turn_user_,
+ password_vec, kNrIceTransportTcp));
+ turn_servers.push_back(
+ *NrIceTurnServer::Create(turn_server_, kDefaultStunServerPort, turn_user_,
+ password_vec, kNrIceTransportUdp));
+ SetTurnServers(turn_servers);
+ AddStream(1);
+ // We have to wait for the UDP-based stuff to time out.
+ ASSERT_TRUE(Gather(kDefaultTimeout * 3));
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectNatBlocksUDP) {
+ if (turn_server_.empty()) return;
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ UseNat();
+ BlockUdp();
+ std::vector<NrIceTurnServer> turn_servers;
+ std::vector<unsigned char> password_vec(turn_password_.begin(),
+ turn_password_.end());
+ turn_servers.push_back(
+ *NrIceTurnServer::Create(turn_server_, kDefaultStunServerPort, turn_user_,
+ password_vec, kNrIceTransportTcp));
+ turn_servers.push_back(
+ *NrIceTurnServer::Create(turn_server_, kDefaultStunServerPort, turn_user_,
+ password_vec, kNrIceTransportUdp));
+ SetTurnServers(turn_servers);
+ p1_->SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
+ NrIceCandidate::Type::ICE_RELAYED, kNrIceTransportTcp);
+ p2_->SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
+ NrIceCandidate::Type::ICE_RELAYED, kNrIceTransportTcp);
+ AddStream(1);
+ ASSERT_TRUE(Gather(kDefaultTimeout * 3));
+ Connect();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectTwoComponents) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(2);
+ ASSERT_TRUE(Gather());
+ Connect();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectTwoComponentsDisableSecond) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(2);
+ ASSERT_TRUE(Gather());
+ p1_->DisableComponent(0, 2);
+ p2_->DisableComponent(0, 2);
+ Connect();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectP2ThenP1) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ ConnectP2();
+ PR_Sleep(1000);
+ ConnectP1();
+ WaitForConnectedStreams();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectP2ThenP1Trickle) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ ConnectP2();
+ PR_Sleep(1000);
+ ConnectP1(TRICKLE_SIMULATE);
+ SimulateTrickleP1(0);
+ WaitForConnectedStreams();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectP2ThenP1TrickleTwoComponents) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ AddStream(2);
+ ASSERT_TRUE(Gather());
+ ConnectP2();
+ PR_Sleep(1000);
+ ConnectP1(TRICKLE_SIMULATE);
+ SimulateTrickleP1(0);
+ std::cerr << "Sleeping between trickle streams" << std::endl;
+ PR_Sleep(1000); // Give this some time to settle but not complete
+ // all of ICE.
+ SimulateTrickleP1(1);
+ WaitForConnectedStreams(2);
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectAutoPrioritize) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ Init();
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ Connect();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectTrickleOneStreamOneComponent) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ ConnectTrickle();
+ SimulateTrickle(0);
+ WaitForConnected(1000);
+ AssertCheckingReached();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectTrickleTwoStreamsOneComponent) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ ConnectTrickle();
+ SimulateTrickle(0);
+ SimulateTrickle(1);
+ WaitForConnected(1000);
+ AssertCheckingReached();
+}
+
+void RealisticTrickleDelay(
+ std::vector<SchedulableTrickleCandidate*>& candidates) {
+ for (size_t i = 0; i < candidates.size(); ++i) {
+ SchedulableTrickleCandidate* cand = candidates[i];
+ if (cand->IsHost()) {
+ cand->Schedule(i * 10);
+ } else if (cand->IsReflexive()) {
+ cand->Schedule(i * 10 + 100);
+ } else if (cand->IsRelay()) {
+ cand->Schedule(i * 10 + 200);
+ }
+ }
+}
+
+void DelayRelayCandidates(std::vector<SchedulableTrickleCandidate*>& candidates,
+ unsigned int ms) {
+ for (auto& candidate : candidates) {
+ if (candidate->IsRelay()) {
+ candidate->Schedule(ms);
+ } else {
+ candidate->Schedule(0);
+ }
+ }
+}
+
+void AddNonPairableCandidates(
+ std::vector<SchedulableTrickleCandidate*>& candidates, IceTestPeer* peer,
+ size_t stream, int net_type, MtransportTestUtils* test_utils_) {
+ for (int i = 1; i < 5; i++) {
+ if (net_type == i) continue;
+ switch (i) {
+ case 1:
+ candidates.push_back(new SchedulableTrickleCandidate(
+ peer, stream,
+ "candidate:0 1 UDP 2113601790 10.0.0.1 12345 typ host", "",
+ test_utils_));
+ break;
+ case 2:
+ candidates.push_back(new SchedulableTrickleCandidate(
+ peer, stream,
+ "candidate:0 1 UDP 2113601791 172.16.1.1 12345 typ host", "",
+ test_utils_));
+ break;
+ case 3:
+ candidates.push_back(new SchedulableTrickleCandidate(
+ peer, stream,
+ "candidate:0 1 UDP 2113601792 192.168.0.1 12345 typ host", "",
+ test_utils_));
+ break;
+ case 4:
+ candidates.push_back(new SchedulableTrickleCandidate(
+ peer, stream,
+ "candidate:0 1 UDP 2113601793 100.64.1.1 12345 typ host", "",
+ test_utils_));
+ break;
+ default:
+ NR_UNIMPLEMENTED;
+ }
+ }
+
+ for (auto i = candidates.rbegin(); i != candidates.rend(); ++i) {
+ std::cerr << "Scheduling candidate: " << (*i)->Candidate().c_str()
+ << std::endl;
+ (*i)->Schedule(0);
+ }
+}
+
+void DropTrickleCandidates(
+ std::vector<SchedulableTrickleCandidate*>& candidates) {}
+
+TEST_F(WebRtcIceConnectTest, TestConnectTrickleAddStreamDuringICE) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ ConnectTrickle();
+ RealisticTrickleDelay(p1_->ControlTrickle(0));
+ RealisticTrickleDelay(p2_->ControlTrickle(0));
+ AddStream(1);
+ RealisticTrickleDelay(p1_->ControlTrickle(1));
+ RealisticTrickleDelay(p2_->ControlTrickle(1));
+ WaitForConnected(1000);
+ AssertCheckingReached();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectTrickleAddStreamAfterICE) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ ConnectTrickle();
+ RealisticTrickleDelay(p1_->ControlTrickle(0));
+ RealisticTrickleDelay(p2_->ControlTrickle(0));
+ WaitForConnected(1000);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ ConnectTrickle();
+ RealisticTrickleDelay(p1_->ControlTrickle(1));
+ RealisticTrickleDelay(p2_->ControlTrickle(1));
+ WaitForConnected(1000);
+ AssertCheckingReached();
+}
+
+TEST_F(WebRtcIceConnectTest, RemoveStream) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ ConnectTrickle();
+ RealisticTrickleDelay(p1_->ControlTrickle(0));
+ RealisticTrickleDelay(p2_->ControlTrickle(0));
+ RealisticTrickleDelay(p1_->ControlTrickle(1));
+ RealisticTrickleDelay(p2_->ControlTrickle(1));
+ WaitForConnected(1000);
+
+ RemoveStream(0);
+ ASSERT_TRUE(Gather());
+ ConnectTrickle();
+}
+
+TEST_F(WebRtcIceConnectTest, P1NoTrickle) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ ConnectTrickle();
+ DropTrickleCandidates(p1_->ControlTrickle(0));
+ RealisticTrickleDelay(p2_->ControlTrickle(0));
+ WaitForConnected(1000);
+}
+
+TEST_F(WebRtcIceConnectTest, P2NoTrickle) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ ConnectTrickle();
+ RealisticTrickleDelay(p1_->ControlTrickle(0));
+ DropTrickleCandidates(p2_->ControlTrickle(0));
+ WaitForConnected(1000);
+}
+
+TEST_F(WebRtcIceConnectTest, RemoveAndAddStream) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ ConnectTrickle();
+ RealisticTrickleDelay(p1_->ControlTrickle(0));
+ RealisticTrickleDelay(p2_->ControlTrickle(0));
+ RealisticTrickleDelay(p1_->ControlTrickle(1));
+ RealisticTrickleDelay(p2_->ControlTrickle(1));
+ WaitForConnected(1000);
+
+ RemoveStream(0);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ ConnectTrickle();
+ RealisticTrickleDelay(p1_->ControlTrickle(2));
+ RealisticTrickleDelay(p2_->ControlTrickle(2));
+ WaitForConnected(1000);
+}
+
+TEST_F(WebRtcIceConnectTest, RemoveStreamBeforeGather) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ AddStream(1);
+ ASSERT_TRUE(Gather(0));
+ RemoveStream(0);
+ WaitForGather();
+ ConnectTrickle();
+ RealisticTrickleDelay(p1_->ControlTrickle(1));
+ RealisticTrickleDelay(p2_->ControlTrickle(1));
+ WaitForConnected(1000);
+}
+
+TEST_F(WebRtcIceConnectTest, RemoveStreamDuringGather) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ AddStream(1);
+ RemoveStream(0);
+ ASSERT_TRUE(Gather());
+ ConnectTrickle();
+ RealisticTrickleDelay(p1_->ControlTrickle(1));
+ RealisticTrickleDelay(p2_->ControlTrickle(1));
+ WaitForConnected(1000);
+}
+
+TEST_F(WebRtcIceConnectTest, RemoveStreamDuringConnect) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ ConnectTrickle();
+ RealisticTrickleDelay(p1_->ControlTrickle(0));
+ RealisticTrickleDelay(p2_->ControlTrickle(0));
+ RealisticTrickleDelay(p1_->ControlTrickle(1));
+ RealisticTrickleDelay(p2_->ControlTrickle(1));
+ RemoveStream(0);
+ WaitForConnected(1000);
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectRealTrickleOneStreamOneComponent) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ AddStream(1);
+ ASSERT_TRUE(Gather(0));
+ ConnectTrickle(TRICKLE_REAL);
+ WaitForConnected();
+ WaitForGather(); // ICE can complete before we finish gathering.
+ AssertCheckingReached();
+}
+
+TEST_F(WebRtcIceConnectTest, TestSendReceive) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ Connect();
+ SendReceive();
+}
+
+TEST_F(WebRtcIceConnectTest, TestSendReceiveTcp) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = true;
+ NrIceCtx::InitializeGlobals(config);
+ Init();
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ SetCandidateFilter(IsTcpCandidate);
+ SetExpectedTypes(NrIceCandidate::Type::ICE_HOST,
+ NrIceCandidate::Type::ICE_HOST, kNrIceTransportTcp);
+ Connect();
+ SendReceive();
+}
+
+// TCP SO tests works on localhost only with delay applied:
+// tc qdisc add dev lo root netem delay 10ms
+TEST_F(WebRtcIceConnectTest, DISABLED_TestSendReceiveTcpSo) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = true;
+ NrIceCtx::InitializeGlobals(config);
+ Init();
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ SetCandidateFilter(IsTcpSoCandidate);
+ SetExpectedTypes(NrIceCandidate::Type::ICE_HOST,
+ NrIceCandidate::Type::ICE_HOST, kNrIceTransportTcp);
+ Connect();
+ SendReceive();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConsent) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ SetupAndCheckConsent();
+ PR_Sleep(1500);
+ AssertConsentRefresh();
+ SendReceive();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConsentTcp) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = true;
+ NrIceCtx::InitializeGlobals(config);
+ Init();
+ AddStream(1);
+ SetCandidateFilter(IsTcpCandidate);
+ SetExpectedTypes(NrIceCandidate::Type::ICE_HOST,
+ NrIceCandidate::Type::ICE_HOST, kNrIceTransportTcp);
+ SetupAndCheckConsent();
+ PR_Sleep(1500);
+ AssertConsentRefresh();
+ SendReceive();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConsentIntermittent) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ SetupAndCheckConsent();
+ p1_->SetBlockStun(true);
+ p2_->SetBlockStun(true);
+ WaitForDisconnected();
+ AssertConsentRefresh(CONSENT_STALE);
+ SendReceive();
+ p1_->SetBlockStun(false);
+ p2_->SetBlockStun(false);
+ WaitForConnected();
+ AssertConsentRefresh();
+ SendReceive();
+ p1_->SetBlockStun(true);
+ p2_->SetBlockStun(true);
+ WaitForDisconnected();
+ AssertConsentRefresh(CONSENT_STALE);
+ SendReceive();
+ p1_->SetBlockStun(false);
+ p2_->SetBlockStun(false);
+ WaitForConnected();
+ AssertConsentRefresh();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConsentTimeout) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ SetupAndCheckConsent();
+ p1_->SetBlockStun(true);
+ p2_->SetBlockStun(true);
+ WaitForDisconnected();
+ AssertConsentRefresh(CONSENT_STALE);
+ SendReceive();
+ WaitForFailed();
+ AssertConsentRefresh(CONSENT_EXPIRED);
+ SendFailure();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConsentDelayed) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ SetupAndCheckConsent();
+ /* Note: We don't have a list of STUN transaction IDs of the previously timed
+ out consent requests. Thus responses after sending the next consent
+ request are ignored. */
+ p1_->SetStunResponseDelay(200);
+ p2_->SetStunResponseDelay(200);
+ PR_Sleep(1000);
+ AssertConsentRefresh();
+ SendReceive();
+}
+
+TEST_F(WebRtcIceConnectTest, TestNetworkForcedOfflineAndRecovery) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ SetupAndCheckConsent();
+ p1_->ChangeNetworkStateToOffline();
+ ASSERT_TRUE_WAIT(p1_->ice_connected() == 0, kDefaultTimeout);
+ // Next round of consent check should switch it back to online
+ ASSERT_TRUE_WAIT(p1_->ice_connected(), kDefaultTimeout);
+}
+
+TEST_F(WebRtcIceConnectTest, TestNetworkForcedOfflineTwice) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ SetupAndCheckConsent();
+ p2_->ChangeNetworkStateToOffline();
+ ASSERT_TRUE_WAIT(p2_->ice_connected() == 0, kDefaultTimeout);
+ p2_->ChangeNetworkStateToOffline();
+ ASSERT_TRUE_WAIT(p2_->ice_connected() == 0, kDefaultTimeout);
+}
+
+TEST_F(WebRtcIceConnectTest, TestNetworkOnlineDoesntChangeState) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ SetupAndCheckConsent();
+ p2_->ChangeNetworkStateToOnline();
+ ASSERT_TRUE(p2_->ice_connected());
+ PR_Sleep(1500);
+ p2_->ChangeNetworkStateToOnline();
+ ASSERT_TRUE(p2_->ice_connected());
+}
+
+TEST_F(WebRtcIceConnectTest, TestNetworkOnlineTriggersConsent) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ // Let's emulate audio + video w/o rtcp-mux
+ AddStream(2);
+ AddStream(2);
+ SetupAndCheckConsent();
+ p1_->ChangeNetworkStateToOffline();
+ p1_->SetBlockStun(true);
+ ASSERT_TRUE_WAIT(p1_->ice_connected() == 0, kDefaultTimeout);
+ PR_Sleep(1500);
+ ASSERT_TRUE(p1_->ice_connected() == 0);
+ p1_->SetBlockStun(false);
+ p1_->ChangeNetworkStateToOnline();
+ ASSERT_TRUE_WAIT(p1_->ice_connected(), 500);
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectTurn) {
+ if (turn_server_.empty()) return;
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_,
+ turn_password_);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ Connect();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectTurnWithDelay) {
+ if (turn_server_.empty()) return;
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_,
+ turn_password_);
+ SetCandidateFilter(SabotageHostCandidateAndDropReflexive);
+ AddStream(1);
+ p1_->Gather();
+ PR_Sleep(500);
+ p2_->Gather();
+ ConnectTrickle(TRICKLE_REAL);
+ WaitForGather();
+ WaitForConnectedStreams();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectTurnWithNormalTrickleDelay) {
+ if (turn_server_.empty()) return;
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_,
+ turn_password_);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ ConnectTrickle();
+ RealisticTrickleDelay(p1_->ControlTrickle(0));
+ RealisticTrickleDelay(p2_->ControlTrickle(0));
+
+ WaitForConnected();
+ AssertCheckingReached();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectTurnWithNormalTrickleDelayOneSided) {
+ if (turn_server_.empty()) return;
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_,
+ turn_password_);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ ConnectTrickle();
+ RealisticTrickleDelay(p1_->ControlTrickle(0));
+ p2_->SimulateTrickle(0);
+
+ WaitForConnected();
+ AssertCheckingReached();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectTurnWithLargeTrickleDelay) {
+ if (turn_server_.empty()) return;
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_,
+ turn_password_);
+ SetCandidateFilter(SabotageHostCandidateAndDropReflexive);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ ConnectTrickle();
+ // Trickle host candidates immediately, but delay relay candidates
+ DelayRelayCandidates(p1_->ControlTrickle(0), 3700);
+ DelayRelayCandidates(p2_->ControlTrickle(0), 3700);
+
+ WaitForConnected();
+ AssertCheckingReached();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectTurnTcp) {
+ if (turn_server_.empty()) return;
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_,
+ turn_password_, kNrIceTransportTcp);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ Connect();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectTurnOnly) {
+ if (turn_server_.empty()) return;
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_,
+ turn_password_);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ SetCandidateFilter(IsRelayCandidate);
+ SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
+ NrIceCandidate::Type::ICE_RELAYED);
+ Connect();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectTurnTcpOnly) {
+ if (turn_server_.empty()) return;
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_,
+ turn_password_, kNrIceTransportTcp);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ SetCandidateFilter(IsRelayCandidate);
+ SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
+ NrIceCandidate::Type::ICE_RELAYED, kNrIceTransportTcp);
+ Connect();
+}
+
+TEST_F(WebRtcIceConnectTest, TestSendReceiveTurnOnly) {
+ if (turn_server_.empty()) return;
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_,
+ turn_password_);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ SetCandidateFilter(IsRelayCandidate);
+ SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
+ NrIceCandidate::Type::ICE_RELAYED);
+ Connect();
+ SendReceive();
+}
+
+TEST_F(WebRtcIceConnectTest, TestSendReceiveTurnTcpOnly) {
+ if (turn_server_.empty()) return;
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ SetTurnServer(turn_server_, kDefaultStunServerPort, turn_user_,
+ turn_password_, kNrIceTransportTcp);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ SetCandidateFilter(IsRelayCandidate);
+ SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
+ NrIceCandidate::Type::ICE_RELAYED, kNrIceTransportTcp);
+ Connect();
+ SendReceive();
+}
+
+TEST_F(WebRtcIceConnectTest, TestSendReceiveTurnBothOnly) {
+ if (turn_server_.empty()) return;
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ std::vector<NrIceTurnServer> turn_servers;
+ std::vector<unsigned char> password_vec(turn_password_.begin(),
+ turn_password_.end());
+ turn_servers.push_back(
+ *NrIceTurnServer::Create(turn_server_, kDefaultStunServerPort, turn_user_,
+ password_vec, kNrIceTransportTcp));
+ turn_servers.push_back(
+ *NrIceTurnServer::Create(turn_server_, kDefaultStunServerPort, turn_user_,
+ password_vec, kNrIceTransportUdp));
+ SetTurnServers(turn_servers);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ SetCandidateFilter(IsRelayCandidate);
+ // UDP is preferred.
+ SetExpectedTypes(NrIceCandidate::Type::ICE_RELAYED,
+ NrIceCandidate::Type::ICE_RELAYED, kNrIceTransportUdp);
+ Connect();
+ SendReceive();
+}
+
+TEST_F(WebRtcIceConnectTest, TestConnectShutdownOneSide) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ ConnectThenDelete();
+}
+
+TEST_F(WebRtcIceConnectTest, TestPollCandPairsBeforeConnect) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+
+ std::vector<NrIceCandidatePair> pairs;
+ nsresult res = p1_->GetCandidatePairs(0, &pairs);
+ // There should be no candidate pairs prior to calling Connect()
+ ASSERT_EQ(NS_OK, res);
+ ASSERT_EQ(0U, pairs.size());
+
+ res = p2_->GetCandidatePairs(0, &pairs);
+ ASSERT_EQ(NS_OK, res);
+ ASSERT_EQ(0U, pairs.size());
+}
+
+TEST_F(WebRtcIceConnectTest, TestPollCandPairsAfterConnect) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ Connect();
+
+ std::vector<NrIceCandidatePair> pairs;
+ nsresult r = p1_->GetCandidatePairs(0, &pairs);
+ ASSERT_EQ(NS_OK, r);
+ // How detailed of a check do we want to do here? If the turn server is
+ // functioning, we'll get at least two pairs, but this is probably not
+ // something we should assume.
+ ASSERT_NE(0U, pairs.size());
+ ASSERT_TRUE(p1_->CandidatePairsPriorityDescending(pairs));
+ ASSERT_TRUE(ContainsSucceededPair(pairs));
+ pairs.clear();
+
+ r = p2_->GetCandidatePairs(0, &pairs);
+ ASSERT_EQ(NS_OK, r);
+ ASSERT_NE(0U, pairs.size());
+ ASSERT_TRUE(p2_->CandidatePairsPriorityDescending(pairs));
+ ASSERT_TRUE(ContainsSucceededPair(pairs));
+}
+
+// TODO Bug 1259842 - disabled until we find a better way to handle two
+// candidates from different RFC1918 ranges
+TEST_F(WebRtcIceConnectTest, DISABLED_TestHostCandPairingFilter) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ Init(false);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ SetCandidateFilter(IsIpv4Candidate);
+
+ int host_net = p1_->GetCandidatesPrivateIpv4Range(0);
+ if (host_net <= 0) {
+ // TODO bug 1226838: make this work with multiple private IPs
+ FAIL() << "This test needs exactly one private IPv4 host candidate to work"
+ << std::endl;
+ }
+
+ ConnectTrickle();
+ AddNonPairableCandidates(p1_->ControlTrickle(0), p1_.get(), 0, host_net,
+ test_utils_);
+ AddNonPairableCandidates(p2_->ControlTrickle(0), p2_.get(), 0, host_net,
+ test_utils_);
+
+ std::vector<NrIceCandidatePair> pairs;
+ p1_->GetCandidatePairs(0, &pairs);
+ for (auto p : pairs) {
+ std::cerr << "Verifying pair:" << std::endl;
+ p1_->DumpCandidatePair(p);
+ nr_transport_addr addr;
+ nr_str_port_to_transport_addr(p.local.local_addr.host.c_str(), 0,
+ IPPROTO_UDP, &addr);
+ ASSERT_TRUE(nr_transport_addr_get_private_addr_range(&addr) == host_net);
+ nr_str_port_to_transport_addr(p.remote.cand_addr.host.c_str(), 0,
+ IPPROTO_UDP, &addr);
+ ASSERT_TRUE(nr_transport_addr_get_private_addr_range(&addr) == host_net);
+ }
+}
+
+// TODO Bug 1226838 - See Comment 2 - this test can't work as written
+TEST_F(WebRtcIceConnectTest, DISABLED_TestSrflxCandPairingFilter) {
+ if (stun_server_address_.empty()) {
+ return;
+ }
+
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ Init(false);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+ SetCandidateFilter(IsSrflxCandidate);
+
+ if (p1_->GetCandidatesPrivateIpv4Range(0) <= 0) {
+ // TODO bug 1226838: make this work with public IP addresses
+ std::cerr << "Don't run this test at IETF meetings!" << std::endl;
+ FAIL() << "This test needs one private IPv4 host candidate to work"
+ << std::endl;
+ }
+
+ ConnectTrickle();
+ SimulateTrickleP1(0);
+ SimulateTrickleP2(0);
+
+ std::vector<NrIceCandidatePair> pairs;
+ p1_->GetCandidatePairs(0, &pairs);
+ for (auto p : pairs) {
+ std::cerr << "Verifying P1 pair:" << std::endl;
+ p1_->DumpCandidatePair(p);
+ nr_transport_addr addr;
+ nr_str_port_to_transport_addr(p.local.local_addr.host.c_str(), 0,
+ IPPROTO_UDP, &addr);
+ ASSERT_TRUE(nr_transport_addr_get_private_addr_range(&addr) != 0);
+ nr_str_port_to_transport_addr(p.remote.cand_addr.host.c_str(), 0,
+ IPPROTO_UDP, &addr);
+ ASSERT_TRUE(nr_transport_addr_get_private_addr_range(&addr) == 0);
+ }
+ p2_->GetCandidatePairs(0, &pairs);
+ for (auto p : pairs) {
+ std::cerr << "Verifying P2 pair:" << std::endl;
+ p2_->DumpCandidatePair(p);
+ nr_transport_addr addr;
+ nr_str_port_to_transport_addr(p.local.local_addr.host.c_str(), 0,
+ IPPROTO_UDP, &addr);
+ ASSERT_TRUE(nr_transport_addr_get_private_addr_range(&addr) != 0);
+ nr_str_port_to_transport_addr(p.remote.cand_addr.host.c_str(), 0,
+ IPPROTO_UDP, &addr);
+ ASSERT_TRUE(nr_transport_addr_get_private_addr_range(&addr) == 0);
+ }
+}
+
+TEST_F(WebRtcIceConnectTest, TestPollCandPairsDuringConnect) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+
+ p2_->Connect(p1_.get(), TRICKLE_NONE, false);
+ p1_->Connect(p2_.get(), TRICKLE_NONE, false);
+
+ std::vector<NrIceCandidatePair> pairs1;
+ std::vector<NrIceCandidatePair> pairs2;
+
+ p1_->StartChecks();
+ p1_->UpdateAndValidateCandidatePairs(0, &pairs1);
+ p2_->UpdateAndValidateCandidatePairs(0, &pairs2);
+
+ p2_->StartChecks();
+ p1_->UpdateAndValidateCandidatePairs(0, &pairs1);
+ p2_->UpdateAndValidateCandidatePairs(0, &pairs2);
+
+ WaitForConnectedStreams();
+ p1_->UpdateAndValidateCandidatePairs(0, &pairs1);
+ p2_->UpdateAndValidateCandidatePairs(0, &pairs2);
+ ASSERT_TRUE(ContainsSucceededPair(pairs1));
+ ASSERT_TRUE(ContainsSucceededPair(pairs2));
+}
+
+TEST_F(WebRtcIceConnectTest, TestRLogConnector) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ ASSERT_TRUE(Gather());
+
+ p2_->Connect(p1_.get(), TRICKLE_NONE, false);
+ p1_->Connect(p2_.get(), TRICKLE_NONE, false);
+
+ std::vector<NrIceCandidatePair> pairs1;
+ std::vector<NrIceCandidatePair> pairs2;
+
+ p1_->StartChecks();
+ p1_->UpdateAndValidateCandidatePairs(0, &pairs1);
+ p2_->UpdateAndValidateCandidatePairs(0, &pairs2);
+
+ p2_->StartChecks();
+ p1_->UpdateAndValidateCandidatePairs(0, &pairs1);
+ p2_->UpdateAndValidateCandidatePairs(0, &pairs2);
+
+ WaitForConnectedStreams();
+ p1_->UpdateAndValidateCandidatePairs(0, &pairs1);
+ p2_->UpdateAndValidateCandidatePairs(0, &pairs2);
+ ASSERT_TRUE(ContainsSucceededPair(pairs1));
+ ASSERT_TRUE(ContainsSucceededPair(pairs2));
+
+ for (auto& p : pairs1) {
+ std::deque<std::string> logs;
+ std::string substring("CAND-PAIR(");
+ substring += p.codeword;
+ RLogConnector::GetInstance()->Filter(substring, 0, &logs);
+ ASSERT_NE(0U, logs.size());
+ }
+
+ for (auto& p : pairs2) {
+ std::deque<std::string> logs;
+ std::string substring("CAND-PAIR(");
+ substring += p.codeword;
+ RLogConnector::GetInstance()->Filter(substring, 0, &logs);
+ ASSERT_NE(0U, logs.size());
+ }
+}
+
+// Verify that a bogus candidate doesn't cause crashes on the
+// main thread. See bug 856433.
+TEST_F(WebRtcIceConnectTest, TestBogusCandidate) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ Gather();
+ ConnectTrickle();
+ p1_->ParseCandidate(0, kBogusIceCandidate, "");
+
+ std::vector<NrIceCandidatePair> pairs;
+ nsresult res = p1_->GetCandidatePairs(0, &pairs);
+ ASSERT_EQ(NS_OK, res);
+ ASSERT_EQ(0U, pairs.size());
+}
+
+TEST_F(WebRtcIceConnectTest, TestNonMDNSCandidate) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ Gather();
+ ConnectTrickle();
+ p1_->ParseCandidate(0, kUnreachableHostIceCandidate, "");
+
+ std::vector<NrIceCandidatePair> pairs;
+ nsresult res = p1_->GetCandidatePairs(0, &pairs);
+ ASSERT_EQ(NS_OK, res);
+ ASSERT_EQ(1U, pairs.size());
+ ASSERT_EQ(pairs[0].remote.mdns_addr, "");
+}
+
+TEST_F(WebRtcIceConnectTest, TestMDNSCandidate) {
+ NrIceCtx::GlobalConfig config;
+ config.mTcpEnabled = false;
+ NrIceCtx::InitializeGlobals(config);
+ AddStream(1);
+ Gather();
+ ConnectTrickle();
+ p1_->ParseCandidate(0, kUnreachableHostIceCandidate, "host.local");
+
+ std::vector<NrIceCandidatePair> pairs;
+ nsresult res = p1_->GetCandidatePairs(0, &pairs);
+ ASSERT_EQ(NS_OK, res);
+ ASSERT_EQ(1U, pairs.size());
+ ASSERT_EQ(pairs[0].remote.mdns_addr, "host.local");
+}
+
+TEST_F(WebRtcIcePrioritizerTest, TestPrioritizer) {
+ SetPriorizer(::mozilla::CreateInterfacePrioritizer());
+
+ AddInterface("0", NR_INTERFACE_TYPE_VPN, 100); // unknown vpn
+ AddInterface("1", NR_INTERFACE_TYPE_VPN | NR_INTERFACE_TYPE_WIRED,
+ 100); // wired vpn
+ AddInterface("2", NR_INTERFACE_TYPE_VPN | NR_INTERFACE_TYPE_WIFI,
+ 100); // wifi vpn
+ AddInterface("3", NR_INTERFACE_TYPE_VPN | NR_INTERFACE_TYPE_MOBILE,
+ 100); // wifi vpn
+ AddInterface("4", NR_INTERFACE_TYPE_WIRED, 1000); // wired, high speed
+ AddInterface("5", NR_INTERFACE_TYPE_WIRED, 10); // wired, low speed
+ AddInterface("6", NR_INTERFACE_TYPE_WIFI, 10); // wifi, low speed
+ AddInterface("7", NR_INTERFACE_TYPE_WIFI, 1000); // wifi, high speed
+ AddInterface("8", NR_INTERFACE_TYPE_MOBILE, 10); // mobile, low speed
+ AddInterface("9", NR_INTERFACE_TYPE_MOBILE, 1000); // mobile, high speed
+ AddInterface("10", NR_INTERFACE_TYPE_UNKNOWN, 10); // unknown, low speed
+ AddInterface("11", NR_INTERFACE_TYPE_UNKNOWN, 1000); // unknown, high speed
+
+ // expected preference "4" > "5" > "1" > "7" > "6" > "2" > "9" > "8" > "3" >
+ // "11" > "10" > "0"
+
+ HasLowerPreference("0", "10");
+ HasLowerPreference("10", "11");
+ HasLowerPreference("11", "3");
+ HasLowerPreference("3", "8");
+ HasLowerPreference("8", "9");
+ HasLowerPreference("9", "2");
+ HasLowerPreference("2", "6");
+ HasLowerPreference("6", "7");
+ HasLowerPreference("7", "1");
+ HasLowerPreference("1", "5");
+ HasLowerPreference("5", "4");
+}
+
+TEST_F(WebRtcIcePacketFilterTest, TestSendNonStunPacket) {
+ const unsigned char data[] = "12345abcde";
+ TestOutgoing(data, sizeof(data), 123, 45, false);
+ TestOutgoingTcp(data, sizeof(data), false);
+}
+
+TEST_F(WebRtcIcePacketFilterTest, TestRecvNonStunPacket) {
+ const unsigned char data[] = "12345abcde";
+ TestIncoming(data, sizeof(data), 123, 45, false);
+ TestIncomingTcp(data, sizeof(data), true);
+}
+
+TEST_F(WebRtcIcePacketFilterTest, TestSendStunPacket) {
+ nr_stun_message* msg;
+ ASSERT_EQ(0, nr_stun_build_req_no_auth(nullptr, &msg));
+ msg->header.type = NR_STUN_MSG_BINDING_REQUEST;
+ ASSERT_EQ(0, nr_stun_encode_message(msg));
+ TestOutgoing(msg->buffer, msg->length, 123, 45, true);
+ TestOutgoingTcp(msg->buffer, msg->length, true);
+ TestOutgoingTcpFramed(msg->buffer, msg->length, true);
+ ASSERT_EQ(0, nr_stun_message_destroy(&msg));
+}
+
+TEST_F(WebRtcIcePacketFilterTest, TestRecvStunPacketWithoutAPendingId) {
+ nr_stun_message* msg;
+ ASSERT_EQ(0, nr_stun_build_req_no_auth(nullptr, &msg));
+
+ msg->header.id.octet[0] = 1;
+ msg->header.type = NR_STUN_MSG_BINDING_REQUEST;
+ ASSERT_EQ(0, nr_stun_encode_message(msg));
+ TestOutgoing(msg->buffer, msg->length, 123, 45, true);
+ TestOutgoingTcp(msg->buffer, msg->length, true);
+
+ msg->header.id.octet[0] = 0;
+ msg->header.type = NR_STUN_MSG_BINDING_RESPONSE;
+ ASSERT_EQ(0, nr_stun_encode_message(msg));
+ TestIncoming(msg->buffer, msg->length, 123, 45, true);
+ TestIncomingTcp(msg->buffer, msg->length, true);
+
+ ASSERT_EQ(0, nr_stun_message_destroy(&msg));
+}
+
+TEST_F(WebRtcIcePacketFilterTest, TestRecvStunBindingRequestWithoutAPendingId) {
+ nr_stun_message* msg;
+ ASSERT_EQ(0, nr_stun_build_req_no_auth(nullptr, &msg));
+
+ msg->header.id.octet[0] = 1;
+ msg->header.type = NR_STUN_MSG_BINDING_REQUEST;
+ ASSERT_EQ(0, nr_stun_encode_message(msg));
+ TestIncoming(msg->buffer, msg->length, 123, 45, true);
+ TestIncomingTcp(msg->buffer, msg->length, true);
+
+ msg->header.id.octet[0] = 1;
+ msg->header.type = NR_STUN_MSG_BINDING_RESPONSE;
+ ASSERT_EQ(0, nr_stun_encode_message(msg));
+ TestOutgoing(msg->buffer, msg->length, 123, 45, true);
+ TestOutgoingTcp(msg->buffer, msg->length, true);
+
+ ASSERT_EQ(0, nr_stun_message_destroy(&msg));
+}
+
+TEST_F(WebRtcIcePacketFilterTest,
+ TestRecvStunPacketWithoutAPendingIdTcpFramed) {
+ nr_stun_message* msg;
+ ASSERT_EQ(0, nr_stun_build_req_no_auth(nullptr, &msg));
+
+ msg->header.id.octet[0] = 1;
+ msg->header.type = NR_STUN_MSG_BINDING_REQUEST;
+ ASSERT_EQ(0, nr_stun_encode_message(msg));
+ TestOutgoingTcpFramed(msg->buffer, msg->length, true);
+
+ msg->header.id.octet[0] = 0;
+ msg->header.type = NR_STUN_MSG_BINDING_RESPONSE;
+ ASSERT_EQ(0, nr_stun_encode_message(msg));
+ TestIncomingTcpFramed(msg->buffer, msg->length, true);
+
+ ASSERT_EQ(0, nr_stun_message_destroy(&msg));
+}
+
+TEST_F(WebRtcIcePacketFilterTest, TestRecvStunPacketWithoutAPendingAddress) {
+ nr_stun_message* msg;
+ ASSERT_EQ(0, nr_stun_build_req_no_auth(nullptr, &msg));
+
+ msg->header.type = NR_STUN_MSG_BINDING_REQUEST;
+ ASSERT_EQ(0, nr_stun_encode_message(msg));
+ TestOutgoing(msg->buffer, msg->length, 123, 45, true);
+ // nothing to test here for the TCP filter
+
+ msg->header.type = NR_STUN_MSG_BINDING_RESPONSE;
+ ASSERT_EQ(0, nr_stun_encode_message(msg));
+ TestIncoming(msg->buffer, msg->length, 123, 46, false);
+ TestIncoming(msg->buffer, msg->length, 124, 45, false);
+
+ ASSERT_EQ(0, nr_stun_message_destroy(&msg));
+}
+
+TEST_F(WebRtcIcePacketFilterTest, TestRecvStunPacketWithPendingIdAndAddress) {
+ nr_stun_message* msg;
+ ASSERT_EQ(0, nr_stun_build_req_no_auth(nullptr, &msg));
+
+ msg->header.type = NR_STUN_MSG_BINDING_REQUEST;
+ ASSERT_EQ(0, nr_stun_encode_message(msg));
+ TestOutgoing(msg->buffer, msg->length, 123, 45, true);
+ TestOutgoingTcp(msg->buffer, msg->length, true);
+
+ msg->header.type = NR_STUN_MSG_BINDING_RESPONSE;
+ ASSERT_EQ(0, nr_stun_encode_message(msg));
+ TestIncoming(msg->buffer, msg->length, 123, 45, true);
+ TestIncomingTcp(msg->buffer, msg->length, true);
+
+ // Test whitelist by filtering non-stun packets.
+ const unsigned char data[] = "12345abcde";
+
+ // 123:45 is white-listed.
+ TestOutgoing(data, sizeof(data), 123, 45, true);
+ TestOutgoingTcp(data, sizeof(data), true);
+ TestIncoming(data, sizeof(data), 123, 45, true);
+ TestIncomingTcp(data, sizeof(data), true);
+
+ // Indications pass as well.
+ msg->header.type = NR_STUN_MSG_BINDING_INDICATION;
+ ASSERT_EQ(0, nr_stun_encode_message(msg));
+ TestOutgoing(msg->buffer, msg->length, 123, 45, true);
+ TestOutgoingTcp(msg->buffer, msg->length, true);
+ TestIncoming(msg->buffer, msg->length, 123, 45, true);
+ TestIncomingTcp(msg->buffer, msg->length, true);
+
+ // Packets from and to other address are still disallowed.
+ // Note: this doesn't apply for TCP connections
+ TestOutgoing(data, sizeof(data), 123, 46, false);
+ TestIncoming(data, sizeof(data), 123, 46, false);
+ TestOutgoing(data, sizeof(data), 124, 45, false);
+ TestIncoming(data, sizeof(data), 124, 45, false);
+
+ ASSERT_EQ(0, nr_stun_message_destroy(&msg));
+}
+
+TEST_F(WebRtcIcePacketFilterTest, TestRecvStunPacketWithPendingIdTcpFramed) {
+ nr_stun_message* msg;
+ ASSERT_EQ(0, nr_stun_build_req_no_auth(nullptr, &msg));
+
+ msg->header.type = NR_STUN_MSG_BINDING_REQUEST;
+ ASSERT_EQ(0, nr_stun_encode_message(msg));
+ TestOutgoingTcpFramed(msg->buffer, msg->length, true);
+
+ msg->header.type = NR_STUN_MSG_BINDING_RESPONSE;
+ ASSERT_EQ(0, nr_stun_encode_message(msg));
+ TestIncomingTcpFramed(msg->buffer, msg->length, true);
+
+ // Test whitelist by filtering non-stun packets.
+ const unsigned char data[] = "12345abcde";
+
+ TestOutgoingTcpFramed(data, sizeof(data), true);
+ TestIncomingTcpFramed(data, sizeof(data), true);
+
+ ASSERT_EQ(0, nr_stun_message_destroy(&msg));
+}
+
+TEST_F(WebRtcIcePacketFilterTest, TestSendNonRequestStunPacket) {
+ nr_stun_message* msg;
+ ASSERT_EQ(0, nr_stun_build_req_no_auth(nullptr, &msg));
+
+ msg->header.type = NR_STUN_MSG_BINDING_RESPONSE;
+ ASSERT_EQ(0, nr_stun_encode_message(msg));
+ TestOutgoing(msg->buffer, msg->length, 123, 45, false);
+ TestOutgoingTcp(msg->buffer, msg->length, false);
+
+ // Send a packet so we allow the incoming request.
+ msg->header.type = NR_STUN_MSG_BINDING_REQUEST;
+ ASSERT_EQ(0, nr_stun_encode_message(msg));
+ TestOutgoing(msg->buffer, msg->length, 123, 45, true);
+ TestOutgoingTcp(msg->buffer, msg->length, true);
+
+ // This packet makes us able to send a response.
+ msg->header.type = NR_STUN_MSG_BINDING_REQUEST;
+ ASSERT_EQ(0, nr_stun_encode_message(msg));
+ TestIncoming(msg->buffer, msg->length, 123, 45, true);
+ TestIncomingTcp(msg->buffer, msg->length, true);
+
+ msg->header.type = NR_STUN_MSG_BINDING_RESPONSE;
+ ASSERT_EQ(0, nr_stun_encode_message(msg));
+ TestOutgoing(msg->buffer, msg->length, 123, 45, true);
+ TestOutgoingTcp(msg->buffer, msg->length, true);
+
+ ASSERT_EQ(0, nr_stun_message_destroy(&msg));
+}
+
+TEST_F(WebRtcIcePacketFilterTest, TestRecvDataPacketWithAPendingAddress) {
+ nr_stun_message* msg;
+ ASSERT_EQ(0, nr_stun_build_req_no_auth(nullptr, &msg));
+
+ msg->header.type = NR_STUN_MSG_BINDING_REQUEST;
+ ASSERT_EQ(0, nr_stun_encode_message(msg));
+ TestOutgoing(msg->buffer, msg->length, 123, 45, true);
+ TestOutgoingTcp(msg->buffer, msg->length, true);
+
+ const unsigned char data[] = "12345abcde";
+ TestIncoming(data, sizeof(data), 123, 45, true);
+ TestIncomingTcp(data, sizeof(data), true);
+
+ ASSERT_EQ(0, nr_stun_message_destroy(&msg));
+}
+
+TEST(WebRtcIceInternalsTest, TestAddBogusAttribute)
+{
+ nr_stun_message* req;
+ ASSERT_EQ(0, nr_stun_message_create(&req));
+ Data* data;
+ ASSERT_EQ(0, r_data_alloc(&data, 3000));
+ memset(data->data, 'A', data->len);
+ ASSERT_TRUE(nr_stun_message_add_message_integrity_attribute(req, data));
+ ASSERT_EQ(0, r_data_destroy(&data));
+ ASSERT_EQ(0, nr_stun_message_destroy(&req));
+}
diff --git a/dom/media/webrtc/transport/test/moz.build b/dom/media/webrtc/transport/test/moz.build
new file mode 100644
index 0000000000..69d3a587a5
--- /dev/null
+++ b/dom/media/webrtc/transport/test/moz.build
@@ -0,0 +1,104 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+if CONFIG["OS_TARGET"] != "WINNT":
+
+ if CONFIG["OS_TARGET"] != "Android":
+ SOURCES += [
+ "ice_unittest.cpp",
+ ]
+
+ SOURCES += [
+ "buffered_stun_socket_unittest.cpp",
+ "multi_tcp_socket_unittest.cpp",
+ "nrappkit_unittest.cpp",
+ "proxy_tunnel_socket_unittest.cpp",
+ "rlogconnector_unittest.cpp",
+ "runnable_utils_unittest.cpp",
+ "simpletokenbucket_unittest.cpp",
+ "sockettransportservice_unittest.cpp",
+ "stunserver.cpp",
+ "test_nr_socket_ice_unittest.cpp",
+ "test_nr_socket_unittest.cpp",
+ "TestSyncRunnable.cpp",
+ "transport_unittests.cpp",
+ "turn_unittest.cpp",
+ "webrtcproxychannel_unittest.cpp",
+ ]
+
+ if CONFIG["MOZ_SCTP"]:
+ SOURCES += [
+ "sctp_unittest.cpp",
+ ]
+
+
+for var in ("HAVE_STRDUP", "NR_SOCKET_IS_VOID_PTR", "SCTP_DEBUG"):
+ DEFINES[var] = True
+
+if CONFIG["OS_TARGET"] == "Android":
+ DEFINES["LINUX"] = True
+ DEFINES["ANDROID"] = True
+ LOCAL_INCLUDES += [
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/port/android/include",
+ ]
+
+if CONFIG["OS_TARGET"] == "Linux":
+ DEFINES["LINUX"] = True
+ LOCAL_INCLUDES += [
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/port/linux/include",
+ ]
+
+if CONFIG["OS_TARGET"] == "Darwin":
+ LOCAL_INCLUDES += [
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/port/darwin/include",
+ ]
+
+if CONFIG["OS_TARGET"] in ("DragonFly", "FreeBSD", "NetBSD", "OpenBSD"):
+ if CONFIG["OS_TARGET"] == "Darwin":
+ DEFINES["DARWIN"] = True
+ else:
+ DEFINES["BSD"] = True
+ LOCAL_INCLUDES += [
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/port/darwin/include",
+ ]
+
+# SCTP DEFINES
+if CONFIG["OS_TARGET"] == "WINNT":
+ DEFINES["WIN"] = True
+ # for stun.h
+ DEFINES["WIN32"] = True
+ DEFINES["__Userspace_os_Windows"] = 1
+else:
+ # Works for Darwin, Linux, Android. Probably doesn't work for others.
+ DEFINES["__Userspace_os_%s" % CONFIG["OS_TARGET"]] = 1
+
+if CONFIG["OS_TARGET"] in ("Darwin", "Android"):
+ DEFINES["GTEST_USE_OWN_TR1_TUPLE"] = 1
+
+LOCAL_INCLUDES += [
+ "/dom/media/webrtc/transport/",
+ "/dom/media/webrtc/transport/third_party/",
+ "/dom/media/webrtc/transport/third_party/nICEr/src/crypto",
+ "/dom/media/webrtc/transport/third_party/nICEr/src/ice",
+ "/dom/media/webrtc/transport/third_party/nICEr/src/net",
+ "/dom/media/webrtc/transport/third_party/nICEr/src/stun",
+ "/dom/media/webrtc/transport/third_party/nICEr/src/util",
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/event",
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/log",
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/plugin",
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/port/generic/include",
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/registry",
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/share",
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/stats",
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/util/",
+ "/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr",
+ "/netwerk/sctp/src/",
+ "/xpcom/tests/",
+]
+
+FINAL_LIBRARY = "xul-gtest"
diff --git a/dom/media/webrtc/transport/test/mtransport_test_utils.h b/dom/media/webrtc/transport/test/mtransport_test_utils.h
new file mode 100644
index 0000000000..04031c0dc2
--- /dev/null
+++ b/dom/media/webrtc/transport/test/mtransport_test_utils.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+
+#ifndef mtransport_test_utils_h__
+#define mtransport_test_utils_h__
+
+#include "nsCOMPtr.h"
+#include "nsNetCID.h"
+
+#include "nsISerialEventTarget.h"
+#include "nsPISocketTransportService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+
+class MtransportTestUtils {
+ public:
+ MtransportTestUtils() { InitServices(); }
+
+ ~MtransportTestUtils() = default;
+
+ void InitServices() {
+ nsresult rv;
+ sts_target_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ sts_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ nsISerialEventTarget* sts_target() { return sts_target_; }
+
+ nsresult SyncDispatchToSTS(nsIRunnable* aRunnable) {
+ return SyncDispatchToSTS(do_AddRef(aRunnable));
+ }
+ nsresult SyncDispatchToSTS(already_AddRefed<nsIRunnable>&& aRunnable) {
+ return NS_DispatchAndSpinEventLoopUntilComplete(
+ "MtransportTestUtils::SyncDispatchToSts"_ns, sts_target_,
+ std::move(aRunnable));
+ }
+
+ private:
+ nsCOMPtr<nsISerialEventTarget> sts_target_;
+ nsCOMPtr<nsPISocketTransportService> sts_;
+};
+
+#define CHECK_ENVIRONMENT_FLAG(envname) \
+ char* test_flag = getenv(envname); \
+ if (!test_flag || strcmp(test_flag, "1")) { \
+ printf("To run this test set %s=1 in your environment\n", envname); \
+ exit(0); \
+ }
+
+#endif
diff --git a/dom/media/webrtc/transport/test/multi_tcp_socket_unittest.cpp b/dom/media/webrtc/transport/test/multi_tcp_socket_unittest.cpp
new file mode 100644
index 0000000000..d0c3ae6e53
--- /dev/null
+++ b/dom/media/webrtc/transport/test/multi_tcp_socket_unittest.cpp
@@ -0,0 +1,501 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <iostream>
+#include <vector>
+
+#include "mozilla/Atomics.h"
+#include "runnable_utils.h"
+#include "pk11pub.h"
+
+extern "C" {
+#include "nr_api.h"
+#include "nr_socket.h"
+#include "transport_addr.h"
+#include "nr_socket_multi_tcp.h"
+}
+
+#include "stunserver.h"
+
+#include "nricectx.h"
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+#include "gtest_utils.h"
+
+using namespace mozilla;
+
+namespace {
+
+class MultiTcpSocketTest : public MtransportTest {
+ public:
+ MultiTcpSocketTest()
+ : MtransportTest(), socks(3, nullptr), readable(false), ice_ctx_() {}
+
+ void SetUp() {
+ MtransportTest::SetUp();
+
+ NrIceCtx::InitializeGlobals(NrIceCtx::GlobalConfig());
+ ice_ctx_ = NrIceCtx::Create("stun");
+
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableNM(&TestStunTcpServer::GetInstance, AF_INET));
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableNM(&TestStunTcpServer::GetInstance, AF_INET6));
+ }
+
+ void TearDown() {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &MultiTcpSocketTest::Shutdown_s));
+
+ MtransportTest::TearDown();
+ }
+
+ DISALLOW_COPY_ASSIGN(MultiTcpSocketTest);
+
+ static void SockReadable(NR_SOCKET s, int how, void* arg) {
+ MultiTcpSocketTest* obj = static_cast<MultiTcpSocketTest*>(arg);
+ obj->SetReadable(true);
+ }
+
+ void Shutdown_s() {
+ ice_ctx_ = nullptr;
+ for (auto& sock : socks) {
+ nr_socket_destroy(&sock);
+ }
+ }
+
+ static uint16_t GetRandomPort() {
+ uint16_t result;
+ if (PK11_GenerateRandom((unsigned char*)&result, 2) != SECSuccess) {
+ MOZ_ASSERT(false);
+ return 0;
+ }
+ return result;
+ }
+
+ static uint16_t EnsureEphemeral(uint16_t port) {
+ // IANA ephemeral port range (49152 to 65535)
+ return port | 49152;
+ }
+
+ void Create_s(nr_socket_tcp_type tcp_type, std::string stun_server_addr,
+ uint16_t stun_server_port, nr_socket** sock) {
+ nr_transport_addr local;
+ // Get start of port range for test
+ static unsigned short port_s = GetRandomPort();
+ int r;
+
+ if (!stun_server_addr.empty()) {
+ std::vector<NrIceStunServer> stun_servers;
+ UniquePtr<NrIceStunServer> server(NrIceStunServer::Create(
+ stun_server_addr, stun_server_port, kNrIceTransportTcp));
+ stun_servers.push_back(*server);
+
+ ASSERT_TRUE(NS_SUCCEEDED(ice_ctx_->SetStunServers(stun_servers)));
+ }
+
+ r = 1;
+ for (int tries = 10; tries && r; --tries) {
+ r = nr_str_port_to_transport_addr(
+ (char*)"127.0.0.1", EnsureEphemeral(port_s++), IPPROTO_TCP, &local);
+ ASSERT_EQ(0, r);
+
+ r = nr_socket_multi_tcp_create(ice_ctx_->ctx(), nullptr, &local, tcp_type,
+ 1, 2048, sock);
+ }
+
+ ASSERT_EQ(0, r);
+ printf("Creating socket on %s\n", local.as_string);
+ r = nr_socket_multi_tcp_set_readable_cb(
+ *sock, &MultiTcpSocketTest::SockReadable, this);
+ ASSERT_EQ(0, r);
+ }
+
+ nr_socket* Create(nr_socket_tcp_type tcp_type,
+ std::string stun_server_addr = "",
+ uint16_t stun_server_port = 0) {
+ nr_socket* sock = nullptr;
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &MultiTcpSocketTest::Create_s, tcp_type,
+ stun_server_addr, stun_server_port, &sock));
+ return sock;
+ }
+
+ void Listen_s(nr_socket* sock) {
+ nr_transport_addr addr;
+ int r = nr_socket_getaddr(sock, &addr);
+ ASSERT_EQ(0, r);
+ printf("Listening on %s\n", addr.as_string);
+ r = nr_socket_listen(sock, 5);
+ ASSERT_EQ(0, r);
+ }
+
+ void Listen(nr_socket* sock) {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &MultiTcpSocketTest::Listen_s, sock));
+ }
+
+ void Destroy_s(nr_socket* sock) {
+ int r = nr_socket_destroy(&sock);
+ ASSERT_EQ(0, r);
+ }
+
+ void Destroy(nr_socket* sock) {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &MultiTcpSocketTest::Destroy_s, sock));
+ }
+
+ void Connect_s(nr_socket* from, nr_socket* to) {
+ nr_transport_addr addr_to;
+ nr_transport_addr addr_from;
+ int r = nr_socket_getaddr(to, &addr_to);
+ ASSERT_EQ(0, r);
+ r = nr_socket_getaddr(from, &addr_from);
+ ASSERT_EQ(0, r);
+ printf("Connecting from %s to %s\n", addr_from.as_string,
+ addr_to.as_string);
+ r = nr_socket_connect(from, &addr_to);
+ ASSERT_EQ(0, r);
+ }
+
+ void Connect(nr_socket* from, nr_socket* to) {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &MultiTcpSocketTest::Connect_s, from, to));
+ }
+
+ void ConnectSo_s(nr_socket* so1, nr_socket* so2) {
+ nr_transport_addr addr_so1;
+ nr_transport_addr addr_so2;
+ int r = nr_socket_getaddr(so1, &addr_so1);
+ ASSERT_EQ(0, r);
+ r = nr_socket_getaddr(so2, &addr_so2);
+ ASSERT_EQ(0, r);
+ printf("Connecting SO %s <-> %s\n", addr_so1.as_string, addr_so2.as_string);
+ r = nr_socket_connect(so1, &addr_so2);
+ ASSERT_EQ(0, r);
+ r = nr_socket_connect(so2, &addr_so1);
+ ASSERT_EQ(0, r);
+ }
+
+ void ConnectSo(nr_socket* from, nr_socket* to) {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &MultiTcpSocketTest::ConnectSo_s, from, to));
+ }
+
+ void SendDataToAddress_s(nr_socket* from, nr_transport_addr* to,
+ const char* data, size_t len) {
+ nr_transport_addr addr_from;
+
+ int r = nr_socket_getaddr(from, &addr_from);
+ ASSERT_EQ(0, r);
+ printf("Sending %lu bytes %s -> %s\n", (unsigned long)len,
+ addr_from.as_string, to->as_string);
+ r = nr_socket_sendto(from, data, len, 0, to);
+ ASSERT_EQ(0, r);
+ }
+
+ void SendData(nr_socket* from, nr_transport_addr* to, const char* data,
+ size_t len) {
+ test_utils_->SyncDispatchToSTS(WrapRunnable(
+ this, &MultiTcpSocketTest::SendDataToAddress_s, from, to, data, len));
+ }
+
+ void SendDataToSocket_s(nr_socket* from, nr_socket* to, const char* data,
+ size_t len) {
+ nr_transport_addr addr_to;
+
+ int r = nr_socket_getaddr(to, &addr_to);
+ ASSERT_EQ(0, r);
+ SendDataToAddress_s(from, &addr_to, data, len);
+ }
+
+ void SendData(nr_socket* from, nr_socket* to, const char* data, size_t len) {
+ test_utils_->SyncDispatchToSTS(WrapRunnable(
+ this, &MultiTcpSocketTest::SendDataToSocket_s, from, to, data, len));
+ }
+
+ void RecvDataFromAddress_s(nr_transport_addr* expected_from,
+ nr_socket* sent_to, const char* expected_data,
+ size_t expected_len) {
+ SetReadable(false);
+ size_t buflen = expected_len ? expected_len + 1 : 100;
+ char received_data[buflen];
+ nr_transport_addr addr_to;
+ nr_transport_addr retaddr;
+ size_t retlen;
+
+ int r = nr_socket_getaddr(sent_to, &addr_to);
+ ASSERT_EQ(0, r);
+ printf("Receiving %lu bytes %s <- %s\n", (unsigned long)expected_len,
+ addr_to.as_string, expected_from->as_string);
+ r = nr_socket_recvfrom(sent_to, received_data, buflen, &retlen, 0,
+ &retaddr);
+ ASSERT_EQ(0, r);
+ r = nr_transport_addr_cmp(&retaddr, expected_from,
+ NR_TRANSPORT_ADDR_CMP_MODE_ALL);
+ ASSERT_EQ(0, r);
+ // expected_len == 0 means we just expected some data
+ if (expected_len == 0) {
+ ASSERT_GT(retlen, 0U);
+ } else {
+ ASSERT_EQ(expected_len, retlen);
+ r = memcmp(expected_data, received_data, retlen);
+ ASSERT_EQ(0, r);
+ }
+ }
+
+ void RecvData(nr_transport_addr* expected_from, nr_socket* sent_to,
+ const char* expected_data = nullptr, size_t expected_len = 0) {
+ ASSERT_TRUE_WAIT(IsReadable(), 1000);
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &MultiTcpSocketTest::RecvDataFromAddress_s,
+ expected_from, sent_to, expected_data, expected_len));
+ }
+
+ void RecvDataFromSocket_s(nr_socket* expected_from, nr_socket* sent_to,
+ const char* expected_data, size_t expected_len) {
+ nr_transport_addr addr_from;
+
+ int r = nr_socket_getaddr(expected_from, &addr_from);
+ ASSERT_EQ(0, r);
+
+ RecvDataFromAddress_s(&addr_from, sent_to, expected_data, expected_len);
+ }
+
+ void RecvData(nr_socket* expected_from, nr_socket* sent_to,
+ const char* expected_data, size_t expected_len) {
+ ASSERT_TRUE_WAIT(IsReadable(), 1000);
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &MultiTcpSocketTest::RecvDataFromSocket_s,
+ expected_from, sent_to, expected_data, expected_len));
+ }
+
+ void RecvDataFailed_s(nr_socket* sent_to, size_t expected_len,
+ int expected_err) {
+ SetReadable(false);
+ char received_data[expected_len + 1];
+ nr_transport_addr addr_to;
+ nr_transport_addr retaddr;
+ size_t retlen;
+
+ int r = nr_socket_getaddr(sent_to, &addr_to);
+ ASSERT_EQ(0, r);
+ r = nr_socket_recvfrom(sent_to, received_data, expected_len + 1, &retlen, 0,
+ &retaddr);
+ ASSERT_EQ(expected_err, r) << "Expecting receive failure " << expected_err
+ << " on " << addr_to.as_string;
+ }
+
+ void RecvDataFailed(nr_socket* sent_to, size_t expected_len,
+ int expected_err) {
+ ASSERT_TRUE_WAIT(IsReadable(), 1000);
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &MultiTcpSocketTest::RecvDataFailed_s, sent_to,
+ expected_len, expected_err));
+ }
+
+ void TransferData(nr_socket* from, nr_socket* to, const char* data,
+ size_t len) {
+ SendData(from, to, data, len);
+ RecvData(from, to, data, len);
+ }
+
+ protected:
+ bool IsReadable() const { return readable; }
+ void SetReadable(bool r) { readable = r; }
+ std::vector<nr_socket*> socks;
+ Atomic<bool> readable;
+ RefPtr<NrIceCtx> ice_ctx_;
+};
+} // namespace
+
+TEST_F(MultiTcpSocketTest, TestListen) {
+ socks[0] = Create(TCP_TYPE_PASSIVE);
+ Listen(socks[0]);
+}
+
+TEST_F(MultiTcpSocketTest, TestConnect) {
+ socks[0] = Create(TCP_TYPE_PASSIVE);
+ socks[1] = Create(TCP_TYPE_ACTIVE);
+ socks[2] = Create(TCP_TYPE_ACTIVE);
+ Listen(socks[0]);
+ Connect(socks[1], socks[0]);
+ Connect(socks[2], socks[0]);
+}
+
+TEST_F(MultiTcpSocketTest, TestTransmit) {
+ const char data[] = "TestTransmit";
+ socks[0] = Create(TCP_TYPE_ACTIVE);
+ socks[1] = Create(TCP_TYPE_PASSIVE);
+ Listen(socks[1]);
+ Connect(socks[0], socks[1]);
+
+ TransferData(socks[0], socks[1], data, sizeof(data));
+ TransferData(socks[1], socks[0], data, sizeof(data));
+}
+
+TEST_F(MultiTcpSocketTest, TestClosePassive) {
+ const char data[] = "TestClosePassive";
+ socks[0] = Create(TCP_TYPE_ACTIVE);
+ socks[1] = Create(TCP_TYPE_PASSIVE);
+ Listen(socks[1]);
+ Connect(socks[0], socks[1]);
+
+ TransferData(socks[0], socks[1], data, sizeof(data));
+ TransferData(socks[1], socks[0], data, sizeof(data));
+
+ /* We have to destroy as only that calls PR_Close() */
+ std::cerr << "Destructing socket" << std::endl;
+ Destroy(socks[1]);
+
+ RecvDataFailed(socks[0], sizeof(data), R_EOD);
+
+ socks[1] = nullptr;
+}
+
+TEST_F(MultiTcpSocketTest, TestCloseActive) {
+ const char data[] = "TestCloseActive";
+ socks[0] = Create(TCP_TYPE_ACTIVE);
+ socks[1] = Create(TCP_TYPE_PASSIVE);
+ Listen(socks[1]);
+ Connect(socks[0], socks[1]);
+
+ TransferData(socks[0], socks[1], data, sizeof(data));
+ TransferData(socks[1], socks[0], data, sizeof(data));
+
+ /* We have to destroy as only that calls PR_Close() */
+ std::cerr << "Destructing socket" << std::endl;
+ Destroy(socks[0]);
+
+ RecvDataFailed(socks[1], sizeof(data), R_EOD);
+
+ socks[0] = nullptr;
+}
+
+TEST_F(MultiTcpSocketTest, TestTwoSendsBeforeReceives) {
+ const char data1[] = "TestTwoSendsBeforeReceives";
+ const char data2[] = "2nd data";
+ socks[0] = Create(TCP_TYPE_ACTIVE);
+ socks[1] = Create(TCP_TYPE_PASSIVE);
+ Listen(socks[1]);
+ Connect(socks[0], socks[1]);
+
+ SendData(socks[0], socks[1], data1, sizeof(data1));
+ SendData(socks[0], socks[1], data2, sizeof(data2));
+ RecvData(socks[0], socks[1], data1, sizeof(data1));
+ /* ICE TCP framing turns TCP effectively into datagram mode */
+ RecvData(socks[0], socks[1], data2, sizeof(data2));
+}
+
+TEST_F(MultiTcpSocketTest, TestTwoActiveBidirectionalTransmit) {
+ const char data1[] = "TestTwoActiveBidirectionalTransmit";
+ const char data2[] = "ReplyToTheFirstSocket";
+ const char data3[] = "TestMessageFromTheSecondSocket";
+ const char data4[] = "ThisIsAReplyToTheSecondSocket";
+ socks[0] = Create(TCP_TYPE_PASSIVE);
+ socks[1] = Create(TCP_TYPE_ACTIVE);
+ socks[2] = Create(TCP_TYPE_ACTIVE);
+ Listen(socks[0]);
+ Connect(socks[1], socks[0]);
+ Connect(socks[2], socks[0]);
+
+ TransferData(socks[1], socks[0], data1, sizeof(data1));
+ TransferData(socks[0], socks[1], data2, sizeof(data2));
+ TransferData(socks[2], socks[0], data3, sizeof(data3));
+ TransferData(socks[0], socks[2], data4, sizeof(data4));
+}
+
+TEST_F(MultiTcpSocketTest, TestTwoPassiveBidirectionalTransmit) {
+ const char data1[] = "TestTwoPassiveBidirectionalTransmit";
+ const char data2[] = "FirstReply";
+ const char data3[] = "TestTwoPassiveBidirectionalTransmitToTheSecondSock";
+ const char data4[] = "SecondReply";
+ socks[0] = Create(TCP_TYPE_PASSIVE);
+ socks[1] = Create(TCP_TYPE_PASSIVE);
+ socks[2] = Create(TCP_TYPE_ACTIVE);
+ Listen(socks[0]);
+ Listen(socks[1]);
+ Connect(socks[2], socks[0]);
+ Connect(socks[2], socks[1]);
+
+ TransferData(socks[2], socks[0], data1, sizeof(data1));
+ TransferData(socks[0], socks[2], data2, sizeof(data2));
+ TransferData(socks[2], socks[1], data3, sizeof(data3));
+ TransferData(socks[1], socks[2], data4, sizeof(data4));
+}
+
+TEST_F(MultiTcpSocketTest, TestActivePassiveWithStunServerMockup) {
+ /* Fake STUN message able to pass the nr_is_stun_msg check
+ used in nr_socket_buffered_stun */
+ const char stunMessage[] = {'\x00', '\x01', '\x00', '\x04', '\x21', '\x12',
+ '\xa4', '\x42', '\x00', '\x00', '\x00', '\x00',
+ '\x00', '\x00', '\x0c', '\x00', '\x00', '\x00',
+ '\x00', '\x00', '\x1c', '\xed', '\xca', '\xfe'};
+ const char data[] = "TestActivePassiveWithStunServerMockup";
+
+ nr_transport_addr stun_srv_addr;
+ std::string stun_addr;
+ uint16_t stun_port;
+ stun_addr = TestStunTcpServer::GetInstance(AF_INET)->addr();
+ stun_port = TestStunTcpServer::GetInstance(AF_INET)->port();
+ int r = nr_str_port_to_transport_addr(stun_addr.c_str(), stun_port,
+ IPPROTO_TCP, &stun_srv_addr);
+ ASSERT_EQ(0, r);
+
+ socks[0] = Create(TCP_TYPE_PASSIVE, stun_addr, stun_port);
+ Listen(socks[0]);
+ socks[1] = Create(TCP_TYPE_ACTIVE, stun_addr, stun_port);
+
+ /* Send a fake STUN request and expect a STUN error response */
+ SendData(socks[0], &stun_srv_addr, stunMessage, sizeof(stunMessage));
+ RecvData(&stun_srv_addr, socks[0]);
+
+ Connect(socks[1], socks[0]);
+ TransferData(socks[1], socks[0], data, sizeof(data));
+ TransferData(socks[0], socks[1], data, sizeof(data));
+}
+
+TEST_F(MultiTcpSocketTest, TestConnectTwoSo) {
+ socks[0] = Create(TCP_TYPE_SO);
+ socks[1] = Create(TCP_TYPE_SO);
+ ConnectSo(socks[0], socks[1]);
+}
+
+// test works on localhost only with delay applied:
+// tc qdisc add dev lo root netem delay 5ms
+TEST_F(MultiTcpSocketTest, DISABLED_TestTwoSoBidirectionalTransmit) {
+ const char data[] = "TestTwoSoBidirectionalTransmit";
+ socks[0] = Create(TCP_TYPE_SO);
+ socks[1] = Create(TCP_TYPE_SO);
+ ConnectSo(socks[0], socks[1]);
+ TransferData(socks[0], socks[1], data, sizeof(data));
+ TransferData(socks[1], socks[0], data, sizeof(data));
+}
+
+TEST_F(MultiTcpSocketTest, TestBigData) {
+ char buf1[2048];
+ char buf2[1024];
+
+ for (unsigned i = 0; i < sizeof(buf1); ++i) {
+ buf1[i] = i & 0xff;
+ }
+ for (unsigned i = 0; i < sizeof(buf2); ++i) {
+ buf2[i] = (i + 0x80) & 0xff;
+ }
+ socks[0] = Create(TCP_TYPE_ACTIVE);
+ socks[1] = Create(TCP_TYPE_PASSIVE);
+ Listen(socks[1]);
+ Connect(socks[0], socks[1]);
+
+ TransferData(socks[0], socks[1], buf1, sizeof(buf1));
+ TransferData(socks[0], socks[1], buf2, sizeof(buf2));
+ // opposite dir
+ SendData(socks[1], socks[0], buf2, sizeof(buf2));
+ SendData(socks[1], socks[0], buf1, sizeof(buf1));
+ RecvData(socks[1], socks[0], buf2, sizeof(buf2));
+ RecvData(socks[1], socks[0], buf1, sizeof(buf1));
+}
diff --git a/dom/media/webrtc/transport/test/nrappkit_unittest.cpp b/dom/media/webrtc/transport/test/nrappkit_unittest.cpp
new file mode 100644
index 0000000000..b6a63fb993
--- /dev/null
+++ b/dom/media/webrtc/transport/test/nrappkit_unittest.cpp
@@ -0,0 +1,123 @@
+
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+#include <iostream>
+
+// nrappkit includes
+extern "C" {
+#include "nr_api.h"
+#include "async_timer.h"
+}
+
+#include "runnable_utils.h"
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+#include "gtest_utils.h"
+
+using namespace mozilla;
+
+namespace {
+
+class TimerTest : public MtransportTest {
+ public:
+ TimerTest() : MtransportTest(), handle_(nullptr), fired_(false) {}
+ virtual ~TimerTest() = default;
+
+ int ArmTimer(int timeout) {
+ int ret;
+
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableRet(&ret, this, &TimerTest::ArmTimer_w, timeout));
+
+ return ret;
+ }
+
+ int ArmCancelTimer(int timeout) {
+ int ret;
+
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableRet(&ret, this, &TimerTest::ArmCancelTimer_w, timeout));
+
+ return ret;
+ }
+
+ int ArmTimer_w(int timeout) {
+ return NR_ASYNC_TIMER_SET(timeout, cb, this, &handle_);
+ }
+
+ int ArmCancelTimer_w(int timeout) {
+ int r;
+ r = ArmTimer_w(timeout);
+ if (r) return r;
+
+ return CancelTimer_w();
+ }
+
+ int CancelTimer() {
+ int ret;
+
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableRet(&ret, this, &TimerTest::CancelTimer_w));
+
+ return ret;
+ }
+
+ int CancelTimer_w() { return NR_async_timer_cancel(handle_); }
+
+ int Schedule() {
+ int ret;
+
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableRet(&ret, this, &TimerTest::Schedule_w));
+
+ return ret;
+ }
+
+ int Schedule_w() {
+ NR_ASYNC_SCHEDULE(cb, this);
+
+ return 0;
+ }
+
+ static void cb(NR_SOCKET r, int how, void* arg) {
+ std::cerr << "Timer fired " << std::endl;
+
+ TimerTest* t = static_cast<TimerTest*>(arg);
+
+ t->fired_ = true;
+ }
+
+ protected:
+ void* handle_;
+ bool fired_;
+};
+} // namespace
+
+TEST_F(TimerTest, SimpleTimer) {
+ ArmTimer(100);
+ ASSERT_TRUE_WAIT(fired_, 1000);
+}
+
+TEST_F(TimerTest, CancelTimer) {
+ ArmTimer(1000);
+ CancelTimer();
+ PR_Sleep(2000);
+ ASSERT_FALSE(fired_);
+}
+
+TEST_F(TimerTest, CancelTimer0) {
+ ArmCancelTimer(0);
+ PR_Sleep(100);
+ ASSERT_FALSE(fired_);
+}
+
+TEST_F(TimerTest, ScheduleTest) {
+ Schedule();
+ ASSERT_TRUE_WAIT(fired_, 1000);
+}
diff --git a/dom/media/webrtc/transport/test/proxy_tunnel_socket_unittest.cpp b/dom/media/webrtc/transport/test/proxy_tunnel_socket_unittest.cpp
new file mode 100644
index 0000000000..1b54126dd6
--- /dev/null
+++ b/dom/media/webrtc/transport/test/proxy_tunnel_socket_unittest.cpp
@@ -0,0 +1,277 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original authors: ekr@rtfm.com; ryan@tokbox.com
+
+#include <vector>
+#include <numeric>
+
+#include "nr_socket_tcp.h"
+#include "WebrtcTCPSocketWrapper.h"
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+#include "gtest_utils.h"
+
+using namespace mozilla;
+
+// update TestReadMultipleSizes if you change this
+const std::string kHelloMessage = "HELLO IS IT ME YOU'RE LOOKING FOR?";
+
+class NrTcpSocketTest : public MtransportTest {
+ public:
+ NrTcpSocketTest()
+ : mSProxy(nullptr),
+ nr_socket_(nullptr),
+ mEmptyArray(0),
+ mReadChunkSize(0),
+ mReadChunkSizeIncrement(1),
+ mReadAllowance(-1),
+ mConnected(false) {}
+
+ void SetUp() override {
+ mSProxy = new NrTcpSocket(nullptr);
+ int r = nr_socket_create_int((void*)mSProxy.get(), mSProxy->vtbl(),
+ &nr_socket_);
+ ASSERT_EQ(0, r);
+
+ // fake calling AsyncOpen() due to IPC calls. must be non-null
+ mSProxy->AssignChannel_DoNotUse(new WebrtcTCPSocketWrapper(nullptr));
+ }
+
+ void TearDown() override { mSProxy->close(); }
+
+ static void readable_cb(NR_SOCKET s, int how, void* cb_arg) {
+ NrTcpSocketTest* test = (NrTcpSocketTest*)cb_arg;
+ size_t capacity = std::min(test->mReadChunkSize, test->mReadAllowance);
+ nsTArray<uint8_t> array(capacity);
+ size_t read;
+
+ nr_socket_read(test->nr_socket_, (char*)array.Elements(), array.Capacity(),
+ &read, 0);
+
+ ASSERT_TRUE(read <= array.Capacity());
+ ASSERT_TRUE(test->mReadAllowance >= read);
+
+ array.SetLength(read);
+ test->mData.AppendElements(array);
+ test->mReadAllowance -= read;
+
+ // We may read more bytes each time we're called. This way we can ensure we
+ // consume buffers partially and across multiple buffers.
+ test->mReadChunkSize += test->mReadChunkSizeIncrement;
+
+ if (test->mReadAllowance > 0) {
+ NR_ASYNC_WAIT(s, how, &NrTcpSocketTest::readable_cb, cb_arg);
+ }
+ }
+
+ static void writable_cb(NR_SOCKET s, int how, void* cb_arg) {
+ NrTcpSocketTest* test = (NrTcpSocketTest*)cb_arg;
+ test->mConnected = true;
+ }
+
+ const std::string DataString() {
+ return std::string((char*)mData.Elements(), mData.Length());
+ }
+
+ protected:
+ RefPtr<NrTcpSocket> mSProxy;
+ nr_socket* nr_socket_;
+
+ nsTArray<uint8_t> mData;
+ nsTArray<uint8_t> mEmptyArray;
+
+ uint32_t mReadChunkSize;
+ uint32_t mReadChunkSizeIncrement;
+ uint32_t mReadAllowance;
+
+ bool mConnected;
+};
+
+TEST_F(NrTcpSocketTest, TestCreate) {}
+
+TEST_F(NrTcpSocketTest, TestConnected) {
+ ASSERT_TRUE(!mConnected);
+
+ NR_ASYNC_WAIT(mSProxy, NR_ASYNC_WAIT_WRITE, &NrTcpSocketTest::writable_cb,
+ this);
+
+ // still not connected just registered for writes...
+ ASSERT_TRUE(!mConnected);
+
+ mSProxy->OnConnected("http"_ns);
+
+ ASSERT_TRUE(mConnected);
+}
+
+TEST_F(NrTcpSocketTest, TestRead) {
+ nsTArray<uint8_t> array;
+ array.AppendElements(kHelloMessage.c_str(), kHelloMessage.length());
+
+ NR_ASYNC_WAIT(mSProxy, NR_ASYNC_WAIT_READ, &NrTcpSocketTest::readable_cb,
+ this);
+ // this will read 0 bytes here
+ mSProxy->OnRead(std::move(array));
+
+ ASSERT_EQ(kHelloMessage.length(), mSProxy->CountUnreadBytes());
+
+ // callback is still set but terminated due to 0 byte read
+ // start callbacks again (first read is 0 then 1,2,3,...)
+ mSProxy->OnRead(std::move(mEmptyArray));
+
+ ASSERT_EQ(kHelloMessage.length(), mData.Length());
+ ASSERT_EQ(kHelloMessage, DataString());
+}
+
+TEST_F(NrTcpSocketTest, TestReadConstantConsumeSize) {
+ std::string data;
+
+ // triangle number
+ const int kCount = 32;
+
+ // ~17kb
+ // triangle number formula n*(n+1)/2
+ for (int i = 0; i < kCount * (kCount + 1) / 2; ++i) {
+ data += kHelloMessage;
+ }
+
+ // decreasing buffer sizes
+ for (int i = 0, start = 0; i < kCount; ++i) {
+ int length = (kCount - i) * kHelloMessage.length();
+
+ nsTArray<uint8_t> array;
+ array.AppendElements(data.c_str() + start, length);
+ start += length;
+
+ mSProxy->OnRead(std::move(array));
+ }
+
+ ASSERT_EQ(data.length(), mSProxy->CountUnreadBytes());
+
+ // read same amount each callback
+ mReadChunkSize = 128;
+ mReadChunkSizeIncrement = 0;
+ NR_ASYNC_WAIT(mSProxy, NR_ASYNC_WAIT_READ, &NrTcpSocketTest::readable_cb,
+ this);
+
+ ASSERT_EQ(data.length(), mSProxy->CountUnreadBytes());
+
+ // start callbacks
+ mSProxy->OnRead(std::move(mEmptyArray));
+
+ ASSERT_EQ(data.length(), mData.Length());
+ ASSERT_EQ(data, DataString());
+}
+
+TEST_F(NrTcpSocketTest, TestReadNone) {
+ char buf[4096];
+ size_t read = 0;
+ int r = nr_socket_read(nr_socket_, buf, sizeof(buf), &read, 0);
+
+ ASSERT_EQ(R_WOULDBLOCK, r);
+
+ nsTArray<uint8_t> array;
+ array.AppendElements(kHelloMessage.c_str(), kHelloMessage.length());
+ mSProxy->OnRead(std::move(array));
+
+ ASSERT_EQ(kHelloMessage.length(), mSProxy->CountUnreadBytes());
+
+ r = nr_socket_read(nr_socket_, buf, sizeof(buf), &read, 0);
+
+ ASSERT_EQ(0, r);
+ ASSERT_EQ(kHelloMessage.length(), read);
+ ASSERT_EQ(kHelloMessage, std::string(buf, read));
+}
+
+TEST_F(NrTcpSocketTest, TestReadMultipleSizes) {
+ using namespace std;
+
+ string data;
+ // 515 * kHelloMessage.length() == 17510
+ const size_t kCount = 515;
+ // randomly generated numbers, sums to 17510, 20 numbers
+ vector<int> varyingSizes = {404, 622, 1463, 1597, 1676, 389, 389,
+ 1272, 781, 81, 1030, 1450, 256, 812,
+ 1571, 29, 1045, 911, 643, 1089};
+
+ // changing varyingSizes or the test message breaks this so check here
+ ASSERT_EQ(kCount, 17510 / kHelloMessage.length());
+ ASSERT_EQ(17510, accumulate(varyingSizes.begin(), varyingSizes.end(), 0));
+
+ // ~17kb
+ for (size_t i = 0; i < kCount; ++i) {
+ data += kHelloMessage;
+ }
+
+ nsTArray<uint8_t> array;
+ array.AppendElements(data.c_str(), data.length());
+
+ for (int amountToRead : varyingSizes) {
+ nsTArray<uint8_t> buffer;
+ buffer.AppendElements(array.Elements(), amountToRead);
+ array.RemoveElementsAt(0, amountToRead);
+ mSProxy->OnRead(std::move(buffer));
+ }
+
+ ASSERT_EQ(data.length(), mSProxy->CountUnreadBytes());
+
+ // don't need to read 0 on the first read, so start at 1 and keep going
+ mReadChunkSize = 1;
+ NR_ASYNC_WAIT(mSProxy, NR_ASYNC_WAIT_READ, &NrTcpSocketTest::readable_cb,
+ this);
+ // start callbacks
+ mSProxy->OnRead(std::move(mEmptyArray));
+
+ ASSERT_EQ(data.length(), mData.Length());
+ ASSERT_EQ(data, DataString());
+}
+
+TEST_F(NrTcpSocketTest, TestReadConsumeReadDrain) {
+ std::string data;
+ // ~26kb total; should be even
+ const int kCount = 512;
+
+ // there's some division by 2 here so check that kCount is even
+ ASSERT_EQ(0, kCount % 2);
+
+ for (int i = 0; i < kCount; ++i) {
+ data += kHelloMessage;
+ nsTArray<uint8_t> array;
+ array.AppendElements(kHelloMessage.c_str(), kHelloMessage.length());
+ mSProxy->OnRead(std::move(array));
+ }
+
+ // read half at first
+ mReadAllowance = kCount / 2 * kHelloMessage.length();
+ // start by reading 1 byte
+ mReadChunkSize = 1;
+ NR_ASYNC_WAIT(mSProxy, NR_ASYNC_WAIT_READ, &NrTcpSocketTest::readable_cb,
+ this);
+ mSProxy->OnRead(std::move(mEmptyArray));
+
+ ASSERT_EQ(data.length() / 2, mSProxy->CountUnreadBytes());
+ ASSERT_EQ(data.length() / 2, mData.Length());
+
+ // fill read buffer back up
+ for (int i = 0; i < kCount / 2; ++i) {
+ data += kHelloMessage;
+ nsTArray<uint8_t> array;
+ array.AppendElements(kHelloMessage.c_str(), kHelloMessage.length());
+ mSProxy->OnRead(std::move(array));
+ }
+
+ // remove read limit
+ mReadAllowance = -1;
+ // used entire read allowance so we need to setup a new await
+ NR_ASYNC_WAIT(mSProxy, NR_ASYNC_WAIT_READ, &NrTcpSocketTest::readable_cb,
+ this);
+ // start callbacks
+ mSProxy->OnRead(std::move(mEmptyArray));
+
+ ASSERT_EQ(data.length(), mData.Length());
+ ASSERT_EQ(data, DataString());
+}
diff --git a/dom/media/webrtc/transport/test/rlogconnector_unittest.cpp b/dom/media/webrtc/transport/test/rlogconnector_unittest.cpp
new file mode 100644
index 0000000000..93fabae481
--- /dev/null
+++ b/dom/media/webrtc/transport/test/rlogconnector_unittest.cpp
@@ -0,0 +1,255 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Original author: bcampen@mozilla.com */
+
+#include "rlogconnector.h"
+
+extern "C" {
+#include "registry.h"
+#include "r_log.h"
+}
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+
+#include <deque>
+#include <string>
+#include <vector>
+
+using mozilla::RLogConnector;
+
+int NR_LOG_TEST = 0;
+
+class RLogConnectorTest : public ::testing::Test {
+ public:
+ RLogConnectorTest() { Init(); }
+
+ ~RLogConnectorTest() { Free(); }
+
+ static void SetUpTestCase() {
+ NR_reg_init(NR_REG_MODE_LOCAL);
+ r_log_init();
+ /* Would be nice to be able to unregister in the fixture */
+ const char* facility = "rlogconnector_test";
+ r_log_register(const_cast<char*>(facility), &NR_LOG_TEST);
+ }
+
+ void Init() { RLogConnector::CreateInstance(); }
+
+ void Free() { RLogConnector::DestroyInstance(); }
+
+ void ReInit() {
+ Free();
+ Init();
+ }
+};
+
+TEST_F(RLogConnectorTest, TestGetFree) {
+ RLogConnector* instance = RLogConnector::GetInstance();
+ ASSERT_NE(nullptr, instance);
+}
+
+TEST_F(RLogConnectorTest, TestFilterEmpty) {
+ std::deque<std::string> logs;
+ RLogConnector::GetInstance()->GetAny(0, &logs);
+ ASSERT_EQ(0U, logs.size());
+}
+
+TEST_F(RLogConnectorTest, TestBasicFilter) {
+ r_log(NR_LOG_TEST, LOG_INFO, "Test");
+ std::deque<std::string> logs;
+ RLogConnector::GetInstance()->Filter("Test", 0, &logs);
+ ASSERT_EQ(1U, logs.size());
+}
+
+TEST_F(RLogConnectorTest, TestBasicFilterContent) {
+ r_log(NR_LOG_TEST, LOG_INFO, "Test");
+ std::deque<std::string> logs;
+ RLogConnector::GetInstance()->Filter("Test", 0, &logs);
+ ASSERT_EQ("Test", logs.back());
+}
+
+TEST_F(RLogConnectorTest, TestFilterAnyFrontMatch) {
+ r_log(NR_LOG_TEST, LOG_INFO, "Test");
+ std::vector<std::string> substrings;
+ substrings.push_back("foo");
+ substrings.push_back("Test");
+ std::deque<std::string> logs;
+ RLogConnector::GetInstance()->FilterAny(substrings, 0, &logs);
+ ASSERT_EQ("Test", logs.back());
+}
+
+TEST_F(RLogConnectorTest, TestFilterAnyBackMatch) {
+ r_log(NR_LOG_TEST, LOG_INFO, "Test");
+ std::vector<std::string> substrings;
+ substrings.push_back("Test");
+ substrings.push_back("foo");
+ std::deque<std::string> logs;
+ RLogConnector::GetInstance()->FilterAny(substrings, 0, &logs);
+ ASSERT_EQ("Test", logs.back());
+}
+
+TEST_F(RLogConnectorTest, TestFilterAnyBothMatch) {
+ r_log(NR_LOG_TEST, LOG_INFO, "Test");
+ std::vector<std::string> substrings;
+ substrings.push_back("Tes");
+ substrings.push_back("est");
+ std::deque<std::string> logs;
+ RLogConnector::GetInstance()->FilterAny(substrings, 0, &logs);
+ ASSERT_EQ("Test", logs.back());
+}
+
+TEST_F(RLogConnectorTest, TestFilterAnyNeitherMatch) {
+ r_log(NR_LOG_TEST, LOG_INFO, "Test");
+ std::vector<std::string> substrings;
+ substrings.push_back("tes");
+ substrings.push_back("esT");
+ std::deque<std::string> logs;
+ RLogConnector::GetInstance()->FilterAny(substrings, 0, &logs);
+ ASSERT_EQ(0U, logs.size());
+}
+
+TEST_F(RLogConnectorTest, TestAllMatch) {
+ r_log(NR_LOG_TEST, LOG_INFO, "Test1");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test2");
+ std::deque<std::string> logs;
+ RLogConnector::GetInstance()->GetAny(0, &logs);
+ ASSERT_EQ(2U, logs.size());
+}
+
+TEST_F(RLogConnectorTest, TestOrder) {
+ r_log(NR_LOG_TEST, LOG_INFO, "Test1");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test2");
+ std::deque<std::string> logs;
+ RLogConnector::GetInstance()->GetAny(0, &logs);
+ ASSERT_EQ("Test2", logs.back());
+ ASSERT_EQ("Test1", logs.front());
+}
+
+TEST_F(RLogConnectorTest, TestNoMatch) {
+ r_log(NR_LOG_TEST, LOG_INFO, "Test1");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test2");
+ std::deque<std::string> logs;
+ RLogConnector::GetInstance()->Filter("foo", 0, &logs);
+ ASSERT_EQ(0U, logs.size());
+}
+
+TEST_F(RLogConnectorTest, TestSubstringFilter) {
+ r_log(NR_LOG_TEST, LOG_INFO, "Test1");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test2");
+ std::deque<std::string> logs;
+ RLogConnector::GetInstance()->Filter("t1", 0, &logs);
+ ASSERT_EQ(1U, logs.size());
+ ASSERT_EQ("Test1", logs.back());
+}
+
+TEST_F(RLogConnectorTest, TestFilterLimit) {
+ r_log(NR_LOG_TEST, LOG_INFO, "Test1");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test2");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test3");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test4");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test5");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test6");
+ std::deque<std::string> logs;
+ RLogConnector::GetInstance()->Filter("Test", 2, &logs);
+ ASSERT_EQ(2U, logs.size());
+ ASSERT_EQ("Test6", logs.back());
+ ASSERT_EQ("Test5", logs.front());
+}
+
+TEST_F(RLogConnectorTest, TestFilterAnyLimit) {
+ r_log(NR_LOG_TEST, LOG_INFO, "TestOne");
+ r_log(NR_LOG_TEST, LOG_INFO, "TestTwo");
+ r_log(NR_LOG_TEST, LOG_INFO, "TestThree");
+ r_log(NR_LOG_TEST, LOG_INFO, "TestFour");
+ r_log(NR_LOG_TEST, LOG_INFO, "TestFive");
+ r_log(NR_LOG_TEST, LOG_INFO, "TestSix");
+ std::vector<std::string> substrings;
+ // Matches Two, Three, Four, and Six
+ substrings.push_back("tT");
+ substrings.push_back("o");
+ substrings.push_back("r");
+ substrings.push_back("S");
+ std::deque<std::string> logs;
+ RLogConnector::GetInstance()->FilterAny(substrings, 2, &logs);
+ ASSERT_EQ(2U, logs.size());
+ ASSERT_EQ("TestSix", logs.back());
+ ASSERT_EQ("TestFour", logs.front());
+}
+
+TEST_F(RLogConnectorTest, TestLimit) {
+ RLogConnector::GetInstance()->SetLogLimit(3);
+ r_log(NR_LOG_TEST, LOG_INFO, "Test1");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test2");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test3");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test4");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test5");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test6");
+ std::deque<std::string> logs;
+ RLogConnector::GetInstance()->GetAny(0, &logs);
+ ASSERT_EQ(3U, logs.size());
+ ASSERT_EQ("Test6", logs.back());
+ ASSERT_EQ("Test4", logs.front());
+}
+
+TEST_F(RLogConnectorTest, TestLimitBulkDiscard) {
+ r_log(NR_LOG_TEST, LOG_INFO, "Test1");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test2");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test3");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test4");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test5");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test6");
+ RLogConnector::GetInstance()->SetLogLimit(3);
+ std::deque<std::string> logs;
+ RLogConnector::GetInstance()->GetAny(0, &logs);
+ ASSERT_EQ(3U, logs.size());
+ ASSERT_EQ("Test6", logs.back());
+ ASSERT_EQ("Test4", logs.front());
+}
+
+TEST_F(RLogConnectorTest, TestIncreaseLimit) {
+ RLogConnector::GetInstance()->SetLogLimit(3);
+ r_log(NR_LOG_TEST, LOG_INFO, "Test1");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test2");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test3");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test4");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test5");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test6");
+ RLogConnector::GetInstance()->SetLogLimit(300);
+ std::deque<std::string> logs;
+ RLogConnector::GetInstance()->GetAny(0, &logs);
+ ASSERT_EQ(3U, logs.size());
+ ASSERT_EQ("Test6", logs.back());
+ ASSERT_EQ("Test4", logs.front());
+}
+
+TEST_F(RLogConnectorTest, TestClear) {
+ r_log(NR_LOG_TEST, LOG_INFO, "Test1");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test2");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test3");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test4");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test5");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test6");
+ RLogConnector::GetInstance()->SetLogLimit(0);
+ RLogConnector::GetInstance()->SetLogLimit(4096);
+ std::deque<std::string> logs;
+ RLogConnector::GetInstance()->GetAny(0, &logs);
+ ASSERT_EQ(0U, logs.size());
+}
+
+TEST_F(RLogConnectorTest, TestReInit) {
+ r_log(NR_LOG_TEST, LOG_INFO, "Test1");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test2");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test3");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test4");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test5");
+ r_log(NR_LOG_TEST, LOG_INFO, "Test6");
+ ReInit();
+ std::deque<std::string> logs;
+ RLogConnector::GetInstance()->GetAny(0, &logs);
+ ASSERT_EQ(0U, logs.size());
+}
diff --git a/dom/media/webrtc/transport/test/runnable_utils_unittest.cpp b/dom/media/webrtc/transport/test/runnable_utils_unittest.cpp
new file mode 100644
index 0000000000..70707b148f
--- /dev/null
+++ b/dom/media/webrtc/transport/test/runnable_utils_unittest.cpp
@@ -0,0 +1,353 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+#include <iostream>
+
+#include "nsCOMPtr.h"
+#include "nsNetCID.h"
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+
+#include "runnable_utils.h"
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+#include "gtest_utils.h"
+
+using namespace mozilla;
+
+namespace {
+
+// Helper used to make sure args are properly copied and/or moved.
+struct CtorDtorState {
+ enum class State { Empty, Copy, Explicit, Move, Moved };
+
+ static const char* ToStr(const State& state) {
+ switch (state) {
+ case State::Empty:
+ return "empty";
+ case State::Copy:
+ return "copy";
+ case State::Explicit:
+ return "explicit";
+ case State::Move:
+ return "move";
+ case State::Moved:
+ return "moved";
+ default:
+ return "unknown";
+ }
+ }
+
+ void DumpState() const { std::cerr << ToStr(state_) << std::endl; }
+
+ CtorDtorState() { DumpState(); }
+
+ explicit CtorDtorState(int* destroyed)
+ : dtor_count_(destroyed), state_(State::Explicit) {
+ DumpState();
+ }
+
+ CtorDtorState(const CtorDtorState& other)
+ : dtor_count_(other.dtor_count_), state_(State::Copy) {
+ DumpState();
+ }
+
+ // Clear the other's dtor counter so it's not counted if moved.
+ CtorDtorState(CtorDtorState&& other)
+ : dtor_count_(std::exchange(other.dtor_count_, nullptr)),
+ state_(State::Move) {
+ other.state_ = State::Moved;
+ DumpState();
+ }
+
+ ~CtorDtorState() {
+ const char* const state = ToStr(state_);
+ std::cerr << "Destructor called with end state: " << state << std::endl;
+
+ if (dtor_count_) {
+ ++*dtor_count_;
+ }
+ }
+
+ int* dtor_count_ = nullptr;
+ State state_ = State::Empty;
+};
+
+class Destructor {
+ private:
+ ~Destructor() {
+ std::cerr << "Destructor called" << std::endl;
+ *destroyed_ = true;
+ }
+
+ public:
+ explicit Destructor(bool* destroyed) : destroyed_(destroyed) {}
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Destructor)
+
+ private:
+ bool* destroyed_;
+};
+
+class TargetClass {
+ public:
+ explicit TargetClass(int* ran) : ran_(ran) {}
+
+ void m1(int x) {
+ std::cerr << __FUNCTION__ << " " << x << std::endl;
+ *ran_ = 1;
+ }
+
+ void m2(int x, int y) {
+ std::cerr << __FUNCTION__ << " " << x << " " << y << std::endl;
+ *ran_ = 2;
+ }
+
+ void m1set(bool* z) {
+ std::cerr << __FUNCTION__ << std::endl;
+ *z = true;
+ }
+ int return_int(int x) {
+ std::cerr << __FUNCTION__ << std::endl;
+ return x;
+ }
+
+ void destructor_target_ref(RefPtr<Destructor> destructor) {}
+
+ int* ran_;
+};
+
+class RunnableArgsTest : public MtransportTest {
+ public:
+ RunnableArgsTest() : MtransportTest(), ran_(0), cl_(&ran_) {}
+
+ void Test1Arg() {
+ Runnable* r = WrapRunnable(&cl_, &TargetClass::m1, 1);
+ r->Run();
+ ASSERT_EQ(1, ran_);
+ }
+
+ void Test2Args() {
+ Runnable* r = WrapRunnable(&cl_, &TargetClass::m2, 1, 2);
+ r->Run();
+ ASSERT_EQ(2, ran_);
+ }
+
+ private:
+ int ran_;
+ TargetClass cl_;
+};
+
+class DispatchTest : public MtransportTest {
+ public:
+ DispatchTest() : MtransportTest(), ran_(0), cl_(&ran_) {}
+
+ void SetUp() {
+ MtransportTest::SetUp();
+
+ nsresult rv;
+ target_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ }
+
+ void Test1Arg() {
+ Runnable* r = WrapRunnable(&cl_, &TargetClass::m1, 1);
+ NS_DispatchAndSpinEventLoopUntilComplete("DispatchTest::Test1Arg"_ns,
+ target_, do_AddRef(r));
+ ASSERT_EQ(1, ran_);
+ }
+
+ void Test2Args() {
+ Runnable* r = WrapRunnable(&cl_, &TargetClass::m2, 1, 2);
+ NS_DispatchAndSpinEventLoopUntilComplete("DispatchTest::Test2Args"_ns,
+ target_, do_AddRef(r));
+ ASSERT_EQ(2, ran_);
+ }
+
+ void Test1Set() {
+ bool x = false;
+ NS_DispatchAndSpinEventLoopUntilComplete(
+ "DispatchTest::Test1Set"_ns, target_,
+ do_AddRef(WrapRunnable(&cl_, &TargetClass::m1set, &x)));
+ ASSERT_TRUE(x);
+ }
+
+ void TestRet() {
+ int z;
+ int x = 10;
+
+ NS_DispatchAndSpinEventLoopUntilComplete(
+ "DispatchTest::TestRet"_ns, target_,
+ do_AddRef(WrapRunnableRet(&z, &cl_, &TargetClass::return_int, x)));
+ ASSERT_EQ(10, z);
+ }
+
+ protected:
+ int ran_;
+ TargetClass cl_;
+ nsCOMPtr<nsIEventTarget> target_;
+};
+
+TEST_F(RunnableArgsTest, OneArgument) { Test1Arg(); }
+
+TEST_F(RunnableArgsTest, TwoArguments) { Test2Args(); }
+
+TEST_F(DispatchTest, OneArgument) { Test1Arg(); }
+
+TEST_F(DispatchTest, TwoArguments) { Test2Args(); }
+
+TEST_F(DispatchTest, Test1Set) { Test1Set(); }
+
+TEST_F(DispatchTest, TestRet) { TestRet(); }
+
+void SetNonMethod(TargetClass* cl, int x) { cl->m1(x); }
+
+int SetNonMethodRet(TargetClass* cl, int x) {
+ cl->m1(x);
+
+ return x;
+}
+
+TEST_F(DispatchTest, TestNonMethod) {
+ test_utils_->SyncDispatchToSTS(WrapRunnableNM(SetNonMethod, &cl_, 10));
+
+ ASSERT_EQ(1, ran_);
+}
+
+TEST_F(DispatchTest, TestNonMethodRet) {
+ int z;
+
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableNMRet(&z, SetNonMethodRet, &cl_, 10));
+
+ ASSERT_EQ(1, ran_);
+ ASSERT_EQ(10, z);
+}
+
+TEST_F(DispatchTest, TestDestructorRef) {
+ bool destroyed = false;
+ {
+ RefPtr<Destructor> destructor = new Destructor(&destroyed);
+ NS_DispatchAndSpinEventLoopUntilComplete(
+ "DispatchTest::TestDestructorRef"_ns, target_,
+ do_AddRef(WrapRunnable(&cl_, &TargetClass::destructor_target_ref,
+ destructor)));
+ ASSERT_FALSE(destroyed);
+ }
+ ASSERT_TRUE(destroyed);
+
+ // Now try with a move.
+ destroyed = false;
+ {
+ RefPtr<Destructor> destructor = new Destructor(&destroyed);
+ NS_DispatchAndSpinEventLoopUntilComplete(
+ "DispatchTest::TestDestructorRef"_ns, target_,
+ do_AddRef(WrapRunnable(&cl_, &TargetClass::destructor_target_ref,
+ std::move(destructor))));
+ ASSERT_TRUE(destroyed);
+ }
+}
+
+TEST_F(DispatchTest, TestMove) {
+ int destroyed = 0;
+ {
+ CtorDtorState state(&destroyed);
+
+ // Dispatch with:
+ // - moved arg
+ // - by-val capture in function should consume a move
+ // - expect destruction in the function scope
+ NS_DispatchAndSpinEventLoopUntilComplete(
+ "DispatchTest::TestMove"_ns, target_,
+ do_AddRef(WrapRunnableNM([](CtorDtorState s) {}, std::move(state))));
+ ASSERT_EQ(1, destroyed);
+ }
+ // Still shouldn't count when we go out of scope as it was moved.
+ ASSERT_EQ(1, destroyed);
+
+ {
+ CtorDtorState state(&destroyed);
+
+ // Dispatch with:
+ // - copied arg
+ // - by-val capture in function should consume a move
+ // - expect destruction in the function scope and call scope
+ NS_DispatchAndSpinEventLoopUntilComplete(
+ "DispatchTest::TestMove"_ns, target_,
+ do_AddRef(WrapRunnableNM([](CtorDtorState s) {}, state)));
+ ASSERT_EQ(2, destroyed);
+ }
+ // Original state should be destroyed
+ ASSERT_EQ(3, destroyed);
+
+ {
+ CtorDtorState state(&destroyed);
+
+ // Dispatch with:
+ // - moved arg
+ // - by-ref in function should accept a moved arg
+ // - expect destruction in the wrapper invocation scope
+ NS_DispatchAndSpinEventLoopUntilComplete(
+ "DispatchTest::TestMove"_ns, target_,
+ do_AddRef(
+ WrapRunnableNM([](const CtorDtorState& s) {}, std::move(state))));
+ ASSERT_EQ(4, destroyed);
+ }
+ // Still shouldn't count when we go out of scope as it was moved.
+ ASSERT_EQ(4, destroyed);
+
+ {
+ CtorDtorState state(&destroyed);
+
+ // Dispatch with:
+ // - moved arg
+ // - r-value function should accept a moved arg
+ // - expect destruction in the wrapper invocation scope
+ NS_DispatchAndSpinEventLoopUntilComplete(
+ "DispatchTest::TestMove"_ns, target_,
+ do_AddRef(WrapRunnableNM([](CtorDtorState&& s) {}, std::move(state))));
+ ASSERT_EQ(5, destroyed);
+ }
+ // Still shouldn't count when we go out of scope as it was moved.
+ ASSERT_EQ(5, destroyed);
+}
+
+TEST_F(DispatchTest, TestUniquePtr) {
+ // Test that holding the class in UniquePtr works
+ int ran = 0;
+ auto cl = MakeUnique<TargetClass>(&ran);
+
+ NS_DispatchAndSpinEventLoopUntilComplete(
+ "DispatchTest::TestUniquePtr"_ns, target_,
+ do_AddRef(WrapRunnable(std::move(cl), &TargetClass::m1, 1)));
+ ASSERT_EQ(1, ran);
+
+ // Test that UniquePtr works as a param to the runnable
+ int destroyed = 0;
+ {
+ auto state = MakeUnique<CtorDtorState>(&destroyed);
+
+ // Dispatch with:
+ // - moved arg
+ // - Function should move construct from arg
+ // - expect destruction in the wrapper invocation scope
+ NS_DispatchAndSpinEventLoopUntilComplete(
+ "DispatchTest::TestUniquePtr"_ns, target_,
+ do_AddRef(WrapRunnableNM([](UniquePtr<CtorDtorState> s) {},
+ std::move(state))));
+ ASSERT_EQ(1, destroyed);
+ }
+ // Still shouldn't count when we go out of scope as it was moved.
+ ASSERT_EQ(1, destroyed);
+}
+
+} // end of namespace
diff --git a/dom/media/webrtc/transport/test/sctp_unittest.cpp b/dom/media/webrtc/transport/test/sctp_unittest.cpp
new file mode 100644
index 0000000000..ea32565fb2
--- /dev/null
+++ b/dom/media/webrtc/transport/test/sctp_unittest.cpp
@@ -0,0 +1,381 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+
+#include <iostream>
+#include <string>
+
+#include "sigslot.h"
+
+#include "nsITimer.h"
+
+#include "transportflow.h"
+#include "transportlayer.h"
+#include "transportlayerloopback.h"
+
+#include "runnable_utils.h"
+#include "usrsctp.h"
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+#include "gtest_utils.h"
+
+using namespace mozilla;
+
+static bool sctp_logging = false;
+static int port_number = 5000;
+
+namespace {
+
+class TransportTestPeer;
+
+class SendPeriodic : public nsITimerCallback, public nsINamed {
+ public:
+ SendPeriodic(TransportTestPeer* peer, int to_send)
+ : peer_(peer), to_send_(to_send) {}
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ protected:
+ virtual ~SendPeriodic() = default;
+
+ TransportTestPeer* peer_;
+ int to_send_;
+};
+
+NS_IMPL_ISUPPORTS(SendPeriodic, nsITimerCallback, nsINamed)
+
+class TransportTestPeer : public sigslot::has_slots<> {
+ public:
+ TransportTestPeer(std::string name, int local_port, int remote_port,
+ MtransportTestUtils* utils)
+ : name_(name),
+ connected_(false),
+ sent_(0),
+ received_(0),
+ flow_(new TransportFlow()),
+ loopback_(new TransportLayerLoopback()),
+ sctp_(usrsctp_socket(AF_CONN, SOCK_STREAM, IPPROTO_SCTP, receive_cb,
+ nullptr, 0, nullptr)),
+ timer_(NS_NewTimer()),
+ periodic_(nullptr),
+ test_utils_(utils) {
+ std::cerr << "Creating TransportTestPeer; flow="
+ << static_cast<void*>(flow_.get()) << " local=" << local_port
+ << " remote=" << remote_port << std::endl;
+
+ usrsctp_register_address(static_cast<void*>(this));
+ int r = usrsctp_set_non_blocking(sctp_, 1);
+ EXPECT_GE(r, 0);
+
+ struct linger l;
+ l.l_onoff = 1;
+ l.l_linger = 0;
+ r = usrsctp_setsockopt(sctp_, SOL_SOCKET, SO_LINGER, &l,
+ (socklen_t)sizeof(l));
+ EXPECT_GE(r, 0);
+
+ struct sctp_event subscription;
+ memset(&subscription, 0, sizeof(subscription));
+ subscription.se_assoc_id = SCTP_ALL_ASSOC;
+ subscription.se_on = 1;
+ subscription.se_type = SCTP_ASSOC_CHANGE;
+ r = usrsctp_setsockopt(sctp_, IPPROTO_SCTP, SCTP_EVENT, &subscription,
+ sizeof(subscription));
+ EXPECT_GE(r, 0);
+
+ memset(&local_addr_, 0, sizeof(local_addr_));
+ local_addr_.sconn_family = AF_CONN;
+#if !defined(__Userspace_os_Linux) && !defined(__Userspace_os_Windows) && \
+ !defined(__Userspace_os_Android)
+ local_addr_.sconn_len = sizeof(struct sockaddr_conn);
+#endif
+ local_addr_.sconn_port = htons(local_port);
+ local_addr_.sconn_addr = static_cast<void*>(this);
+
+ memset(&remote_addr_, 0, sizeof(remote_addr_));
+ remote_addr_.sconn_family = AF_CONN;
+#if !defined(__Userspace_os_Linux) && !defined(__Userspace_os_Windows) && \
+ !defined(__Userspace_os_Android)
+ remote_addr_.sconn_len = sizeof(struct sockaddr_conn);
+#endif
+ remote_addr_.sconn_port = htons(remote_port);
+ remote_addr_.sconn_addr = static_cast<void*>(this);
+
+ nsresult res;
+ res = loopback_->Init();
+ EXPECT_EQ((nsresult)NS_OK, res);
+ }
+
+ ~TransportTestPeer() {
+ std::cerr << "Destroying sctp connection flow="
+ << static_cast<void*>(flow_.get()) << std::endl;
+ usrsctp_close(sctp_);
+ usrsctp_deregister_address(static_cast<void*>(this));
+
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &TransportTestPeer::Disconnect_s));
+
+ std::cerr << "~TransportTestPeer() completed" << std::endl;
+ }
+
+ void ConnectSocket(TransportTestPeer* peer) {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &TransportTestPeer::ConnectSocket_s, peer));
+ }
+
+ void ConnectSocket_s(TransportTestPeer* peer) {
+ loopback_->Connect(peer->loopback_);
+ ASSERT_EQ((nsresult)NS_OK, loopback_->Init());
+ flow_->PushLayer(loopback_);
+
+ loopback_->SignalPacketReceived.connect(this,
+ &TransportTestPeer::PacketReceived);
+
+ // SCTP here!
+ ASSERT_TRUE(sctp_);
+ std::cerr << "Calling usrsctp_bind()" << std::endl;
+ int r =
+ usrsctp_bind(sctp_, reinterpret_cast<struct sockaddr*>(&local_addr_),
+ sizeof(local_addr_));
+ ASSERT_GE(0, r);
+
+ std::cerr << "Calling usrsctp_connect()" << std::endl;
+ r = usrsctp_connect(sctp_,
+ reinterpret_cast<struct sockaddr*>(&remote_addr_),
+ sizeof(remote_addr_));
+ ASSERT_GE(0, r);
+ }
+
+ void Disconnect_s() {
+ disconnect_all();
+ if (flow_) {
+ flow_ = nullptr;
+ }
+ }
+
+ void Disconnect() { loopback_->Disconnect(); }
+
+ void StartTransfer(size_t to_send) {
+ periodic_ = new SendPeriodic(this, to_send);
+ timer_->SetTarget(test_utils_->sts_target());
+ timer_->InitWithCallback(periodic_, 10, nsITimer::TYPE_REPEATING_SLACK);
+ }
+
+ void SendOne() {
+ unsigned char buf[100];
+ memset(buf, sent_ & 0xff, sizeof(buf));
+
+ struct sctp_sndinfo info;
+ info.snd_sid = 1;
+ info.snd_flags = 0;
+ info.snd_ppid = 50; // What the heck is this?
+ info.snd_context = 0;
+ info.snd_assoc_id = 0;
+
+ int r = usrsctp_sendv(sctp_, buf, sizeof(buf), nullptr, 0,
+ static_cast<void*>(&info), sizeof(info),
+ SCTP_SENDV_SNDINFO, 0);
+ ASSERT_TRUE(r >= 0);
+ ASSERT_EQ(sizeof(buf), (size_t)r);
+
+ ++sent_;
+ }
+
+ int sent() const { return sent_; }
+ int received() const { return received_; }
+ bool connected() const { return connected_; }
+
+ static TransportResult SendPacket_s(UniquePtr<MediaPacket> packet,
+ const RefPtr<TransportFlow>& flow,
+ TransportLayer* layer) {
+ return layer->SendPacket(*packet);
+ }
+
+ TransportResult SendPacket(const unsigned char* data, size_t len) {
+ UniquePtr<MediaPacket> packet(new MediaPacket);
+ packet->Copy(data, len);
+
+ // Uses DISPATCH_NORMAL to avoid possible deadlocks when we're called
+ // from MainThread especially during shutdown (same as DataChannels).
+ // RUN_ON_THREAD short-circuits if already on the STS thread, which is
+ // normal for most transfers outside of connect() and close(). Passes
+ // a refptr to flow_ to avoid any async deletion issues (since we can't
+ // make 'this' into a refptr as it isn't refcounted)
+ RUN_ON_THREAD(test_utils_->sts_target(),
+ WrapRunnableNM(&TransportTestPeer::SendPacket_s,
+ std::move(packet), flow_, loopback_),
+ NS_DISPATCH_NORMAL);
+
+ return 0;
+ }
+
+ void PacketReceived(TransportLayer* layer, MediaPacket& packet) {
+ std::cerr << "Received " << packet.len() << " bytes" << std::endl;
+
+ // Pass the data to SCTP
+
+ usrsctp_conninput(static_cast<void*>(this), packet.data(), packet.len(), 0);
+ }
+
+ // Process SCTP notification
+ void Notification(union sctp_notification* msg, size_t len) {
+ ASSERT_EQ(msg->sn_header.sn_length, len);
+
+ if (msg->sn_header.sn_type == SCTP_ASSOC_CHANGE) {
+ struct sctp_assoc_change* change = &msg->sn_assoc_change;
+
+ if (change->sac_state == SCTP_COMM_UP) {
+ std::cerr << "Connection up" << std::endl;
+ SetConnected(true);
+ } else {
+ std::cerr << "Connection down" << std::endl;
+ SetConnected(false);
+ }
+ }
+ }
+
+ void SetConnected(bool state) { connected_ = state; }
+
+ static int conn_output(void* addr, void* buffer, size_t length, uint8_t tos,
+ uint8_t set_df) {
+ TransportTestPeer* peer = static_cast<TransportTestPeer*>(addr);
+
+ peer->SendPacket(static_cast<unsigned char*>(buffer), length);
+
+ return 0;
+ }
+
+ static int receive_cb(struct socket* sock, union sctp_sockstore addr,
+ void* data, size_t datalen, struct sctp_rcvinfo rcv,
+ int flags, void* ulp_info) {
+ TransportTestPeer* me =
+ static_cast<TransportTestPeer*>(addr.sconn.sconn_addr);
+ MOZ_ASSERT(me);
+
+ if (flags & MSG_NOTIFICATION) {
+ union sctp_notification* notif =
+ static_cast<union sctp_notification*>(data);
+
+ me->Notification(notif, datalen);
+ return 0;
+ }
+
+ me->received_ += datalen;
+
+ std::cerr << "receive_cb: sock " << sock << " data " << data << "("
+ << datalen << ") total received bytes = " << me->received_
+ << std::endl;
+
+ return 0;
+ }
+
+ private:
+ std::string name_;
+ bool connected_;
+ size_t sent_;
+ size_t received_;
+ // Owns the TransportLayerLoopback, but basically does nothing else.
+ RefPtr<TransportFlow> flow_;
+ TransportLayerLoopback* loopback_;
+
+ struct sockaddr_conn local_addr_;
+ struct sockaddr_conn remote_addr_;
+ struct socket* sctp_;
+ nsCOMPtr<nsITimer> timer_;
+ RefPtr<SendPeriodic> periodic_;
+ MtransportTestUtils* test_utils_;
+};
+
+// Implemented here because it calls a method of TransportTestPeer
+NS_IMETHODIMP SendPeriodic::Notify(nsITimer* timer) {
+ peer_->SendOne();
+ --to_send_;
+ if (!to_send_) {
+ timer->Cancel();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+SendPeriodic::GetName(nsACString& aName) {
+ aName.AssignLiteral("SendPeriodic");
+ return NS_OK;
+}
+
+class SctpTransportTest : public MtransportTest {
+ public:
+ SctpTransportTest() = default;
+
+ ~SctpTransportTest() = default;
+
+ static void debug_printf(const char* format, ...) {
+ va_list ap;
+
+ va_start(ap, format);
+ vprintf(format, ap);
+ va_end(ap);
+ }
+
+ static void SetUpTestCase() {
+ if (sctp_logging) {
+ usrsctp_init(0, &TransportTestPeer::conn_output, debug_printf);
+ usrsctp_sysctl_set_sctp_debug_on(0xffffffff);
+ } else {
+ usrsctp_init(0, &TransportTestPeer::conn_output, nullptr);
+ }
+ }
+
+ void TearDown() override {
+ if (p1_) p1_->Disconnect();
+ if (p2_) p2_->Disconnect();
+ delete p1_;
+ delete p2_;
+
+ MtransportTest::TearDown();
+ }
+
+ void ConnectSocket(int p1port = 0, int p2port = 0) {
+ if (!p1port) p1port = port_number++;
+ if (!p2port) p2port = port_number++;
+
+ p1_ = new TransportTestPeer("P1", p1port, p2port, test_utils_);
+ p2_ = new TransportTestPeer("P2", p2port, p1port, test_utils_);
+
+ p1_->ConnectSocket(p2_);
+ p2_->ConnectSocket(p1_);
+ ASSERT_TRUE_WAIT(p1_->connected(), 2000);
+ ASSERT_TRUE_WAIT(p2_->connected(), 2000);
+ }
+
+ void TestTransfer(int expected = 1) {
+ std::cerr << "Starting trasnsfer test" << std::endl;
+ p1_->StartTransfer(expected);
+ ASSERT_TRUE_WAIT(p1_->sent() == expected, 10000);
+ ASSERT_TRUE_WAIT(p2_->received() == (expected * 100), 10000);
+ std::cerr << "P2 received " << p2_->received() << std::endl;
+ }
+
+ protected:
+ TransportTestPeer* p1_ = nullptr;
+ TransportTestPeer* p2_ = nullptr;
+};
+
+TEST_F(SctpTransportTest, TestConnect) { ConnectSocket(); }
+
+TEST_F(SctpTransportTest, TestConnectSymmetricalPorts) {
+ ConnectSocket(5002, 5002);
+}
+
+TEST_F(SctpTransportTest, TestTransfer) {
+ ConnectSocket();
+ TestTransfer(50);
+}
+
+} // end namespace
diff --git a/dom/media/webrtc/transport/test/simpletokenbucket_unittest.cpp b/dom/media/webrtc/transport/test/simpletokenbucket_unittest.cpp
new file mode 100644
index 0000000000..66622d795b
--- /dev/null
+++ b/dom/media/webrtc/transport/test/simpletokenbucket_unittest.cpp
@@ -0,0 +1,114 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Original author: bcampen@mozilla.com */
+
+#include "simpletokenbucket.h"
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+
+using mozilla::SimpleTokenBucket;
+
+class TestSimpleTokenBucket : public SimpleTokenBucket {
+ public:
+ TestSimpleTokenBucket(size_t bucketSize, size_t tokensPerSecond)
+ : SimpleTokenBucket(bucketSize, tokensPerSecond) {}
+
+ void fastForward(int32_t timeMilliSeconds) {
+ if (timeMilliSeconds >= 0) {
+ last_time_tokens_added_ -= PR_MillisecondsToInterval(timeMilliSeconds);
+ } else {
+ last_time_tokens_added_ += PR_MillisecondsToInterval(-timeMilliSeconds);
+ }
+ }
+};
+
+TEST(SimpleTokenBucketTest, TestConstruct)
+{ TestSimpleTokenBucket b(10, 1); }
+
+TEST(SimpleTokenBucketTest, TestGet)
+{
+ TestSimpleTokenBucket b(10, 1);
+ ASSERT_EQ(5U, b.getTokens(5));
+}
+
+TEST(SimpleTokenBucketTest, TestGetAll)
+{
+ TestSimpleTokenBucket b(10, 1);
+ ASSERT_EQ(10U, b.getTokens(10));
+}
+
+TEST(SimpleTokenBucketTest, TestGetInsufficient)
+{
+ TestSimpleTokenBucket b(10, 1);
+ ASSERT_EQ(5U, b.getTokens(5));
+ ASSERT_EQ(5U, b.getTokens(6));
+}
+
+TEST(SimpleTokenBucketTest, TestGetBucketCount)
+{
+ TestSimpleTokenBucket b(10, 1);
+ ASSERT_EQ(10U, b.getTokens(UINT32_MAX));
+ ASSERT_EQ(5U, b.getTokens(5));
+ ASSERT_EQ(5U, b.getTokens(UINT32_MAX));
+}
+
+TEST(SimpleTokenBucketTest, TestTokenRefill)
+{
+ TestSimpleTokenBucket b(10, 1);
+ ASSERT_EQ(5U, b.getTokens(5));
+ b.fastForward(1000);
+ ASSERT_EQ(6U, b.getTokens(6));
+}
+
+TEST(SimpleTokenBucketTest, TestNoTimeWasted)
+{
+ // Makes sure that when the time elapsed is insufficient to add any
+ // tokens to the bucket, the internal timestamp that is used in this
+ // calculation is not updated (ie; two subsequent 0.5 second elapsed times
+ // counts as a full second)
+ TestSimpleTokenBucket b(10, 1);
+ ASSERT_EQ(5U, b.getTokens(5));
+ b.fastForward(500);
+ ASSERT_EQ(5U, b.getTokens(6));
+ b.fastForward(500);
+ ASSERT_EQ(6U, b.getTokens(6));
+}
+
+TEST(SimpleTokenBucketTest, TestNegativeTime)
+{
+ TestSimpleTokenBucket b(10, 1);
+ b.fastForward(-1000);
+ // Make sure we don't end up with an invalid number of tokens, but otherwise
+ // permit anything.
+ ASSERT_GT(11U, b.getTokens(100));
+}
+
+TEST(SimpleTokenBucketTest, TestEmptyBucket)
+{
+ TestSimpleTokenBucket b(10, 1);
+ ASSERT_EQ(10U, b.getTokens(10));
+ ASSERT_EQ(0U, b.getTokens(10));
+}
+
+TEST(SimpleTokenBucketTest, TestEmptyThenFillBucket)
+{
+ TestSimpleTokenBucket b(10, 1);
+ ASSERT_EQ(10U, b.getTokens(10));
+ ASSERT_EQ(0U, b.getTokens(1));
+ b.fastForward(50000);
+ ASSERT_EQ(10U, b.getTokens(10));
+}
+
+TEST(SimpleTokenBucketTest, TestNoOverflow)
+{
+ TestSimpleTokenBucket b(10, 1);
+ ASSERT_EQ(10U, b.getTokens(10));
+ ASSERT_EQ(0U, b.getTokens(1));
+ b.fastForward(50000);
+ ASSERT_EQ(10U, b.getTokens(11));
+}
diff --git a/dom/media/webrtc/transport/test/sockettransportservice_unittest.cpp b/dom/media/webrtc/transport/test/sockettransportservice_unittest.cpp
new file mode 100644
index 0000000000..ffa87fe91f
--- /dev/null
+++ b/dom/media/webrtc/transport/test/sockettransportservice_unittest.cpp
@@ -0,0 +1,181 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+#include <iostream>
+
+#include "prio.h"
+
+#include "nsCOMPtr.h"
+#include "nsNetCID.h"
+
+#include "nsISocketTransportService.h"
+
+#include "nsASocketHandler.h"
+#include "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+#include "gtest_utils.h"
+
+using namespace mozilla;
+
+namespace {
+class SocketTransportServiceTest : public MtransportTest {
+ public:
+ SocketTransportServiceTest()
+ : MtransportTest(),
+ received_(0),
+ readpipe_(nullptr),
+ writepipe_(nullptr),
+ registered_(false) {}
+
+ ~SocketTransportServiceTest() {
+ if (readpipe_) PR_Close(readpipe_);
+ if (writepipe_) PR_Close(writepipe_);
+ }
+
+ void SetUp();
+ void RegisterHandler();
+ void SendEvent();
+ void SendPacket();
+
+ void ReceivePacket() { ++received_; }
+
+ void ReceiveEvent() { ++received_; }
+
+ size_t Received() { return received_; }
+
+ private:
+ nsCOMPtr<nsISocketTransportService> stservice_;
+ nsCOMPtr<nsIEventTarget> target_;
+ size_t received_;
+ PRFileDesc* readpipe_;
+ PRFileDesc* writepipe_;
+ bool registered_;
+};
+
+// Received an event.
+class EventReceived : public Runnable {
+ public:
+ explicit EventReceived(SocketTransportServiceTest* test)
+ : Runnable("EventReceived"), test_(test) {}
+
+ NS_IMETHOD Run() override {
+ test_->ReceiveEvent();
+ return NS_OK;
+ }
+
+ SocketTransportServiceTest* test_;
+};
+
+// Register our listener on the socket
+class RegisterEvent : public Runnable {
+ public:
+ explicit RegisterEvent(SocketTransportServiceTest* test)
+ : Runnable("RegisterEvent"), test_(test) {}
+
+ NS_IMETHOD Run() override {
+ test_->RegisterHandler();
+ return NS_OK;
+ }
+
+ SocketTransportServiceTest* test_;
+};
+
+class SocketHandler : public nsASocketHandler {
+ public:
+ explicit SocketHandler(SocketTransportServiceTest* test) : test_(test) {}
+
+ void OnSocketReady(PRFileDesc* fd, int16_t outflags) override {
+ unsigned char buf[1600];
+
+ int32_t rv;
+ rv = PR_Recv(fd, buf, sizeof(buf), 0, PR_INTERVAL_NO_WAIT);
+ if (rv > 0) {
+ std::cerr << "Read " << rv << " bytes" << std::endl;
+ test_->ReceivePacket();
+ }
+ }
+
+ void OnSocketDetached(PRFileDesc* fd) override {}
+
+ void IsLocal(bool* aIsLocal) override {
+ // TODO(jesup): better check? Does it matter? (likely no)
+ *aIsLocal = false;
+ }
+
+ virtual uint64_t ByteCountSent() override { return 0; }
+ virtual uint64_t ByteCountReceived() override { return 0; }
+
+ NS_DECL_ISUPPORTS
+
+ protected:
+ virtual ~SocketHandler() = default;
+
+ private:
+ SocketTransportServiceTest* test_;
+};
+
+NS_IMPL_ISUPPORTS0(SocketHandler)
+
+void SocketTransportServiceTest::SetUp() {
+ MtransportTest::SetUp();
+
+ // Get the transport service as a dispatch target
+ nsresult rv;
+ target_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ // Get the transport service as a transport service
+ stservice_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ // Create a loopback pipe
+ PRStatus status = PR_CreatePipe(&readpipe_, &writepipe_);
+ ASSERT_EQ(status, PR_SUCCESS);
+
+ // Register ourselves as a listener for the read side of the
+ // socket. The registration has to happen on the STS thread,
+ // hence this event stuff.
+ rv = target_->Dispatch(new RegisterEvent(this), 0);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ ASSERT_TRUE_WAIT(registered_, 10000);
+}
+
+void SocketTransportServiceTest::RegisterHandler() {
+ nsresult rv;
+
+ rv = stservice_->AttachSocket(readpipe_, new SocketHandler(this));
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ registered_ = true;
+}
+
+void SocketTransportServiceTest::SendEvent() {
+ nsresult rv;
+
+ rv = target_->Dispatch(new EventReceived(this), 0);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+ ASSERT_TRUE_WAIT(Received() == 1, 10000);
+}
+
+void SocketTransportServiceTest::SendPacket() {
+ unsigned char buffer[1024];
+ memset(buffer, 0, sizeof(buffer));
+
+ int32_t status = PR_Write(writepipe_, buffer, sizeof(buffer));
+ uint32_t size = status & 0xffff;
+ ASSERT_EQ(sizeof(buffer), size);
+}
+
+// The unit tests themselves
+TEST_F(SocketTransportServiceTest, SendEvent) { SendEvent(); }
+
+TEST_F(SocketTransportServiceTest, SendPacket) { SendPacket(); }
+
+} // end namespace
diff --git a/dom/media/webrtc/transport/test/stunserver.cpp b/dom/media/webrtc/transport/test/stunserver.cpp
new file mode 100644
index 0000000000..b5fce21e19
--- /dev/null
+++ b/dom/media/webrtc/transport/test/stunserver.cpp
@@ -0,0 +1,652 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+
+/*
+Original code from nICEr and nrappkit.
+
+nICEr copyright:
+
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+
+
+nrappkit copyright:
+
+ Copyright (C) 2001-2003, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ ekr@rtfm.com Thu Dec 20 20:14:49 2001
+*/
+#include "logging.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "mediapacket.h"
+
+// mozilla/utils.h defines this as well
+#ifdef UNIMPLEMENTED
+# undef UNIMPLEMENTED
+#endif
+
+extern "C" {
+#include "nr_api.h"
+#include "async_wait.h"
+#include "async_timer.h"
+#include "nr_socket.h"
+#include "nr_socket_local.h"
+#include "transport_addr.h"
+#include "stun_util.h"
+#include "registry.h"
+#include "nr_socket_buffered_stun.h"
+}
+
+#include "stunserver.h"
+
+#include <string>
+
+MOZ_MTLOG_MODULE("stunserver");
+
+namespace mozilla {
+
+// Wrapper nr_socket which allows us to lie to the stun server about the
+// IP address.
+struct nr_socket_wrapped {
+ nr_socket* sock_;
+ nr_transport_addr addr_;
+};
+
+static int nr_socket_wrapped_destroy(void** objp) {
+ if (!objp || !*objp) return 0;
+
+ nr_socket_wrapped* wrapped = static_cast<nr_socket_wrapped*>(*objp);
+ *objp = nullptr;
+
+ delete wrapped;
+
+ return 0;
+}
+
+static int nr_socket_wrapped_sendto(void* obj, const void* msg, size_t len,
+ int flags, const nr_transport_addr* addr) {
+ nr_socket_wrapped* wrapped = static_cast<nr_socket_wrapped*>(obj);
+
+ return nr_socket_sendto(wrapped->sock_, msg, len, flags, &wrapped->addr_);
+}
+
+static int nr_socket_wrapped_recvfrom(void* obj, void* restrict buf,
+ size_t maxlen, size_t* len, int flags,
+ nr_transport_addr* addr) {
+ nr_socket_wrapped* wrapped = static_cast<nr_socket_wrapped*>(obj);
+
+ return nr_socket_recvfrom(wrapped->sock_, buf, maxlen, len, flags, addr);
+}
+
+static int nr_socket_wrapped_getfd(void* obj, NR_SOCKET* fd) {
+ nr_socket_wrapped* wrapped = static_cast<nr_socket_wrapped*>(obj);
+
+ return nr_socket_getfd(wrapped->sock_, fd);
+}
+
+static int nr_socket_wrapped_getaddr(void* obj, nr_transport_addr* addrp) {
+ nr_socket_wrapped* wrapped = static_cast<nr_socket_wrapped*>(obj);
+
+ return nr_socket_getaddr(wrapped->sock_, addrp);
+}
+
+static int nr_socket_wrapped_close(void* obj) { MOZ_CRASH(); }
+
+static int nr_socket_wrapped_set_send_addr(nr_socket* sock,
+ nr_transport_addr* addr) {
+ nr_socket_wrapped* wrapped = static_cast<nr_socket_wrapped*>(sock->obj);
+
+ return nr_transport_addr_copy(&wrapped->addr_, addr);
+}
+
+static nr_socket_vtbl nr_socket_wrapped_vtbl = {2,
+ nr_socket_wrapped_destroy,
+ nr_socket_wrapped_sendto,
+ nr_socket_wrapped_recvfrom,
+ nr_socket_wrapped_getfd,
+ nr_socket_wrapped_getaddr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nr_socket_wrapped_close,
+ nullptr,
+ nullptr};
+
+int nr_socket_wrapped_create(nr_socket* inner, nr_socket** outp) {
+ auto wrapped = MakeUnique<nr_socket_wrapped>();
+
+ wrapped->sock_ = inner;
+
+ int r = nr_socket_create_int(wrapped.get(), &nr_socket_wrapped_vtbl, outp);
+ if (r) return r;
+
+ Unused << wrapped.release();
+ return 0;
+}
+
+// Instance static.
+// Note: Calling Create() at static init time is not going to be safe, since
+// we have no reason to expect this will be initted to a nullptr yet.
+TestStunServer* TestStunServer::instance;
+TestStunTcpServer* TestStunTcpServer::instance;
+TestStunServer* TestStunServer::instance6;
+TestStunTcpServer* TestStunTcpServer::instance6;
+uint16_t TestStunServer::instance_port = 3478;
+uint16_t TestStunTcpServer::instance_port = 3478;
+
+TestStunServer::~TestStunServer() {
+ // TODO(ekr@rtfm.com): Put this on the right thread.
+
+ // Unhook callback from our listen socket.
+ if (listen_sock_) {
+ NR_SOCKET fd;
+ if (!nr_socket_getfd(listen_sock_, &fd)) {
+ NR_ASYNC_CANCEL(fd, NR_ASYNC_WAIT_READ);
+ }
+ }
+
+ // Free up stun context and network resources
+ nr_stun_server_ctx_destroy(&stun_server_);
+ nr_socket_destroy(&listen_sock_);
+ nr_socket_destroy(&send_sock_);
+
+ // Make sure we aren't still waiting on a deferred response timer to pop
+ if (timer_handle_) NR_async_timer_cancel(timer_handle_);
+
+ delete response_addr_;
+}
+
+int TestStunServer::SetInternalPort(nr_local_addr* addr, uint16_t port) {
+ if (nr_transport_addr_set_port(&addr->addr, port)) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't set port");
+ return R_INTERNAL;
+ }
+
+ if (nr_transport_addr_fmt_addr_string(&addr->addr)) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't re-set addr string");
+ return R_INTERNAL;
+ }
+
+ return 0;
+}
+
+int TestStunServer::TryOpenListenSocket(nr_local_addr* addr, uint16_t port) {
+ int r = SetInternalPort(addr, port);
+
+ if (r) return r;
+
+ if (nr_socket_local_create(nullptr, &addr->addr, &listen_sock_)) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't create listen socket");
+ return R_ALREADY;
+ }
+
+ return 0;
+}
+
+static int addressFamilyToIpVersion(int address_family) {
+ switch (address_family) {
+ case AF_INET:
+ return NR_IPV4;
+ case AF_INET6:
+ return NR_IPV6;
+ default:
+ MOZ_CRASH();
+ }
+ return NR_IPV4;
+}
+
+int TestStunServer::Initialize(int address_family) {
+ static const size_t max_addrs = 100;
+ nr_local_addr addrs[max_addrs];
+ int addr_ct;
+ int r;
+ int i;
+
+ r = nr_stun_find_local_addresses(addrs, max_addrs, &addr_ct);
+ if (r) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't retrieve addresses");
+ return R_INTERNAL;
+ }
+
+ // removes duplicates and, based on prefs, loopback and link_local addrs
+ r = nr_stun_filter_local_addresses(addrs, &addr_ct);
+ if (r) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't filter addresses");
+ return R_INTERNAL;
+ }
+
+ if (addr_ct < 1) {
+ MOZ_MTLOG(ML_ERROR, "No local addresses");
+ return R_INTERNAL;
+ }
+
+ for (i = 0; i < addr_ct; ++i) {
+ if (addrs[i].addr.ip_version == addressFamilyToIpVersion(address_family)) {
+ break;
+ }
+ }
+
+ if (i == addr_ct) {
+ MOZ_MTLOG(ML_ERROR, "No local addresses of the configured IP version");
+ return R_INTERNAL;
+ }
+
+ int tries = 100;
+ while (tries--) {
+ // Bind on configured port (default 3478)
+ r = TryOpenListenSocket(&addrs[i], instance_port);
+ // We interpret R_ALREADY to mean the addr is probably in use. Try another.
+ // Otherwise, it either worked or it didn't, and we check below.
+ if (r != R_ALREADY) {
+ break;
+ }
+ ++instance_port;
+ }
+
+ if (r) {
+ return R_INTERNAL;
+ }
+
+ r = nr_socket_wrapped_create(listen_sock_, &send_sock_);
+ if (r) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't create send socket");
+ return R_INTERNAL;
+ }
+
+ r = nr_stun_server_ctx_create(const_cast<char*>("Test STUN server"),
+ &stun_server_);
+ if (r) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't create STUN server");
+ return R_INTERNAL;
+ }
+
+ // Cache the address and port.
+ char addr_string[INET6_ADDRSTRLEN];
+ r = nr_transport_addr_get_addrstring(&addrs[i].addr, addr_string,
+ sizeof(addr_string));
+ if (r) {
+ MOZ_MTLOG(ML_ERROR,
+ "Failed to convert listen addr to a string representation");
+ return R_INTERNAL;
+ }
+
+ listen_addr_ = addr_string;
+ listen_port_ = instance_port;
+
+ return 0;
+}
+
+UniquePtr<TestStunServer> TestStunServer::Create(int address_family) {
+ NR_reg_init(NR_REG_MODE_LOCAL);
+
+ UniquePtr<TestStunServer> server(new TestStunServer());
+
+ if (server->Initialize(address_family)) return nullptr;
+
+ NR_SOCKET fd;
+ int r = nr_socket_getfd(server->listen_sock_, &fd);
+ if (r) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't get fd");
+ return nullptr;
+ }
+
+ NR_ASYNC_WAIT(fd, NR_ASYNC_WAIT_READ, &TestStunServer::readable_cb,
+ server.get());
+
+ return server;
+}
+
+void TestStunServer::ConfigurePort(uint16_t port) { instance_port = port; }
+
+TestStunServer* TestStunServer::GetInstance(int address_family) {
+ switch (address_family) {
+ case AF_INET:
+ if (!instance) instance = Create(address_family).release();
+
+ MOZ_ASSERT(instance);
+ return instance;
+ case AF_INET6:
+ if (!instance6) instance6 = Create(address_family).release();
+
+ return instance6;
+ default:
+ MOZ_CRASH();
+ }
+}
+
+void TestStunServer::ShutdownInstance() {
+ delete instance;
+ instance = nullptr;
+ delete instance6;
+ instance6 = nullptr;
+}
+
+struct DeferredStunOperation {
+ DeferredStunOperation(TestStunServer* server, const char* data, size_t len,
+ nr_transport_addr* addr, nr_socket* sock)
+ : server_(server), buffer_(), sock_(sock) {
+ buffer_.Copy(reinterpret_cast<const uint8_t*>(data), len);
+ nr_transport_addr_copy(&addr_, addr);
+ }
+
+ TestStunServer* server_;
+ MediaPacket buffer_;
+ nr_transport_addr addr_;
+ nr_socket* sock_;
+};
+
+void TestStunServer::Process(const uint8_t* msg, size_t len,
+ nr_transport_addr* addr, nr_socket* sock) {
+ if (!sock) {
+ sock = send_sock_;
+ }
+
+ // Set the wrapped address so that the response goes to the right place.
+ nr_socket_wrapped_set_send_addr(sock, addr);
+
+ nr_stun_server_process_request(
+ stun_server_, sock, const_cast<char*>(reinterpret_cast<const char*>(msg)),
+ len, response_addr_ ? response_addr_ : addr, NR_STUN_AUTH_RULE_OPTIONAL);
+}
+
+void TestStunServer::process_cb(NR_SOCKET s, int how, void* cb_arg) {
+ DeferredStunOperation* op = static_cast<DeferredStunOperation*>(cb_arg);
+ op->server_->timer_handle_ = nullptr;
+ op->server_->Process(op->buffer_.data(), op->buffer_.len(), &op->addr_,
+ op->sock_);
+
+ delete op;
+}
+
+nr_socket* TestStunServer::GetReceivingSocket(NR_SOCKET s) {
+ return listen_sock_;
+}
+
+nr_socket* TestStunServer::GetSendingSocket(nr_socket* sock) {
+ return send_sock_;
+}
+
+void TestStunServer::readable_cb(NR_SOCKET s, int how, void* cb_arg) {
+ TestStunServer* server = static_cast<TestStunServer*>(cb_arg);
+
+ char message[max_stun_message_size];
+ size_t message_len;
+ nr_transport_addr addr;
+ nr_socket* recv_sock = server->GetReceivingSocket(s);
+ if (!recv_sock) {
+ MOZ_MTLOG(ML_ERROR, "Failed to lookup receiving socket");
+ return;
+ }
+ nr_socket* send_sock = server->GetSendingSocket(recv_sock);
+
+ /* Re-arm. */
+ NR_ASYNC_WAIT(s, NR_ASYNC_WAIT_READ, &TestStunServer::readable_cb, server);
+
+ if (nr_socket_recvfrom(recv_sock, message, sizeof(message), &message_len, 0,
+ &addr)) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't read STUN message");
+ return;
+ }
+
+ MOZ_MTLOG(ML_DEBUG, "Received data of length " << message_len);
+
+ // If we have initial dropping set, check at this point.
+ std::string key(addr.as_string);
+
+ if (server->received_ct_.count(key) == 0) {
+ server->received_ct_[key] = 0;
+ }
+
+ ++server->received_ct_[key];
+
+ if (!server->active_ || (server->received_ct_[key] <= server->initial_ct_)) {
+ MOZ_MTLOG(ML_DEBUG, "Dropping message #" << server->received_ct_[key]
+ << " from " << key);
+ return;
+ }
+
+ if (server->delay_ms_) {
+ NR_ASYNC_TIMER_SET(server->delay_ms_, process_cb,
+ new DeferredStunOperation(server, message, message_len,
+ &addr, send_sock),
+ &server->timer_handle_);
+ } else {
+ server->Process(reinterpret_cast<const uint8_t*>(message), message_len,
+ &addr, send_sock);
+ }
+}
+
+void TestStunServer::SetActive(bool active) { active_ = active; }
+
+void TestStunServer::SetDelay(uint32_t delay_ms) { delay_ms_ = delay_ms; }
+
+void TestStunServer::SetDropInitialPackets(uint32_t count) {
+ initial_ct_ = count;
+}
+
+nsresult TestStunServer::SetResponseAddr(nr_transport_addr* addr) {
+ delete response_addr_;
+
+ response_addr_ = new nr_transport_addr();
+
+ int r = nr_transport_addr_copy(response_addr_, addr);
+ if (r) return NS_ERROR_FAILURE;
+
+ return NS_OK;
+}
+
+nsresult TestStunServer::SetResponseAddr(const std::string& addr,
+ uint16_t port) {
+ nr_transport_addr addr2;
+
+ int r =
+ nr_str_port_to_transport_addr(addr.c_str(), port, IPPROTO_UDP, &addr2);
+ if (r) return NS_ERROR_FAILURE;
+
+ return SetResponseAddr(&addr2);
+}
+
+void TestStunServer::Reset() {
+ delay_ms_ = 0;
+ if (timer_handle_) {
+ NR_async_timer_cancel(timer_handle_);
+ timer_handle_ = nullptr;
+ }
+ delete response_addr_;
+ response_addr_ = nullptr;
+ received_ct_.clear();
+}
+
+// TestStunTcpServer
+
+void TestStunTcpServer::ConfigurePort(uint16_t port) { instance_port = port; }
+
+TestStunTcpServer* TestStunTcpServer::GetInstance(int address_family) {
+ switch (address_family) {
+ case AF_INET:
+ if (!instance) instance = Create(address_family).release();
+
+ MOZ_ASSERT(instance);
+ return instance;
+ case AF_INET6:
+ if (!instance6) instance6 = Create(address_family).release();
+
+ return instance6;
+ default:
+ MOZ_CRASH();
+ }
+}
+
+void TestStunTcpServer::ShutdownInstance() {
+ delete instance;
+ instance = nullptr;
+ delete instance6;
+ instance6 = nullptr;
+}
+
+int TestStunTcpServer::TryOpenListenSocket(nr_local_addr* addr, uint16_t port) {
+ addr->addr.protocol = IPPROTO_TCP;
+
+ int r = SetInternalPort(addr, port);
+
+ if (r) return r;
+
+ nr_socket* sock;
+ if (nr_socket_local_create(nullptr, &addr->addr, &sock)) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't create listen tcp socket");
+ return R_ALREADY;
+ }
+
+ if (nr_socket_buffered_stun_create(sock, 2048, TURN_TCP_FRAMING,
+ &listen_sock_)) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't create listen tcp socket");
+ return R_ALREADY;
+ }
+
+ if (nr_socket_listen(listen_sock_, 10)) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't listen on socket");
+ return R_ALREADY;
+ }
+
+ return 0;
+}
+
+nr_socket* TestStunTcpServer::GetReceivingSocket(NR_SOCKET s) {
+ return connections_[s];
+}
+
+nr_socket* TestStunTcpServer::GetSendingSocket(nr_socket* sock) { return sock; }
+
+void TestStunTcpServer::accept_cb(NR_SOCKET s, int how, void* cb_arg) {
+ TestStunTcpServer* server = static_cast<TestStunTcpServer*>(cb_arg);
+ nr_socket *newsock, *bufsock, *wrapsock;
+ nr_transport_addr remote_addr;
+ NR_SOCKET fd;
+
+ /* rearm */
+ NR_ASYNC_WAIT(s, NR_ASYNC_WAIT_READ, &TestStunTcpServer::accept_cb, cb_arg);
+
+ /* accept */
+ if (nr_socket_accept(server->listen_sock_, &remote_addr, &newsock)) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't accept incoming tcp connection");
+ return;
+ }
+
+ if (nr_socket_buffered_stun_create(newsock, 2048, TURN_TCP_FRAMING,
+ &bufsock)) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't create connected tcp socket");
+ nr_socket_destroy(&newsock);
+ return;
+ }
+
+ nr_socket_buffered_set_connected_to(bufsock, &remote_addr);
+
+ if (nr_socket_wrapped_create(bufsock, &wrapsock)) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't wrap connected tcp socket");
+ nr_socket_destroy(&bufsock);
+ return;
+ }
+
+ if (nr_socket_getfd(wrapsock, &fd)) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't get fd from connected tcp socket");
+ nr_socket_destroy(&wrapsock);
+ return;
+ }
+
+ server->connections_[fd] = wrapsock;
+
+ NR_ASYNC_WAIT(fd, NR_ASYNC_WAIT_READ, &TestStunServer::readable_cb, server);
+}
+
+UniquePtr<TestStunTcpServer> TestStunTcpServer::Create(int address_family) {
+ NR_reg_init(NR_REG_MODE_LOCAL);
+
+ UniquePtr<TestStunTcpServer> server(new TestStunTcpServer());
+
+ if (server->Initialize(address_family)) {
+ return nullptr;
+ }
+
+ NR_SOCKET fd;
+ if (nr_socket_getfd(server->listen_sock_, &fd)) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't get tcp fd");
+ return nullptr;
+ }
+
+ NR_ASYNC_WAIT(fd, NR_ASYNC_WAIT_READ, &TestStunTcpServer::accept_cb,
+ server.get());
+
+ return server;
+}
+
+TestStunTcpServer::~TestStunTcpServer() {
+ for (auto it = connections_.begin(); it != connections_.end();) {
+ NR_ASYNC_CANCEL(it->first, NR_ASYNC_WAIT_READ);
+ nr_socket_destroy(&it->second);
+ connections_.erase(it++);
+ }
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/transport/test/stunserver.h b/dom/media/webrtc/transport/test/stunserver.h
new file mode 100644
index 0000000000..4903cb89ad
--- /dev/null
+++ b/dom/media/webrtc/transport/test/stunserver.h
@@ -0,0 +1,123 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+
+#ifndef stunserver_h__
+#define stunserver_h__
+
+#include <map>
+#include <string>
+#include "nsError.h"
+#include "mozilla/UniquePtr.h"
+
+typedef struct nr_stun_server_ctx_ nr_stun_server_ctx;
+typedef struct nr_socket_ nr_socket;
+typedef struct nr_local_addr_ nr_local_addr;
+
+namespace mozilla {
+
+class TestStunServer {
+ public:
+ // Generally, you should only call API in this class from the same thread that
+ // the initial |GetInstance| call was made from.
+ static TestStunServer* GetInstance(int address_family = AF_INET);
+ static void ShutdownInstance();
+ // |ConfigurePort| will only have an effect if called before the first call
+ // to |GetInstance| (possibly following a |ShutdownInstance| call)
+ static void ConfigurePort(uint16_t port);
+ // AF_INET, AF_INET6
+ static UniquePtr<TestStunServer> Create(int address_family);
+
+ virtual ~TestStunServer();
+
+ void SetActive(bool active);
+ void SetDelay(uint32_t delay_ms);
+ void SetDropInitialPackets(uint32_t count);
+ const std::string& addr() const { return listen_addr_; }
+ uint16_t port() const { return listen_port_; }
+
+ // These should only be called from the same thread as the initial
+ // |GetInstance| call.
+ nsresult SetResponseAddr(nr_transport_addr* addr);
+ nsresult SetResponseAddr(const std::string& addr, uint16_t port);
+
+ void Reset();
+
+ static const size_t max_stun_message_size = 4096;
+
+ virtual nr_socket* GetReceivingSocket(NR_SOCKET s);
+ virtual nr_socket* GetSendingSocket(nr_socket* sock);
+
+ protected:
+ TestStunServer()
+ : listen_port_(0),
+ listen_sock_(nullptr),
+ send_sock_(nullptr),
+ stun_server_(nullptr),
+ active_(true),
+ delay_ms_(0),
+ initial_ct_(0),
+ response_addr_(nullptr),
+ timer_handle_(nullptr) {}
+
+ int SetInternalPort(nr_local_addr* addr, uint16_t port);
+ int Initialize(int address_family);
+
+ static void readable_cb(NR_SOCKET sock, int how, void* cb_arg);
+
+ private:
+ void Process(const uint8_t* msg, size_t len, nr_transport_addr* addr_in,
+ nr_socket* sock);
+ virtual int TryOpenListenSocket(nr_local_addr* addr, uint16_t port);
+ static void process_cb(NR_SOCKET sock, int how, void* cb_arg);
+
+ protected:
+ std::string listen_addr_;
+ uint16_t listen_port_;
+ nr_socket* listen_sock_;
+ nr_socket* send_sock_;
+ nr_stun_server_ctx* stun_server_;
+
+ private:
+ bool active_;
+ uint32_t delay_ms_;
+ uint32_t initial_ct_;
+ nr_transport_addr* response_addr_;
+ void* timer_handle_;
+ std::map<std::string, uint32_t> received_ct_;
+
+ static TestStunServer* instance;
+ static TestStunServer* instance6;
+ static uint16_t instance_port;
+};
+
+class TestStunTcpServer : public TestStunServer {
+ public:
+ static TestStunTcpServer* GetInstance(int address_family);
+ static void ShutdownInstance();
+ static void ConfigurePort(uint16_t port);
+ virtual ~TestStunTcpServer();
+
+ virtual nr_socket* GetReceivingSocket(NR_SOCKET s);
+ virtual nr_socket* GetSendingSocket(nr_socket* sock);
+
+ protected:
+ TestStunTcpServer() = default;
+ static void accept_cb(NR_SOCKET sock, int how, void* cb_arg);
+
+ private:
+ virtual int TryOpenListenSocket(nr_local_addr* addr, uint16_t port);
+ static UniquePtr<TestStunTcpServer> Create(int address_family);
+
+ static TestStunTcpServer* instance;
+ static TestStunTcpServer* instance6;
+ static uint16_t instance_port;
+
+ std::map<NR_SOCKET, nr_socket*> connections_;
+};
+} // End of namespace mozilla
+#endif
diff --git a/dom/media/webrtc/transport/test/test_nr_socket_ice_unittest.cpp b/dom/media/webrtc/transport/test/test_nr_socket_ice_unittest.cpp
new file mode 100644
index 0000000000..b55b05f10c
--- /dev/null
+++ b/dom/media/webrtc/transport/test/test_nr_socket_ice_unittest.cpp
@@ -0,0 +1,409 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Some of this code is taken from nricectx.cpp and nricemediastream.cpp
+// which in turn contains code cut-and-pasted from nICEr. Copyright is:
+
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+*/
+
+#include "gtest/gtest.h"
+#include "gtest_utils.h"
+
+extern "C" {
+#include "ice_ctx.h"
+#include "ice_peer_ctx.h"
+#include "nICEr/src/net/transport_addr.h"
+}
+
+#include "mtransport_test_utils.h"
+#include "nricectx.h"
+#include "nricemediastream.h"
+#include "runnable_utils.h"
+#include "test_nr_socket.h"
+
+namespace mozilla {
+
+static unsigned int kDefaultTimeout = 7000;
+
+class IcePeer {
+ public:
+ IcePeer(const char* name, TestNat* nat, UINT4 flags,
+ MtransportTestUtils* test_utils)
+ : name_(name),
+ ice_checking_(false),
+ ice_connected_(false),
+ ice_disconnected_(false),
+ gather_cb_(false),
+ stream_ready_(false),
+ stream_failed_(false),
+ ice_ctx_(nullptr),
+ peer_ctx_(nullptr),
+ nat_(nat),
+ test_utils_(test_utils) {
+ nr_ice_ctx_create(const_cast<char*>(name_.c_str()), flags, &ice_ctx_);
+
+ if (nat_) {
+ nr_socket_factory* factory;
+ nat_->create_socket_factory(&factory);
+ nr_ice_ctx_set_socket_factory(ice_ctx_, factory);
+ }
+
+ // Create the handler objects
+ ice_handler_vtbl_ = new nr_ice_handler_vtbl();
+ ice_handler_vtbl_->select_pair = &IcePeer::select_pair;
+ ice_handler_vtbl_->stream_ready = &IcePeer::stream_ready;
+ ice_handler_vtbl_->stream_failed = &IcePeer::stream_failed;
+ ice_handler_vtbl_->ice_connected = &IcePeer::ice_connected;
+ ice_handler_vtbl_->msg_recvd = &IcePeer::msg_recvd;
+ ice_handler_vtbl_->ice_checking = &IcePeer::ice_checking;
+ ice_handler_vtbl_->ice_disconnected = &IcePeer::ice_disconnected;
+
+ ice_handler_ = new nr_ice_handler();
+ ice_handler_->vtbl = ice_handler_vtbl_;
+ ice_handler_->obj = this;
+
+ nr_ice_peer_ctx_create(ice_ctx_, ice_handler_,
+ const_cast<char*>(name_.c_str()), &peer_ctx_);
+
+ nr_ice_add_media_stream(ice_ctx_, const_cast<char*>(name_.c_str()), "ufrag",
+ "pass", 2, &ice_media_stream_);
+ EXPECT_EQ(2UL, GetStreamAttributes().size());
+
+ nr_ice_media_stream_initialize(ice_ctx_, ice_media_stream_);
+ }
+
+ virtual ~IcePeer() { Destroy(); }
+
+ void Destroy() {
+ test_utils_->SyncDispatchToSTS(WrapRunnable(this, &IcePeer::Destroy_s));
+ }
+
+ void Destroy_s() {
+ nr_ice_peer_ctx_destroy(&peer_ctx_);
+ delete ice_handler_;
+ delete ice_handler_vtbl_;
+ nr_ice_ctx_destroy(&ice_ctx_);
+ }
+
+ void Gather(bool default_route_only = false) {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &IcePeer::Gather_s, default_route_only));
+ }
+
+ void Gather_s(bool default_route_only = false) {
+ int r = nr_ice_gather(ice_ctx_, &IcePeer::gather_cb, this);
+ ASSERT_TRUE(r == 0 || r == R_WOULDBLOCK);
+ }
+
+ std::vector<std::string> GetStreamAttributes() {
+ std::vector<std::string> attributes;
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableRet(&attributes, this, &IcePeer::GetStreamAttributes_s));
+ return attributes;
+ }
+
+ std::vector<std::string> GetStreamAttributes_s() {
+ char** attrs = nullptr;
+ int attrct;
+ std::vector<std::string> ret;
+
+ int r =
+ nr_ice_media_stream_get_attributes(ice_media_stream_, &attrs, &attrct);
+ EXPECT_EQ(0, r);
+
+ for (int i = 0; i < attrct; i++) {
+ ret.push_back(std::string(attrs[i]));
+ RFREE(attrs[i]);
+ }
+ RFREE(attrs);
+
+ return ret;
+ }
+
+ std::vector<std::string> GetGlobalAttributes() {
+ std::vector<std::string> attributes;
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableRet(&attributes, this, &IcePeer::GetGlobalAttributes_s));
+ return attributes;
+ }
+
+ std::vector<std::string> GetGlobalAttributes_s() {
+ char** attrs = nullptr;
+ int attrct;
+ std::vector<std::string> ret;
+
+ nr_ice_get_global_attributes(ice_ctx_, &attrs, &attrct);
+
+ for (int i = 0; i < attrct; i++) {
+ ret.push_back(std::string(attrs[i]));
+ RFREE(attrs[i]);
+ }
+ RFREE(attrs);
+
+ return ret;
+ }
+
+ void ParseGlobalAttributes(std::vector<std::string> attrs) {
+ std::vector<char*> attrs_in;
+ attrs_in.reserve(attrs.size());
+ for (auto& attr : attrs) {
+ attrs_in.push_back(const_cast<char*>(attr.c_str()));
+ }
+
+ int r = nr_ice_peer_ctx_parse_global_attributes(
+ peer_ctx_, attrs_in.empty() ? nullptr : &attrs_in[0], attrs_in.size());
+ ASSERT_EQ(0, r);
+ }
+
+ void SetControlling(bool controlling) {
+ peer_ctx_->controlling = controlling ? 1 : 0;
+ }
+
+ void SetRemoteAttributes(std::vector<std::string> attributes) {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &IcePeer::SetRemoteAttributes_s, attributes));
+ }
+
+ void SetRemoteAttributes_s(std::vector<std::string> attributes) {
+ int r;
+
+ std::vector<char*> attrs;
+ attrs.reserve(attributes.size());
+ for (auto& attr : attributes) {
+ attrs.push_back(const_cast<char*>(attr.c_str()));
+ }
+
+ if (!attrs.empty()) {
+ r = nr_ice_peer_ctx_parse_stream_attributes(peer_ctx_, ice_media_stream_,
+ &attrs[0], attrs.size());
+ ASSERT_EQ(0, r);
+ }
+ }
+
+ void StartChecks() {
+ test_utils_->SyncDispatchToSTS(WrapRunnable(this, &IcePeer::StartChecks_s));
+ }
+
+ void StartChecks_s() {
+ int r = nr_ice_peer_ctx_pair_candidates(peer_ctx_);
+ ASSERT_EQ(0, r);
+
+ r = nr_ice_peer_ctx_start_checks2(peer_ctx_, 1);
+ ASSERT_EQ(0, r);
+ }
+
+ // Handler callbacks
+ static int select_pair(void* obj, nr_ice_media_stream* stream,
+ int component_id, nr_ice_cand_pair** potentials,
+ int potential_ct) {
+ return 0;
+ }
+
+ static int stream_ready(void* obj, nr_ice_media_stream* stream) {
+ IcePeer* peer = static_cast<IcePeer*>(obj);
+ peer->stream_ready_ = true;
+ return 0;
+ }
+
+ static int stream_failed(void* obj, nr_ice_media_stream* stream) {
+ IcePeer* peer = static_cast<IcePeer*>(obj);
+ peer->stream_failed_ = true;
+ return 0;
+ }
+
+ static int ice_checking(void* obj, nr_ice_peer_ctx* pctx) {
+ IcePeer* peer = static_cast<IcePeer*>(obj);
+ peer->ice_checking_ = true;
+ return 0;
+ }
+
+ static int ice_connected(void* obj, nr_ice_peer_ctx* pctx) {
+ IcePeer* peer = static_cast<IcePeer*>(obj);
+ peer->ice_connected_ = true;
+ return 0;
+ }
+
+ static int ice_disconnected(void* obj, nr_ice_peer_ctx* pctx) {
+ IcePeer* peer = static_cast<IcePeer*>(obj);
+ peer->ice_disconnected_ = true;
+ return 0;
+ }
+
+ static int msg_recvd(void* obj, nr_ice_peer_ctx* pctx,
+ nr_ice_media_stream* stream, int component_id,
+ UCHAR* msg, int len) {
+ return 0;
+ }
+
+ static void gather_cb(NR_SOCKET s, int h, void* arg) {
+ IcePeer* peer = static_cast<IcePeer*>(arg);
+ peer->gather_cb_ = true;
+ }
+
+ std::string name_;
+
+ bool ice_checking_;
+ bool ice_connected_;
+ bool ice_disconnected_;
+ bool gather_cb_;
+ bool stream_ready_;
+ bool stream_failed_;
+
+ nr_ice_ctx* ice_ctx_;
+ nr_ice_handler* ice_handler_;
+ nr_ice_handler_vtbl* ice_handler_vtbl_;
+ nr_ice_media_stream* ice_media_stream_;
+ nr_ice_peer_ctx* peer_ctx_;
+ TestNat* nat_;
+ MtransportTestUtils* test_utils_;
+};
+
+class TestNrSocketIceUnitTest : public ::testing::Test {
+ public:
+ void SetUp() override {
+ NSS_NoDB_Init(nullptr);
+ NSS_SetDomesticPolicy();
+
+ test_utils_ = new MtransportTestUtils();
+ test_utils2_ = new MtransportTestUtils();
+
+ NrIceCtx::InitializeGlobals(NrIceCtx::GlobalConfig());
+ }
+
+ void TearDown() override {
+ delete test_utils_;
+ delete test_utils2_;
+ }
+
+ MtransportTestUtils* test_utils_;
+ MtransportTestUtils* test_utils2_;
+};
+
+TEST_F(TestNrSocketIceUnitTest, TestIcePeer) {
+ IcePeer peer("IcePeer", nullptr, NR_ICE_CTX_FLAGS_AGGRESSIVE_NOMINATION,
+ test_utils_);
+ ASSERT_NE(peer.ice_ctx_, nullptr);
+ ASSERT_NE(peer.peer_ctx_, nullptr);
+ ASSERT_NE(peer.ice_media_stream_, nullptr);
+ ASSERT_EQ(2UL, peer.GetStreamAttributes().size())
+ << "Should have ice-ufrag and ice-pwd";
+ peer.Gather();
+ ASSERT_LT(2UL, peer.GetStreamAttributes().size())
+ << "Should have ice-ufrag, ice-pwd, and at least one candidate.";
+}
+
+TEST_F(TestNrSocketIceUnitTest, TestIcePeersNoNAT) {
+ IcePeer peer("IcePeer", nullptr, NR_ICE_CTX_FLAGS_AGGRESSIVE_NOMINATION,
+ test_utils_);
+ IcePeer peer2("IcePeer2", nullptr, NR_ICE_CTX_FLAGS_AGGRESSIVE_NOMINATION,
+ test_utils2_);
+ peer.SetControlling(true);
+ peer2.SetControlling(false);
+
+ peer.Gather();
+ peer2.Gather();
+ std::vector<std::string> attrs = peer.GetGlobalAttributes();
+ peer2.ParseGlobalAttributes(attrs);
+ std::vector<std::string> attributes = peer.GetStreamAttributes();
+ peer2.SetRemoteAttributes(attributes);
+
+ attrs = peer2.GetGlobalAttributes();
+ peer.ParseGlobalAttributes(attrs);
+ attributes = peer2.GetStreamAttributes();
+ peer.SetRemoteAttributes(attributes);
+ peer2.StartChecks();
+ peer.StartChecks();
+
+ ASSERT_TRUE_WAIT(peer.ice_connected_, kDefaultTimeout);
+ ASSERT_TRUE_WAIT(peer2.ice_connected_, kDefaultTimeout);
+}
+
+TEST_F(TestNrSocketIceUnitTest, TestIcePeersPacketLoss) {
+ IcePeer peer("IcePeer", nullptr, NR_ICE_CTX_FLAGS_AGGRESSIVE_NOMINATION,
+ test_utils_);
+
+ RefPtr<TestNat> nat(new TestNat);
+ class NatDelegate : public TestNat::NatDelegate {
+ public:
+ NatDelegate() : messages(0) {}
+
+ int on_read(TestNat* nat, void* buf, size_t maxlen, size_t* len) override {
+ return 0;
+ }
+
+ int on_sendto(TestNat* nat, const void* msg, size_t len, int flags,
+ const nr_transport_addr* to) override {
+ ++messages;
+ // 25% packet loss
+ if (messages % 4 == 0) {
+ return 1;
+ }
+ return 0;
+ }
+
+ int on_write(TestNat* nat, const void* msg, size_t len,
+ size_t* written) override {
+ return 0;
+ }
+
+ int messages;
+ } delegate;
+ nat->nat_delegate_ = &delegate;
+
+ IcePeer peer2("IcePeer2", nat, NR_ICE_CTX_FLAGS_AGGRESSIVE_NOMINATION,
+ test_utils2_);
+ peer.SetControlling(true);
+ peer2.SetControlling(false);
+
+ peer.Gather();
+ peer2.Gather();
+ std::vector<std::string> attrs = peer.GetGlobalAttributes();
+ peer2.ParseGlobalAttributes(attrs);
+ std::vector<std::string> attributes = peer.GetStreamAttributes();
+ peer2.SetRemoteAttributes(attributes);
+
+ attrs = peer2.GetGlobalAttributes();
+ peer.ParseGlobalAttributes(attrs);
+ attributes = peer2.GetStreamAttributes();
+ peer.SetRemoteAttributes(attributes);
+ peer2.StartChecks();
+ peer.StartChecks();
+
+ ASSERT_TRUE_WAIT(peer.ice_connected_, kDefaultTimeout);
+ ASSERT_TRUE_WAIT(peer2.ice_connected_, kDefaultTimeout);
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/transport/test/test_nr_socket_unittest.cpp b/dom/media/webrtc/transport/test/test_nr_socket_unittest.cpp
new file mode 100644
index 0000000000..af2779accd
--- /dev/null
+++ b/dom/media/webrtc/transport/test/test_nr_socket_unittest.cpp
@@ -0,0 +1,800 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: bcampen@mozilla.com
+
+#include <cstddef>
+
+extern "C" {
+#include "r_errors.h"
+#include "async_wait.h"
+}
+
+#include "test_nr_socket.h"
+
+#include "nsCOMPtr.h"
+#include "nsNetCID.h"
+#include "nsServiceManagerUtils.h"
+#include "runnable_utils.h"
+
+#include <vector>
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+#include "gtest_utils.h"
+
+#define DATA_BUF_SIZE 1024
+
+namespace mozilla {
+
+class TestNrSocketTest : public MtransportTest {
+ public:
+ TestNrSocketTest()
+ : MtransportTest(),
+ wait_done_for_main_(false),
+ sts_(),
+ public_addrs_(),
+ private_addrs_(),
+ nats_() {}
+
+ void SetUp() override {
+ MtransportTest::SetUp();
+
+ // Get the transport service as a dispatch target
+ nsresult rv;
+ sts_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ EXPECT_TRUE(NS_SUCCEEDED(rv)) << "Failed to get STS: " << (int)rv;
+ }
+
+ void TearDown() override {
+ SyncDispatchToSTS(WrapRunnable(this, &TestNrSocketTest::TearDown_s));
+
+ MtransportTest::TearDown();
+ }
+
+ void TearDown_s() {
+ public_addrs_.clear();
+ private_addrs_.clear();
+ nats_.clear();
+ sts_ = nullptr;
+ }
+
+ RefPtr<TestNrSocket> CreateTestNrSocket_s(const char* ip_str, int proto,
+ TestNat* nat) {
+ // If no nat is supplied, we create a default NAT which is disabled. This
+ // is how we simulate a non-natted socket.
+ RefPtr<TestNrSocket> sock(new TestNrSocket(nat ? nat : new TestNat));
+ nr_transport_addr address;
+ nr_str_port_to_transport_addr(ip_str, 0, proto, &address);
+ int r = sock->create(&address);
+ if (r) {
+ return nullptr;
+ }
+ return sock;
+ }
+
+ void CreatePublicAddrs(size_t count, const char* ip_str = "127.0.0.1",
+ int proto = IPPROTO_UDP) {
+ SyncDispatchToSTS(WrapRunnable(this, &TestNrSocketTest::CreatePublicAddrs_s,
+ count, ip_str, proto));
+ }
+
+ void CreatePublicAddrs_s(size_t count, const char* ip_str, int proto) {
+ while (count--) {
+ auto sock = CreateTestNrSocket_s(ip_str, proto, nullptr);
+ ASSERT_TRUE(sock)
+ << "Failed to create socket";
+ public_addrs_.push_back(sock);
+ }
+ }
+
+ RefPtr<TestNat> CreatePrivateAddrs(size_t size,
+ const char* ip_str = "127.0.0.1",
+ int proto = IPPROTO_UDP) {
+ RefPtr<TestNat> result;
+ SyncDispatchToSTS(WrapRunnableRet(&result, this,
+ &TestNrSocketTest::CreatePrivateAddrs_s,
+ size, ip_str, proto));
+ return result;
+ }
+
+ RefPtr<TestNat> CreatePrivateAddrs_s(size_t count, const char* ip_str,
+ int proto) {
+ RefPtr<TestNat> nat(new TestNat);
+ while (count--) {
+ auto sock = CreateTestNrSocket_s(ip_str, proto, nat);
+ if (!sock) {
+ EXPECT_TRUE(false) << "Failed to create socket";
+ break;
+ }
+ private_addrs_.push_back(sock);
+ }
+ nat->enabled_ = true;
+ nats_.push_back(nat);
+ return nat;
+ }
+
+ bool CheckConnectivityVia(
+ TestNrSocket* from, TestNrSocket* to, const nr_transport_addr& via,
+ nr_transport_addr* sender_external_address = nullptr) {
+ MOZ_ASSERT(from);
+
+ if (!WaitForWriteable(from)) {
+ return false;
+ }
+
+ int result = 0;
+ SyncDispatchToSTS(WrapRunnableRet(
+ &result, this, &TestNrSocketTest::SendData_s, from, via));
+ if (result) {
+ return false;
+ }
+
+ if (!WaitForReadable(to)) {
+ return false;
+ }
+
+ nr_transport_addr dummy_outparam;
+ if (!sender_external_address) {
+ sender_external_address = &dummy_outparam;
+ }
+
+ MOZ_ASSERT(to);
+ SyncDispatchToSTS(WrapRunnableRet(&result, this,
+ &TestNrSocketTest::RecvData_s, to,
+ sender_external_address));
+
+ return !result;
+ }
+
+ bool CheckConnectivity(TestNrSocket* from, TestNrSocket* to,
+ nr_transport_addr* sender_external_address = nullptr) {
+ nr_transport_addr destination_address;
+ int r = GetAddress(to, &destination_address);
+ if (r) {
+ return false;
+ }
+
+ return CheckConnectivityVia(from, to, destination_address,
+ sender_external_address);
+ }
+
+ bool CheckTcpConnectivity(TestNrSocket* from, TestNrSocket* to) {
+ NrSocketBase* accepted_sock;
+ if (!Connect(from, to, &accepted_sock)) {
+ std::cerr << "Connect failed" << std::endl;
+ return false;
+ }
+
+ // write on |from|, recv on |accepted_sock|
+ if (!WaitForWriteable(from)) {
+ std::cerr << __LINE__ << "WaitForWriteable (1) failed" << std::endl;
+ return false;
+ }
+
+ int r;
+ SyncDispatchToSTS(
+ WrapRunnableRet(&r, this, &TestNrSocketTest::SendDataTcp_s, from));
+ if (r) {
+ std::cerr << "SendDataTcp_s (1) failed" << std::endl;
+ return false;
+ }
+
+ if (!WaitForReadable(accepted_sock)) {
+ std::cerr << __LINE__ << "WaitForReadable (1) failed" << std::endl;
+ return false;
+ }
+
+ SyncDispatchToSTS(WrapRunnableRet(
+ &r, this, &TestNrSocketTest::RecvDataTcp_s, accepted_sock));
+ if (r) {
+ std::cerr << "RecvDataTcp_s (1) failed" << std::endl;
+ return false;
+ }
+
+ if (!WaitForWriteable(accepted_sock)) {
+ std::cerr << __LINE__ << "WaitForWriteable (2) failed" << std::endl;
+ return false;
+ }
+
+ SyncDispatchToSTS(WrapRunnableRet(
+ &r, this, &TestNrSocketTest::SendDataTcp_s, accepted_sock));
+ if (r) {
+ std::cerr << "SendDataTcp_s (2) failed" << std::endl;
+ return false;
+ }
+
+ if (!WaitForReadable(from)) {
+ std::cerr << __LINE__ << "WaitForReadable (2) failed" << std::endl;
+ return false;
+ }
+
+ SyncDispatchToSTS(
+ WrapRunnableRet(&r, this, &TestNrSocketTest::RecvDataTcp_s, from));
+ if (r) {
+ std::cerr << "RecvDataTcp_s (2) failed" << std::endl;
+ return false;
+ }
+
+ return true;
+ }
+
+ int GetAddress(TestNrSocket* sock, nr_transport_addr_* address) {
+ MOZ_ASSERT(sock);
+ MOZ_ASSERT(address);
+ int r;
+ SyncDispatchToSTS(WrapRunnableRet(&r, this, &TestNrSocketTest::GetAddress_s,
+ sock, address));
+ return r;
+ }
+
+ int GetAddress_s(TestNrSocket* sock, nr_transport_addr* address) {
+ return sock->getaddr(address);
+ }
+
+ int SendData_s(TestNrSocket* from, const nr_transport_addr& to) {
+ // It is up to caller to ensure that |from| is writeable.
+ const char buf[] = "foobajooba";
+ return from->sendto(buf, sizeof(buf), 0, &to);
+ }
+
+ int SendDataTcp_s(NrSocketBase* from) {
+ // It is up to caller to ensure that |from| is writeable.
+ const char buf[] = "foobajooba";
+ size_t written;
+ return from->write(buf, sizeof(buf), &written);
+ }
+
+ int RecvData_s(TestNrSocket* to, nr_transport_addr* from) {
+ // It is up to caller to ensure that |to| is readable
+ char buf[DATA_BUF_SIZE];
+ size_t len;
+ // Maybe check that data matches?
+ int r = to->recvfrom(buf, sizeof(buf), &len, 0, from);
+ if (!r && (len == 0)) {
+ r = R_INTERNAL;
+ }
+ return r;
+ }
+
+ int RecvDataTcp_s(NrSocketBase* to) {
+ // It is up to caller to ensure that |to| is readable
+ char buf[DATA_BUF_SIZE];
+ size_t len;
+ // Maybe check that data matches?
+ int r = to->read(buf, sizeof(buf), &len);
+ if (!r && (len == 0)) {
+ r = R_INTERNAL;
+ }
+ return r;
+ }
+
+ int Listen_s(TestNrSocket* to) {
+ // listen on |to|
+ int r = to->listen(1);
+ if (r) {
+ return r;
+ }
+ return 0;
+ }
+
+ int Connect_s(TestNrSocket* from, TestNrSocket* to) {
+ // connect on |from|
+ nr_transport_addr destination_address;
+ int r = to->getaddr(&destination_address);
+ if (r) {
+ return r;
+ }
+
+ r = from->connect(&destination_address);
+ if (r) {
+ return r;
+ }
+
+ return 0;
+ }
+
+ int Accept_s(TestNrSocket* to, NrSocketBase** accepted_sock) {
+ nr_socket* sock;
+ nr_transport_addr source_address;
+ int r = to->accept(&source_address, &sock);
+ if (r) {
+ return r;
+ }
+
+ *accepted_sock = reinterpret_cast<NrSocketBase*>(sock->obj);
+ return 0;
+ }
+
+ bool Connect(TestNrSocket* from, TestNrSocket* to,
+ NrSocketBase** accepted_sock) {
+ int r;
+ SyncDispatchToSTS(
+ WrapRunnableRet(&r, this, &TestNrSocketTest::Listen_s, to));
+ if (r) {
+ std::cerr << "Listen_s failed: " << r << std::endl;
+ return false;
+ }
+
+ SyncDispatchToSTS(
+ WrapRunnableRet(&r, this, &TestNrSocketTest::Connect_s, from, to));
+ if (r && r != R_WOULDBLOCK) {
+ std::cerr << "Connect_s failed: " << r << std::endl;
+ return false;
+ }
+
+ if (!WaitForReadable(to)) {
+ std::cerr << "WaitForReadable failed" << std::endl;
+ return false;
+ }
+
+ SyncDispatchToSTS(WrapRunnableRet(&r, this, &TestNrSocketTest::Accept_s, to,
+ accepted_sock));
+
+ if (r) {
+ std::cerr << "Accept_s failed: " << r << std::endl;
+ return false;
+ }
+ return true;
+ }
+
+ bool WaitForSocketState(NrSocketBase* sock, int state) {
+ MOZ_ASSERT(sock);
+ SyncDispatchToSTS(WrapRunnable(
+ this, &TestNrSocketTest::WaitForSocketState_s, sock, state));
+
+ bool res;
+ WAIT_(wait_done_for_main_, 500, res);
+ wait_done_for_main_ = false;
+
+ if (!res) {
+ SyncDispatchToSTS(
+ WrapRunnable(this, &TestNrSocketTest::CancelWait_s, sock, state));
+ }
+
+ return res;
+ }
+
+ void WaitForSocketState_s(NrSocketBase* sock, int state) {
+ NR_ASYNC_WAIT(sock, state, &WaitDone, this);
+ }
+
+ void CancelWait_s(NrSocketBase* sock, int state) { sock->cancel(state); }
+
+ bool WaitForReadable(NrSocketBase* sock) {
+ return WaitForSocketState(sock, NR_ASYNC_WAIT_READ);
+ }
+
+ bool WaitForWriteable(NrSocketBase* sock) {
+ return WaitForSocketState(sock, NR_ASYNC_WAIT_WRITE);
+ }
+
+ void SyncDispatchToSTS(nsIRunnable* runnable) {
+ NS_DispatchAndSpinEventLoopUntilComplete(
+ "TestNrSocketTest::SyncDispatchToSTS"_ns, sts_, do_AddRef(runnable));
+ }
+
+ static void WaitDone(void* sock, int how, void* test_fixture) {
+ TestNrSocketTest* test = static_cast<TestNrSocketTest*>(test_fixture);
+ test->wait_done_for_main_ = true;
+ }
+
+ // Simple busywait boolean for the test cases to spin on.
+ Atomic<bool> wait_done_for_main_;
+
+ nsCOMPtr<nsIEventTarget> sts_;
+ std::vector<RefPtr<TestNrSocket>> public_addrs_;
+ std::vector<RefPtr<TestNrSocket>> private_addrs_;
+ std::vector<RefPtr<TestNat>> nats_;
+};
+
+} // namespace mozilla
+
+using mozilla::NrSocketBase;
+using mozilla::TestNat;
+using mozilla::TestNrSocketTest;
+
+TEST_F(TestNrSocketTest, UnsafePortRejectedUDP) {
+ nr_transport_addr address;
+ ASSERT_FALSE(nr_str_port_to_transport_addr("127.0.0.1",
+ // ssh
+ 22, IPPROTO_UDP, &address));
+ ASSERT_TRUE(NrSocketBase::IsForbiddenAddress(&address));
+}
+
+TEST_F(TestNrSocketTest, UnsafePortRejectedTCP) {
+ nr_transport_addr address;
+ ASSERT_FALSE(nr_str_port_to_transport_addr("127.0.0.1",
+ // ssh
+ 22, IPPROTO_TCP, &address));
+ ASSERT_TRUE(NrSocketBase::IsForbiddenAddress(&address));
+}
+
+TEST_F(TestNrSocketTest, SafePortAcceptedUDP) {
+ nr_transport_addr address;
+ ASSERT_FALSE(nr_str_port_to_transport_addr("127.0.0.1",
+ // stuns
+ 5349, IPPROTO_UDP, &address));
+ ASSERT_FALSE(NrSocketBase::IsForbiddenAddress(&address));
+}
+
+TEST_F(TestNrSocketTest, SafePortAcceptedTCP) {
+ nr_transport_addr address;
+ ASSERT_FALSE(nr_str_port_to_transport_addr("127.0.0.1",
+ // turns
+ 5349, IPPROTO_TCP, &address));
+ ASSERT_FALSE(NrSocketBase::IsForbiddenAddress(&address));
+}
+
+TEST_F(TestNrSocketTest, PublicConnectivity) {
+ CreatePublicAddrs(2);
+
+ ASSERT_TRUE(CheckConnectivity(public_addrs_[0], public_addrs_[1]));
+ ASSERT_TRUE(CheckConnectivity(public_addrs_[1], public_addrs_[0]));
+ ASSERT_TRUE(CheckConnectivity(public_addrs_[0], public_addrs_[0]));
+ ASSERT_TRUE(CheckConnectivity(public_addrs_[1], public_addrs_[1]));
+}
+
+TEST_F(TestNrSocketTest, PrivateConnectivity) {
+ RefPtr<TestNat> nat(CreatePrivateAddrs(2));
+ nat->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT;
+ nat->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT;
+
+ ASSERT_TRUE(CheckConnectivity(private_addrs_[0], private_addrs_[1]));
+ ASSERT_TRUE(CheckConnectivity(private_addrs_[1], private_addrs_[0]));
+ ASSERT_TRUE(CheckConnectivity(private_addrs_[0], private_addrs_[0]));
+ ASSERT_TRUE(CheckConnectivity(private_addrs_[1], private_addrs_[1]));
+}
+
+TEST_F(TestNrSocketTest, NoConnectivityWithoutPinhole) {
+ RefPtr<TestNat> nat(CreatePrivateAddrs(1));
+ nat->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT;
+ nat->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT;
+ CreatePublicAddrs(1);
+
+ ASSERT_FALSE(CheckConnectivity(public_addrs_[0], private_addrs_[0]));
+}
+
+TEST_F(TestNrSocketTest, NoConnectivityBetweenSubnets) {
+ RefPtr<TestNat> nat1(CreatePrivateAddrs(1));
+ nat1->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT;
+ nat1->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT;
+ RefPtr<TestNat> nat2(CreatePrivateAddrs(1));
+ nat2->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT;
+ nat2->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT;
+
+ ASSERT_FALSE(CheckConnectivity(private_addrs_[0], private_addrs_[1]));
+ ASSERT_FALSE(CheckConnectivity(private_addrs_[1], private_addrs_[0]));
+ ASSERT_TRUE(CheckConnectivity(private_addrs_[0], private_addrs_[0]));
+ ASSERT_TRUE(CheckConnectivity(private_addrs_[1], private_addrs_[1]));
+}
+
+TEST_F(TestNrSocketTest, FullConeAcceptIngress) {
+ RefPtr<TestNat> nat(CreatePrivateAddrs(1));
+ nat->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT;
+ nat->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT;
+ CreatePublicAddrs(2);
+
+ nr_transport_addr sender_external_address;
+ // Open pinhole to public IP 0
+ ASSERT_TRUE(CheckConnectivity(private_addrs_[0], public_addrs_[0],
+ &sender_external_address));
+
+ // Verify that return traffic works
+ ASSERT_TRUE(CheckConnectivityVia(public_addrs_[0], private_addrs_[0],
+ sender_external_address));
+
+ // Verify that other public IP can use the pinhole
+ ASSERT_TRUE(CheckConnectivityVia(public_addrs_[1], private_addrs_[0],
+ sender_external_address));
+}
+
+TEST_F(TestNrSocketTest, FullConeOnePinhole) {
+ RefPtr<TestNat> nat(CreatePrivateAddrs(1));
+ nat->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT;
+ nat->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT;
+ CreatePublicAddrs(2);
+
+ nr_transport_addr sender_external_address;
+ // Open pinhole to public IP 0
+ ASSERT_TRUE(CheckConnectivity(private_addrs_[0], public_addrs_[0],
+ &sender_external_address));
+
+ // Verify that return traffic works
+ ASSERT_TRUE(CheckConnectivityVia(public_addrs_[0], private_addrs_[0],
+ sender_external_address));
+
+ // Send traffic to other public IP, verify that it uses the same pinhole
+ nr_transport_addr sender_external_address2;
+ ASSERT_TRUE(CheckConnectivity(private_addrs_[0], public_addrs_[1],
+ &sender_external_address2));
+ ASSERT_FALSE(nr_transport_addr_cmp(&sender_external_address,
+ &sender_external_address2,
+ NR_TRANSPORT_ADDR_CMP_MODE_ALL))
+ << "addr1: " << sender_external_address.as_string
+ << " addr2: " << sender_external_address2.as_string;
+}
+
+// OS 10.6 doesn't seem to allow us to open ports on 127.0.0.2, and while linux
+// does allow this, it has other behavior (see below) that prevents this test
+// from working.
+TEST_F(TestNrSocketTest, DISABLED_AddressRestrictedCone) {
+ RefPtr<TestNat> nat(CreatePrivateAddrs(1));
+ nat->filtering_type_ = TestNat::ADDRESS_DEPENDENT;
+ nat->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT;
+ CreatePublicAddrs(2, "127.0.0.1");
+ CreatePublicAddrs(1, "127.0.0.2");
+
+ nr_transport_addr sender_external_address;
+ // Open pinhole to public IP 0
+ ASSERT_TRUE(CheckConnectivity(private_addrs_[0], public_addrs_[0],
+ &sender_external_address));
+
+ // Verify that return traffic works
+ ASSERT_TRUE(CheckConnectivityVia(public_addrs_[0], private_addrs_[0],
+ sender_external_address));
+
+ // Verify that another address on the same host can use the pinhole
+ ASSERT_TRUE(CheckConnectivityVia(public_addrs_[1], private_addrs_[0],
+ sender_external_address));
+
+ // Linux has a tendency to monkey around with source addresses, doing
+ // stuff like substituting 127.0.0.1 for packets sent by 127.0.0.2, and even
+ // going as far as substituting localhost for a packet sent from a real IP
+ // address when the destination is localhost. The only way to make this test
+ // work on linux is to have two real IP addresses.
+#ifndef __linux__
+ // Verify that an address on a different host can't use the pinhole
+ ASSERT_FALSE(CheckConnectivityVia(public_addrs_[2], private_addrs_[0],
+ sender_external_address));
+#endif
+
+ // Send traffic to other public IP, verify that it uses the same pinhole
+ nr_transport_addr sender_external_address2;
+ ASSERT_TRUE(CheckConnectivity(private_addrs_[0], public_addrs_[1],
+ &sender_external_address2));
+ ASSERT_FALSE(nr_transport_addr_cmp(&sender_external_address,
+ &sender_external_address2,
+ NR_TRANSPORT_ADDR_CMP_MODE_ALL))
+ << "addr1: " << sender_external_address.as_string
+ << " addr2: " << sender_external_address2.as_string;
+
+ // Verify that the other public IP can now use the pinhole
+ ASSERT_TRUE(CheckConnectivityVia(public_addrs_[1], private_addrs_[0],
+ sender_external_address2));
+
+ // Send traffic to other public IP, verify that it uses the same pinhole
+ nr_transport_addr sender_external_address3;
+ ASSERT_TRUE(CheckConnectivity(private_addrs_[0], public_addrs_[2],
+ &sender_external_address3));
+ ASSERT_FALSE(nr_transport_addr_cmp(&sender_external_address,
+ &sender_external_address3,
+ NR_TRANSPORT_ADDR_CMP_MODE_ALL))
+ << "addr1: " << sender_external_address.as_string
+ << " addr2: " << sender_external_address3.as_string;
+
+ // Verify that the other public IP can now use the pinhole
+ ASSERT_TRUE(CheckConnectivityVia(public_addrs_[2], private_addrs_[0],
+ sender_external_address3));
+}
+
+TEST_F(TestNrSocketTest, RestrictedCone) {
+ RefPtr<TestNat> nat(CreatePrivateAddrs(1));
+ nat->filtering_type_ = TestNat::PORT_DEPENDENT;
+ nat->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT;
+ CreatePublicAddrs(2);
+
+ nr_transport_addr sender_external_address;
+ // Open pinhole to public IP 0
+ ASSERT_TRUE(CheckConnectivity(private_addrs_[0], public_addrs_[0],
+ &sender_external_address));
+
+ // Verify that return traffic works
+ ASSERT_TRUE(CheckConnectivityVia(public_addrs_[0], private_addrs_[0],
+ sender_external_address));
+
+ // Verify that other public IP cannot use the pinhole
+ ASSERT_FALSE(CheckConnectivityVia(public_addrs_[1], private_addrs_[0],
+ sender_external_address));
+
+ // Send traffic to other public IP, verify that it uses the same pinhole
+ nr_transport_addr sender_external_address2;
+ ASSERT_TRUE(CheckConnectivity(private_addrs_[0], public_addrs_[1],
+ &sender_external_address2));
+ ASSERT_FALSE(nr_transport_addr_cmp(&sender_external_address,
+ &sender_external_address2,
+ NR_TRANSPORT_ADDR_CMP_MODE_ALL))
+ << "addr1: " << sender_external_address.as_string
+ << " addr2: " << sender_external_address2.as_string;
+
+ // Verify that the other public IP can now use the pinhole
+ ASSERT_TRUE(CheckConnectivityVia(public_addrs_[1], private_addrs_[0],
+ sender_external_address2));
+}
+
+TEST_F(TestNrSocketTest, PortDependentMappingFullCone) {
+ RefPtr<TestNat> nat(CreatePrivateAddrs(1));
+ nat->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT;
+ nat->mapping_type_ = TestNat::PORT_DEPENDENT;
+ CreatePublicAddrs(2);
+
+ nr_transport_addr sender_external_address0;
+ // Open pinhole to public IP 0
+ ASSERT_TRUE(CheckConnectivity(private_addrs_[0], public_addrs_[0],
+ &sender_external_address0));
+
+ // Verify that return traffic works
+ ASSERT_TRUE(CheckConnectivityVia(public_addrs_[0], private_addrs_[0],
+ sender_external_address0));
+
+ // Verify that other public IP can use the pinhole
+ ASSERT_TRUE(CheckConnectivityVia(public_addrs_[1], private_addrs_[0],
+ sender_external_address0));
+
+ // Send traffic to other public IP, verify that it uses a different pinhole
+ nr_transport_addr sender_external_address1;
+ ASSERT_TRUE(CheckConnectivity(private_addrs_[0], public_addrs_[1],
+ &sender_external_address1));
+ ASSERT_TRUE(nr_transport_addr_cmp(&sender_external_address0,
+ &sender_external_address1,
+ NR_TRANSPORT_ADDR_CMP_MODE_ALL))
+ << "addr1: " << sender_external_address0.as_string
+ << " addr2: " << sender_external_address1.as_string;
+
+ // Verify that return traffic works
+ ASSERT_TRUE(CheckConnectivityVia(public_addrs_[1], private_addrs_[0],
+ sender_external_address1));
+
+ // Verify that other public IP can use the original pinhole
+ ASSERT_TRUE(CheckConnectivityVia(public_addrs_[0], private_addrs_[0],
+ sender_external_address1));
+}
+
+TEST_F(TestNrSocketTest, Symmetric) {
+ RefPtr<TestNat> nat(CreatePrivateAddrs(1));
+ nat->filtering_type_ = TestNat::PORT_DEPENDENT;
+ nat->mapping_type_ = TestNat::PORT_DEPENDENT;
+ CreatePublicAddrs(2);
+
+ nr_transport_addr sender_external_address;
+ // Open pinhole to public IP 0
+ ASSERT_TRUE(CheckConnectivity(private_addrs_[0], public_addrs_[0],
+ &sender_external_address));
+
+ // Verify that return traffic works
+ ASSERT_TRUE(CheckConnectivityVia(public_addrs_[0], private_addrs_[0],
+ sender_external_address));
+
+ // Verify that other public IP cannot use the pinhole
+ ASSERT_FALSE(CheckConnectivityVia(public_addrs_[1], private_addrs_[0],
+ sender_external_address));
+
+ // Send traffic to other public IP, verify that it uses a new pinhole
+ nr_transport_addr sender_external_address2;
+ ASSERT_TRUE(CheckConnectivity(private_addrs_[0], public_addrs_[1],
+ &sender_external_address2));
+ ASSERT_TRUE(nr_transport_addr_cmp(&sender_external_address,
+ &sender_external_address2,
+ NR_TRANSPORT_ADDR_CMP_MODE_ALL));
+
+ // Verify that the other public IP can use the new pinhole
+ ASSERT_TRUE(CheckConnectivityVia(public_addrs_[1], private_addrs_[0],
+ sender_external_address2));
+}
+
+TEST_F(TestNrSocketTest, BlockUdp) {
+ RefPtr<TestNat> nat(CreatePrivateAddrs(2));
+ nat->block_udp_ = true;
+ CreatePublicAddrs(1);
+
+ nr_transport_addr sender_external_address;
+ ASSERT_FALSE(CheckConnectivity(private_addrs_[0], public_addrs_[0],
+ &sender_external_address));
+
+ // Make sure UDP behind the NAT still works
+ ASSERT_TRUE(CheckConnectivity(private_addrs_[0], private_addrs_[1]));
+ ASSERT_TRUE(CheckConnectivity(private_addrs_[1], private_addrs_[0]));
+}
+
+TEST_F(TestNrSocketTest, DenyHairpinning) {
+ RefPtr<TestNat> nat(CreatePrivateAddrs(2));
+ nat->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT;
+ nat->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT;
+ CreatePublicAddrs(1);
+
+ nr_transport_addr sender_external_address;
+ // Open pinhole to public IP 0
+ ASSERT_TRUE(CheckConnectivity(private_addrs_[0], public_addrs_[0],
+ &sender_external_address));
+
+ // Verify that hairpinning is disallowed
+ ASSERT_FALSE(CheckConnectivityVia(private_addrs_[1], private_addrs_[0],
+ sender_external_address));
+}
+
+TEST_F(TestNrSocketTest, AllowHairpinning) {
+ RefPtr<TestNat> nat(CreatePrivateAddrs(2));
+ nat->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT;
+ nat->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT;
+ nat->mapping_timeout_ = 30000;
+ nat->allow_hairpinning_ = true;
+ CreatePublicAddrs(1);
+
+ nr_transport_addr sender_external_address;
+ // Open pinhole to public IP 0, obtain external address
+ ASSERT_TRUE(CheckConnectivity(private_addrs_[0], public_addrs_[0],
+ &sender_external_address));
+
+ // Verify that hairpinning is allowed
+ ASSERT_TRUE(CheckConnectivityVia(private_addrs_[1], private_addrs_[0],
+ sender_external_address));
+}
+
+TEST_F(TestNrSocketTest, FullConeTimeout) {
+ RefPtr<TestNat> nat(CreatePrivateAddrs(1));
+ nat->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT;
+ nat->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT;
+ nat->mapping_timeout_ = 200;
+ CreatePublicAddrs(2);
+
+ nr_transport_addr sender_external_address;
+ // Open pinhole to public IP 0
+ ASSERT_TRUE(CheckConnectivity(private_addrs_[0], public_addrs_[0],
+ &sender_external_address));
+
+ // Verify that return traffic works
+ ASSERT_TRUE(CheckConnectivityVia(public_addrs_[0], private_addrs_[0],
+ sender_external_address));
+
+ PR_Sleep(201);
+
+ // Verify that return traffic does not work
+ ASSERT_FALSE(CheckConnectivityVia(public_addrs_[0], private_addrs_[0],
+ sender_external_address));
+}
+
+TEST_F(TestNrSocketTest, PublicConnectivityTcp) {
+ CreatePublicAddrs(2, "127.0.0.1", IPPROTO_TCP);
+
+ ASSERT_TRUE(CheckTcpConnectivity(public_addrs_[0], public_addrs_[1]));
+}
+
+TEST_F(TestNrSocketTest, PrivateConnectivityTcp) {
+ RefPtr<TestNat> nat(CreatePrivateAddrs(2, "127.0.0.1", IPPROTO_TCP));
+ nat->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT;
+ nat->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT;
+
+ ASSERT_TRUE(CheckTcpConnectivity(private_addrs_[0], private_addrs_[1]));
+}
+
+TEST_F(TestNrSocketTest, PrivateToPublicConnectivityTcp) {
+ RefPtr<TestNat> nat(CreatePrivateAddrs(1, "127.0.0.1", IPPROTO_TCP));
+ nat->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT;
+ nat->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT;
+ CreatePublicAddrs(1, "127.0.0.1", IPPROTO_TCP);
+
+ ASSERT_TRUE(CheckTcpConnectivity(private_addrs_[0], public_addrs_[0]));
+}
+
+TEST_F(TestNrSocketTest, NoConnectivityBetweenSubnetsTcp) {
+ RefPtr<TestNat> nat1(CreatePrivateAddrs(1, "127.0.0.1", IPPROTO_TCP));
+ nat1->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT;
+ nat1->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT;
+ RefPtr<TestNat> nat2(CreatePrivateAddrs(1, "127.0.0.1", IPPROTO_TCP));
+ nat2->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT;
+ nat2->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT;
+
+ ASSERT_FALSE(CheckTcpConnectivity(private_addrs_[0], private_addrs_[1]));
+}
+
+TEST_F(TestNrSocketTest, NoConnectivityPublicToPrivateTcp) {
+ RefPtr<TestNat> nat(CreatePrivateAddrs(1, "127.0.0.1", IPPROTO_TCP));
+ nat->filtering_type_ = TestNat::ENDPOINT_INDEPENDENT;
+ nat->mapping_type_ = TestNat::ENDPOINT_INDEPENDENT;
+ CreatePublicAddrs(1, "127.0.0.1", IPPROTO_TCP);
+
+ ASSERT_FALSE(CheckTcpConnectivity(public_addrs_[0], private_addrs_[0]));
+}
diff --git a/dom/media/webrtc/transport/test/transport_unittests.cpp b/dom/media/webrtc/transport/test/transport_unittests.cpp
new file mode 100644
index 0000000000..28f9359afb
--- /dev/null
+++ b/dom/media/webrtc/transport/test/transport_unittests.cpp
@@ -0,0 +1,1400 @@
+
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+
+#include <iostream>
+#include <string>
+#include <algorithm>
+#include <functional>
+
+#ifdef XP_MACOSX
+// ensure that Apple Security kit enum goes before "sslproto.h"
+# include <CoreFoundation/CFAvailability.h>
+# include <Security/CipherSuite.h>
+#endif
+
+#include "mozilla/UniquePtr.h"
+
+#include "sigslot.h"
+
+#include "logging.h"
+#include "ssl.h"
+#include "sslexp.h"
+#include "sslproto.h"
+
+#include "nsThreadUtils.h"
+
+#include "mediapacket.h"
+#include "dtlsidentity.h"
+#include "nricectx.h"
+#include "nricemediastream.h"
+#include "transportflow.h"
+#include "transportlayer.h"
+#include "transportlayerdtls.h"
+#include "transportlayerice.h"
+#include "transportlayerlog.h"
+#include "transportlayerloopback.h"
+
+#include "runnable_utils.h"
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+#include "gtest_utils.h"
+
+using namespace mozilla;
+MOZ_MTLOG_MODULE("mtransport")
+
+const uint8_t kTlsChangeCipherSpecType = 0x14;
+const uint8_t kTlsHandshakeType = 0x16;
+
+const uint8_t kTlsHandshakeCertificate = 0x0b;
+const uint8_t kTlsHandshakeServerKeyExchange = 0x0c;
+
+const uint8_t kTlsFakeChangeCipherSpec[] = {
+ kTlsChangeCipherSpecType, // Type
+ 0xfe,
+ 0xff, // Version
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x10, // Fictitious sequence #
+ 0x00,
+ 0x01, // Length
+ 0x01 // Value
+};
+
+// Layer class which can't be initialized.
+class TransportLayerDummy : public TransportLayer {
+ public:
+ TransportLayerDummy(bool allow_init, bool* destroyed)
+ : allow_init_(allow_init), destroyed_(destroyed) {
+ *destroyed_ = false;
+ }
+
+ virtual ~TransportLayerDummy() { *destroyed_ = true; }
+
+ nsresult InitInternal() override {
+ return allow_init_ ? NS_OK : NS_ERROR_FAILURE;
+ }
+
+ TransportResult SendPacket(MediaPacket& packet) override {
+ MOZ_CRASH(); // Should never be called.
+ return 0;
+ }
+
+ TRANSPORT_LAYER_ID("lossy")
+
+ private:
+ bool allow_init_;
+ bool* destroyed_;
+};
+
+class Inspector {
+ public:
+ virtual ~Inspector() = default;
+
+ virtual void Inspect(TransportLayer* layer, const unsigned char* data,
+ size_t len) = 0;
+};
+
+// Class to simulate various kinds of network lossage
+class TransportLayerLossy : public TransportLayer {
+ public:
+ TransportLayerLossy() : loss_mask_(0), packet_(0), inspector_(nullptr) {}
+ ~TransportLayerLossy() = default;
+
+ TransportResult SendPacket(MediaPacket& packet) override {
+ MOZ_MTLOG(ML_NOTICE, LAYER_INFO << "SendPacket(" << packet.len() << ")");
+
+ if (loss_mask_ & (1 << (packet_ % 32))) {
+ MOZ_MTLOG(ML_NOTICE, "Dropping packet");
+ ++packet_;
+ return packet.len();
+ }
+ if (inspector_) {
+ inspector_->Inspect(this, packet.data(), packet.len());
+ }
+
+ ++packet_;
+
+ return downward_->SendPacket(packet);
+ }
+
+ void SetLoss(uint32_t packet) { loss_mask_ |= (1 << (packet & 32)); }
+
+ void SetInspector(UniquePtr<Inspector> inspector) {
+ inspector_ = std::move(inspector);
+ }
+
+ void StateChange(TransportLayer* layer, State state) { TL_SET_STATE(state); }
+
+ void PacketReceived(TransportLayer* layer, MediaPacket& packet) {
+ SignalPacketReceived(this, packet);
+ }
+
+ TRANSPORT_LAYER_ID("lossy")
+
+ protected:
+ void WasInserted() override {
+ downward_->SignalPacketReceived.connect(
+ this, &TransportLayerLossy::PacketReceived);
+ downward_->SignalStateChange.connect(this,
+ &TransportLayerLossy::StateChange);
+
+ TL_SET_STATE(downward_->state());
+ }
+
+ private:
+ uint32_t loss_mask_;
+ uint32_t packet_;
+ UniquePtr<Inspector> inspector_;
+};
+
+// Process DTLS Records
+#define CHECK_LENGTH(expected) \
+ do { \
+ EXPECT_GE(remaining(), expected); \
+ if (remaining() < expected) return false; \
+ } while (0)
+
+class TlsParser {
+ public:
+ TlsParser(const unsigned char* data, size_t len) : buffer_(), offset_(0) {
+ buffer_.Copy(data, len);
+ }
+
+ bool Read(unsigned char* val) {
+ if (remaining() < 1) {
+ return false;
+ }
+ *val = *ptr();
+ consume(1);
+ return true;
+ }
+
+ // Read an integral type of specified width.
+ bool Read(uint32_t* val, size_t len) {
+ if (len > sizeof(uint32_t)) return false;
+
+ *val = 0;
+
+ for (size_t i = 0; i < len; ++i) {
+ unsigned char tmp;
+
+ if (!Read(&tmp)) return false;
+
+ (*val) = ((*val) << 8) + tmp;
+ }
+
+ return true;
+ }
+
+ bool Read(unsigned char* val, size_t len) {
+ if (remaining() < len) {
+ return false;
+ }
+
+ if (val) {
+ memcpy(val, ptr(), len);
+ }
+ consume(len);
+
+ return true;
+ }
+
+ private:
+ size_t remaining() const { return buffer_.len() - offset_; }
+ const uint8_t* ptr() const { return buffer_.data() + offset_; }
+ void consume(size_t len) { offset_ += len; }
+
+ MediaPacket buffer_;
+ size_t offset_;
+};
+
+class DtlsRecordParser {
+ public:
+ DtlsRecordParser(const unsigned char* data, size_t len)
+ : buffer_(), offset_(0) {
+ buffer_.Copy(data, len);
+ }
+
+ bool NextRecord(uint8_t* ct, UniquePtr<MediaPacket>* buffer) {
+ if (!remaining()) return false;
+
+ CHECK_LENGTH(13U);
+ const uint8_t* ctp = reinterpret_cast<const uint8_t*>(ptr());
+ consume(11); // ct + version + length
+
+ const uint16_t* tmp = reinterpret_cast<const uint16_t*>(ptr());
+ size_t length = ntohs(*tmp);
+ consume(2);
+
+ CHECK_LENGTH(length);
+ auto db = MakeUnique<MediaPacket>();
+ db->Copy(ptr(), length);
+ consume(length);
+
+ *ct = *ctp;
+ *buffer = std::move(db);
+
+ return true;
+ }
+
+ private:
+ size_t remaining() const { return buffer_.len() - offset_; }
+ const uint8_t* ptr() const { return buffer_.data() + offset_; }
+ void consume(size_t len) { offset_ += len; }
+
+ MediaPacket buffer_;
+ size_t offset_;
+};
+
+// Inspector that parses out DTLS records and passes
+// them on.
+class DtlsRecordInspector : public Inspector {
+ public:
+ virtual void Inspect(TransportLayer* layer, const unsigned char* data,
+ size_t len) {
+ DtlsRecordParser parser(data, len);
+
+ uint8_t ct;
+ UniquePtr<MediaPacket> buf;
+ while (parser.NextRecord(&ct, &buf)) {
+ OnRecord(layer, ct, buf->data(), buf->len());
+ }
+ }
+
+ virtual void OnRecord(TransportLayer* layer, uint8_t content_type,
+ const unsigned char* record, size_t len) = 0;
+};
+
+// Inspector that injects arbitrary packets based on
+// DTLS records of various types.
+class DtlsInspectorInjector : public DtlsRecordInspector {
+ public:
+ DtlsInspectorInjector(uint8_t packet_type, uint8_t handshake_type,
+ const unsigned char* data, size_t len)
+ : packet_type_(packet_type), handshake_type_(handshake_type) {
+ packet_.Copy(data, len);
+ }
+
+ virtual void OnRecord(TransportLayer* layer, uint8_t content_type,
+ const unsigned char* data, size_t len) {
+ // Only inject once.
+ if (!packet_.data()) {
+ return;
+ }
+
+ // Check that the first byte is as requested.
+ if (content_type != packet_type_) {
+ return;
+ }
+
+ if (handshake_type_ != 0xff) {
+ // Check that the packet is plausibly long enough.
+ if (len < 1) {
+ return;
+ }
+
+ // Check that the handshake type is as requested.
+ if (data[0] != handshake_type_) {
+ return;
+ }
+ }
+
+ layer->SendPacket(packet_);
+ packet_.Reset();
+ }
+
+ private:
+ uint8_t packet_type_;
+ uint8_t handshake_type_;
+ MediaPacket packet_;
+};
+
+// Make a copy of the first instance of a message.
+class DtlsInspectorRecordHandshakeMessage : public DtlsRecordInspector {
+ public:
+ explicit DtlsInspectorRecordHandshakeMessage(uint8_t handshake_type)
+ : handshake_type_(handshake_type), buffer_() {}
+
+ virtual void OnRecord(TransportLayer* layer, uint8_t content_type,
+ const unsigned char* data, size_t len) {
+ // Only do this once.
+ if (buffer_.len()) {
+ return;
+ }
+
+ // Check that the first byte is as requested.
+ if (content_type != kTlsHandshakeType) {
+ return;
+ }
+
+ TlsParser parser(data, len);
+ unsigned char message_type;
+ // Read the handshake message type.
+ if (!parser.Read(&message_type)) {
+ return;
+ }
+ if (message_type != handshake_type_) {
+ return;
+ }
+
+ uint32_t length;
+ if (!parser.Read(&length, 3)) {
+ return;
+ }
+
+ uint32_t message_seq;
+ if (!parser.Read(&message_seq, 2)) {
+ return;
+ }
+
+ uint32_t fragment_offset;
+ if (!parser.Read(&fragment_offset, 3)) {
+ return;
+ }
+
+ uint32_t fragment_length;
+ if (!parser.Read(&fragment_length, 3)) {
+ return;
+ }
+
+ if ((fragment_offset != 0) || (fragment_length != length)) {
+ // This shouldn't happen because all current tests where we
+ // are using this code don't fragment.
+ return;
+ }
+
+ UniquePtr<uint8_t[]> buffer(new uint8_t[length]);
+ if (!parser.Read(buffer.get(), length)) {
+ return;
+ }
+ buffer_.Take(std::move(buffer), length);
+ }
+
+ const MediaPacket& buffer() { return buffer_; }
+
+ private:
+ uint8_t handshake_type_;
+ MediaPacket buffer_;
+};
+
+class TlsServerKeyExchangeECDHE {
+ public:
+ bool Parse(const unsigned char* data, size_t len) {
+ TlsParser parser(data, len);
+
+ uint8_t curve_type;
+ if (!parser.Read(&curve_type)) {
+ return false;
+ }
+
+ if (curve_type != 3) { // named_curve
+ return false;
+ }
+
+ uint32_t named_curve;
+ if (!parser.Read(&named_curve, 2)) {
+ return false;
+ }
+
+ uint32_t point_length;
+ if (!parser.Read(&point_length, 1)) {
+ return false;
+ }
+
+ UniquePtr<uint8_t[]> key(new uint8_t[point_length]);
+ if (!parser.Read(key.get(), point_length)) {
+ return false;
+ }
+ public_key_.Take(std::move(key), point_length);
+
+ return true;
+ }
+
+ MediaPacket public_key_;
+};
+
+namespace {
+class TransportTestPeer : public sigslot::has_slots<> {
+ public:
+ TransportTestPeer(nsCOMPtr<nsIEventTarget> target, std::string name,
+ MtransportTestUtils* utils)
+ : name_(name),
+ offerer_(name == "P1"),
+ target_(target),
+ received_packets_(0),
+ received_bytes_(0),
+ flow_(new TransportFlow(name)),
+ loopback_(new TransportLayerLoopback()),
+ logging_(new TransportLayerLogging()),
+ lossy_(new TransportLayerLossy()),
+ dtls_(new TransportLayerDtls()),
+ identity_(DtlsIdentity::Generate()),
+ ice_ctx_(),
+ streams_(),
+ peer_(nullptr),
+ gathering_complete_(false),
+ digest_("sha-1"),
+ enabled_cipersuites_(),
+ disabled_cipersuites_(),
+ test_utils_(utils) {
+ NrIceCtx::InitializeGlobals(NrIceCtx::GlobalConfig());
+ ice_ctx_ = NrIceCtx::Create(name);
+ std::vector<NrIceStunServer> stun_servers;
+ UniquePtr<NrIceStunServer> server(NrIceStunServer::Create(
+ std::string((char*)"stun.services.mozilla.com"), 3478));
+ stun_servers.push_back(*server);
+ EXPECT_TRUE(NS_SUCCEEDED(ice_ctx_->SetStunServers(stun_servers)));
+
+ dtls_->SetIdentity(identity_);
+ dtls_->SetRole(offerer_ ? TransportLayerDtls::SERVER
+ : TransportLayerDtls::CLIENT);
+
+ nsresult res = identity_->ComputeFingerprint(&digest_);
+ EXPECT_TRUE(NS_SUCCEEDED(res));
+ EXPECT_EQ(20u, digest_.value_.size());
+ }
+
+ ~TransportTestPeer() {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &TransportTestPeer::DestroyFlow));
+ }
+
+ void DestroyFlow() {
+ disconnect_all();
+ if (flow_) {
+ loopback_->Disconnect();
+ flow_ = nullptr;
+ }
+ ice_ctx_->Destroy();
+ ice_ctx_ = nullptr;
+ streams_.clear();
+ }
+
+ void DisconnectDestroyFlow() {
+ test_utils_->SyncDispatchToSTS(NS_NewRunnableFunction(__func__, [this] {
+ loopback_->Disconnect();
+ disconnect_all(); // Disconnect from the signals;
+ flow_ = nullptr;
+ }));
+ }
+
+ void SetDtlsAllowAll() {
+ nsresult res = dtls_->SetVerificationAllowAll();
+ ASSERT_TRUE(NS_SUCCEEDED(res));
+ }
+
+ void SetAlpn(std::string str, bool withDefault, std::string extra = "") {
+ std::set<std::string> alpn;
+ alpn.insert(str); // the one we want to select
+ if (!extra.empty()) {
+ alpn.insert(extra);
+ }
+ nsresult res = dtls_->SetAlpn(alpn, withDefault ? str : "");
+ ASSERT_EQ(NS_OK, res);
+ }
+
+ const std::string& GetAlpn() const { return dtls_->GetNegotiatedAlpn(); }
+
+ void SetDtlsPeer(TransportTestPeer* peer, int digests, unsigned int damage) {
+ unsigned int mask = 1;
+
+ for (int i = 0; i < digests; i++) {
+ DtlsDigest digest_to_set(peer->digest_);
+
+ if (damage & mask) digest_to_set.value_.data()[0]++;
+
+ nsresult res = dtls_->SetVerificationDigest(digest_to_set);
+
+ ASSERT_TRUE(NS_SUCCEEDED(res));
+
+ mask <<= 1;
+ }
+ }
+
+ void SetupSrtp() {
+ std::vector<uint16_t> srtp_ciphers =
+ TransportLayerDtls::GetDefaultSrtpCiphers();
+ SetSrtpCiphers(srtp_ciphers);
+ }
+
+ void SetSrtpCiphers(std::vector<uint16_t>& srtp_ciphers) {
+ ASSERT_TRUE(NS_SUCCEEDED(dtls_->SetSrtpCiphers(srtp_ciphers)));
+ }
+
+ void ConnectSocket_s(TransportTestPeer* peer) {
+ nsresult res;
+ res = loopback_->Init();
+ ASSERT_EQ((nsresult)NS_OK, res);
+
+ loopback_->Connect(peer->loopback_);
+ ASSERT_EQ((nsresult)NS_OK, loopback_->Init());
+ ASSERT_EQ((nsresult)NS_OK, logging_->Init());
+ ASSERT_EQ((nsresult)NS_OK, lossy_->Init());
+ ASSERT_EQ((nsresult)NS_OK, dtls_->Init());
+ dtls_->Chain(lossy_);
+ lossy_->Chain(logging_);
+ logging_->Chain(loopback_);
+
+ flow_->PushLayer(loopback_);
+ flow_->PushLayer(logging_);
+ flow_->PushLayer(lossy_);
+ flow_->PushLayer(dtls_);
+
+ if (dtls_->state() != TransportLayer::TS_ERROR) {
+ // Don't execute these blocks if DTLS didn't initialize.
+ TweakCiphers(dtls_->internal_fd());
+ if (post_setup_) {
+ post_setup_(dtls_->internal_fd());
+ }
+ }
+
+ dtls_->SignalPacketReceived.connect(this,
+ &TransportTestPeer::PacketReceived);
+ }
+
+ void TweakCiphers(PRFileDesc* fd) {
+ for (unsigned short& enabled_cipersuite : enabled_cipersuites_) {
+ SSL_CipherPrefSet(fd, enabled_cipersuite, PR_TRUE);
+ }
+ for (unsigned short& disabled_cipersuite : disabled_cipersuites_) {
+ SSL_CipherPrefSet(fd, disabled_cipersuite, PR_FALSE);
+ }
+ }
+
+ void ConnectSocket(TransportTestPeer* peer) {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &TransportTestPeer::ConnectSocket_s, peer));
+ }
+
+ nsresult InitIce_s() {
+ nsresult rv = ice_->Init();
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = dtls_->Init();
+ NS_ENSURE_SUCCESS(rv, rv);
+ dtls_->Chain(ice_);
+ flow_->PushLayer(ice_);
+ flow_->PushLayer(dtls_);
+ return NS_OK;
+ }
+
+ void InitIce() {
+ nsresult res;
+
+ // Attach our slots
+ ice_ctx_->SignalGatheringStateChange.connect(
+ this, &TransportTestPeer::GatheringStateChange);
+
+ char name[100];
+ snprintf(name, sizeof(name), "%s:stream%d", name_.c_str(),
+ (int)streams_.size());
+
+ // Create the media stream
+ RefPtr<NrIceMediaStream> stream = ice_ctx_->CreateStream(name, name, 1);
+
+ ASSERT_TRUE(stream != nullptr);
+ stream->SetIceCredentials("ufrag", "pass");
+ streams_.push_back(stream);
+
+ // Listen for candidates
+ stream->SignalCandidate.connect(this, &TransportTestPeer::GotCandidate);
+
+ // Create the transport layer
+ ice_ = new TransportLayerIce();
+ ice_->SetParameters(stream, 1);
+
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableRet(&res, this, &TransportTestPeer::InitIce_s));
+
+ ASSERT_EQ((nsresult)NS_OK, res);
+
+ // Listen for media events
+ dtls_->SignalPacketReceived.connect(this,
+ &TransportTestPeer::PacketReceived);
+ dtls_->SignalStateChange.connect(this, &TransportTestPeer::StateChanged);
+
+ // Start gathering
+ test_utils_->SyncDispatchToSTS(WrapRunnableRet(
+ &res, ice_ctx_, &NrIceCtx::StartGathering, false, false));
+ ASSERT_TRUE(NS_SUCCEEDED(res));
+ }
+
+ void ConnectIce(TransportTestPeer* peer) {
+ peer_ = peer;
+
+ // If gathering is already complete, push the candidates over
+ if (gathering_complete_) GatheringComplete();
+ }
+
+ // New candidate
+ void GotCandidate(NrIceMediaStream* stream, const std::string& candidate,
+ const std::string& ufrag, const std::string& mdns_addr,
+ const std::string& actual_addr) {
+ std::cerr << "Got candidate " << candidate << " (ufrag=" << ufrag << ")"
+ << std::endl;
+ }
+
+ void GatheringStateChange(NrIceCtx* ctx, NrIceCtx::GatheringState state) {
+ (void)ctx;
+ if (state == NrIceCtx::ICE_CTX_GATHER_COMPLETE) {
+ GatheringComplete();
+ }
+ }
+
+ // Gathering complete, so send our candidates and start
+ // connecting on the other peer.
+ void GatheringComplete() {
+ nsresult res;
+
+ // Don't send to the other side
+ if (!peer_) {
+ gathering_complete_ = true;
+ return;
+ }
+
+ // First send attributes
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableRet(&res, peer_->ice_ctx_, &NrIceCtx::ParseGlobalAttributes,
+ ice_ctx_->GetGlobalAttributes()));
+ ASSERT_TRUE(NS_SUCCEEDED(res));
+
+ for (size_t i = 0; i < streams_.size(); ++i) {
+ test_utils_->SyncDispatchToSTS(WrapRunnableRet(
+ &res, peer_->streams_[i], &NrIceMediaStream::ConnectToPeer, "ufrag",
+ "pass", streams_[i]->GetAttributes()));
+
+ ASSERT_TRUE(NS_SUCCEEDED(res));
+ }
+
+ // Start checks on the other peer.
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnableRet(&res, peer_->ice_ctx_, &NrIceCtx::StartChecks));
+ ASSERT_TRUE(NS_SUCCEEDED(res));
+ }
+
+ // WrapRunnable/lambda and move semantics (MediaPacket is not copyable) don't
+ // get along yet, so we need a wrapper. Gross.
+ static TransportResult SendPacketWrapper(TransportLayer* layer,
+ MediaPacket* packet) {
+ return layer->SendPacket(*packet);
+ }
+
+ TransportResult SendPacket(MediaPacket& packet) {
+ TransportResult ret;
+
+ test_utils_->SyncDispatchToSTS(WrapRunnableNMRet(
+ &ret, &TransportTestPeer::SendPacketWrapper, dtls_, &packet));
+
+ return ret;
+ }
+
+ void StateChanged(TransportLayer* layer, TransportLayer::State state) {
+ if (state == TransportLayer::TS_OPEN) {
+ std::cerr << "Now connected" << std::endl;
+ }
+ }
+
+ void PacketReceived(TransportLayer* layer, MediaPacket& packet) {
+ std::cerr << "Received " << packet.len() << " bytes" << std::endl;
+ ++received_packets_;
+ received_bytes_ += packet.len();
+ }
+
+ void SetLoss(uint32_t loss) { lossy_->SetLoss(loss); }
+
+ void SetCombinePackets(bool combine) { loopback_->CombinePackets(combine); }
+
+ void SetInspector(UniquePtr<Inspector> inspector) {
+ lossy_->SetInspector(std::move(inspector));
+ }
+
+ void SetInspector(Inspector* in) {
+ UniquePtr<Inspector> inspector(in);
+
+ lossy_->SetInspector(std::move(inspector));
+ }
+
+ void SetCipherSuiteChanges(const std::vector<uint16_t>& enableThese,
+ const std::vector<uint16_t>& disableThese) {
+ disabled_cipersuites_ = disableThese;
+ enabled_cipersuites_ = enableThese;
+ }
+
+ void SetPostSetup(const std::function<void(PRFileDesc*)>& setup) {
+ post_setup_ = std::move(setup);
+ }
+
+ TransportLayer::State state() {
+ TransportLayer::State tstate;
+
+ RUN_ON_THREAD(test_utils_->sts_target(),
+ WrapRunnableRet(&tstate, dtls_, &TransportLayer::state));
+
+ return tstate;
+ }
+
+ bool connected() { return state() == TransportLayer::TS_OPEN; }
+
+ bool failed() { return state() == TransportLayer::TS_ERROR; }
+
+ size_t receivedPackets() { return received_packets_; }
+
+ size_t receivedBytes() { return received_bytes_; }
+
+ uint16_t cipherSuite() const {
+ nsresult rv;
+ uint16_t cipher;
+ RUN_ON_THREAD(
+ test_utils_->sts_target(),
+ WrapRunnableRet(&rv, dtls_, &TransportLayerDtls::GetCipherSuite,
+ &cipher));
+
+ if (NS_FAILED(rv)) {
+ return TLS_NULL_WITH_NULL_NULL; // i.e., not good
+ }
+ return cipher;
+ }
+
+ uint16_t srtpCipher() const {
+ nsresult rv;
+ uint16_t cipher;
+ RUN_ON_THREAD(test_utils_->sts_target(),
+ WrapRunnableRet(&rv, dtls_,
+ &TransportLayerDtls::GetSrtpCipher, &cipher));
+ if (NS_FAILED(rv)) {
+ return 0; // the SRTP equivalent of TLS_NULL_WITH_NULL_NULL
+ }
+ return cipher;
+ }
+
+ private:
+ std::string name_;
+ bool offerer_;
+ nsCOMPtr<nsIEventTarget> target_;
+ size_t received_packets_;
+ size_t received_bytes_;
+ RefPtr<TransportFlow> flow_;
+ TransportLayerLoopback* loopback_;
+ TransportLayerLogging* logging_;
+ TransportLayerLossy* lossy_;
+ TransportLayerDtls* dtls_;
+ TransportLayerIce* ice_;
+ RefPtr<DtlsIdentity> identity_;
+ RefPtr<NrIceCtx> ice_ctx_;
+ std::vector<RefPtr<NrIceMediaStream> > streams_;
+ TransportTestPeer* peer_;
+ bool gathering_complete_;
+ DtlsDigest digest_;
+ std::vector<uint16_t> enabled_cipersuites_;
+ std::vector<uint16_t> disabled_cipersuites_;
+ MtransportTestUtils* test_utils_;
+ std::function<void(PRFileDesc* fd)> post_setup_ = nullptr;
+};
+
+class TransportTest : public MtransportTest {
+ public:
+ TransportTest() {
+ fds_[0] = nullptr;
+ fds_[1] = nullptr;
+ p1_ = nullptr;
+ p2_ = nullptr;
+ }
+
+ void TearDown() override {
+ delete p1_;
+ delete p2_;
+
+ // Can't detach these
+ // PR_Close(fds_[0]);
+ // PR_Close(fds_[1]);
+ MtransportTest::TearDown();
+ }
+
+ void DestroyPeerFlows() {
+ p1_->DisconnectDestroyFlow();
+ p2_->DisconnectDestroyFlow();
+ }
+
+ void SetUp() override {
+ MtransportTest::SetUp();
+
+ nsresult rv;
+ target_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ Reset();
+ }
+
+ void Reset() {
+ if (p1_) {
+ delete p1_;
+ }
+ if (p2_) {
+ delete p2_;
+ }
+ p1_ = new TransportTestPeer(target_, "P1", test_utils_);
+ p2_ = new TransportTestPeer(target_, "P2", test_utils_);
+ }
+
+ void SetupSrtp() {
+ p1_->SetupSrtp();
+ p2_->SetupSrtp();
+ }
+
+ void SetDtlsPeer(int digests = 1, unsigned int damage = 0) {
+ p1_->SetDtlsPeer(p2_, digests, damage);
+ p2_->SetDtlsPeer(p1_, digests, damage);
+ }
+
+ void SetDtlsAllowAll() {
+ p1_->SetDtlsAllowAll();
+ p2_->SetDtlsAllowAll();
+ }
+
+ void SetAlpn(std::string first, std::string second,
+ bool withDefaults = true) {
+ if (!first.empty()) {
+ p1_->SetAlpn(first, withDefaults, "bogus");
+ }
+ if (!second.empty()) {
+ p2_->SetAlpn(second, withDefaults);
+ }
+ }
+
+ void CheckAlpn(std::string first, std::string second) {
+ ASSERT_EQ(first, p1_->GetAlpn());
+ ASSERT_EQ(second, p2_->GetAlpn());
+ }
+
+ void ConnectSocket() {
+ ConnectSocketInternal();
+ ASSERT_TRUE_WAIT(p1_->connected(), 10000);
+ ASSERT_TRUE_WAIT(p2_->connected(), 10000);
+
+ ASSERT_EQ(p1_->cipherSuite(), p2_->cipherSuite());
+ ASSERT_EQ(p1_->srtpCipher(), p2_->srtpCipher());
+ }
+
+ void ConnectSocketExpectFail() {
+ ConnectSocketInternal();
+ ASSERT_TRUE_WAIT(p1_->failed(), 10000);
+ ASSERT_TRUE_WAIT(p2_->failed(), 10000);
+ }
+
+ void ConnectSocketExpectState(TransportLayer::State s1,
+ TransportLayer::State s2) {
+ ConnectSocketInternal();
+ ASSERT_EQ_WAIT(s1, p1_->state(), 10000);
+ ASSERT_EQ_WAIT(s2, p2_->state(), 10000);
+ }
+
+ void ConnectIce() {
+ p1_->InitIce();
+ p2_->InitIce();
+ p1_->ConnectIce(p2_);
+ p2_->ConnectIce(p1_);
+ ASSERT_TRUE_WAIT(p1_->connected(), 10000);
+ ASSERT_TRUE_WAIT(p2_->connected(), 10000);
+ }
+
+ void TransferTest(size_t count, size_t bytes = 1024) {
+ unsigned char buf[bytes];
+
+ for (size_t i = 0; i < count; ++i) {
+ memset(buf, count & 0xff, sizeof(buf));
+ MediaPacket packet;
+ packet.Copy(buf, sizeof(buf));
+ TransportResult rv = p1_->SendPacket(packet);
+ ASSERT_TRUE(rv > 0);
+ }
+
+ std::cerr << "Received == " << p2_->receivedPackets() << " packets"
+ << std::endl;
+ ASSERT_TRUE_WAIT(count == p2_->receivedPackets(), 10000);
+ ASSERT_TRUE((count * sizeof(buf)) == p2_->receivedBytes());
+ }
+
+ protected:
+ void ConnectSocketInternal() {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(p1_, &TransportTestPeer::ConnectSocket, p2_));
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(p2_, &TransportTestPeer::ConnectSocket, p1_));
+ }
+
+ PRFileDesc* fds_[2];
+ TransportTestPeer* p1_;
+ TransportTestPeer* p2_;
+ nsCOMPtr<nsIEventTarget> target_;
+};
+
+TEST_F(TransportTest, TestNoDtlsVerificationSettings) {
+ ConnectSocketExpectFail();
+}
+
+static void DisableChaCha(TransportTestPeer* peer) {
+ // On ARM, ChaCha20Poly1305 might be preferred; disable it for the tests that
+ // want to check the cipher suite. It doesn't matter which peer disables the
+ // suite, disabling on either side has the same effect.
+ std::vector<uint16_t> chachaSuites;
+ chachaSuites.push_back(TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256);
+ chachaSuites.push_back(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256);
+ peer->SetCipherSuiteChanges(std::vector<uint16_t>(), chachaSuites);
+}
+
+TEST_F(TransportTest, TestConnect) {
+ SetDtlsPeer();
+ DisableChaCha(p1_);
+ ConnectSocket();
+
+ // check that we got the right suite
+ ASSERT_EQ(TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, p1_->cipherSuite());
+
+ // no SRTP on this one
+ ASSERT_EQ(0, p1_->srtpCipher());
+}
+
+TEST_F(TransportTest, TestConnectSrtp) {
+ SetupSrtp();
+ SetDtlsPeer();
+ DisableChaCha(p2_);
+ ConnectSocket();
+
+ ASSERT_EQ(TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, p1_->cipherSuite());
+
+ // SRTP is on with default value
+ ASSERT_EQ(kDtlsSrtpAeadAes128Gcm, p1_->srtpCipher());
+}
+
+TEST_F(TransportTest, TestConnectDestroyFlowsMainThread) {
+ SetDtlsPeer();
+ ConnectSocket();
+ DestroyPeerFlows();
+}
+
+TEST_F(TransportTest, TestConnectAllowAll) {
+ SetDtlsAllowAll();
+ ConnectSocket();
+}
+
+TEST_F(TransportTest, TestConnectAlpn) {
+ SetDtlsPeer();
+ SetAlpn("a", "a");
+ ConnectSocket();
+ CheckAlpn("a", "a");
+}
+
+TEST_F(TransportTest, TestConnectAlpnMismatch) {
+ SetDtlsPeer();
+ SetAlpn("something", "different");
+ ConnectSocketExpectFail();
+}
+
+TEST_F(TransportTest, TestConnectAlpnServerDefault) {
+ SetDtlsPeer();
+ SetAlpn("def", "");
+ // server allows default, client doesn't support
+ ConnectSocket();
+ CheckAlpn("def", "");
+}
+
+TEST_F(TransportTest, TestConnectAlpnClientDefault) {
+ SetDtlsPeer();
+ SetAlpn("", "clientdef");
+ // client allows default, but server will ignore the extension
+ ConnectSocket();
+ CheckAlpn("", "clientdef");
+}
+
+TEST_F(TransportTest, TestConnectClientNoAlpn) {
+ SetDtlsPeer();
+ // Here the server has ALPN, but no default is allowed.
+ // Reminder: p1 == server, p2 == client
+ SetAlpn("server-nodefault", "", false);
+ // The server doesn't see the extension, so negotiates without it.
+ // But then the server is forced to close when it discovers that ALPN wasn't
+ // negotiated; the client sees a close.
+ ConnectSocketExpectState(TransportLayer::TS_ERROR, TransportLayer::TS_CLOSED);
+}
+
+TEST_F(TransportTest, TestConnectServerNoAlpn) {
+ SetDtlsPeer();
+ SetAlpn("", "client-nodefault", false);
+ // The client aborts; the server doesn't realize this is a problem and just
+ // sees the close.
+ ConnectSocketExpectState(TransportLayer::TS_CLOSED, TransportLayer::TS_ERROR);
+}
+
+TEST_F(TransportTest, TestConnectNoDigest) {
+ SetDtlsPeer(0, 0);
+
+ ConnectSocketExpectFail();
+}
+
+TEST_F(TransportTest, TestConnectBadDigest) {
+ SetDtlsPeer(1, 1);
+
+ ConnectSocketExpectFail();
+}
+
+TEST_F(TransportTest, TestConnectTwoDigests) {
+ SetDtlsPeer(2, 0);
+
+ ConnectSocket();
+}
+
+TEST_F(TransportTest, TestConnectTwoDigestsFirstBad) {
+ SetDtlsPeer(2, 1);
+
+ ConnectSocket();
+}
+
+TEST_F(TransportTest, TestConnectTwoDigestsSecondBad) {
+ SetDtlsPeer(2, 2);
+
+ ConnectSocket();
+}
+
+TEST_F(TransportTest, TestConnectTwoDigestsBothBad) {
+ SetDtlsPeer(2, 3);
+
+ ConnectSocketExpectFail();
+}
+
+TEST_F(TransportTest, TestConnectInjectCCS) {
+ SetDtlsPeer();
+ p2_->SetInspector(MakeUnique<DtlsInspectorInjector>(
+ kTlsHandshakeType, kTlsHandshakeCertificate, kTlsFakeChangeCipherSpec,
+ sizeof(kTlsFakeChangeCipherSpec)));
+
+ ConnectSocket();
+}
+
+TEST_F(TransportTest, TestConnectVerifyNewECDHE) {
+ SetDtlsPeer();
+ DtlsInspectorRecordHandshakeMessage* i1 =
+ new DtlsInspectorRecordHandshakeMessage(kTlsHandshakeServerKeyExchange);
+ p1_->SetInspector(i1);
+ ConnectSocket();
+ TlsServerKeyExchangeECDHE dhe1;
+ ASSERT_TRUE(dhe1.Parse(i1->buffer().data(), i1->buffer().len()));
+
+ Reset();
+ SetDtlsPeer();
+ DtlsInspectorRecordHandshakeMessage* i2 =
+ new DtlsInspectorRecordHandshakeMessage(kTlsHandshakeServerKeyExchange);
+ p1_->SetInspector(i2);
+ ConnectSocket();
+ TlsServerKeyExchangeECDHE dhe2;
+ ASSERT_TRUE(dhe2.Parse(i2->buffer().data(), i2->buffer().len()));
+
+ // Now compare these two to see if they are the same.
+ ASSERT_FALSE((dhe1.public_key_.len() == dhe2.public_key_.len()) &&
+ (!memcmp(dhe1.public_key_.data(), dhe2.public_key_.data(),
+ dhe1.public_key_.len())));
+}
+
+TEST_F(TransportTest, TestConnectVerifyReusedECDHE) {
+ auto set_reuse_ecdhe_key = [](PRFileDesc* fd) {
+ // TransportLayerDtls automatically sets this pref to false
+ // so set it back for test.
+ // This is pretty gross. Dig directly into the NSS FD. The problem
+ // is that we are testing a feature which TransaportLayerDtls doesn't
+ // expose.
+ SECStatus rv = SSL_OptionSet(fd, SSL_REUSE_SERVER_ECDHE_KEY, PR_TRUE);
+ ASSERT_EQ(SECSuccess, rv);
+ };
+
+ SetDtlsPeer();
+ DtlsInspectorRecordHandshakeMessage* i1 =
+ new DtlsInspectorRecordHandshakeMessage(kTlsHandshakeServerKeyExchange);
+ p1_->SetInspector(i1);
+ p1_->SetPostSetup(set_reuse_ecdhe_key);
+ ConnectSocket();
+ TlsServerKeyExchangeECDHE dhe1;
+ ASSERT_TRUE(dhe1.Parse(i1->buffer().data(), i1->buffer().len()));
+
+ Reset();
+ SetDtlsPeer();
+ DtlsInspectorRecordHandshakeMessage* i2 =
+ new DtlsInspectorRecordHandshakeMessage(kTlsHandshakeServerKeyExchange);
+
+ p1_->SetInspector(i2);
+ p1_->SetPostSetup(set_reuse_ecdhe_key);
+
+ ConnectSocket();
+ TlsServerKeyExchangeECDHE dhe2;
+ ASSERT_TRUE(dhe2.Parse(i2->buffer().data(), i2->buffer().len()));
+
+ // Now compare these two to see if they are the same.
+ ASSERT_EQ(dhe1.public_key_.len(), dhe2.public_key_.len());
+ ASSERT_TRUE(!memcmp(dhe1.public_key_.data(), dhe2.public_key_.data(),
+ dhe1.public_key_.len()));
+}
+
+TEST_F(TransportTest, TestTransfer) {
+ SetDtlsPeer();
+ ConnectSocket();
+ TransferTest(1);
+}
+
+TEST_F(TransportTest, TestTransferMaxSize) {
+ SetDtlsPeer();
+ ConnectSocket();
+ /* transportlayerdtls uses a 9216 bytes buffer - as this test uses the
+ * loopback implementation it does not have to take into account the extra
+ * bytes added by the DTLS layer below. */
+ TransferTest(1, 9216);
+}
+
+TEST_F(TransportTest, TestTransferMultiple) {
+ SetDtlsPeer();
+ ConnectSocket();
+ TransferTest(3);
+}
+
+TEST_F(TransportTest, TestTransferCombinedPackets) {
+ SetDtlsPeer();
+ ConnectSocket();
+ p2_->SetCombinePackets(true);
+ TransferTest(3);
+}
+
+TEST_F(TransportTest, TestConnectLoseFirst) {
+ SetDtlsPeer();
+ p1_->SetLoss(0);
+ ConnectSocket();
+ TransferTest(1);
+}
+
+TEST_F(TransportTest, TestConnectIce) {
+ SetDtlsPeer();
+ ConnectIce();
+}
+
+TEST_F(TransportTest, TestTransferIceMaxSize) {
+ SetDtlsPeer();
+ ConnectIce();
+ /* nICEr and transportlayerdtls both use 9216 bytes buffers. But the DTLS
+ * layer add extra bytes to the packet, which size depends on chosen cipher
+ * etc. Sending more then 9216 bytes works, but on the receiving side the call
+ * to PR_recvfrom() will truncate any packet bigger then nICEr's buffer size
+ * of 9216 bytes, which then results in the DTLS layer discarding the packet.
+ * Therefore we leave some headroom (according to
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1214269#c29 256 bytes should
+ * be save choice) here for the DTLS bytes to make it safely into the
+ * receiving buffer in nICEr. */
+ TransferTest(1, 8960);
+}
+
+TEST_F(TransportTest, TestTransferIceMultiple) {
+ SetDtlsPeer();
+ ConnectIce();
+ TransferTest(3);
+}
+
+TEST_F(TransportTest, TestTransferIceCombinedPackets) {
+ SetDtlsPeer();
+ ConnectIce();
+ p2_->SetCombinePackets(true);
+ TransferTest(3);
+}
+
+// test the default configuration against a peer that supports only
+// one of the mandatory-to-implement suites, which should succeed
+static void ConfigureOneCipher(TransportTestPeer* peer, uint16_t suite) {
+ std::vector<uint16_t> justOne;
+ justOne.push_back(suite);
+ std::vector<uint16_t> everythingElse(
+ SSL_GetImplementedCiphers(),
+ SSL_GetImplementedCiphers() + SSL_GetNumImplementedCiphers());
+ std::remove(everythingElse.begin(), everythingElse.end(), suite);
+ peer->SetCipherSuiteChanges(justOne, everythingElse);
+}
+
+TEST_F(TransportTest, TestCipherMismatch) {
+ SetDtlsPeer();
+ ConfigureOneCipher(p1_, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256);
+ ConfigureOneCipher(p2_, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA);
+ ConnectSocketExpectFail();
+}
+
+TEST_F(TransportTest, TestCipherMandatoryOnlyGcm) {
+ SetDtlsPeer();
+ ConfigureOneCipher(p1_, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256);
+ ConnectSocket();
+ ASSERT_EQ(TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, p1_->cipherSuite());
+}
+
+TEST_F(TransportTest, TestCipherMandatoryOnlyCbc) {
+ SetDtlsPeer();
+ ConfigureOneCipher(p1_, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA);
+ ConnectSocket();
+ ASSERT_EQ(TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, p1_->cipherSuite());
+}
+
+TEST_F(TransportTest, TestSrtpMismatch) {
+ std::vector<uint16_t> setA;
+ setA.push_back(kDtlsSrtpAes128CmHmacSha1_80);
+ std::vector<uint16_t> setB;
+ setB.push_back(kDtlsSrtpAes128CmHmacSha1_32);
+
+ p1_->SetSrtpCiphers(setA);
+ p2_->SetSrtpCiphers(setB);
+ SetDtlsPeer();
+ ConnectSocketExpectFail();
+
+ ASSERT_EQ(0, p1_->srtpCipher());
+ ASSERT_EQ(0, p2_->srtpCipher());
+}
+
+static SECStatus NoopXtnHandler(PRFileDesc* fd, SSLHandshakeType message,
+ const uint8_t* data, unsigned int len,
+ SSLAlertDescription* alert, void* arg) {
+ return SECSuccess;
+}
+
+static PRBool WriteFixedXtn(PRFileDesc* fd, SSLHandshakeType message,
+ uint8_t* data, unsigned int* len,
+ unsigned int max_len, void* arg) {
+ // When we enable TLS 1.3, change ssl_hs_server_hello here to
+ // ssl_hs_encrypted_extensions. At the same time, add a test that writes to
+ // ssl_hs_server_hello, which should fail.
+ if (message != ssl_hs_client_hello && message != ssl_hs_server_hello) {
+ return false;
+ }
+
+ auto v = reinterpret_cast<std::vector<uint8_t>*>(arg);
+ memcpy(data, &((*v)[0]), v->size());
+ *len = v->size();
+ return true;
+}
+
+// Note that |value| needs to be readable after this function returns.
+static void InstallBadSrtpExtensionWriter(TransportTestPeer* peer,
+ std::vector<uint8_t>* value) {
+ peer->SetPostSetup([value](PRFileDesc* fd) {
+ // Override the handler that is installed by the DTLS setup.
+ SECStatus rv = SSL_InstallExtensionHooks(
+ fd, ssl_use_srtp_xtn, WriteFixedXtn, value, NoopXtnHandler, nullptr);
+ ASSERT_EQ(SECSuccess, rv);
+ });
+}
+
+TEST_F(TransportTest, TestSrtpErrorServerSendsTwoSrtpCiphers) {
+ // Server (p1_) sends an extension with two values, and empty MKI.
+ std::vector<uint8_t> xtn = {0x04, 0x00, 0x01, 0x00, 0x02, 0x00};
+ InstallBadSrtpExtensionWriter(p1_, &xtn);
+ SetupSrtp();
+ SetDtlsPeer();
+ ConnectSocketExpectFail();
+}
+
+TEST_F(TransportTest, TestSrtpErrorServerSendsTwoMki) {
+ // Server (p1_) sends an MKI.
+ std::vector<uint8_t> xtn = {0x02, 0x00, 0x01, 0x01, 0x00};
+ InstallBadSrtpExtensionWriter(p1_, &xtn);
+ SetupSrtp();
+ SetDtlsPeer();
+ ConnectSocketExpectFail();
+}
+
+TEST_F(TransportTest, TestSrtpErrorServerSendsUnknownValue) {
+ std::vector<uint8_t> xtn = {0x02, 0x9a, 0xf1, 0x00};
+ InstallBadSrtpExtensionWriter(p1_, &xtn);
+ SetupSrtp();
+ SetDtlsPeer();
+ ConnectSocketExpectFail();
+}
+
+TEST_F(TransportTest, TestSrtpErrorServerSendsOverflow) {
+ std::vector<uint8_t> xtn = {0x32, 0x00, 0x01, 0x00};
+ InstallBadSrtpExtensionWriter(p1_, &xtn);
+ SetupSrtp();
+ SetDtlsPeer();
+ ConnectSocketExpectFail();
+}
+
+TEST_F(TransportTest, TestSrtpErrorServerSendsUnevenList) {
+ std::vector<uint8_t> xtn = {0x01, 0x00, 0x00};
+ InstallBadSrtpExtensionWriter(p1_, &xtn);
+ SetupSrtp();
+ SetDtlsPeer();
+ ConnectSocketExpectFail();
+}
+
+TEST_F(TransportTest, TestSrtpErrorClientSendsUnevenList) {
+ std::vector<uint8_t> xtn = {0x01, 0x00, 0x00};
+ InstallBadSrtpExtensionWriter(p2_, &xtn);
+ SetupSrtp();
+ SetDtlsPeer();
+ ConnectSocketExpectFail();
+}
+
+TEST_F(TransportTest, OnlyServerSendsSrtpXtn) {
+ p1_->SetupSrtp();
+ SetDtlsPeer();
+ // This should connect, but with no SRTP extension neogtiated.
+ // The client side might negotiate a data channel only.
+ ConnectSocket();
+ ASSERT_NE(TLS_NULL_WITH_NULL_NULL, p1_->cipherSuite());
+ ASSERT_EQ(0, p1_->srtpCipher());
+}
+
+TEST_F(TransportTest, OnlyClientSendsSrtpXtn) {
+ p2_->SetupSrtp();
+ SetDtlsPeer();
+ // This should connect, but with no SRTP extension neogtiated.
+ // The server side might negotiate a data channel only.
+ ConnectSocket();
+ ASSERT_NE(TLS_NULL_WITH_NULL_NULL, p1_->cipherSuite());
+ ASSERT_EQ(0, p1_->srtpCipher());
+}
+
+class TransportSrtpParameterTest
+ : public TransportTest,
+ public ::testing::WithParamInterface<uint16_t> {};
+
+INSTANTIATE_TEST_SUITE_P(
+ SrtpParamInit, TransportSrtpParameterTest,
+ ::testing::ValuesIn(TransportLayerDtls::GetDefaultSrtpCiphers()));
+
+TEST_P(TransportSrtpParameterTest, TestSrtpCiphersMismatchCombinations) {
+ uint16_t cipher = GetParam();
+ std::cerr << "Checking cipher: " << cipher << std::endl;
+
+ p1_->SetupSrtp();
+
+ std::vector<uint16_t> setB;
+ setB.push_back(cipher);
+
+ p2_->SetSrtpCiphers(setB);
+ SetDtlsPeer();
+ ConnectSocket();
+
+ ASSERT_EQ(cipher, p1_->srtpCipher());
+ ASSERT_EQ(cipher, p2_->srtpCipher());
+}
+
+// NSS doesn't support DHE suites on the server end.
+// This checks to see if we barf when that's the only option available.
+TEST_F(TransportTest, TestDheOnlyFails) {
+ SetDtlsPeer();
+
+ // p2_ is the client
+ // setting this on p1_ (the server) causes NSS to assert
+ ConfigureOneCipher(p2_, TLS_DHE_RSA_WITH_AES_128_CBC_SHA);
+ ConnectSocketExpectFail();
+}
+
+} // end namespace
diff --git a/dom/media/webrtc/transport/test/turn_unittest.cpp b/dom/media/webrtc/transport/test/turn_unittest.cpp
new file mode 100644
index 0000000000..ae5d0386d9
--- /dev/null
+++ b/dom/media/webrtc/transport/test/turn_unittest.cpp
@@ -0,0 +1,432 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+
+// Some code copied from nICEr. License is:
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+*/
+
+#include <stdlib.h>
+#include <iostream>
+
+#include "runnable_utils.h"
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+#include "gtest_utils.h"
+
+#define USE_TURN
+
+// nICEr includes
+extern "C" {
+#include "nr_api.h"
+#include "transport_addr.h"
+#include "nr_crypto.h"
+#include "nr_socket.h"
+#include "nr_socket_local.h"
+#include "nr_socket_buffered_stun.h"
+#include "stun_client_ctx.h"
+#include "turn_client_ctx.h"
+}
+
+#include "nricectx.h"
+
+using namespace mozilla;
+
+static std::string kDummyTurnServer("192.0.2.1"); // From RFC 5737
+
+class TurnClient : public MtransportTest {
+ public:
+ TurnClient()
+ : MtransportTest(),
+ real_socket_(nullptr),
+ net_socket_(nullptr),
+ buffered_socket_(nullptr),
+ net_fd_(nullptr),
+ turn_ctx_(nullptr),
+ allocated_(false),
+ received_(0),
+ protocol_(IPPROTO_UDP) {}
+
+ ~TurnClient() = default;
+
+ static void SetUpTestCase() {
+ NrIceCtx::InitializeGlobals(NrIceCtx::GlobalConfig());
+ }
+
+ void SetTcp() { protocol_ = IPPROTO_TCP; }
+
+ void Init_s() {
+ int r;
+ nr_transport_addr addr;
+ r = nr_ip4_port_to_transport_addr(0, 0, protocol_, &addr);
+ ASSERT_EQ(0, r);
+
+ r = nr_socket_local_create(nullptr, &addr, &real_socket_);
+ ASSERT_EQ(0, r);
+
+ if (protocol_ == IPPROTO_TCP) {
+ int r = nr_socket_buffered_stun_create(
+ real_socket_, 100000, TURN_TCP_FRAMING, &buffered_socket_);
+ ASSERT_EQ(0, r);
+ net_socket_ = buffered_socket_;
+ } else {
+ net_socket_ = real_socket_;
+ }
+
+ r = nr_str_port_to_transport_addr(turn_server_.c_str(), 3478, protocol_,
+ &addr);
+ ASSERT_EQ(0, r);
+
+ std::vector<unsigned char> password_vec(turn_password_.begin(),
+ turn_password_.end());
+ Data password;
+ INIT_DATA(password, &password_vec[0], password_vec.size());
+ r = nr_turn_client_ctx_create("test", net_socket_, turn_user_.c_str(),
+ &password, &addr, nullptr, &turn_ctx_);
+ ASSERT_EQ(0, r);
+
+ r = nr_socket_getfd(net_socket_, &net_fd_);
+ ASSERT_EQ(0, r);
+
+ NR_ASYNC_WAIT(net_fd_, NR_ASYNC_WAIT_READ, socket_readable_cb, (void*)this);
+ }
+
+ void TearDown_s() {
+ nr_turn_client_ctx_destroy(&turn_ctx_);
+ if (net_fd_) {
+ NR_ASYNC_CANCEL(net_fd_, NR_ASYNC_WAIT_READ);
+ }
+
+ nr_socket_destroy(&buffered_socket_);
+ }
+
+ void TearDown() {
+ test_utils_->SyncDispatchToSTS(WrapRunnable(this, &TurnClient::TearDown_s));
+ }
+
+ void Allocate_s() {
+ Init_s();
+ ASSERT_TRUE(turn_ctx_);
+
+ int r = nr_turn_client_allocate(turn_ctx_, allocate_success_cb, this);
+ ASSERT_EQ(0, r);
+ }
+
+ void Allocate(bool expect_success = true) {
+ test_utils_->SyncDispatchToSTS(WrapRunnable(this, &TurnClient::Allocate_s));
+
+ if (expect_success) {
+ ASSERT_TRUE_WAIT(allocated_, 5000);
+ } else {
+ PR_Sleep(10000);
+ ASSERT_FALSE(allocated_);
+ }
+ }
+
+ void Allocated() {
+ if (turn_ctx_->state != NR_TURN_CLIENT_STATE_ALLOCATED) {
+ std::cerr << "Allocation failed" << std::endl;
+ return;
+ }
+ allocated_ = true;
+
+ int r;
+ nr_transport_addr addr;
+
+ r = nr_turn_client_get_relayed_address(turn_ctx_, &addr);
+ ASSERT_EQ(0, r);
+
+ relay_addr_ = addr.as_string;
+
+ std::cerr << "Allocation succeeded with addr=" << relay_addr_ << std::endl;
+ }
+
+ void Deallocate_s() {
+ ASSERT_TRUE(turn_ctx_);
+
+ std::cerr << "De-Allocating..." << std::endl;
+ int r = nr_turn_client_deallocate(turn_ctx_);
+ ASSERT_EQ(0, r);
+ }
+
+ void Deallocate() {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &TurnClient::Deallocate_s));
+ }
+
+ void RequestPermission_s(const std::string& target) {
+ nr_transport_addr addr;
+ int r;
+
+ // Expected pattern here is "IP4:127.0.0.1:3487"
+ ASSERT_EQ(0, target.compare(0, 4, "IP4:"));
+
+ size_t offset = target.rfind(':');
+ ASSERT_NE(std::string::npos, offset);
+
+ std::string host = target.substr(4, offset - 4);
+ std::string port = target.substr(offset + 1);
+
+ r = nr_str_port_to_transport_addr(host.c_str(), atoi(port.c_str()),
+ IPPROTO_UDP, &addr);
+ ASSERT_EQ(0, r);
+
+ r = nr_turn_client_ensure_perm(turn_ctx_, &addr);
+ ASSERT_EQ(0, r);
+ }
+
+ void RequestPermission(const std::string& target) {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &TurnClient::RequestPermission_s, target));
+ }
+
+ void Readable(NR_SOCKET s, int how, void* arg) {
+ // Re-arm
+ std::cerr << "Socket is readable" << std::endl;
+ NR_ASYNC_WAIT(s, how, socket_readable_cb, arg);
+
+ UCHAR buf[8192];
+ size_t len_s;
+ nr_transport_addr addr;
+
+ int r = nr_socket_recvfrom(net_socket_, buf, sizeof(buf), &len_s, 0, &addr);
+ if (r) {
+ std::cerr << "Error reading from socket" << std::endl;
+ return;
+ }
+
+ ASSERT_LT(len_s, (size_t)INT_MAX);
+ int len = (int)len_s;
+
+ if (nr_is_stun_response_message(buf, len)) {
+ std::cerr << "STUN response" << std::endl;
+ r = nr_turn_client_process_response(turn_ctx_, buf, len, &addr);
+
+ if (r && r != R_REJECTED && r != R_RETRY) {
+ std::cerr << "Error processing STUN: " << r << std::endl;
+ }
+ } else if (nr_is_stun_indication_message(buf, len)) {
+ std::cerr << "STUN indication" << std::endl;
+
+ /* Process the indication */
+ unsigned char data[NR_STUN_MAX_MESSAGE_SIZE];
+ size_t datal;
+ nr_transport_addr remote_addr;
+
+ r = nr_turn_client_parse_data_indication(
+ turn_ctx_, &addr, buf, len, data, &datal, sizeof(data), &remote_addr);
+ ASSERT_EQ(0, r);
+ std::cerr << "Received " << datal << " bytes from "
+ << remote_addr.as_string << std::endl;
+
+ received_ += datal;
+
+ for (size_t i = 0; i < datal; i++) {
+ ASSERT_EQ(i & 0xff, data[i]);
+ }
+ } else {
+ if (nr_is_stun_message(buf, len)) {
+ std::cerr << "STUN message of unexpected type" << std::endl;
+ } else {
+ std::cerr << "Not a STUN message" << std::endl;
+ }
+ return;
+ }
+ }
+
+ void SendTo_s(const std::string& target, int expect_return) {
+ nr_transport_addr addr;
+ int r;
+
+ // Expected pattern here is "IP4:127.0.0.1:3487"
+ ASSERT_EQ(0, target.compare(0, 4, "IP4:"));
+
+ size_t offset = target.rfind(':');
+ ASSERT_NE(std::string::npos, offset);
+
+ std::string host = target.substr(4, offset - 4);
+ std::string port = target.substr(offset + 1);
+
+ r = nr_str_port_to_transport_addr(host.c_str(), atoi(port.c_str()),
+ IPPROTO_UDP, &addr);
+ ASSERT_EQ(0, r);
+
+ unsigned char test[100];
+ for (size_t i = 0; i < sizeof(test); i++) {
+ test[i] = i & 0xff;
+ }
+
+ std::cerr << "Sending test message to " << target << " ..." << std::endl;
+
+ r = nr_turn_client_send_indication(turn_ctx_, test, sizeof(test), 0, &addr);
+ if (expect_return >= 0) {
+ ASSERT_EQ(expect_return, r);
+ }
+ }
+
+ void SendTo(const std::string& target, int expect_return = 0) {
+ test_utils_->SyncDispatchToSTS(
+ WrapRunnable(this, &TurnClient::SendTo_s, target, expect_return));
+ }
+
+ int received() const { return received_; }
+
+ static void socket_readable_cb(NR_SOCKET s, int how, void* arg) {
+ static_cast<TurnClient*>(arg)->Readable(s, how, arg);
+ }
+
+ static void allocate_success_cb(NR_SOCKET s, int how, void* arg) {
+ static_cast<TurnClient*>(arg)->Allocated();
+ }
+
+ protected:
+ std::string turn_server_;
+ nr_socket* real_socket_;
+ nr_socket* net_socket_;
+ nr_socket* buffered_socket_;
+ NR_SOCKET net_fd_;
+ nr_turn_client_ctx* turn_ctx_;
+ std::string relay_addr_;
+ bool allocated_;
+ int received_;
+ int protocol_;
+};
+
+TEST_F(TurnClient, Allocate) {
+ if (WarnIfTurnNotConfigured()) return;
+
+ Allocate();
+}
+
+TEST_F(TurnClient, AllocateTcp) {
+ if (WarnIfTurnNotConfigured()) return;
+
+ SetTcp();
+ Allocate();
+}
+
+TEST_F(TurnClient, AllocateAndHold) {
+ if (WarnIfTurnNotConfigured()) return;
+
+ Allocate();
+ PR_Sleep(20000);
+ ASSERT_TRUE(turn_ctx_->state == NR_TURN_CLIENT_STATE_ALLOCATED);
+}
+
+TEST_F(TurnClient, SendToSelf) {
+ if (WarnIfTurnNotConfigured()) return;
+
+ Allocate();
+ SendTo(relay_addr_);
+ ASSERT_TRUE_WAIT(received() == 100, 5000);
+ SendTo(relay_addr_);
+ ASSERT_TRUE_WAIT(received() == 200, 1000);
+}
+
+TEST_F(TurnClient, SendToSelfTcp) {
+ if (WarnIfTurnNotConfigured()) return;
+
+ SetTcp();
+ Allocate();
+ SendTo(relay_addr_);
+ ASSERT_TRUE_WAIT(received() == 100, 5000);
+ SendTo(relay_addr_);
+ ASSERT_TRUE_WAIT(received() == 200, 1000);
+}
+
+TEST_F(TurnClient, PermissionDenied) {
+ if (WarnIfTurnNotConfigured()) return;
+
+ Allocate();
+ RequestPermission(relay_addr_);
+ PR_Sleep(1000);
+
+ /* Fake a 403 response */
+ nr_turn_permission* perm;
+ perm = STAILQ_FIRST(&turn_ctx_->permissions);
+ ASSERT_TRUE(perm);
+ while (perm) {
+ perm->stun->last_error_code = 403;
+ std::cerr << "Set 403's on permission" << std::endl;
+ perm = STAILQ_NEXT(perm, entry);
+ }
+
+ SendTo(relay_addr_, R_NOT_PERMITTED);
+ ASSERT_TRUE(received() == 0);
+
+ // TODO: We should check if we can still send to a second destination, but
+ // we would need a second TURN client as one client can only handle one
+ // allocation (maybe as part of bug 1128128 ?).
+}
+
+TEST_F(TurnClient, DeallocateReceiveFailure) {
+ if (WarnIfTurnNotConfigured()) return;
+
+ Allocate();
+ SendTo(relay_addr_);
+ ASSERT_TRUE_WAIT(received() == 100, 5000);
+ Deallocate();
+ turn_ctx_->state = NR_TURN_CLIENT_STATE_ALLOCATED;
+ SendTo(relay_addr_);
+ PR_Sleep(1000);
+ ASSERT_TRUE(received() == 100);
+}
+
+TEST_F(TurnClient, DeallocateReceiveFailureTcp) {
+ if (WarnIfTurnNotConfigured()) return;
+
+ SetTcp();
+ Allocate();
+ SendTo(relay_addr_);
+ ASSERT_TRUE_WAIT(received() == 100, 5000);
+ Deallocate();
+ turn_ctx_->state = NR_TURN_CLIENT_STATE_ALLOCATED;
+ /* Either the connection got closed by the TURN server already, then the send
+ * is going to fail, which we simply ignore. Or the connection is still alive
+ * and we cand send the data, but it should not get forwarded to us. In either
+ * case we should not receive more data. */
+ SendTo(relay_addr_, -1);
+ PR_Sleep(1000);
+ ASSERT_TRUE(received() == 100);
+}
+
+TEST_F(TurnClient, AllocateDummyServer) {
+ if (WarnIfTurnNotConfigured()) return;
+
+ turn_server_ = kDummyTurnServer;
+ Allocate(false);
+}
diff --git a/dom/media/webrtc/transport/test/webrtcproxychannel_unittest.cpp b/dom/media/webrtc/transport/test/webrtcproxychannel_unittest.cpp
new file mode 100644
index 0000000000..5bfddc7a3f
--- /dev/null
+++ b/dom/media/webrtc/transport/test/webrtcproxychannel_unittest.cpp
@@ -0,0 +1,754 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <algorithm>
+#include <mutex>
+
+#include "mozilla/net/WebrtcTCPSocket.h"
+#include "mozilla/net/WebrtcTCPSocketCallback.h"
+
+#include "nsISocketTransport.h"
+
+#define GTEST_HAS_RTTI 0
+#include "gtest/gtest.h"
+#include "gtest_utils.h"
+
+static const uint32_t kDefaultTestTimeout = 2000;
+static const char kReadData[] = "Hello, World!";
+static const size_t kReadDataLength = sizeof(kReadData) - 1;
+static const std::string kReadDataString =
+ std::string(kReadData, kReadDataLength);
+static int kDataLargeOuterLoopCount = 128;
+static int kDataLargeInnerLoopCount = 1024;
+
+namespace mozilla {
+
+using namespace net;
+using namespace testing;
+
+class WebrtcTCPSocketTestCallback;
+
+class FakeSocketTransportProvider : public nsISocketTransport {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // nsISocketTransport
+ NS_IMETHOD GetHost(nsACString& aHost) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetPort(int32_t* aPort) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetScriptableOriginAttributes(
+ JSContext* cx, JS::MutableHandle<JS::Value> aOriginAttributes) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD SetScriptableOriginAttributes(
+ JSContext* cx, JS::Handle<JS::Value> aOriginAttributes) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ virtual nsresult GetOriginAttributes(
+ mozilla::OriginAttributes* _retval) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ virtual nsresult SetOriginAttributes(
+ const mozilla::OriginAttributes& aOriginAttrs) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetPeerAddr(mozilla::net::NetAddr* _retval) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetSelfAddr(mozilla::net::NetAddr* _retval) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD Bind(mozilla::net::NetAddr* aLocalAddr) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetScriptablePeerAddr(nsINetAddr** _retval) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetScriptableSelfAddr(nsINetAddr** _retval) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetTlsSocketControl(
+ nsITLSSocketControl** aTLSSocketControl) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetSecurityCallbacks(
+ nsIInterfaceRequestor** aSecurityCallbacks) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD SetSecurityCallbacks(
+ nsIInterfaceRequestor* aSecurityCallbacks) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD IsAlive(bool* _retval) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetTimeout(uint32_t aType, uint32_t* _retval) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD SetTimeout(uint32_t aType, uint32_t aValue) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD SetLinger(bool aPolarity, int16_t aTimeout) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD SetReuseAddrPort(bool reuseAddrPort) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetConnectionFlags(uint32_t* aConnectionFlags) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD SetConnectionFlags(uint32_t aConnectionFlags) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD SetIsPrivate(bool) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetTlsFlags(uint32_t* aTlsFlags) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD SetTlsFlags(uint32_t aTlsFlags) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetQoSBits(uint8_t* aQoSBits) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD SetQoSBits(uint8_t aQoSBits) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetRecvBufferSize(uint32_t* aRecvBufferSize) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetSendBufferSize(uint32_t* aSendBufferSize) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetKeepaliveEnabled(bool* aKeepaliveEnabled) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD SetKeepaliveEnabled(bool aKeepaliveEnabled) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD SetKeepaliveVals(int32_t keepaliveIdleTime,
+ int32_t keepaliveRetryInterval) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetResetIPFamilyPreference(
+ bool* aResetIPFamilyPreference) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetEchConfigUsed(bool* aEchConfigUsed) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD SetEchConfig(const nsACString& aEchConfig) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD ResolvedByTRR(bool* _retval) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetEffectiveTRRMode(
+ nsIRequest::TRRMode* aEffectiveTRRMode) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetTrrSkipReason(nsITRRSkipReason::value* aSkipReason) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetRetryDnsIfPossible(bool* aRetryDns) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD GetStatus(nsresult* aStatus) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+
+ // nsITransport
+ NS_IMETHOD OpenInputStream(uint32_t aFlags, uint32_t aSegmentSize,
+ uint32_t aSegmentCount,
+ nsIInputStream** _retval) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD OpenOutputStream(uint32_t aFlags, uint32_t aSegmentSize,
+ uint32_t aSegmentCount,
+ nsIOutputStream** _retval) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+ NS_IMETHOD SetEventSink(nsITransportEventSink* aSink,
+ nsIEventTarget* aEventTarget) override {
+ MOZ_ASSERT(false);
+ return NS_OK;
+ }
+
+ // fake except for these methods which are OK to call
+ // nsISocketTransport
+ NS_IMETHOD SetRecvBufferSize(uint32_t aRecvBufferSize) override {
+ return NS_OK;
+ }
+ NS_IMETHOD SetSendBufferSize(uint32_t aSendBufferSize) override {
+ return NS_OK;
+ }
+ // nsITransport
+ NS_IMETHOD Close(nsresult aReason) override { return NS_OK; }
+
+ protected:
+ virtual ~FakeSocketTransportProvider() = default;
+};
+
+NS_IMPL_ISUPPORTS(FakeSocketTransportProvider, nsISocketTransport, nsITransport)
+
+// Implements some common elements to WebrtcTCPSocketTestOutputStream and
+// WebrtcTCPSocketTestInputStream.
+class WebrtcTCPSocketTestStream {
+ public:
+ WebrtcTCPSocketTestStream();
+
+ void Fail() { mMustFail = true; }
+
+ size_t DataLength();
+ template <typename T>
+ void AppendElements(const T* aBuffer, size_t aLength);
+
+ protected:
+ virtual ~WebrtcTCPSocketTestStream() = default;
+
+ nsTArray<uint8_t> mData;
+ std::mutex mDataMutex;
+
+ bool mMustFail;
+};
+
+WebrtcTCPSocketTestStream::WebrtcTCPSocketTestStream() : mMustFail(false) {}
+
+template <typename T>
+void WebrtcTCPSocketTestStream::AppendElements(const T* aBuffer,
+ size_t aLength) {
+ std::lock_guard<std::mutex> guard(mDataMutex);
+ mData.AppendElements(aBuffer, aLength);
+}
+
+size_t WebrtcTCPSocketTestStream::DataLength() {
+ std::lock_guard<std::mutex> guard(mDataMutex);
+ return mData.Length();
+}
+
+class WebrtcTCPSocketTestInputStream : public nsIAsyncInputStream,
+ public WebrtcTCPSocketTestStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIASYNCINPUTSTREAM
+ NS_DECL_NSIINPUTSTREAM
+
+ WebrtcTCPSocketTestInputStream()
+ : mMaxReadSize(1024 * 1024), mAllowCallbacks(false) {}
+
+ void DoCallback();
+ void CallCallback(const nsCOMPtr<nsIInputStreamCallback>& aCallback);
+ void AllowCallbacks() { mAllowCallbacks = true; }
+
+ size_t mMaxReadSize;
+
+ protected:
+ virtual ~WebrtcTCPSocketTestInputStream() = default;
+
+ private:
+ nsCOMPtr<nsIInputStreamCallback> mCallback;
+ nsCOMPtr<nsIEventTarget> mCallbackTarget;
+
+ bool mAllowCallbacks;
+};
+
+NS_IMPL_ISUPPORTS(WebrtcTCPSocketTestInputStream, nsIAsyncInputStream,
+ nsIInputStream)
+
+nsresult WebrtcTCPSocketTestInputStream::AsyncWait(
+ nsIInputStreamCallback* aCallback, uint32_t aFlags,
+ uint32_t aRequestedCount, nsIEventTarget* aEventTarget) {
+ MOZ_ASSERT(!aEventTarget, "no event target should be set");
+
+ mCallback = aCallback;
+ mCallbackTarget = NS_GetCurrentThread();
+
+ if (mAllowCallbacks && DataLength() > 0) {
+ DoCallback();
+ }
+
+ return NS_OK;
+}
+
+nsresult WebrtcTCPSocketTestInputStream::CloseWithStatus(nsresult aStatus) {
+ return Close();
+}
+
+nsresult WebrtcTCPSocketTestInputStream::Close() { return NS_OK; }
+
+nsresult WebrtcTCPSocketTestInputStream::Available(uint64_t* aAvailable) {
+ *aAvailable = DataLength();
+ return NS_OK;
+}
+
+nsresult WebrtcTCPSocketTestInputStream::StreamStatus() { return NS_OK; }
+
+nsresult WebrtcTCPSocketTestInputStream::Read(char* aBuffer, uint32_t aCount,
+ uint32_t* aRead) {
+ std::lock_guard<std::mutex> guard(mDataMutex);
+ if (mMustFail) {
+ return NS_ERROR_FAILURE;
+ }
+ *aRead = std::min({(size_t)aCount, mData.Length(), mMaxReadSize});
+ memcpy(aBuffer, mData.Elements(), *aRead);
+ mData.RemoveElementsAt(0, *aRead);
+ return *aRead > 0 ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
+}
+
+nsresult WebrtcTCPSocketTestInputStream::ReadSegments(nsWriteSegmentFun aWriter,
+ void* aClosure,
+ uint32_t aCount,
+ uint32_t* _retval) {
+ MOZ_ASSERT(false);
+ return NS_OK;
+}
+
+nsresult WebrtcTCPSocketTestInputStream::IsNonBlocking(bool* aIsNonBlocking) {
+ *aIsNonBlocking = true;
+ return NS_OK;
+}
+
+void WebrtcTCPSocketTestInputStream::CallCallback(
+ const nsCOMPtr<nsIInputStreamCallback>& aCallback) {
+ aCallback->OnInputStreamReady(this);
+}
+
+void WebrtcTCPSocketTestInputStream::DoCallback() {
+ if (mCallback) {
+ mCallbackTarget->Dispatch(
+ NewRunnableMethod<const nsCOMPtr<nsIInputStreamCallback>&>(
+ "WebrtcTCPSocketTestInputStream::DoCallback", this,
+ &WebrtcTCPSocketTestInputStream::CallCallback,
+ std::move(mCallback)));
+
+ mCallbackTarget = nullptr;
+ }
+}
+
+class WebrtcTCPSocketTestOutputStream : public nsIAsyncOutputStream,
+ public WebrtcTCPSocketTestStream {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIASYNCOUTPUTSTREAM
+ NS_DECL_NSIOUTPUTSTREAM
+
+ WebrtcTCPSocketTestOutputStream() : mMaxWriteSize(1024 * 1024) {}
+
+ void DoCallback();
+ void CallCallback(const nsCOMPtr<nsIOutputStreamCallback>& aCallback);
+
+ std::string DataString();
+
+ uint32_t mMaxWriteSize;
+
+ protected:
+ virtual ~WebrtcTCPSocketTestOutputStream() = default;
+
+ private:
+ nsCOMPtr<nsIOutputStreamCallback> mCallback;
+ nsCOMPtr<nsIEventTarget> mCallbackTarget;
+};
+
+NS_IMPL_ISUPPORTS(WebrtcTCPSocketTestOutputStream, nsIAsyncOutputStream,
+ nsIOutputStream)
+
+nsresult WebrtcTCPSocketTestOutputStream::AsyncWait(
+ nsIOutputStreamCallback* aCallback, uint32_t aFlags,
+ uint32_t aRequestedCount, nsIEventTarget* aEventTarget) {
+ MOZ_ASSERT(!aEventTarget, "no event target should be set");
+
+ mCallback = aCallback;
+ mCallbackTarget = NS_GetCurrentThread();
+
+ return NS_OK;
+}
+
+nsresult WebrtcTCPSocketTestOutputStream::CloseWithStatus(nsresult aStatus) {
+ return Close();
+}
+
+nsresult WebrtcTCPSocketTestOutputStream::Close() { return NS_OK; }
+
+nsresult WebrtcTCPSocketTestOutputStream::Flush() { return NS_OK; }
+
+nsresult WebrtcTCPSocketTestOutputStream::StreamStatus() {
+ return mMustFail ? NS_ERROR_FAILURE : NS_OK;
+}
+
+nsresult WebrtcTCPSocketTestOutputStream::Write(const char* aBuffer,
+ uint32_t aCount,
+ uint32_t* aWrote) {
+ if (mMustFail) {
+ return NS_ERROR_FAILURE;
+ }
+ *aWrote = std::min(aCount, mMaxWriteSize);
+ AppendElements(aBuffer, *aWrote);
+ return NS_OK;
+}
+
+nsresult WebrtcTCPSocketTestOutputStream::WriteSegments(
+ nsReadSegmentFun aReader, void* aClosure, uint32_t aCount,
+ uint32_t* _retval) {
+ MOZ_ASSERT(false);
+ return NS_OK;
+}
+
+nsresult WebrtcTCPSocketTestOutputStream::WriteFrom(nsIInputStream* aFromStream,
+ uint32_t aCount,
+ uint32_t* _retval) {
+ MOZ_ASSERT(false);
+ return NS_OK;
+}
+
+nsresult WebrtcTCPSocketTestOutputStream::IsNonBlocking(bool* aIsNonBlocking) {
+ *aIsNonBlocking = true;
+ return NS_OK;
+}
+
+void WebrtcTCPSocketTestOutputStream::CallCallback(
+ const nsCOMPtr<nsIOutputStreamCallback>& aCallback) {
+ aCallback->OnOutputStreamReady(this);
+}
+
+void WebrtcTCPSocketTestOutputStream::DoCallback() {
+ if (mCallback) {
+ mCallbackTarget->Dispatch(
+ NewRunnableMethod<const nsCOMPtr<nsIOutputStreamCallback>&>(
+ "WebrtcTCPSocketTestOutputStream::CallCallback", this,
+ &WebrtcTCPSocketTestOutputStream::CallCallback,
+ std::move(mCallback)));
+
+ mCallbackTarget = nullptr;
+ }
+}
+
+std::string WebrtcTCPSocketTestOutputStream::DataString() {
+ std::lock_guard<std::mutex> guard(mDataMutex);
+ return std::string((char*)mData.Elements(), mData.Length());
+}
+
+// Fake as in not the real WebrtcTCPSocket but real enough
+class FakeWebrtcTCPSocket : public WebrtcTCPSocket {
+ public:
+ explicit FakeWebrtcTCPSocket(WebrtcTCPSocketCallback* aCallback)
+ : WebrtcTCPSocket(aCallback) {}
+
+ protected:
+ virtual ~FakeWebrtcTCPSocket() = default;
+
+ void InvokeOnClose(nsresult aReason) override;
+ void InvokeOnConnected() override;
+ void InvokeOnRead(nsTArray<uint8_t>&& aReadData) override;
+};
+
+void FakeWebrtcTCPSocket::InvokeOnClose(nsresult aReason) {
+ mProxyCallbacks->OnClose(aReason);
+}
+
+void FakeWebrtcTCPSocket::InvokeOnConnected() {
+ mProxyCallbacks->OnConnected("http"_ns);
+}
+
+void FakeWebrtcTCPSocket::InvokeOnRead(nsTArray<uint8_t>&& aReadData) {
+ mProxyCallbacks->OnRead(std::move(aReadData));
+}
+
+class WebrtcTCPSocketTest : public MtransportTest {
+ public:
+ WebrtcTCPSocketTest()
+ : MtransportTest(),
+ mSocketThread(nullptr),
+ mSocketTransport(nullptr),
+ mInputStream(nullptr),
+ mOutputStream(nullptr),
+ mChannel(nullptr),
+ mCallback(nullptr),
+ mOnCloseCalled(false),
+ mOnConnectedCalled(false) {}
+
+ // WebrtcTCPSocketCallback forwards from mCallback
+ void OnClose(nsresult aReason);
+ void OnConnected(const nsACString& aProxyType);
+ void OnRead(nsTArray<uint8_t>&& aReadData);
+
+ void SetUp() override;
+ void TearDown() override;
+
+ void DoTransportAvailable();
+
+ std::string ReadDataAsString();
+ std::string GetDataLarge();
+
+ nsCOMPtr<nsIEventTarget> mSocketThread;
+
+ nsCOMPtr<nsISocketTransport> mSocketTransport;
+ RefPtr<WebrtcTCPSocketTestInputStream> mInputStream;
+ RefPtr<WebrtcTCPSocketTestOutputStream> mOutputStream;
+ RefPtr<FakeWebrtcTCPSocket> mChannel;
+ RefPtr<WebrtcTCPSocketTestCallback> mCallback;
+
+ bool mOnCloseCalled;
+ bool mOnConnectedCalled;
+
+ size_t ReadDataLength();
+ template <typename T>
+ void AppendReadData(const T* aBuffer, size_t aLength);
+
+ private:
+ nsTArray<uint8_t> mReadData;
+ std::mutex mReadDataMutex;
+};
+
+class WebrtcTCPSocketTestCallback : public WebrtcTCPSocketCallback {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebrtcTCPSocketTestCallback, override)
+
+ explicit WebrtcTCPSocketTestCallback(WebrtcTCPSocketTest* aTest)
+ : mTest(aTest) {}
+
+ // WebrtcTCPSocketCallback
+ void OnClose(nsresult aReason) override;
+ void OnConnected(const nsACString& aProxyType) override;
+ void OnRead(nsTArray<uint8_t>&& aReadData) override;
+
+ protected:
+ virtual ~WebrtcTCPSocketTestCallback() = default;
+
+ private:
+ WebrtcTCPSocketTest* mTest;
+};
+
+void WebrtcTCPSocketTest::SetUp() {
+ nsresult rv;
+ // WebrtcTCPSocket's threading model is the same as mtransport
+ // all socket operations are done on the socket thread
+ // callbacks are invoked on the main thread
+ mSocketThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ ASSERT_TRUE(NS_SUCCEEDED(rv));
+
+ mSocketTransport = new FakeSocketTransportProvider();
+ mInputStream = new WebrtcTCPSocketTestInputStream();
+ mOutputStream = new WebrtcTCPSocketTestOutputStream();
+ mCallback = new WebrtcTCPSocketTestCallback(this);
+ mChannel = new FakeWebrtcTCPSocket(mCallback.get());
+}
+
+void WebrtcTCPSocketTest::TearDown() {}
+
+// WebrtcTCPSocketCallback
+void WebrtcTCPSocketTest::OnRead(nsTArray<uint8_t>&& aReadData) {
+ AppendReadData(aReadData.Elements(), aReadData.Length());
+}
+
+void WebrtcTCPSocketTest::OnConnected(const nsACString& aProxyType) {
+ mOnConnectedCalled = true;
+}
+
+void WebrtcTCPSocketTest::OnClose(nsresult aReason) { mOnCloseCalled = true; }
+
+void WebrtcTCPSocketTest::DoTransportAvailable() {
+ if (!mSocketThread->IsOnCurrentThread()) {
+ mSocketThread->Dispatch(
+ NS_NewRunnableFunction("DoTransportAvailable", [this]() -> void {
+ nsresult rv;
+ rv = mChannel->OnTransportAvailable(mSocketTransport, mInputStream,
+ mOutputStream);
+ ASSERT_EQ(NS_OK, rv);
+ }));
+ } else {
+ // should always be called on the main thread
+ MOZ_ASSERT(0);
+ }
+}
+
+std::string WebrtcTCPSocketTest::ReadDataAsString() {
+ std::lock_guard<std::mutex> guard(mReadDataMutex);
+ return std::string((char*)mReadData.Elements(), mReadData.Length());
+}
+
+std::string WebrtcTCPSocketTest::GetDataLarge() {
+ std::string data;
+ for (int i = 0; i < kDataLargeOuterLoopCount * kDataLargeInnerLoopCount;
+ ++i) {
+ data += kReadData;
+ }
+ return data;
+}
+
+template <typename T>
+void WebrtcTCPSocketTest::AppendReadData(const T* aBuffer, size_t aLength) {
+ std::lock_guard<std::mutex> guard(mReadDataMutex);
+ mReadData.AppendElements(aBuffer, aLength);
+}
+
+size_t WebrtcTCPSocketTest::ReadDataLength() {
+ std::lock_guard<std::mutex> guard(mReadDataMutex);
+ return mReadData.Length();
+}
+
+void WebrtcTCPSocketTestCallback::OnClose(nsresult aReason) {
+ mTest->OnClose(aReason);
+}
+
+void WebrtcTCPSocketTestCallback::OnConnected(const nsACString& aProxyType) {
+ mTest->OnConnected(aProxyType);
+}
+
+void WebrtcTCPSocketTestCallback::OnRead(nsTArray<uint8_t>&& aReadData) {
+ mTest->OnRead(std::move(aReadData));
+}
+
+} // namespace mozilla
+
+typedef mozilla::WebrtcTCPSocketTest WebrtcTCPSocketTest;
+
+TEST_F(WebrtcTCPSocketTest, SetUp) {}
+
+TEST_F(WebrtcTCPSocketTest, TransportAvailable) {
+ DoTransportAvailable();
+ ASSERT_TRUE_WAIT(mOnConnectedCalled, kDefaultTestTimeout);
+}
+
+TEST_F(WebrtcTCPSocketTest, Read) {
+ DoTransportAvailable();
+ ASSERT_TRUE_WAIT(mOnConnectedCalled, kDefaultTestTimeout);
+
+ mInputStream->AppendElements(kReadData, kReadDataLength);
+ mInputStream->DoCallback();
+
+ ASSERT_TRUE_WAIT(ReadDataAsString() == kReadDataString, kDefaultTestTimeout);
+}
+
+TEST_F(WebrtcTCPSocketTest, Write) {
+ DoTransportAvailable();
+ ASSERT_TRUE_WAIT(mOnConnectedCalled, kDefaultTestTimeout);
+
+ nsTArray<uint8_t> data;
+ data.AppendElements(kReadData, kReadDataLength);
+ mChannel->Write(std::move(data));
+
+ ASSERT_TRUE_WAIT(mChannel->CountUnwrittenBytes() == kReadDataLength,
+ kDefaultTestTimeout);
+
+ mOutputStream->DoCallback();
+
+ ASSERT_TRUE_WAIT(mOutputStream->DataString() == kReadDataString,
+ kDefaultTestTimeout);
+}
+
+TEST_F(WebrtcTCPSocketTest, ReadFail) {
+ DoTransportAvailable();
+ ASSERT_TRUE_WAIT(mOnConnectedCalled, kDefaultTestTimeout);
+
+ mInputStream->AppendElements(kReadData, kReadDataLength);
+ mInputStream->Fail();
+ mInputStream->DoCallback();
+
+ ASSERT_TRUE_WAIT(mOnCloseCalled, kDefaultTestTimeout);
+ ASSERT_EQ(0U, ReadDataLength());
+}
+
+TEST_F(WebrtcTCPSocketTest, WriteFail) {
+ DoTransportAvailable();
+ ASSERT_TRUE_WAIT(mOnConnectedCalled, kDefaultTestTimeout);
+
+ nsTArray<uint8_t> array;
+ array.AppendElements(kReadData, kReadDataLength);
+ mChannel->Write(std::move(array));
+
+ ASSERT_TRUE_WAIT(mChannel->CountUnwrittenBytes() == kReadDataLength,
+ kDefaultTestTimeout);
+
+ mOutputStream->Fail();
+ mOutputStream->DoCallback();
+
+ ASSERT_TRUE_WAIT(mOnCloseCalled, kDefaultTestTimeout);
+ ASSERT_EQ(0U, mOutputStream->DataLength());
+}
+
+TEST_F(WebrtcTCPSocketTest, ReadLarge) {
+ DoTransportAvailable();
+ ASSERT_TRUE_WAIT(mOnConnectedCalled, kDefaultTestTimeout);
+
+ const std::string data = GetDataLarge();
+
+ mInputStream->AppendElements(data.c_str(), data.length());
+ // make sure reading loops more than once
+ mInputStream->mMaxReadSize = 3072;
+ mInputStream->AllowCallbacks();
+ mInputStream->DoCallback();
+
+ ASSERT_TRUE_WAIT(ReadDataAsString() == data, kDefaultTestTimeout);
+}
+
+TEST_F(WebrtcTCPSocketTest, WriteLarge) {
+ DoTransportAvailable();
+ ASSERT_TRUE_WAIT(mOnConnectedCalled, kDefaultTestTimeout);
+
+ const std::string data = GetDataLarge();
+
+ for (int i = 0; i < kDataLargeOuterLoopCount; ++i) {
+ nsTArray<uint8_t> array;
+ int chunkSize = kReadDataString.length() * kDataLargeInnerLoopCount;
+ int offset = i * chunkSize;
+ array.AppendElements(data.c_str() + offset, chunkSize);
+ mChannel->Write(std::move(array));
+ }
+
+ ASSERT_TRUE_WAIT(mChannel->CountUnwrittenBytes() == data.length(),
+ kDefaultTestTimeout);
+
+ // make sure writing loops more than once per write request
+ mOutputStream->mMaxWriteSize = 1024;
+ mOutputStream->DoCallback();
+
+ ASSERT_TRUE_WAIT(mOutputStream->DataString() == data, kDefaultTestTimeout);
+}
diff --git a/dom/media/webrtc/transport/test_nr_socket.cpp b/dom/media/webrtc/transport/test_nr_socket.cpp
new file mode 100644
index 0000000000..064013a8b7
--- /dev/null
+++ b/dom/media/webrtc/transport/test_nr_socket.cpp
@@ -0,0 +1,1135 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+/*
+ */
+
+/*
+Based partially on original code from nICEr and nrappkit.
+
+nICEr copyright:
+
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+
+
+nrappkit copyright:
+
+ Copyright (C) 2001-2003, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ ekr@rtfm.com Thu Dec 20 20:14:49 2001
+*/
+
+// Original author: bcampen@mozilla.com [:bwc]
+
+extern "C" {
+#include "stun_msg.h" // for NR_STUN_MAX_MESSAGE_SIZE
+#include "async_wait.h"
+#include "async_timer.h"
+#include "nr_socket.h"
+#include "stun.h"
+#include "transport_addr.h"
+}
+
+#include "mozilla/RefPtr.h"
+#include "test_nr_socket.h"
+
+namespace mozilla {
+
+static int test_nat_socket_create(void* obj, nr_transport_addr* addr,
+ nr_socket** sockp) {
+ RefPtr<NrSocketBase> sock = new TestNrSocket(static_cast<TestNat*>(obj));
+
+ int r, _status;
+
+ r = sock->create(addr);
+ if (r) ABORT(r);
+
+ r = nr_socket_create_int(static_cast<void*>(sock), sock->vtbl(), sockp);
+ if (r) ABORT(r);
+
+ _status = 0;
+
+ {
+ // We will release this reference in destroy(), not exactly the normal
+ // ownership model, but it is what it is.
+ NrSocketBase* dummy = sock.forget().take();
+ (void)dummy;
+ }
+
+abort:
+ return _status;
+}
+
+static int test_nat_socket_factory_destroy(void** obj) {
+ TestNat* nat = static_cast<TestNat*>(*obj);
+ *obj = nullptr;
+ nat->Release();
+ return 0;
+}
+
+static nr_socket_factory_vtbl test_nat_socket_factory_vtbl = {
+ test_nat_socket_create, test_nat_socket_factory_destroy};
+
+/* static */
+TestNat::NatBehavior TestNat::ToNatBehavior(const std::string& type) {
+ if (type.empty() || !type.compare("ENDPOINT_INDEPENDENT")) {
+ return TestNat::ENDPOINT_INDEPENDENT;
+ }
+ if (!type.compare("ADDRESS_DEPENDENT")) {
+ return TestNat::ADDRESS_DEPENDENT;
+ }
+ if (!type.compare("PORT_DEPENDENT")) {
+ return TestNat::PORT_DEPENDENT;
+ }
+
+ MOZ_ASSERT(false, "Invalid NAT behavior");
+ return TestNat::ENDPOINT_INDEPENDENT;
+}
+
+bool TestNat::has_port_mappings() const {
+ for (TestNrSocket* sock : sockets_) {
+ if (sock->has_port_mappings()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool TestNat::is_my_external_tuple(const nr_transport_addr& addr) const {
+ for (TestNrSocket* sock : sockets_) {
+ if (sock->is_my_external_tuple(addr)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool TestNat::is_an_internal_tuple(const nr_transport_addr& addr) const {
+ for (TestNrSocket* sock : sockets_) {
+ nr_transport_addr addr_behind_nat;
+ if (sock->getaddr(&addr_behind_nat)) {
+ MOZ_CRASH("TestNrSocket::getaddr failed!");
+ }
+
+ if (!nr_transport_addr_cmp(&addr, &addr_behind_nat,
+ NR_TRANSPORT_ADDR_CMP_MODE_ALL)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+int TestNat::create_socket_factory(nr_socket_factory** factorypp) {
+ int r = nr_socket_factory_create_int(this, &test_nat_socket_factory_vtbl,
+ factorypp);
+ if (!r) {
+ AddRef();
+ }
+ return r;
+}
+
+void TestNat::set_proxy_config(
+ std::shared_ptr<NrSocketProxyConfig> aProxyConfig) {
+ proxy_config_ = std::move(aProxyConfig);
+}
+
+TestNrSocket::TestNrSocket(TestNat* nat)
+ : nat_(nat), tls_(false), timer_handle_(nullptr) {
+ nat_->insert_socket(this);
+}
+
+TestNrSocket::~TestNrSocket() { nat_->erase_socket(this); }
+
+RefPtr<NrSocketBase> TestNrSocket::create_external_socket(
+ const nr_transport_addr& dest_addr) const {
+ MOZ_ASSERT(nat_->enabled_);
+ MOZ_ASSERT(!nat_->is_an_internal_tuple(dest_addr));
+
+ int r;
+ nr_transport_addr nat_external_addr;
+
+ // Open the socket on an arbitrary port, on the same address.
+ if ((r = nr_transport_addr_copy(&nat_external_addr,
+ &internal_socket_->my_addr()))) {
+ r_log(LOG_GENERIC, LOG_CRIT, "%s: Failure in nr_transport_addr_copy: %d",
+ __FUNCTION__, r);
+ return nullptr;
+ }
+
+ if ((r = nr_transport_addr_set_port(&nat_external_addr, 0))) {
+ r_log(LOG_GENERIC, LOG_CRIT,
+ "%s: Failure in nr_transport_addr_set_port: %d", __FUNCTION__, r);
+ return nullptr;
+ }
+
+ RefPtr<NrSocketBase> external_socket;
+ r = NrSocketBase::CreateSocket(&nat_external_addr, &external_socket,
+ nat_->proxy_config_);
+
+ if (r) {
+ r_log(LOG_GENERIC, LOG_CRIT, "%s: Failure in NrSocket::create: %d",
+ __FUNCTION__, r);
+ return nullptr;
+ }
+
+ return external_socket;
+}
+
+int TestNrSocket::create(nr_transport_addr* addr) {
+ tls_ = addr->tls;
+
+ r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %p create %s", this,
+ addr->as_string);
+ return NrSocketBase::CreateSocket(addr, &internal_socket_, nullptr);
+}
+
+int TestNrSocket::getaddr(nr_transport_addr* addrp) {
+ return internal_socket_->getaddr(addrp);
+}
+
+void TestNrSocket::close() {
+ r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %p %s closing", this,
+ internal_socket_->my_addr().as_string);
+ if (timer_handle_) {
+ NR_async_timer_cancel(timer_handle_);
+ timer_handle_ = nullptr;
+ }
+ internal_socket_->close();
+ for (RefPtr<PortMapping>& port_mapping : port_mappings_) {
+ port_mapping->external_socket_->close();
+ }
+}
+
+int TestNrSocket::listen(int backlog) {
+ MOZ_ASSERT(internal_socket_->my_addr().protocol == IPPROTO_TCP);
+ r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %p %s listening", this,
+ internal_socket_->my_addr().as_string);
+
+ return internal_socket_->listen(backlog);
+}
+
+int TestNrSocket::accept(nr_transport_addr* addrp, nr_socket** sockp) {
+ MOZ_ASSERT(internal_socket_->my_addr().protocol == IPPROTO_TCP);
+ int r = internal_socket_->accept(addrp, sockp);
+ if (r) {
+ return r;
+ }
+
+ if (nat_->enabled_ && !nat_->is_an_internal_tuple(*addrp)) {
+ nr_socket_destroy(sockp);
+ return R_IO_ERROR;
+ }
+
+ return 0;
+}
+
+void TestNrSocket::process_delayed_cb(NR_SOCKET s, int how, void* cb_arg) {
+ DeferredPacket* op = static_cast<DeferredPacket*>(cb_arg);
+ op->socket_->timer_handle_ = nullptr;
+ r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %s sending delayed STUN response",
+ op->internal_socket_->my_addr().as_string);
+ op->internal_socket_->sendto(op->buffer_.data(), op->buffer_.len(),
+ op->flags_, &op->to_);
+
+ delete op;
+}
+
+int TestNrSocket::sendto(const void* msg, size_t len, int flags,
+ const nr_transport_addr* to) {
+ MOZ_ASSERT(internal_socket_->my_addr().protocol != IPPROTO_TCP);
+ r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %p %s %s", this, __FUNCTION__,
+ to->as_string);
+
+ if (nat_->nat_delegate_ &&
+ nat_->nat_delegate_->on_sendto(nat_, msg, len, flags, to)) {
+ return nat_->error_code_for_drop_;
+ }
+
+ UCHAR* buf = static_cast<UCHAR*>(const_cast<void*>(msg));
+ if (nat_->block_stun_ && nr_is_stun_message(buf, len)) {
+ return nat_->error_code_for_drop_;
+ }
+
+ if (nr_is_stun_request_message(buf, len) &&
+ maybe_send_fake_response(buf, len, to)) {
+ return 0;
+ }
+
+ /* TODO: improve the functionality of this in bug 1253657 */
+ if (!nat_->enabled_ || nat_->is_an_internal_tuple(*to)) {
+ if (nat_->delay_stun_resp_ms_ && nr_is_stun_response_message(buf, len)) {
+ NR_ASYNC_TIMER_SET(
+ nat_->delay_stun_resp_ms_, process_delayed_cb,
+ new DeferredPacket(this, msg, len, flags, to, internal_socket_),
+ &timer_handle_);
+ return 0;
+ }
+ return internal_socket_->sendto(msg, len, flags, to);
+ }
+
+ destroy_stale_port_mappings();
+
+ if (to->protocol == IPPROTO_UDP && nat_->block_udp_) {
+ return nat_->error_code_for_drop_;
+ }
+
+ // Choose our port mapping based on our most selective criteria
+ PortMapping* port_mapping = get_port_mapping(
+ *to, std::max(nat_->filtering_type_, nat_->mapping_type_));
+
+ if (!port_mapping) {
+ // See if we have already made the external socket we need to use.
+ PortMapping* similar_port_mapping =
+ get_port_mapping(*to, nat_->mapping_type_);
+ RefPtr<NrSocketBase> external_socket;
+
+ if (similar_port_mapping) {
+ external_socket = similar_port_mapping->external_socket_;
+ } else {
+ external_socket = create_external_socket(*to);
+ if (!external_socket) {
+ MOZ_ASSERT(false);
+ return R_INTERNAL;
+ }
+ }
+
+ port_mapping = create_port_mapping(*to, external_socket);
+ port_mappings_.push_back(port_mapping);
+
+ if (poll_flags() & PR_POLL_READ) {
+ // Make sure the new port mapping is ready to receive traffic if the
+ // TestNrSocket is already waiting.
+ port_mapping->async_wait(NR_ASYNC_WAIT_READ, socket_readable_callback,
+ this, (char*)__FUNCTION__, __LINE__);
+ }
+ }
+
+ // We probably don't want to propagate the flags, since this is a simulated
+ // external IP address.
+ return port_mapping->sendto(msg, len, *to);
+}
+
+int TestNrSocket::recvfrom(void* buf, size_t maxlen, size_t* len, int flags,
+ nr_transport_addr* from) {
+ MOZ_ASSERT(internal_socket_->my_addr().protocol != IPPROTO_TCP);
+
+ if (!read_buffer_.empty()) {
+ UdpPacket& packet = read_buffer_.front();
+ *len = std::min(maxlen, packet.buffer_->len());
+ memcpy(buf, packet.buffer_->data(), *len);
+ nr_transport_addr_copy(from, &packet.remote_address_);
+ read_buffer_.pop_front();
+ return 0;
+ }
+
+ int r;
+ bool ingress_allowed = false;
+
+ if (readable_socket_) {
+ // If any of the external sockets got data, see if it will be passed through
+ r = readable_socket_->recvfrom(buf, maxlen, len, 0, from);
+ const nr_transport_addr to = readable_socket_->my_addr();
+ readable_socket_ = nullptr;
+ if (!r) {
+ PortMapping* port_mapping_used;
+ ingress_allowed = allow_ingress(to, *from, &port_mapping_used);
+ if (ingress_allowed) {
+ r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %s received from %s via %s",
+ internal_socket_->my_addr().as_string, from->as_string,
+ port_mapping_used->external_socket_->my_addr().as_string);
+ if (nat_->refresh_on_ingress_) {
+ port_mapping_used->last_used_ = PR_IntervalNow();
+ }
+ }
+ }
+ } else {
+ // If no external socket has data, see if there's any data that was sent
+ // directly to the TestNrSocket, and eat it if it isn't supposed to get
+ // through.
+ r = internal_socket_->recvfrom(buf, maxlen, len, flags, from);
+ if (!r) {
+ // We do not use allow_ingress() here because that only handles traffic
+ // landing on an external port.
+ ingress_allowed = (!nat_->enabled_ || nat_->is_an_internal_tuple(*from));
+ if (!ingress_allowed) {
+ r_log(LOG_GENERIC, LOG_INFO,
+ "TestNrSocket %s denying ingress from %s: "
+ "Not behind the same NAT",
+ internal_socket_->my_addr().as_string, from->as_string);
+ } else {
+ r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %s received from %s",
+ internal_socket_->my_addr().as_string, from->as_string);
+ }
+ }
+ }
+
+ // Kinda bad that we are forced to give the app a readable callback and then
+ // say "Oh, never mind...", but the alternative is to totally decouple the
+ // callbacks from STS and the callbacks the app sets. On the bright side, this
+ // speeds up unit tests where we are verifying that ingress is forbidden,
+ // since they'll get a readable callback and then an error, instead of having
+ // to wait for a timeout.
+ if (!ingress_allowed) {
+ *len = 0;
+ r = R_WOULDBLOCK;
+ }
+
+ return r;
+}
+
+bool TestNrSocket::allow_ingress(const nr_transport_addr& to,
+ const nr_transport_addr& from,
+ PortMapping** port_mapping_used) const {
+ // This is only called for traffic arriving at a port mapping
+ MOZ_ASSERT(nat_->enabled_);
+ MOZ_ASSERT(!nat_->is_an_internal_tuple(from));
+
+ // Find the port mapping (if any) that this packet landed on
+ for (PortMapping* port_mapping : port_mappings_) {
+ if (!nr_transport_addr_cmp(&to, &port_mapping->external_socket_->my_addr(),
+ NR_TRANSPORT_ADDR_CMP_MODE_ALL)) {
+ *port_mapping_used = port_mapping;
+ }
+ }
+
+ if (NS_WARN_IF(!(*port_mapping_used))) {
+ MOZ_ASSERT(false);
+ r_log(LOG_GENERIC, LOG_INFO,
+ "TestNrSocket %s denying ingress from %s: "
+ "No port mapping for this local port! What?",
+ internal_socket_->my_addr().as_string, from.as_string);
+ return false;
+ }
+
+ if (!port_mapping_matches(**port_mapping_used, from, nat_->filtering_type_)) {
+ r_log(LOG_GENERIC, LOG_INFO,
+ "TestNrSocket %s denying ingress from %s: "
+ "Filtered (no port mapping for source)",
+ internal_socket_->my_addr().as_string, from.as_string);
+ return false;
+ }
+
+ if (is_port_mapping_stale(**port_mapping_used)) {
+ r_log(LOG_GENERIC, LOG_INFO,
+ "TestNrSocket %s denying ingress from %s: "
+ "Stale port mapping",
+ internal_socket_->my_addr().as_string, from.as_string);
+ return false;
+ }
+
+ if (!nat_->allow_hairpinning_ && nat_->is_my_external_tuple(from)) {
+ r_log(LOG_GENERIC, LOG_INFO,
+ "TestNrSocket %s denying ingress from %s: "
+ "Hairpinning disallowed",
+ internal_socket_->my_addr().as_string, from.as_string);
+ return false;
+ }
+
+ return true;
+}
+
+int TestNrSocket::connect(const nr_transport_addr* addr) {
+ r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %p %s connecting to %s", this,
+ internal_socket_->my_addr().as_string, addr->as_string);
+
+ if (connect_invoked_ || !port_mappings_.empty()) {
+ MOZ_CRASH("TestNrSocket::connect() called more than once!");
+ return R_INTERNAL;
+ }
+
+ if (maybe_get_redirect_targets(addr).isSome()) {
+ // If we are simulating STUN redirects for |addr|, we need to pretend that
+ // the TCP connection worked, since |addr| probably does not actually point
+ // at something that exists.
+ connect_fake_stun_address_.reset(new nr_transport_addr);
+ nr_transport_addr_copy(connect_fake_stun_address_.get(), addr);
+
+ // We dispatch this, otherwise nICEr can trip over its shoelaces
+ GetCurrentSerialEventTarget()->Dispatch(
+ NS_NewRunnableFunction("Async writeable callback for TestNrSocket",
+ [this, self = RefPtr<TestNrSocket>(this)] {
+ if (poll_flags() & PR_POLL_WRITE) {
+ fire_callback(NR_ASYNC_WAIT_WRITE);
+ }
+ }));
+
+ return R_WOULDBLOCK;
+ }
+
+ if (!nat_->enabled_ ||
+ addr->protocol == IPPROTO_UDP // Horrible hack to allow default address
+ // discovery to work. Only works because
+ // we don't normally connect on UDP.
+ || nat_->is_an_internal_tuple(*addr)) {
+ // This will set connect_invoked_
+ return internal_socket_->connect(addr);
+ }
+
+ RefPtr<NrSocketBase> external_socket(create_external_socket(*addr));
+ if (!external_socket) {
+ return R_INTERNAL;
+ }
+
+ PortMapping* port_mapping = create_port_mapping(*addr, external_socket);
+ port_mappings_.push_back(port_mapping);
+ int r = port_mapping->external_socket_->connect(addr);
+ if (r && r != R_WOULDBLOCK) {
+ return r;
+ }
+
+ port_mapping->last_used_ = PR_IntervalNow();
+
+ if (poll_flags() & PR_POLL_READ) {
+ port_mapping->async_wait(NR_ASYNC_WAIT_READ,
+ port_mapping_tcp_passthrough_callback, this,
+ (char*)__FUNCTION__, __LINE__);
+ }
+
+ return r;
+}
+
+int TestNrSocket::write(const void* msg, size_t len, size_t* written) {
+ r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %p %s writing", this,
+ internal_socket_->my_addr().as_string);
+
+ UCHAR* buf = static_cast<UCHAR*>(const_cast<void*>(msg));
+
+ if (nat_->nat_delegate_ &&
+ nat_->nat_delegate_->on_write(nat_, msg, len, written)) {
+ return R_INTERNAL;
+ }
+
+ if (nat_->block_stun_ && nr_is_stun_message(buf, len)) {
+ // Should cause this socket to be abandoned
+ r_log(LOG_GENERIC, LOG_DEBUG,
+ "TestNrSocket %s dropping outgoing TCP "
+ "because it is configured to drop STUN",
+ my_addr().as_string);
+ return R_INTERNAL;
+ }
+
+ if (nr_is_stun_request_message(buf, len) && connect_fake_stun_address_ &&
+ maybe_send_fake_response(buf, len, connect_fake_stun_address_.get())) {
+ return 0;
+ }
+
+ if (nat_->block_tcp_ && !tls_) {
+ // Should cause this socket to be abandoned
+ r_log(LOG_GENERIC, LOG_DEBUG,
+ "TestNrSocket %s dropping outgoing TCP "
+ "because it is configured to drop TCP",
+ my_addr().as_string);
+ return R_INTERNAL;
+ }
+
+ if (nat_->block_tls_ && tls_) {
+ // Should cause this socket to be abandoned
+ r_log(LOG_GENERIC, LOG_DEBUG,
+ "TestNrSocket %s dropping outgoing TLS "
+ "because it is configured to drop TLS",
+ my_addr().as_string);
+ return R_INTERNAL;
+ }
+
+ if (port_mappings_.empty()) {
+ // The no-nat case, just pass call through.
+ r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %s writing",
+ my_addr().as_string);
+
+ return internal_socket_->write(msg, len, written);
+ }
+ destroy_stale_port_mappings();
+ if (port_mappings_.empty()) {
+ r_log(LOG_GENERIC, LOG_DEBUG,
+ "TestNrSocket %s dropping outgoing TCP "
+ "because the port mapping was stale",
+ my_addr().as_string);
+ return R_INTERNAL;
+ }
+ // This is TCP only
+ MOZ_ASSERT(port_mappings_.size() == 1);
+ r_log(LOG_GENERIC, LOG_DEBUG, "PortMapping %s -> %s writing",
+ port_mappings_.front()->external_socket_->my_addr().as_string,
+ port_mappings_.front()->remote_address_.as_string);
+ port_mappings_.front()->last_used_ = PR_IntervalNow();
+ return port_mappings_.front()->external_socket_->write(msg, len, written);
+}
+
+int TestNrSocket::read(void* buf, size_t maxlen, size_t* len) {
+ r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %p %s reading", this,
+ internal_socket_->my_addr().as_string);
+
+ if (!read_buffer_.empty()) {
+ r_log(LOG_GENERIC, LOG_DEBUG,
+ "TestNrSocket %p %s has stuff in read_buffer_", this,
+ internal_socket_->my_addr().as_string);
+ UdpPacket packet(std::move(read_buffer_.front()));
+ read_buffer_.pop_front();
+ *len = std::min(maxlen, packet.buffer_->len());
+ memcpy(buf, packet.buffer_->data(), *len);
+ if (*len != packet.buffer_->len()) {
+ // Put remaining bytes in new packet, at the front.
+ read_buffer_.emplace_front(packet.buffer_->data() + *len,
+ packet.buffer_->len() - *len,
+ packet.remote_address_);
+ }
+ return 0;
+ }
+
+ if (connect_fake_stun_address_) {
+ return R_WOULDBLOCK;
+ }
+
+ int r;
+
+ if (port_mappings_.empty()) {
+ r = internal_socket_->read(buf, maxlen, len);
+ } else {
+ MOZ_ASSERT(port_mappings_.size() == 1);
+ r = port_mappings_.front()->external_socket_->read(buf, maxlen, len);
+ if (!r && nat_->refresh_on_ingress_) {
+ port_mappings_.front()->last_used_ = PR_IntervalNow();
+ }
+ }
+
+ if (r) {
+ return r;
+ }
+
+ if (nat_->nat_delegate_ &&
+ nat_->nat_delegate_->on_read(nat_, buf, maxlen, len)) {
+ return R_INTERNAL;
+ }
+
+ if (nat_->block_tcp_ && !tls_) {
+ // Should cause this socket to be abandoned
+ return R_INTERNAL;
+ }
+
+ if (nat_->block_tls_ && tls_) {
+ // Should cause this socket to be abandoned
+ return R_INTERNAL;
+ }
+
+ UCHAR* cbuf = static_cast<UCHAR*>(const_cast<void*>(buf));
+ if (nat_->block_stun_ && nr_is_stun_message(cbuf, *len)) {
+ // Should cause this socket to be abandoned
+ return R_INTERNAL;
+ }
+
+ return r;
+}
+
+int TestNrSocket::async_wait(int how, NR_async_cb cb, void* cb_arg,
+ char* function, int line) {
+ r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %s waiting for %s",
+ internal_socket_->my_addr().as_string,
+ how == NR_ASYNC_WAIT_READ ? "read" : "write");
+
+ int r;
+
+ if (how == NR_ASYNC_WAIT_READ) {
+ NrSocketBase::async_wait(how, cb, cb_arg, function, line);
+ if (!read_buffer_.empty()) {
+ fire_readable_callback();
+ return 0;
+ }
+
+ // Make sure we're waiting on the socket for the internal address
+ r = internal_socket_->async_wait(how, socket_readable_callback, this,
+ function, line);
+ } else {
+ if (connect_fake_stun_address_) {
+ // Fake TCP connection case; register the callback on this socket, not
+ // a real one.
+ return NrSocketBase::async_wait(how, cb, cb_arg, function, line);
+ }
+
+ // For write, just use the readiness of the internal socket, since we queue
+ // everything for the port mappings.
+ r = internal_socket_->async_wait(how, cb, cb_arg, function, line);
+ }
+
+ if (r) {
+ r_log(LOG_GENERIC, LOG_ERR,
+ "TestNrSocket %s failed to async_wait for "
+ "internal socket: %d\n",
+ internal_socket_->my_addr().as_string, r);
+ return r;
+ }
+
+ if (is_tcp_connection_behind_nat()) {
+ // Bypass all port-mapping related logic
+ return 0;
+ }
+
+ if (internal_socket_->my_addr().protocol == IPPROTO_TCP) {
+ // For a TCP connection through a simulated NAT, these signals are
+ // just passed through.
+ MOZ_ASSERT(port_mappings_.size() == 1);
+
+ return port_mappings_.front()->async_wait(
+ how, port_mapping_tcp_passthrough_callback, this, function, line);
+ }
+ if (how == NR_ASYNC_WAIT_READ) {
+ // For UDP port mappings, we decouple the writeable callbacks
+ for (PortMapping* port_mapping : port_mappings_) {
+ // Be ready to receive traffic on our port mappings
+ r = port_mapping->async_wait(how, socket_readable_callback, this,
+ function, line);
+ if (r) {
+ r_log(LOG_GENERIC, LOG_ERR,
+ "TestNrSocket %s failed to async_wait for "
+ "port mapping: %d\n",
+ internal_socket_->my_addr().as_string, r);
+ return r;
+ }
+ }
+ }
+
+ return 0;
+}
+
+void TestNrSocket::cancel_port_mapping_async_wait(int how) {
+ for (PortMapping* port_mapping : port_mappings_) {
+ port_mapping->cancel(how);
+ }
+}
+
+int TestNrSocket::cancel(int how) {
+ r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %s stop waiting for %s",
+ internal_socket_->my_addr().as_string,
+ how == NR_ASYNC_WAIT_READ ? "read" : "write");
+
+ if (connect_fake_stun_address_) {
+ return NrSocketBase::cancel(how);
+ }
+
+ // Writable callbacks are decoupled except for the TCP case
+ if (how == NR_ASYNC_WAIT_READ ||
+ internal_socket_->my_addr().protocol == IPPROTO_TCP) {
+ cancel_port_mapping_async_wait(how);
+ }
+
+ return internal_socket_->cancel(how);
+}
+
+bool TestNrSocket::has_port_mappings() const { return !port_mappings_.empty(); }
+
+bool TestNrSocket::is_my_external_tuple(const nr_transport_addr& addr) const {
+ for (PortMapping* port_mapping : port_mappings_) {
+ nr_transport_addr port_mapping_addr;
+ if (port_mapping->external_socket_->getaddr(&port_mapping_addr)) {
+ MOZ_CRASH("NrSocket::getaddr failed!");
+ }
+
+ if (!nr_transport_addr_cmp(&addr, &port_mapping_addr,
+ NR_TRANSPORT_ADDR_CMP_MODE_ALL)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool TestNrSocket::is_port_mapping_stale(
+ const PortMapping& port_mapping) const {
+ PRIntervalTime now = PR_IntervalNow();
+ PRIntervalTime elapsed_ticks = now - port_mapping.last_used_;
+ uint32_t idle_duration = PR_IntervalToMilliseconds(elapsed_ticks);
+ return idle_duration > nat_->mapping_timeout_;
+}
+
+void TestNrSocket::destroy_stale_port_mappings() {
+ for (auto i = port_mappings_.begin(); i != port_mappings_.end();) {
+ auto temp = i;
+ ++i;
+ if (is_port_mapping_stale(**temp)) {
+ r_log(LOG_GENERIC, LOG_INFO,
+ "TestNrSocket %s destroying port mapping %s -> %s",
+ internal_socket_->my_addr().as_string,
+ (*temp)->external_socket_->my_addr().as_string,
+ (*temp)->remote_address_.as_string);
+
+ port_mappings_.erase(temp);
+ }
+ }
+}
+
+void TestNrSocket::socket_readable_callback(void* real_sock_v, int how,
+ void* test_sock_v) {
+ TestNrSocket* test_socket = static_cast<TestNrSocket*>(test_sock_v);
+ NrSocketBase* real_socket = static_cast<NrSocketBase*>(real_sock_v);
+
+ test_socket->on_socket_readable(real_socket);
+}
+
+void TestNrSocket::on_socket_readable(NrSocketBase* real_socket) {
+ if (!readable_socket_ && (real_socket != internal_socket_)) {
+ readable_socket_ = real_socket;
+ }
+
+ fire_readable_callback();
+}
+
+void TestNrSocket::fire_readable_callback() {
+ MOZ_ASSERT(poll_flags() & PR_POLL_READ);
+ r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %p %s ready for read", this,
+ internal_socket_->my_addr().as_string);
+ fire_callback(NR_ASYNC_WAIT_READ);
+}
+
+void TestNrSocket::port_mapping_writeable_callback(void* ext_sock_v, int how,
+ void* test_sock_v) {
+ TestNrSocket* test_socket = static_cast<TestNrSocket*>(test_sock_v);
+ NrSocketBase* external_socket = static_cast<NrSocketBase*>(ext_sock_v);
+
+ test_socket->write_to_port_mapping(external_socket);
+}
+
+void TestNrSocket::write_to_port_mapping(NrSocketBase* external_socket) {
+ MOZ_ASSERT(internal_socket_->my_addr().protocol != IPPROTO_TCP);
+
+ int r = 0;
+ for (PortMapping* port_mapping : port_mappings_) {
+ if (port_mapping->external_socket_ == external_socket) {
+ // If the send succeeds, or if there was nothing to send, we keep going
+ r = port_mapping->send_from_queue();
+ if (r) {
+ break;
+ }
+ }
+ }
+
+ if (r == R_WOULDBLOCK) {
+ // Re-register for writeable callbacks, since we still have stuff to send
+ NR_ASYNC_WAIT(external_socket, NR_ASYNC_WAIT_WRITE,
+ &TestNrSocket::port_mapping_writeable_callback, this);
+ }
+}
+
+void TestNrSocket::port_mapping_tcp_passthrough_callback(void* ext_sock_v,
+ int how,
+ void* test_sock_v) {
+ TestNrSocket* test_socket = static_cast<TestNrSocket*>(test_sock_v);
+ r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket %s firing %s callback",
+ test_socket->internal_socket_->my_addr().as_string,
+ how == NR_ASYNC_WAIT_READ ? "readable" : "writeable");
+
+ test_socket->internal_socket_->fire_callback(how);
+}
+
+bool TestNrSocket::is_tcp_connection_behind_nat() const {
+ return internal_socket_->my_addr().protocol == IPPROTO_TCP &&
+ port_mappings_.empty();
+}
+
+TestNrSocket::PortMapping* TestNrSocket::get_port_mapping(
+ const nr_transport_addr& remote_address,
+ TestNat::NatBehavior filter) const {
+ for (PortMapping* port_mapping : port_mappings_) {
+ if (port_mapping_matches(*port_mapping, remote_address, filter)) {
+ return port_mapping;
+ }
+ }
+ return nullptr;
+}
+
+/* static */
+bool TestNrSocket::port_mapping_matches(const PortMapping& port_mapping,
+ const nr_transport_addr& remote_addr,
+ TestNat::NatBehavior filter) {
+ int compare_flags;
+ switch (filter) {
+ case TestNat::ENDPOINT_INDEPENDENT:
+ compare_flags = NR_TRANSPORT_ADDR_CMP_MODE_PROTOCOL;
+ break;
+ case TestNat::ADDRESS_DEPENDENT:
+ compare_flags = NR_TRANSPORT_ADDR_CMP_MODE_ADDR;
+ break;
+ case TestNat::PORT_DEPENDENT:
+ compare_flags = NR_TRANSPORT_ADDR_CMP_MODE_ALL;
+ break;
+ }
+
+ return !nr_transport_addr_cmp(&remote_addr, &port_mapping.remote_address_,
+ compare_flags);
+}
+
+TestNrSocket::PortMapping* TestNrSocket::create_port_mapping(
+ const nr_transport_addr& remote_address,
+ const RefPtr<NrSocketBase>& external_socket) const {
+ r_log(LOG_GENERIC, LOG_INFO, "TestNrSocket %s creating port mapping %s -> %s",
+ internal_socket_->my_addr().as_string,
+ external_socket->my_addr().as_string, remote_address.as_string);
+
+ return new PortMapping(remote_address, external_socket);
+}
+
+TestNrSocket::PortMapping::PortMapping(
+ const nr_transport_addr& remote_address,
+ const RefPtr<NrSocketBase>& external_socket)
+ : external_socket_(external_socket) {
+ nr_transport_addr_copy(&remote_address_, &remote_address);
+}
+
+int TestNrSocket::PortMapping::send_from_queue() {
+ MOZ_ASSERT(remote_address_.protocol != IPPROTO_TCP);
+ int r = 0;
+
+ while (!send_queue_.empty()) {
+ UdpPacket& packet = send_queue_.front();
+ r_log(LOG_GENERIC, LOG_DEBUG,
+ "PortMapping %s -> %s sending from queue to %s",
+ external_socket_->my_addr().as_string, remote_address_.as_string,
+ packet.remote_address_.as_string);
+
+ r = external_socket_->sendto(packet.buffer_->data(), packet.buffer_->len(),
+ 0, &packet.remote_address_);
+
+ if (r) {
+ if (r != R_WOULDBLOCK) {
+ r_log(LOG_GENERIC, LOG_ERR, "%s: Fatal error %d, stop trying",
+ __FUNCTION__, r);
+ send_queue_.clear();
+ } else {
+ r_log(LOG_GENERIC, LOG_DEBUG, "Would block, will retry later");
+ }
+ break;
+ }
+
+ send_queue_.pop_front();
+ }
+
+ return r;
+}
+
+int TestNrSocket::PortMapping::sendto(const void* msg, size_t len,
+ const nr_transport_addr& to) {
+ MOZ_ASSERT(remote_address_.protocol != IPPROTO_TCP);
+ r_log(LOG_GENERIC, LOG_DEBUG, "PortMapping %s -> %s sending to %s",
+ external_socket_->my_addr().as_string, remote_address_.as_string,
+ to.as_string);
+
+ last_used_ = PR_IntervalNow();
+ int r = external_socket_->sendto(msg, len, 0, &to);
+
+ if (r == R_WOULDBLOCK) {
+ r_log(LOG_GENERIC, LOG_DEBUG, "Enqueueing UDP packet to %s", to.as_string);
+ send_queue_.emplace_back(msg, len, to);
+ return 0;
+ }
+ if (r) {
+ r_log(LOG_GENERIC, LOG_ERR, "Error: %d", r);
+ }
+
+ return r;
+}
+
+int TestNrSocket::PortMapping::async_wait(int how, NR_async_cb cb, void* cb_arg,
+ char* function, int line) {
+ r_log(LOG_GENERIC, LOG_DEBUG, "PortMapping %s -> %s waiting for %s",
+ external_socket_->my_addr().as_string, remote_address_.as_string,
+ how == NR_ASYNC_WAIT_READ ? "read" : "write");
+
+ return external_socket_->async_wait(how, cb, cb_arg, function, line);
+}
+
+int TestNrSocket::PortMapping::cancel(int how) {
+ r_log(LOG_GENERIC, LOG_DEBUG, "PortMapping %s -> %s stop waiting for %s",
+ external_socket_->my_addr().as_string, remote_address_.as_string,
+ how == NR_ASYNC_WAIT_READ ? "read" : "write");
+
+ return external_socket_->cancel(how);
+}
+
+class nr_stun_message_deleter {
+ public:
+ nr_stun_message_deleter() = default;
+ void operator()(nr_stun_message* msg) const { nr_stun_message_destroy(&msg); }
+};
+
+bool TestNrSocket::maybe_send_fake_response(const void* msg, size_t len,
+ const nr_transport_addr* to) {
+ Maybe<nsTArray<nsCString>> redirect_targets = maybe_get_redirect_targets(to);
+ if (!redirect_targets.isSome()) {
+ return false;
+ }
+
+ std::unique_ptr<nr_stun_message, nr_stun_message_deleter> request;
+ {
+ nr_stun_message* temp = nullptr;
+ if (NS_WARN_IF(nr_stun_message_create2(&temp, (unsigned char*)msg, len))) {
+ return false;
+ }
+ request.reset(temp);
+ }
+
+ if (NS_WARN_IF(nr_stun_decode_message(request.get(), nullptr, nullptr))) {
+ return false;
+ }
+
+ std::unique_ptr<nr_stun_message, nr_stun_message_deleter> response;
+ {
+ nr_stun_message* temp = nullptr;
+ if (nr_stun_message_create(&temp)) {
+ MOZ_CRASH("nr_stun_message_create failed!");
+ }
+ response.reset(temp);
+ }
+
+ nr_stun_form_error_response(request.get(), response.get(), 300,
+ (char*)"Try alternate");
+
+ int port = 0;
+ if (nr_transport_addr_get_port(to, &port)) {
+ MOZ_CRASH();
+ }
+
+ for (const nsCString& address : *redirect_targets) {
+ r_log(LOG_GENERIC, LOG_DEBUG,
+ "TestNrSocket attempting to add alternate server %s", address.Data());
+ nr_transport_addr addr;
+ if (NS_WARN_IF(nr_str_port_to_transport_addr(address.Data(), port,
+ IPPROTO_UDP, &addr))) {
+ continue;
+ }
+ if (nr_stun_message_add_alternate_server_attribute(response.get(), &addr)) {
+ MOZ_CRASH("nr_stun_message_add_alternate_server_attribute failed!");
+ }
+ }
+
+ if (nr_stun_encode_message(response.get())) {
+ MOZ_CRASH("nr_stun_encode_message failed!");
+ }
+
+ nr_transport_addr response_from;
+ if (nr_transport_addr_is_wildcard(to)) {
+ // |to| points to an FQDN, and nICEr is delegating DNS lookup to us; we
+ // aren't _actually_ going to do that though, so we select a bogus address
+ // for the response to come from. TEST-NET is a fairly reasonable thing to
+ // use for this.
+ int port = 0;
+ if (nr_transport_addr_get_port(to, &port)) {
+ MOZ_CRASH();
+ }
+ switch (to->ip_version) {
+ case NR_IPV4:
+ if (nr_str_port_to_transport_addr("198.51.100.1", port, to->protocol,
+ &response_from)) {
+ MOZ_CRASH();
+ }
+ break;
+ case NR_IPV6:
+ if (nr_str_port_to_transport_addr("::ffff:198.51.100.1", port,
+ to->protocol, &response_from)) {
+ MOZ_CRASH();
+ }
+ break;
+ default:
+ MOZ_CRASH();
+ }
+ } else {
+ nr_transport_addr_copy(&response_from, to);
+ }
+
+ read_buffer_.emplace_back(response->buffer, response->length, response_from);
+
+ // We dispatch this, otherwise nICEr can trip over its shoelaces
+ r_log(LOG_GENERIC, LOG_DEBUG,
+ "TestNrSocket %p scheduling callback for redirect response", this);
+ GetCurrentSerialEventTarget()->Dispatch(NS_NewRunnableFunction(
+ "Async readable callback for TestNrSocket",
+ [this, self = RefPtr<TestNrSocket>(this)] {
+ if (poll_flags() & PR_POLL_READ) {
+ fire_readable_callback();
+ } else {
+ r_log(LOG_GENERIC, LOG_DEBUG,
+ "TestNrSocket %p deferring callback for redirect response",
+ this);
+ }
+ }));
+
+ return true;
+}
+
+Maybe<nsTArray<nsCString>> TestNrSocket::maybe_get_redirect_targets(
+ const nr_transport_addr* to) const {
+ Maybe<nsTArray<nsCString>> result;
+
+ // 256 is overkill, but it hardly matters
+ char addrstring[256];
+ if (nr_transport_addr_get_addrstring(to, addrstring, 256)) {
+ MOZ_CRASH("nr_transport_addr_get_addrstring failed!");
+ }
+
+ r_log(LOG_GENERIC, LOG_DEBUG, "TestNrSocket checking redirect rules for %s",
+ addrstring);
+ auto it = nat_->stun_redirect_map_.find(nsCString(addrstring));
+ if (it != nat_->stun_redirect_map_.end()) {
+ result = Some(it->second);
+ }
+
+ return result;
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/transport/test_nr_socket.h b/dom/media/webrtc/transport/test_nr_socket.h
new file mode 100644
index 0000000000..c6a796c063
--- /dev/null
+++ b/dom/media/webrtc/transport/test_nr_socket.h
@@ -0,0 +1,370 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+/*
+ */
+
+/*
+Based partially on original code from nICEr and nrappkit.
+
+nICEr copyright:
+
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+
+
+nrappkit copyright:
+
+ Copyright (C) 2001-2003, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ ekr@rtfm.com Thu Dec 20 20:14:49 2001
+*/
+
+// Original author: bcampen@mozilla.com [:bwc]
+
+#ifndef test_nr_socket__
+#define test_nr_socket__
+
+extern "C" {
+#include "transport_addr.h"
+}
+
+#include "nr_socket_prsock.h"
+
+extern "C" {
+#include "nr_socket.h"
+}
+
+#include <set>
+#include <map>
+#include <list>
+#include <string>
+
+#include "mozilla/UniquePtr.h"
+#include "prinrval.h"
+#include "mediapacket.h"
+
+namespace mozilla {
+
+class TestNrSocket;
+class NrSocketProxyConfig;
+
+/**
+ * A group of TestNrSockets that behave as if they were behind the same NAT.
+ * @note We deliberately avoid addref/release of TestNrSocket here to avoid
+ * masking lifetime errors elsewhere.
+ */
+class TestNat {
+ public:
+ /**
+ * This allows TestNat traffic to be passively inspected.
+ * If a non-zero (error) value is returned, the packet will be dropped,
+ * allowing for tests to extend how packet manipulation is done by
+ * TestNat with having to modify TestNat itself.
+ */
+ class NatDelegate {
+ public:
+ virtual int on_read(TestNat* nat, void* buf, size_t maxlen,
+ size_t* len) = 0;
+ virtual int on_sendto(TestNat* nat, const void* msg, size_t len, int flags,
+ const nr_transport_addr* to) = 0;
+ virtual int on_write(TestNat* nat, const void* msg, size_t len,
+ size_t* written) = 0;
+ };
+
+ typedef enum {
+ /** For mapping, one port is used for all destinations.
+ * For filtering, allow any external address/port. */
+ ENDPOINT_INDEPENDENT,
+
+ /** For mapping, one port for each destination address (for any port).
+ * For filtering, allow incoming traffic from addresses that outgoing
+ * traffic has been sent to. */
+ ADDRESS_DEPENDENT,
+
+ /** For mapping, one port for each destination address/port.
+ * For filtering, allow incoming traffic only from addresses/ports that
+ * outgoing traffic has been sent to. */
+ PORT_DEPENDENT,
+ } NatBehavior;
+
+ TestNat()
+ : enabled_(false),
+ filtering_type_(ENDPOINT_INDEPENDENT),
+ mapping_type_(ENDPOINT_INDEPENDENT),
+ mapping_timeout_(30000),
+ allow_hairpinning_(false),
+ refresh_on_ingress_(false),
+ block_udp_(false),
+ block_stun_(false),
+ block_tcp_(false),
+ block_tls_(false),
+ error_code_for_drop_(0),
+ delay_stun_resp_ms_(0),
+ nat_delegate_(nullptr),
+ sockets_() {}
+
+ bool has_port_mappings() const;
+
+ // Helps determine whether we're hairpinning
+ bool is_my_external_tuple(const nr_transport_addr& addr) const;
+ bool is_an_internal_tuple(const nr_transport_addr& addr) const;
+
+ int create_socket_factory(nr_socket_factory** factorypp);
+
+ void insert_socket(TestNrSocket* socket) { sockets_.insert(socket); }
+
+ void erase_socket(TestNrSocket* socket) { sockets_.erase(socket); }
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TestNat);
+
+ static NatBehavior ToNatBehavior(const std::string& type);
+
+ void set_proxy_config(std::shared_ptr<NrSocketProxyConfig> aProxyConfig);
+
+ bool enabled_;
+ TestNat::NatBehavior filtering_type_;
+ TestNat::NatBehavior mapping_type_;
+ uint32_t mapping_timeout_;
+ bool allow_hairpinning_;
+ bool refresh_on_ingress_;
+ bool block_udp_;
+ bool block_stun_;
+ bool block_tcp_;
+ bool block_tls_;
+ bool error_code_for_drop_;
+ /* Note: this can only delay a single response so far (bug 1253657) */
+ uint32_t delay_stun_resp_ms_;
+
+ // When we see an outgoing STUN request with a destination address or
+ // destination FQDN that matches a key in this map, we respond with a STUN/300
+ // with a list of ALTERNATE-SERVER fields based on the value in this map.
+ std::map<nsCString, CopyableTArray<nsCString>> stun_redirect_map_;
+
+ NatDelegate* nat_delegate_;
+ std::shared_ptr<NrSocketProxyConfig> proxy_config_;
+
+ private:
+ std::set<TestNrSocket*> sockets_;
+
+ ~TestNat() = default;
+};
+
+/**
+ * Subclass of NrSocketBase that can simulate things like being behind a NAT,
+ * packet loss, latency, packet rewriting, etc. Also exposes some stuff that
+ * assists in diagnostics.
+ * This is accomplished by wrapping an "internal" socket (that handles traffic
+ * behind the NAT), and a collection of "external" sockets (that handle traffic
+ * into/out of the NAT)
+ */
+class TestNrSocket : public NrSocketBase {
+ public:
+ explicit TestNrSocket(TestNat* nat);
+
+ bool has_port_mappings() const;
+ bool is_my_external_tuple(const nr_transport_addr& addr) const;
+
+ // Overrides of NrSocketBase
+ int create(nr_transport_addr* addr) override;
+ int sendto(const void* msg, size_t len, int flags,
+ const nr_transport_addr* to) override;
+ int recvfrom(void* buf, size_t maxlen, size_t* len, int flags,
+ nr_transport_addr* from) override;
+ int getaddr(nr_transport_addr* addrp) override;
+ void close() override;
+ int connect(const nr_transport_addr* addr) override;
+ int write(const void* msg, size_t len, size_t* written) override;
+ int read(void* buf, size_t maxlen, size_t* len) override;
+
+ int listen(int backlog) override;
+ int accept(nr_transport_addr* addrp, nr_socket** sockp) override;
+ int async_wait(int how, NR_async_cb cb, void* cb_arg, char* function,
+ int line) override;
+ int cancel(int how) override;
+
+ // Need override since this is virtual in NrSocketBase
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TestNrSocket, override)
+
+ private:
+ virtual ~TestNrSocket();
+
+ class UdpPacket {
+ public:
+ UdpPacket(const void* msg, size_t len, const nr_transport_addr& addr)
+ : buffer_(new MediaPacket) {
+ buffer_->Copy(static_cast<const uint8_t*>(msg), len);
+ nr_transport_addr_copy(&remote_address_, &addr);
+ }
+
+ UdpPacket(UdpPacket&& aOrig) = default;
+
+ ~UdpPacket() = default;
+
+ nr_transport_addr remote_address_;
+ UniquePtr<MediaPacket> buffer_;
+ };
+
+ class PortMapping {
+ public:
+ PortMapping(const nr_transport_addr& remote_address,
+ const RefPtr<NrSocketBase>& external_socket);
+
+ int sendto(const void* msg, size_t len, const nr_transport_addr& to);
+ int async_wait(int how, NR_async_cb cb, void* cb_arg, char* function,
+ int line);
+ int cancel(int how);
+ int send_from_queue();
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PortMapping);
+
+ PRIntervalTime last_used_;
+ RefPtr<NrSocketBase> external_socket_;
+ // For non-symmetric, most of the data here doesn't matter
+ nr_transport_addr remote_address_;
+
+ private:
+ ~PortMapping() { external_socket_->close(); }
+
+ // If external_socket_ returns E_WOULDBLOCK, we don't want to propagate
+ // that to the code using the TestNrSocket. We can also perhaps use this
+ // to help simulate things like latency.
+ std::list<UdpPacket> send_queue_;
+ };
+
+ struct DeferredPacket {
+ DeferredPacket(TestNrSocket* sock, const void* data, size_t len, int flags,
+ const nr_transport_addr* addr,
+ RefPtr<NrSocketBase> internal_socket)
+ : socket_(sock),
+ buffer_(),
+ flags_(flags),
+ internal_socket_(internal_socket) {
+ buffer_.Copy(reinterpret_cast<const uint8_t*>(data), len);
+ nr_transport_addr_copy(&to_, addr);
+ }
+
+ TestNrSocket* socket_;
+ MediaPacket buffer_;
+ int flags_;
+ nr_transport_addr to_;
+ RefPtr<NrSocketBase> internal_socket_;
+ };
+
+ bool is_port_mapping_stale(const PortMapping& port_mapping) const;
+ bool allow_ingress(const nr_transport_addr& to, const nr_transport_addr& from,
+ PortMapping** port_mapping_used) const;
+ void destroy_stale_port_mappings();
+
+ static void socket_readable_callback(void* real_sock_v, int how,
+ void* test_sock_v);
+ void on_socket_readable(NrSocketBase* external_or_internal_socket);
+ void fire_readable_callback();
+
+ static void port_mapping_tcp_passthrough_callback(void* ext_sock_v, int how,
+ void* test_sock_v);
+ void cancel_port_mapping_async_wait(int how);
+
+ static void port_mapping_writeable_callback(void* ext_sock_v, int how,
+ void* test_sock_v);
+ void write_to_port_mapping(NrSocketBase* external_socket);
+ bool is_tcp_connection_behind_nat() const;
+
+ PortMapping* get_port_mapping(const nr_transport_addr& remote_addr,
+ TestNat::NatBehavior filter) const;
+ static bool port_mapping_matches(const PortMapping& port_mapping,
+ const nr_transport_addr& remote_addr,
+ TestNat::NatBehavior filter);
+ PortMapping* create_port_mapping(
+ const nr_transport_addr& remote_addr,
+ const RefPtr<NrSocketBase>& external_socket) const;
+ RefPtr<NrSocketBase> create_external_socket(
+ const nr_transport_addr& remote_addr) const;
+
+ static void process_delayed_cb(NR_SOCKET s, int how, void* cb_arg);
+
+ bool maybe_send_fake_response(const void* msg, size_t len,
+ const nr_transport_addr* to);
+ Maybe<nsTArray<nsCString>> maybe_get_redirect_targets(
+ const nr_transport_addr* to) const;
+
+ RefPtr<NrSocketBase> readable_socket_;
+ // The socket for the "internal" address; used to talk to stuff behind the
+ // same nat.
+ RefPtr<NrSocketBase> internal_socket_;
+ RefPtr<TestNat> nat_;
+ bool tls_;
+ // Since our comparison logic is different depending on what kind of NAT
+ // we simulate, and the STL does not make it very easy to switch out the
+ // comparison function at runtime, and these lists are going to be very
+ // small anyway, we just brute-force it.
+ std::list<RefPtr<PortMapping>> port_mappings_;
+
+ void* timer_handle_;
+
+ // Just used for fake stun responses right now. Not _necessarily_ just UDP
+ // stuff, UdpPacket just has what we need to make this work for UDP.
+ std::list<UdpPacket> read_buffer_;
+ std::unique_ptr<nr_transport_addr> connect_fake_stun_address_;
+};
+
+} // namespace mozilla
+
+#endif // test_nr_socket__
diff --git a/dom/media/webrtc/transport/third_party/moz.build b/dom/media/webrtc/transport/third_party/moz.build
new file mode 100644
index 0000000000..852ce4ac4b
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/moz.build
@@ -0,0 +1,39 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+include('/build/gyp.mozbuild')
+
+GYP_DIRS += [
+ 'nICEr',
+ 'nrappkit',
+]
+
+# Set gyp vars that webrtc needs when building under various analysis tools.
+# Primarily this prevents webrtc from setting NVALGRIND and breaking builds.
+gyp_vars_copy = gyp_vars.copy()
+if CONFIG['MOZ_VALGRIND']:
+ gyp_vars_copy.update(build_for_tool="memcheck")
+elif CONFIG['MOZ_ASAN']:
+ gyp_vars_copy.update(build_for_tool="asan")
+elif CONFIG['MOZ_TSAN']:
+ gyp_vars_copy.update(build_for_tool="tsan")
+
+# These files cannot be built in unified mode because of name clashes on RCSSTRING
+
+GYP_DIRS['nICEr'].input = 'nICEr/nicer.gyp'
+GYP_DIRS['nICEr'].variables = gyp_vars_copy
+GYP_DIRS['nICEr'].sandbox_vars['FINAL_LIBRARY'] = 'xul'
+GYP_DIRS['nrappkit'].input = 'nrappkit/nrappkit.gyp'
+GYP_DIRS['nrappkit'].variables = gyp_vars_copy
+GYP_DIRS['nrappkit'].sandbox_vars['FINAL_LIBRARY'] = 'xul'
+
+
+if CONFIG["CC_TYPE"] == "gcc":
+ GYP_DIRS['nICEr'].sandbox_vars['CFLAGS'] = ['-Wno-stringop-overflow']
+ GYP_DIRS['nrappkit'].sandbox_vars['CFLAGS'] = ['-Wno-stringop-overflow']
+ if int(CONFIG["CC_VERSION"].split(".")[0]) >= 8:
+ GYP_DIRS['nICEr'].sandbox_vars['CFLAGS'] += ['-Wno-stringop-truncation']
+ GYP_DIRS['nrappkit'].sandbox_vars['CFLAGS'] += ['-Wno-stringop-truncation']
diff --git a/dom/media/webrtc/transport/third_party/nICEr/COPYRIGHT b/dom/media/webrtc/transport/third_party/nICEr/COPYRIGHT
new file mode 100644
index 0000000000..2005fbd104
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/COPYRIGHT
@@ -0,0 +1,36 @@
+Portions of this software are subject to the following copyrights:
+
+ Copyright (C) 2007, Adobe Systems Inc.
+ Copyright (C) 2007-2008, Network Resonance, Inc.
+
+Each source file bears an individual copyright notice.
+
+The following license applies to this distribution as a whole.
+
+
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
diff --git a/dom/media/webrtc/transport/third_party/nICEr/README b/dom/media/webrtc/transport/third_party/nICEr/README
new file mode 100644
index 0000000000..390203dec5
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/README
@@ -0,0 +1,74 @@
+ nICEr 1.0
+
+PREREQUISITES:
+-------------
+You must first obtain and build the following packages:
+
+* nrappkit
+ - http://nrappkit.sourceforge.net/
+
+* OpenSSL-0.9.8g
+ - http://www.openssl.org/source/openssl-0.9.8g.tar.gz
+
+
+For best results, the "ice-<version>" directory should be at
+the same level as the "nrappkit" and "openssl-0.9.8g"
+directories. I.e., the directory structure should look like:
+
+ nrappkit/
+ ice-<version>/
+ openssl/
+ include/
+ lib/VC/
+
+
+BUILDING ON UNIX:
+----------------
+Once the prerequisite packages are built, 'cd' to the
+relevant build directory, one of:
+
+ ice-<version>/make/darwin
+ ice-<version>/make/linux-fedora
+ ice-<version>/make/ubuntu
+
+and simply do a "make".
+
+
+BUILDING ON WINDOWS:
+-------------------
+The Visual C++ project files are configured to expect the
+directory structure described above.
+
+Note: Binary Windows builds of OpenSSL can be found at:
+ http://www.slproweb.com/products/Win32OpenSSL.html
+
+Once the prerequisite packages are built, open the VC++ 9.0
+solution file: ICE/make/win32/ice.sln and build the solution.
+Note: Since the VC++ project/solution files are version 9.0,
+Visual Studio 2008 is required.
+
+
+STATUS:
+------
+The ICE code has been tested on the following platforms:
+-- Fedora Core 4 (Intel 32-bit)
+-- Fedora Core 6 (Intel 32-bit)
+-- Ubuntu 6.10
+-- MacOSX 10.4.9
+-- Windows Vista (Home Premium)
+-- Windows XP Pro
+-- Windows 2000 SP4
+
+
+KNOWN ISSUES:
+------------
+-- TURN SET-ACTIVE-DESTINATION mode not yet supported.
+
+-- Problems may exist with the TURN client implementation; the TURN code
+ has received minimal testing due to the unavailability of a real
+ TURN server to test against.
+
+-- The ICE-Lite implementation is not complete.
+
+-- The new "impatient" timeout has not yet been thoroughly tested.
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/moz.yaml b/dom/media/webrtc/transport/third_party/nICEr/moz.yaml
new file mode 100644
index 0000000000..873769f9c2
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/moz.yaml
@@ -0,0 +1,117 @@
+schema: 1
+
+bugzilla:
+ product: Core
+ component: "WebRTC: Networking"
+
+origin:
+ name: nICEr
+ description: FIXME
+
+ url: https://github.com/resiprocate/nICEr/
+
+ release: b14598f34d12373069693a1f0535fe354a3f1fd5
+ revision: b14598f34d12373069693a1f0535fe354a3f1fd5
+
+ license: BSD-3-Clause
+ license-file: COPYRIGHT
+
+vendoring:
+ url: https://github.com/resiprocate/nICEr/
+ source-hosting: github
+ tracking: commit
+
+ exclude:
+ - api.txt
+ - src/
+
+ keep:
+ - nicer.gyp
+ # Crypto
+ - src/crypto/nr_crypto.c
+ - src/crypto/nr_crypto.h
+ #./src/crypto/nr_crypto_openssl.c
+ #./src/crypto/nr_crypto_openssl.h
+
+ # ICE
+ - src/ice/ice_candidate.c
+ - src/ice/ice_candidate.h
+ - src/ice/ice_candidate_pair.c
+ - src/ice/ice_candidate_pair.h
+ - src/ice/ice_codeword.h
+ - src/ice/ice_component.c
+ - src/ice/ice_component.h
+ - src/ice/ice_ctx.c
+ - src/ice/ice_ctx.h
+ - src/ice/ice_handler.h
+ - src/ice/ice_media_stream.c
+ - src/ice/ice_media_stream.h
+ - src/ice/ice_parser.c
+ - src/ice/ice_peer_ctx.c
+ - src/ice/ice_peer_ctx.h
+ - src/ice/ice_reg.h
+ - src/ice/ice_socket.c
+ - src/ice/ice_socket.h
+
+ # Net
+ - src/net/nr_socket.c
+ - src/net/nr_socket.h
+ #./src/net/nr_socket_local.c
+ - src/net/nr_socket_local.h
+ - src/net/transport_addr.c
+ - src/net/transport_addr.h
+ - src/net/transport_addr_reg.c
+ - src/net/transport_addr_reg.h
+ - src/net/nr_interface_prioritizer.c
+ - src/net/nr_interface_prioritizer.h
+ - src/net/nr_resolver.h
+ - src/net/nr_resolver.c
+ - src/net/nr_socket_multi_tcp.h
+ - src/net/nr_socket_multi_tcp.c
+ - src/net/nr_socket_wrapper.h
+ - src/net/nr_socket_wrapper.c
+ - src/net/local_addr.h
+ - src/net/local_addr.c
+
+ # STUN
+ - src/stun/addrs.c
+ - src/stun/addrs.h
+ - src/stun/addrs-bsd.c
+ - src/stun/addrs-bsd.h
+ - src/stun/addrs-netlink.c
+ - src/stun/addrs-netlink.h
+ - src/stun/addrs-win32.c
+ - src/stun/addrs-win32.h
+ - src/stun/nr_socket_buffered_stun.h
+ - src/stun/nr_socket_buffered_stun.c
+ - src/stun/nr_socket_turn.c
+ - src/stun/nr_socket_turn.h
+ - src/stun/stun.h
+ - src/stun/stun_build.c
+ - src/stun/stun_build.h
+ - src/stun/stun_client_ctx.c
+ - src/stun/stun_client_ctx.h
+ - src/stun/stun_codec.c
+ - src/stun/stun_codec.h
+ - src/stun/stun_hint.c
+ - src/stun/stun_hint.h
+ - src/stun/stun_msg.c
+ - src/stun/stun_msg.h
+ - src/stun/stun_proc.c
+ - src/stun/stun_proc.h
+ - src/stun/stun_reg.h
+ - src/stun/stun_server_ctx.c
+ - src/stun/stun_server_ctx.h
+ - src/stun/stun_util.c
+ - src/stun/stun_util.h
+ - src/stun/turn_client_ctx.c
+ - src/stun/turn_client_ctx.h
+
+ # Util
+ - src/util/cb_args.c
+ - src/util/cb_args.h
+ - src/util/ice_util.c
+ - src/util/ice_util.h
+
+ patches:
+ - non-unified-build.patch
diff --git a/dom/media/webrtc/transport/third_party/nICEr/nicer.gyp b/dom/media/webrtc/transport/third_party/nICEr/nicer.gyp
new file mode 100644
index 0000000000..488b9b229c
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/nicer.gyp
@@ -0,0 +1,276 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# nrappkit.gyp
+#
+#
+{
+ 'variables' : {
+ 'have_ethtool_cmd_speed_hi%': 1
+ },
+ 'targets' : [
+ {
+ 'target_name' : 'nicer',
+ 'type' : 'static_library',
+
+ 'include_dirs' : [
+ ## EXTERNAL
+ # nrappkit
+ '../nrappkit/src/event',
+ '../nrappkit/src/log',
+ '../nrappkit/src/plugin',
+ '../nrappkit/src/registry',
+ '../nrappkit/src/share',
+ '../nrappkit/src/stats',
+ '../nrappkit/src/util',
+ '../nrappkit/src/util/libekr',
+ '../nrappkit/src/port/generic/include',
+
+ # INTERNAL
+ "./src/crypto",
+ "./src/ice",
+ "./src/net",
+ "./src/stun",
+ "./src/util",
+ ],
+
+ 'sources' : [
+ # Crypto
+ "./src/crypto/nr_crypto.c",
+ "./src/crypto/nr_crypto.h",
+ #"./src/crypto/nr_crypto_openssl.c",
+ #"./src/crypto/nr_crypto_openssl.h",
+
+ # ICE
+ "./src/ice/ice_candidate.c",
+ "./src/ice/ice_candidate.h",
+ "./src/ice/ice_candidate_pair.c",
+ "./src/ice/ice_candidate_pair.h",
+ "./src/ice/ice_codeword.h",
+ "./src/ice/ice_component.c",
+ "./src/ice/ice_component.h",
+ "./src/ice/ice_ctx.c",
+ "./src/ice/ice_ctx.h",
+ "./src/ice/ice_handler.h",
+ "./src/ice/ice_media_stream.c",
+ "./src/ice/ice_media_stream.h",
+ "./src/ice/ice_parser.c",
+ "./src/ice/ice_peer_ctx.c",
+ "./src/ice/ice_peer_ctx.h",
+ "./src/ice/ice_reg.h",
+ "./src/ice/ice_socket.c",
+ "./src/ice/ice_socket.h",
+
+ # Net
+ "./src/net/nr_resolver.c",
+ "./src/net/nr_resolver.h",
+ "./src/net/nr_socket_wrapper.c",
+ "./src/net/nr_socket_wrapper.h",
+ "./src/net/nr_socket.c",
+ "./src/net/nr_socket.h",
+ #"./src/net/nr_socket_local.c",
+ "./src/net/nr_socket_local.h",
+ "./src/net/nr_socket_multi_tcp.c",
+ "./src/net/nr_socket_multi_tcp.h",
+ "./src/net/transport_addr.c",
+ "./src/net/transport_addr.h",
+ "./src/net/transport_addr_reg.c",
+ "./src/net/transport_addr_reg.h",
+ "./src/net/local_addr.c",
+ "./src/net/local_addr.h",
+ "./src/net/nr_interface_prioritizer.c",
+ "./src/net/nr_interface_prioritizer.h",
+
+ # STUN
+ "./src/stun/addrs.c",
+ "./src/stun/addrs.h",
+ "./src/stun/addrs-bsd.c",
+ "./src/stun/addrs-bsd.h",
+ "./src/stun/addrs-netlink.c",
+ "./src/stun/addrs-netlink.h",
+ "./src/stun/addrs-win32.c",
+ "./src/stun/addrs-win32.h",
+ "./src/stun/nr_socket_turn.c",
+ "./src/stun/nr_socket_turn.h",
+ "./src/stun/nr_socket_buffered_stun.c",
+ "./src/stun/nr_socket_buffered_stun.h",
+ "./src/stun/stun.h",
+ "./src/stun/stun_build.c",
+ "./src/stun/stun_build.h",
+ "./src/stun/stun_client_ctx.c",
+ "./src/stun/stun_client_ctx.h",
+ "./src/stun/stun_codec.c",
+ "./src/stun/stun_codec.h",
+ "./src/stun/stun_hint.c",
+ "./src/stun/stun_hint.h",
+ "./src/stun/stun_msg.c",
+ "./src/stun/stun_msg.h",
+ "./src/stun/stun_proc.c",
+ "./src/stun/stun_proc.h",
+ "./src/stun/stun_reg.h",
+ "./src/stun/stun_server_ctx.c",
+ "./src/stun/stun_server_ctx.h",
+ "./src/stun/stun_util.c",
+ "./src/stun/stun_util.h",
+ "./src/stun/turn_client_ctx.c",
+ "./src/stun/turn_client_ctx.h",
+
+ # Util
+ "./src/util/cb_args.c",
+ "./src/util/cb_args.h",
+ "./src/util/ice_util.c",
+ "./src/util/ice_util.h",
+
+
+ ],
+
+ 'defines' : [
+ 'SANITY_CHECKS',
+ 'USE_TURN',
+ 'USE_ICE',
+ 'USE_RFC_3489_BACKWARDS_COMPATIBLE',
+ 'USE_STUND_0_96',
+ 'USE_STUN_PEDANTIC',
+ 'USE_TURN',
+ 'NR_SOCKET_IS_VOID_PTR',
+ 'restrict=',
+ 'R_PLATFORM_INT_TYPES=<stdint.h>',
+ 'R_DEFINED_INT2=int16_t',
+ 'R_DEFINED_UINT2=uint16_t',
+ 'R_DEFINED_INT4=int32_t',
+ 'R_DEFINED_UINT4=uint32_t',
+ 'R_DEFINED_INT8=int64_t',
+ 'R_DEFINED_UINT8=uint64_t',
+ ],
+
+ 'conditions' : [
+ ## Mac and BSDs
+ [ 'OS == "mac" or OS == "ios"', {
+ 'defines' : [
+ 'DARWIN',
+ ],
+ }],
+ [ 'os_bsd == 1', {
+ 'defines' : [
+ 'BSD',
+ ],
+ }],
+ [ 'OS == "mac" or OS == "ios" or os_bsd == 1', {
+ 'cflags_mozilla': [
+ '-Wall',
+ '-Wno-parentheses',
+ '-Wno-strict-prototypes',
+ '-Wmissing-prototypes',
+ '-Wno-format',
+ '-Wno-format-security',
+ ],
+ 'defines' : [
+ 'HAVE_LIBM=1',
+ 'HAVE_STRDUP=1',
+ 'HAVE_STRLCPY=1',
+ 'HAVE_SYS_TIME_H=1',
+ 'HAVE_VFPRINTF=1',
+ 'NEW_STDIO'
+ 'RETSIGTYPE=void',
+ 'TIME_WITH_SYS_TIME_H=1',
+ '__UNUSED__=__attribute__((unused))',
+ ],
+
+ 'include_dirs': [
+ '../nrappkit/src/port/darwin/include'
+ ],
+
+ 'sources': [
+ ],
+ }],
+
+ ## Win
+ [ 'OS == "win"', {
+ 'defines' : [
+ 'WIN32',
+ '_WINSOCK_DEPRECATED_NO_WARNINGS',
+ 'USE_ICE',
+ 'USE_TURN',
+ 'USE_RFC_3489_BACKWARDS_COMPATIBLE',
+ 'USE_STUND_0_96',
+ 'USE_STUN_PEDANTIC',
+ '_CRT_SECURE_NO_WARNINGS',
+ '__UNUSED__=',
+ 'HAVE_STRDUP',
+ 'NO_REG_RPC'
+ ],
+
+ 'include_dirs': [
+ '../nrappkit/src/port/win32/include'
+ ],
+ }],
+
+ # Windows, clang-cl build
+ [ 'clang_cl == 1', {
+ 'cflags_mozilla': [
+ '-Xclang',
+ '-Wall',
+ '-Xclang',
+ '-Wno-parentheses',
+ '-Wno-pointer-sign',
+ '-Wno-strict-prototypes',
+ '-Xclang',
+ '-Wno-unused-function',
+ '-Wmissing-prototypes',
+ '-Wno-format',
+ '-Wno-format-security',
+ ],
+ }],
+
+ ## Linux/Android
+ [ '(OS == "linux") or (OS == "android")', {
+ 'cflags_mozilla': [
+ '-Wall',
+ '-Wno-parentheses',
+ '-Wno-strict-prototypes',
+ '-Wmissing-prototypes',
+ '-Wno-format',
+ '-Wno-format-security',
+ ],
+ 'defines' : [
+ 'LINUX',
+ 'HAVE_LIBM=1',
+ 'HAVE_STRDUP=1',
+ 'HAVE_STRLCPY=1',
+ 'HAVE_SYS_TIME_H=1',
+ 'HAVE_VFPRINTF=1',
+ 'NEW_STDIO'
+ 'RETSIGTYPE=void',
+ 'TIME_WITH_SYS_TIME_H=1',
+ '__UNUSED__=__attribute__((unused))',
+ ],
+
+ 'include_dirs': [
+ '../nrappkit/src/port/linux/include'
+ ],
+
+ 'sources': [
+ ],
+ }],
+ ['have_ethtool_cmd_speed_hi==0', {
+ 'defines': [
+ "DONT_HAVE_ETHTOOL_SPEED_HI",
+ ]
+ }],
+ # libFuzzer instrumentation is not compatible with TSan.
+ # See also the comment in build/moz.configure/toolchain.configure.
+ ['(libfuzzer == 1) and (tsan == 0) and (libfuzzer_fuzzer_no_link_flag == 1)', {
+ 'cflags_mozilla': [
+ '-fsanitize=fuzzer-no-link'
+ ],
+ }],
+ ['(libfuzzer == 1) and (tsan == 0) and (libfuzzer_fuzzer_no_link_flag == 0)', {
+ 'cflags_mozilla': [
+ '-fsanitize-coverage=trace-pc-guard,trace-cmp'
+ ],
+ }],
+ ],
+ }]
+}
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/non-unified-build.patch b/dom/media/webrtc/transport/third_party/nICEr/non-unified-build.patch
new file mode 100644
index 0000000000..41f206e67a
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/non-unified-build.patch
@@ -0,0 +1,40 @@
+diff --git a/src/net/nr_socket_wrapper.c b/src/net/nr_socket_wrapper.c
+index 0c9ec5674407d..4ad59527c12ec 100644
+--- a/src/net/nr_socket_wrapper.c
++++ b/src/net/nr_socket_wrapper.c
+@@ -36,6 +36,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ #include <nr_api.h>
+ #include "nr_socket_wrapper.h"
+
++#include <assert.h>
++
+ int nr_socket_wrapper_factory_create_int(void *obj, nr_socket_wrapper_factory_vtbl *vtbl,
+ nr_socket_wrapper_factory **wrapperp)
+ {
+diff --git a/src/net/transport_addr.h b/nICEr/src/net/transport_addr.h
+index c75c70a1d94fa..1783d2e4506a8 100644
+--- a/src/net/transport_addr.h
++++ b/src/net/transport_addr.h
+@@ -38,7 +38,22 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ #include <stdbool.h>
+ #include <sys/types.h>
+ #ifdef WIN32
++
++// FIXME: This is dangerous, but exactly the pattern used in
++// nrappkit/src/port/win32/include/csi_platform.h
++// Not good because INT8 are typedefed to different values in
++// <winsock2.h> and <r_types.h>.
++// {
++
++#define UINT8 UBLAH_IGNORE_ME_PLEASE
++#define INT8 BLAH_IGNORE_ME_PLEASE
+ #include <winsock2.h>
++#undef UINT8
++#undef INT8
++#include <r_types.h>
++
++// }
++
+ #include <ws2tcpip.h>
+ #else
+ #include <sys/socket.h>
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/crypto/nr_crypto.c b/dom/media/webrtc/transport/third_party/nICEr/src/crypto/nr_crypto.c
new file mode 100644
index 0000000000..4c430b0e8b
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/crypto/nr_crypto.c
@@ -0,0 +1,67 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+*/
+
+#include <nr_api.h>
+#include "nr_crypto.h"
+
+static int nr_ice_crypto_dummy_random_bytes(UCHAR *buf, size_t len)
+ {
+ fprintf(stderr,"Need to define crypto API implementation\n");
+
+ exit(1);
+ }
+
+static int nr_ice_crypto_dummy_hmac_sha1(UCHAR *key, size_t key_l, UCHAR *buf, size_t buf_l, UCHAR digest[20])
+ {
+ fprintf(stderr,"Need to define crypto API implementation\n");
+
+ exit(1);
+ }
+
+static int nr_ice_crypto_dummy_md5(UCHAR *buf, size_t buf_l, UCHAR digest[16])
+ {
+ fprintf(stderr,"Need to define crypto API implementation\n");
+
+ exit(1);
+ }
+
+static nr_ice_crypto_vtbl nr_ice_crypto_dummy_vtbl= {
+ nr_ice_crypto_dummy_random_bytes,
+ nr_ice_crypto_dummy_hmac_sha1,
+ nr_ice_crypto_dummy_md5
+};
+
+
+
+nr_ice_crypto_vtbl *nr_crypto_vtbl=&nr_ice_crypto_dummy_vtbl;
+
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/crypto/nr_crypto.h b/dom/media/webrtc/transport/third_party/nICEr/src/crypto/nr_crypto.h
new file mode 100644
index 0000000000..5fbbd8f097
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/crypto/nr_crypto.h
@@ -0,0 +1,52 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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 _nr_crypto_h
+#define _nr_crypto_h
+
+
+typedef struct nr_ice_crypto_vtbl_ {
+ int (*random_bytes)(UCHAR *buf, size_t len);
+ int (*hmac_sha1)(UCHAR *key, size_t key_l, UCHAR *buf, size_t buf_l, UCHAR digest[20]);
+ int (*md5)(UCHAR *buf, size_t buf_l, UCHAR digest[16]);
+} nr_ice_crypto_vtbl;
+
+extern nr_ice_crypto_vtbl *nr_crypto_vtbl;
+
+#define nr_crypto_random_bytes(a,b) nr_crypto_vtbl->random_bytes(a,b)
+#define nr_crypto_hmac_sha1(a,b,c,d,e) nr_crypto_vtbl->hmac_sha1(a,b,c,d,e)
+#define nr_crypto_md5(a,b,c) nr_crypto_vtbl->md5(a,b,c)
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_candidate.c b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_candidate.c
new file mode 100644
index 0000000000..b7cf2c9a99
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_candidate.c
@@ -0,0 +1,1052 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+*/
+
+#include <csi_platform.h>
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#ifdef WIN32
+#include <winsock2.h>
+#else
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#endif
+#include "nr_api.h"
+#include "registry.h"
+#include "nr_socket.h"
+#include "async_timer.h"
+
+#include "stun_client_ctx.h"
+#include "stun_server_ctx.h"
+#include "turn_client_ctx.h"
+#include "ice_ctx.h"
+#include "ice_candidate.h"
+#include "ice_codeword.h"
+#include "ice_reg.h"
+#include "ice_util.h"
+#include "nr_socket_turn.h"
+#include "nr_socket.h"
+#include "nr_socket_multi_tcp.h"
+
+static int next_automatic_preference = 127;
+
+static int nr_ice_candidate_initialize2(nr_ice_candidate *cand);
+static int nr_ice_get_foundation(nr_ice_ctx *ctx,nr_ice_candidate *cand);
+static int nr_ice_srvrflx_start_stun(nr_ice_candidate *cand);
+static void nr_ice_srvrflx_stun_finished_cb(NR_SOCKET sock, int how, void *cb_arg);
+#ifdef USE_TURN
+static int nr_ice_start_relay_turn(nr_ice_candidate *cand);
+static void nr_ice_turn_allocated_cb(NR_SOCKET sock, int how, void *cb_arg);
+static int nr_ice_candidate_resolved_cb(void *cb_arg, nr_transport_addr *addr);
+#endif /* USE_TURN */
+
+void nr_ice_candidate_compute_codeword(nr_ice_candidate *cand)
+ {
+ char as_string[1024];
+
+ snprintf(as_string,
+ sizeof(as_string),
+ "%s(%s)",
+ cand->addr.as_string,
+ cand->label);
+
+ nr_ice_compute_codeword(as_string,strlen(as_string),cand->codeword);
+ }
+
+char *nr_ice_candidate_type_names[]={0,"host","srflx","prflx","relay",0};
+char *nr_ice_candidate_tcp_type_names[]={0,"active","passive","so",0};
+
+static const char *nr_ctype_name(nr_ice_candidate_type ctype) {
+ assert(ctype<CTYPE_MAX && ctype>0);
+ if (ctype <= 0 || ctype >= CTYPE_MAX) {
+ return "ERROR";
+ }
+ return nr_ice_candidate_type_names[ctype];
+}
+
+static const char *nr_tcp_type_name(nr_socket_tcp_type tcp_type) {
+ assert(tcp_type<TCP_TYPE_MAX && tcp_type>0);
+ if (tcp_type <= 0 || tcp_type >= TCP_TYPE_MAX) {
+ return "ERROR";
+ }
+ return nr_ice_candidate_tcp_type_names[tcp_type];
+}
+
+static int nr_ice_candidate_format_stun_label(char *label, size_t size, nr_ice_candidate *cand)
+ {
+ *label = 0;
+ snprintf(label, size, "%s(%s|%s)", nr_ctype_name(cand->type),
+ cand->base.as_string, cand->stun_server->addr.as_string);
+
+ return (0);
+ }
+
+int nr_ice_candidate_create(nr_ice_ctx *ctx,nr_ice_component *comp,nr_ice_socket *isock, nr_socket *osock, nr_ice_candidate_type ctype, nr_socket_tcp_type tcp_type, nr_ice_stun_server *stun_server, UCHAR component_id, nr_ice_candidate **candp)
+ {
+ assert(!(comp->stream->flags & NR_ICE_CTX_FLAGS_RELAY_ONLY) || ctype == RELAYED);
+ nr_ice_candidate *cand=0;
+ nr_ice_candidate *tmp=0;
+ int r,_status;
+ char label[512];
+
+ if(!(cand=RCALLOC(sizeof(nr_ice_candidate))))
+ ABORT(R_NO_MEMORY);
+ cand->state=NR_ICE_CAND_STATE_CREATED;
+ cand->ctx=ctx;
+ cand->isock=isock;
+ cand->osock=osock;
+ cand->type=ctype;
+ cand->tcp_type=tcp_type;
+ cand->stun_server=stun_server;
+ cand->component_id=component_id;
+ cand->component=comp;
+ cand->stream=comp->stream;
+
+ /* Extract the addr as the base */
+ if(r=nr_socket_getaddr(cand->isock->sock,&cand->base))
+ ABORT(r);
+
+ switch(ctype) {
+ case HOST:
+ snprintf(label, sizeof(label), "host(%s)", cand->base.as_string);
+ break;
+
+ case SERVER_REFLEXIVE:
+ if(r=nr_ice_candidate_format_stun_label(label, sizeof(label), cand))
+ ABORT(r);
+ break;
+
+ case RELAYED:
+ if(r=nr_ice_candidate_format_stun_label(label, sizeof(label), cand))
+ ABORT(r);
+ break;
+
+ case PEER_REFLEXIVE:
+ snprintf(label, sizeof(label), "prflx");
+ break;
+
+ default:
+ assert(0); /* Can't happen */
+ ABORT(R_BAD_ARGS);
+ }
+
+ if (tcp_type) {
+ const char* ttype = nr_tcp_type_name(tcp_type);
+ const int tlen = strlen(ttype)+1; /* plus space */
+ const size_t llen=strlen(label);
+ if (snprintf(label+llen, sizeof(label)-llen, " %s", ttype) != tlen) {
+ r_log(LOG_ICE,LOG_ERR,"ICE(%s): truncated tcp type added to buffer",
+ ctx->label);
+ }
+ }
+
+ if(!(cand->label=r_strdup(label)))
+ ABORT(R_NO_MEMORY);
+
+ if(r=nr_ice_get_foundation(ctx,cand))
+ ABORT(r);
+ if(r=nr_ice_candidate_compute_priority(cand))
+ ABORT(r);
+
+ TAILQ_FOREACH(tmp,&isock->candidates,entry_sock){
+ if(cand->priority==tmp->priority){
+ r_log(LOG_ICE,LOG_ERR,"ICE(%s): duplicate priority %u candidate %s and candidate %s",
+ ctx->label,cand->priority,cand->label,tmp->label);
+ }
+ }
+
+ if(ctype==RELAYED)
+ cand->u.relayed.turn_sock=osock;
+
+
+ /* Add the candidate to the isock list*/
+ TAILQ_INSERT_TAIL(&isock->candidates,cand,entry_sock);
+
+ nr_ice_candidate_compute_codeword(cand);
+
+ r_log(LOG_ICE,LOG_DEBUG,"ICE(%s)/CAND(%s): created candidate %s with type %s",
+ ctx->label,cand->codeword,cand->label,nr_ctype_name(ctype));
+
+ *candp=cand;
+
+ _status=0;
+ abort:
+ if (_status){
+ r_log(LOG_ICE,LOG_ERR,"ICE(%s): Failed to create candidate of type %s", ctx->label,nr_ctype_name(ctype));
+ nr_ice_candidate_destroy(&cand);
+ }
+ return(_status);
+ }
+
+
+/* Create a peer reflexive candidate */
+int nr_ice_peer_peer_rflx_candidate_create(nr_ice_ctx *ctx,char *label, nr_ice_component *comp,nr_transport_addr *addr, nr_ice_candidate **candp)
+ {
+ nr_ice_candidate *cand=0;
+ nr_ice_candidate_type ctype=PEER_REFLEXIVE;
+ int r,_status;
+
+ if(!(cand=RCALLOC(sizeof(nr_ice_candidate))))
+ ABORT(R_NO_MEMORY);
+ if(!(cand->label=r_strdup(label)))
+ ABORT(R_NO_MEMORY);
+
+ cand->state=NR_ICE_CAND_STATE_INITIALIZED;
+ cand->ctx=ctx;
+ cand->type=ctype;
+ cand->component_id=comp->component_id;
+ cand->component=comp;
+ cand->stream=comp->stream;
+
+
+ r_log(LOG_ICE,LOG_DEBUG,"ICE(%s)/CAND(%s): creating candidate with type %s",
+ ctx->label,label,nr_ctype_name(ctype));
+
+ if(r=nr_transport_addr_copy(&cand->base,addr))
+ ABORT(r);
+ if(r=nr_transport_addr_copy(&cand->addr,addr))
+ ABORT(r);
+ /* Bogus foundation */
+ if(!(cand->foundation=r_strdup(cand->addr.as_string)))
+ ABORT(R_NO_MEMORY);
+
+ nr_ice_candidate_compute_codeword(cand);
+
+ *candp=cand;
+
+ _status=0;
+ abort:
+ if (_status){
+ nr_ice_candidate_destroy(&cand);
+ }
+ return(_status);
+ }
+
+static void nr_ice_candidate_mark_done(nr_ice_candidate *cand, int state)
+ {
+ if (!cand) {
+ assert(0);
+ return;
+ }
+
+ /* If this is a relay candidate, there's likely to be a srflx that is
+ * piggybacking on it. Make sure it is marked done too. */
+ if ((cand->type == RELAYED) && cand->u.relayed.srvflx_candidate) {
+ nr_ice_candidate *srflx=cand->u.relayed.srvflx_candidate;
+ if (state == NR_ICE_CAND_STATE_INITIALIZED &&
+ nr_turn_client_get_mapped_address(cand->u.relayed.turn,
+ &srflx->addr)) {
+ r_log(LOG_ICE, LOG_WARNING, "ICE(%s)/CAND(%s): Failed to get mapped address from TURN allocate response, srflx failed.", cand->ctx->label, cand->label);
+ nr_ice_candidate_mark_done(srflx, NR_ICE_CAND_STATE_FAILED);
+ } else {
+ nr_ice_candidate_mark_done(srflx, state);
+ }
+ }
+
+ NR_async_cb done_cb=cand->done_cb;
+ cand->done_cb=0;
+ cand->state=state;
+ /* This might destroy cand! */
+ if (done_cb) {
+ done_cb(0,0,cand->cb_arg);
+ }
+ }
+
+int nr_ice_candidate_destroy(nr_ice_candidate **candp)
+ {
+ nr_ice_candidate *cand=0;
+
+ if(!candp || !*candp)
+ return(0);
+
+ cand=*candp;
+
+ nr_ice_candidate_stop_gathering(cand);
+
+ switch(cand->type){
+ case HOST:
+ break;
+#ifdef USE_TURN
+ case RELAYED:
+ // record stats back to the ice ctx on destruction
+ if (cand->u.relayed.turn) {
+ nr_accumulate_count(&(cand->ctx->stats.turn_401s), cand->u.relayed.turn->cnt_401s);
+ nr_accumulate_count(&(cand->ctx->stats.turn_403s), cand->u.relayed.turn->cnt_403s);
+ nr_accumulate_count(&(cand->ctx->stats.turn_438s), cand->u.relayed.turn->cnt_438s);
+
+ nr_turn_stun_ctx* stun_ctx;
+ stun_ctx = STAILQ_FIRST(&cand->u.relayed.turn->stun_ctxs);
+ while (stun_ctx) {
+ nr_accumulate_count(&(cand->ctx->stats.stun_retransmits), stun_ctx->stun->retransmit_ct);
+
+ stun_ctx = STAILQ_NEXT(stun_ctx, entry);
+ }
+ }
+ if (cand->u.relayed.turn_handle)
+ nr_ice_socket_deregister(cand->isock, cand->u.relayed.turn_handle);
+ if (cand->u.relayed.srvflx_candidate)
+ cand->u.relayed.srvflx_candidate->u.srvrflx.relay_candidate=0;
+ nr_turn_client_ctx_destroy(&cand->u.relayed.turn);
+ nr_socket_destroy(&cand->u.relayed.turn_sock);
+ break;
+#endif /* USE_TURN */
+ case SERVER_REFLEXIVE:
+ if (cand->u.srvrflx.stun_handle)
+ nr_ice_socket_deregister(cand->isock, cand->u.srvrflx.stun_handle);
+ if (cand->u.srvrflx.relay_candidate)
+ cand->u.srvrflx.relay_candidate->u.relayed.srvflx_candidate=0;
+ nr_stun_client_ctx_destroy(&cand->u.srvrflx.stun);
+ break;
+ default:
+ break;
+ }
+
+ RFREE(cand->mdns_addr);
+ RFREE(cand->foundation);
+ RFREE(cand->label);
+ RFREE(cand);
+
+ return(0);
+ }
+
+void nr_ice_candidate_stop_gathering(nr_ice_candidate *cand)
+ {
+ if (cand->state == NR_ICE_CAND_STATE_INITIALIZING) {
+ /* Make sure the ICE ctx isn't still waiting around for this candidate
+ * to init. */
+ nr_ice_candidate_mark_done(cand, NR_ICE_CAND_STATE_FAILED);
+ }
+
+ NR_async_timer_cancel(cand->delay_timer);
+ cand->delay_timer=0;
+ NR_async_timer_cancel(cand->ready_cb_timer);
+ cand->ready_cb_timer=0;
+
+ if(cand->resolver_handle){
+ nr_resolver_cancel(cand->ctx->resolver,cand->resolver_handle);
+ cand->resolver_handle=0;
+ }
+ }
+
+/* This algorithm is not super-fast, but I don't think we need a hash
+ table just yet and it produces a small foundation string */
+static int nr_ice_get_foundation(nr_ice_ctx *ctx,nr_ice_candidate *cand)
+ {
+ nr_ice_foundation *foundation;
+ int i=0;
+ char fnd[20];
+ int _status;
+
+ foundation=STAILQ_FIRST(&ctx->foundations);
+ while(foundation){
+ if(nr_transport_addr_cmp(&cand->base,&foundation->addr,NR_TRANSPORT_ADDR_CMP_MODE_ADDR))
+ goto next;
+ // cast necessary because there is no guarantee that enum is signed.
+ // foundation->type should probably match nr_ice_candidate_type
+ if((int)cand->type != foundation->type)
+ goto next;
+ if(cand->type == SERVER_REFLEXIVE || cand->type == RELAYED) {
+ if(nr_transport_addr_cmp(&cand->stun_server->addr, &foundation->stun_server_addr, NR_TRANSPORT_ADDR_CMP_MODE_ADDR))
+ goto next;
+ }
+
+ snprintf(fnd,sizeof(fnd),"%d",i);
+ if(!(cand->foundation=r_strdup(fnd)))
+ ABORT(R_NO_MEMORY);
+ return(0);
+
+ next:
+ foundation=STAILQ_NEXT(foundation,entry);
+ i++;
+ }
+
+ if(!(foundation=RCALLOC(sizeof(nr_ice_foundation))))
+ ABORT(R_NO_MEMORY);
+ nr_transport_addr_copy(&foundation->addr,&cand->base);
+ foundation->type=cand->type;
+ if(cand->type == SERVER_REFLEXIVE || cand->type == RELAYED) {
+ nr_transport_addr_copy(&foundation->stun_server_addr, &cand->stun_server->addr);
+ }
+ STAILQ_INSERT_TAIL(&ctx->foundations,foundation,entry);
+
+ snprintf(fnd,sizeof(fnd),"%d",i);
+ if(!(cand->foundation=r_strdup(fnd)))
+ ABORT(R_NO_MEMORY);
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_ice_candidate_compute_priority(nr_ice_candidate *cand)
+ {
+ UCHAR type_preference;
+ UCHAR interface_preference;
+ UCHAR stun_priority;
+ UCHAR direction_priority=0;
+ int r,_status;
+
+ if (cand->base.protocol != IPPROTO_UDP && cand->base.protocol != IPPROTO_TCP){
+ r_log(LOG_ICE,LOG_ERR,"Unknown protocol type %u",
+ (unsigned int)cand->base.protocol);
+ ABORT(R_INTERNAL);
+ }
+
+ switch(cand->type){
+ case HOST:
+ if(cand->base.protocol == IPPROTO_UDP) {
+ if(r=NR_reg_get_uchar(NR_ICE_REG_PREF_TYPE_HOST,&type_preference))
+ ABORT(r);
+ } else if(cand->base.protocol == IPPROTO_TCP) {
+ if(r=NR_reg_get_uchar(NR_ICE_REG_PREF_TYPE_HOST_TCP,&type_preference))
+ ABORT(r);
+ }
+ stun_priority=0;
+ break;
+ case RELAYED:
+ if(cand->base.protocol == IPPROTO_UDP) {
+ if(r=NR_reg_get_uchar(NR_ICE_REG_PREF_TYPE_RELAYED,&type_preference))
+ ABORT(r);
+ } else if(cand->base.protocol == IPPROTO_TCP) {
+ if(r=NR_reg_get_uchar(NR_ICE_REG_PREF_TYPE_RELAYED_TCP,&type_preference))
+ ABORT(r);
+ }
+ stun_priority=31-cand->stun_server->id;
+ break;
+ case SERVER_REFLEXIVE:
+ if(cand->base.protocol == IPPROTO_UDP) {
+ if(r=NR_reg_get_uchar(NR_ICE_REG_PREF_TYPE_SRV_RFLX,&type_preference))
+ ABORT(r);
+ } else if(cand->base.protocol == IPPROTO_TCP) {
+ if(r=NR_reg_get_uchar(NR_ICE_REG_PREF_TYPE_SRV_RFLX_TCP,&type_preference))
+ ABORT(r);
+ }
+ stun_priority=31-cand->stun_server->id;
+ break;
+ case PEER_REFLEXIVE:
+ if(cand->base.protocol == IPPROTO_UDP) {
+ if(r=NR_reg_get_uchar(NR_ICE_REG_PREF_TYPE_PEER_RFLX,&type_preference))
+ ABORT(r);
+ } else if(cand->base.protocol == IPPROTO_TCP) {
+ if(r=NR_reg_get_uchar(NR_ICE_REG_PREF_TYPE_PEER_RFLX_TCP,&type_preference))
+ ABORT(r);
+ }
+ stun_priority=0;
+ break;
+ default:
+ ABORT(R_INTERNAL);
+ }
+
+ if(cand->base.protocol == IPPROTO_TCP){
+ switch (cand->tcp_type) {
+ case TCP_TYPE_ACTIVE:
+ if (cand->type == HOST)
+ direction_priority=6;
+ else
+ direction_priority=4;
+ break;
+ case TCP_TYPE_PASSIVE:
+ if (cand->type == HOST)
+ direction_priority=4;
+ else
+ direction_priority=2;
+ break;
+ case TCP_TYPE_SO:
+ if (cand->type == HOST)
+ direction_priority=2;
+ else
+ direction_priority=6;
+ break;
+ case TCP_TYPE_NONE:
+ break;
+ case TCP_TYPE_MAX:
+ default:
+ assert(0);
+ ABORT(R_INTERNAL);
+ }
+ }
+
+ if(type_preference > 126)
+ r_log(LOG_ICE,LOG_ERR,"Illegal type preference %d",type_preference);
+
+ if(!cand->ctx->interface_prioritizer) {
+ /* Prioritizer is not set, read from registry */
+ if(r=NR_reg_get2_uchar(NR_ICE_REG_PREF_INTERFACE_PRFX,cand->base.ifname,
+ &interface_preference)) {
+ if (r==R_NOT_FOUND) {
+ if (next_automatic_preference == 1) {
+ r_log(LOG_ICE,LOG_ERR,"Out of preference values. Can't assign one for interface %s",cand->base.ifname);
+ ABORT(R_NOT_FOUND);
+ }
+ r_log(LOG_ICE,LOG_DEBUG,"Automatically assigning preference for interface %s->%d",cand->base.ifname,
+ next_automatic_preference);
+ if (r=NR_reg_set2_uchar(NR_ICE_REG_PREF_INTERFACE_PRFX,cand->base.ifname,next_automatic_preference)){
+ ABORT(r);
+ }
+ interface_preference=next_automatic_preference << 1;
+ next_automatic_preference--;
+ if (cand->base.ip_version == NR_IPV6) {
+ /* Prefer IPV6 over IPV4 on the same interface. */
+ interface_preference += 1;
+ }
+ }
+ else {
+ ABORT(r);
+ }
+ }
+ }
+ else {
+ char key_of_interface[MAXIFNAME + 41];
+ nr_transport_addr addr;
+
+ if(r=nr_socket_getaddr(cand->isock->sock, &addr))
+ ABORT(r);
+
+ if(r=nr_transport_addr_fmt_ifname_addr_string(&addr,key_of_interface,
+ sizeof(key_of_interface))) {
+ ABORT(r);
+ }
+ if(r=nr_interface_prioritizer_get_priority(cand->ctx->interface_prioritizer,
+ key_of_interface,&interface_preference)) {
+ ABORT(r);
+ }
+ }
+
+ assert(stun_priority < 32);
+ assert(direction_priority < 8);
+
+ cand->priority=
+ (type_preference << 24) |
+ (interface_preference << 16) |
+ (direction_priority << 13) |
+ (stun_priority << 8) |
+ (256 - cand->component_id);
+
+ /* S 4.1.2 */
+ assert(cand->priority>=1&&cand->priority<=2147483647);
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+static void nr_ice_candidate_fire_ready_cb(NR_SOCKET s, int how, void *cb_arg)
+ {
+ nr_ice_candidate *cand = cb_arg;
+
+ cand->ready_cb_timer = 0;
+ cand->ready_cb(0, 0, cand->ready_cb_arg);
+ }
+
+static int nr_ice_candidate_use_nr_resolver(nr_transport_addr *addr)
+ {
+ if(addr->fqdn[0] == 0) {
+ return 0;
+ }
+
+ char use = 0;
+ if(addr->protocol == IPPROTO_UDP) {
+ NR_reg_get_char(NR_ICE_REG_USE_NR_RESOLVER_FOR_UDP, &use);
+ } else if(addr->protocol == IPPROTO_TCP) {
+ NR_reg_get_char(NR_ICE_REG_USE_NR_RESOLVER_FOR_TCP, &use);
+ } else {
+ assert(0);
+ }
+
+ return use;
+ }
+
+int nr_ice_candidate_initialize(nr_ice_candidate *cand, NR_async_cb ready_cb, void *cb_arg)
+ {
+ int r,_status;
+ int protocol=NR_RESOLVE_PROTOCOL_STUN;
+ cand->done_cb=ready_cb;
+ cand->cb_arg=cb_arg;
+ cand->state=NR_ICE_CAND_STATE_INITIALIZING;
+
+ switch(cand->type){
+ case HOST:
+ if(r=nr_socket_getaddr(cand->isock->sock,&cand->addr))
+ ABORT(r);
+ cand->osock=cand->isock->sock;
+ // Post this so that it doesn't happen in-line
+ cand->ready_cb = ready_cb;
+ cand->ready_cb_arg = cb_arg;
+ NR_ASYNC_TIMER_SET(0, nr_ice_candidate_fire_ready_cb, (void *)cand, &cand->ready_cb_timer);
+ break;
+#ifdef USE_TURN
+ case RELAYED:
+ protocol=NR_RESOLVE_PROTOCOL_TURN;
+ /* Fall through */
+#endif
+ case SERVER_REFLEXIVE:
+ if (nr_transport_addr_cmp(&cand->base, &cand->stun_server->addr,
+ NR_TRANSPORT_ADDR_CMP_MODE_PROTOCOL)) {
+ r_log(LOG_ICE, LOG_INFO,
+ "ICE-CANDIDATE(%s): Skipping srflx/relayed candidate because "
+ "of IP version/transport mis-match with STUN/TURN server "
+ "(%u/%s - %u/%s).",
+ cand->label, cand->base.ip_version,
+ cand->base.protocol == IPPROTO_UDP ? "UDP" : "TCP",
+ cand->stun_server->addr.ip_version,
+ cand->stun_server->addr.protocol == IPPROTO_UDP ? "UDP"
+ : "TCP");
+ ABORT(R_NOT_FOUND); /* Same error code when DNS lookup fails */
+ }
+
+ if(nr_ice_candidate_use_nr_resolver(&cand->stun_server->addr)) {
+ r_log(
+ LOG_ICE, LOG_DEBUG,
+ "ICE-CANDIDATE(%s): Starting DNS resolution (%u/%s - %u/%s).",
+ cand->label, cand->base.ip_version,
+ cand->base.protocol == IPPROTO_UDP ? "UDP" : "TCP",
+ cand->stun_server->addr.ip_version,
+ cand->stun_server->addr.protocol == IPPROTO_UDP ? "UDP" : "TCP");
+ nr_resolver_resource resource;
+ int port;
+ resource.domain_name = cand->stun_server->addr.fqdn;
+ if (r = nr_transport_addr_get_port(&cand->stun_server->addr, &port)) {
+ ABORT(r);
+ }
+ resource.port = (uint16_t)port;
+ resource.stun_turn=protocol;
+ resource.transport_protocol = cand->stun_server->addr.protocol;
+
+ switch (cand->base.ip_version) {
+ case NR_IPV4:
+ resource.address_family=AF_INET;
+ break;
+ case NR_IPV6:
+ resource.address_family=AF_INET6;
+ break;
+ default:
+ assert(0);
+ ABORT(R_BAD_ARGS);
+ }
+
+ /* Try to resolve */
+ if(!cand->ctx->resolver) {
+ r_log(LOG_ICE, LOG_ERR, "ICE-CANDIDATE(%s): Can't use DNS names without a resolver", cand->label);
+ ABORT(R_BAD_ARGS);
+ }
+
+ if(r=nr_resolver_resolve(cand->ctx->resolver,
+ &resource,
+ nr_ice_candidate_resolved_cb,
+ (void *)cand,
+ &cand->resolver_handle)){
+ r_log(LOG_ICE,LOG_ERR,"ICE-CANDIDATE(%s): Could not invoke DNS resolver",cand->label);
+ ABORT(r);
+ }
+ } else {
+ /* No nr_resolver for this, just copy the address and finish init */
+ if (r = nr_transport_addr_copy(&cand->stun_server_addr,
+ &cand->stun_server->addr)) {
+ r_log(LOG_ICE,LOG_ERR,"ICE-CANDIDATE(%s): Could not copy STUN server addr", cand->label);
+ ABORT(r);
+ }
+
+ if(r=nr_ice_candidate_initialize2(cand))
+ ABORT(r);
+ }
+ break;
+ default:
+ ABORT(R_INTERNAL);
+ }
+
+ nr_ice_candidate_compute_codeword(cand);
+
+ _status=0;
+ abort:
+ if(_status && _status!=R_WOULDBLOCK)
+ nr_ice_candidate_mark_done(cand, NR_ICE_CAND_STATE_FAILED);
+ return(_status);
+ }
+
+
+static int nr_ice_candidate_resolved_cb(void *cb_arg, nr_transport_addr *addr)
+ {
+ nr_ice_candidate *cand=cb_arg;
+ int r,_status;
+
+ cand->resolver_handle=0;
+
+ if(addr){
+ r_log(LOG_ICE,LOG_DEBUG,"ICE(%s): resolved candidate %s. addr=%s",
+ cand->ctx->label,cand->label,addr->as_string);
+ }
+ else {
+ r_log(LOG_ICE,LOG_WARNING,"ICE(%s): failed to resolve candidate %s.",
+ cand->ctx->label,cand->label);
+ ABORT(R_NOT_FOUND);
+ }
+
+ if (nr_transport_addr_check_compatibility(addr, &cand->base)) {
+ r_log(LOG_ICE,LOG_WARNING,"ICE(%s): Skipping STUN server because of link local mis-match for candidate %s",cand->ctx->label,cand->label);
+ ABORT(R_NOT_FOUND);
+ }
+
+ /* Copy the address */
+ if(r=nr_transport_addr_copy(&cand->stun_server_addr,addr))
+ ABORT(r);
+
+ if (cand->tcp_type == TCP_TYPE_PASSIVE || cand->tcp_type == TCP_TYPE_SO){
+ if (r=nr_socket_multi_tcp_stun_server_connect(cand->osock, addr))
+ ABORT(r);
+ }
+
+ /* Now start initializing */
+ if(r=nr_ice_candidate_initialize2(cand))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ if(_status && _status!=R_WOULDBLOCK) {
+ nr_ice_candidate_mark_done(cand, NR_ICE_CAND_STATE_FAILED);
+ }
+ return(_status);
+ }
+
+static int nr_ice_candidate_initialize2(nr_ice_candidate *cand)
+ {
+ int r,_status;
+
+ switch(cand->type){
+ case HOST:
+ assert(0); /* Can't happen */
+ ABORT(R_INTERNAL);
+ break;
+#ifdef USE_TURN
+ case RELAYED:
+ if(r=nr_ice_start_relay_turn(cand))
+ ABORT(r);
+ ABORT(R_WOULDBLOCK);
+ break;
+#endif /* USE_TURN */
+ case SERVER_REFLEXIVE:
+ /* Need to start stun */
+ if(r=nr_ice_srvrflx_start_stun(cand))
+ ABORT(r);
+ cand->osock=cand->isock->sock;
+ ABORT(R_WOULDBLOCK);
+ break;
+ default:
+ ABORT(R_INTERNAL);
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+static void nr_ice_srvrflx_start_stun_timer_cb(NR_SOCKET s, int how, void *cb_arg)
+ {
+ nr_ice_candidate *cand=cb_arg;
+ int r,_status;
+
+ cand->delay_timer=0;
+
+/* TODO: if the response is a BINDING-ERROR-RESPONSE, then restart
+ * TODO: using NR_STUN_CLIENT_MODE_BINDING_REQUEST because the
+ * TODO: server may not have understood the 0.96-style request */
+ if(r=nr_stun_client_start(cand->u.srvrflx.stun, NR_STUN_CLIENT_MODE_BINDING_REQUEST_NO_AUTH, nr_ice_srvrflx_stun_finished_cb, cand))
+ ABORT(r);
+
+ if(r=nr_ice_ctx_remember_id(cand->ctx, cand->u.srvrflx.stun->request))
+ ABORT(r);
+
+ if(r=nr_ice_socket_register_stun_client(cand->isock,cand->u.srvrflx.stun,&cand->u.srvrflx.stun_handle))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ if (_status && (cand->u.srvrflx.stun->state==NR_STUN_CLIENT_STATE_RUNNING)) {
+ nr_stun_client_failed(cand->u.srvrflx.stun);
+ }
+ return;
+ }
+
+static int nr_ice_srvrflx_start_stun(nr_ice_candidate *cand)
+ {
+ int r,_status;
+
+ assert(!cand->delay_timer);
+ if(r=nr_stun_client_ctx_create(cand->label, cand->isock->sock,
+ &cand->stun_server_addr, cand->stream->ctx->gather_rto,
+ &cand->u.srvrflx.stun))
+ ABORT(r);
+
+ NR_ASYNC_TIMER_SET(cand->stream->ctx->stun_delay,nr_ice_srvrflx_start_stun_timer_cb,cand,&cand->delay_timer);
+ cand->stream->ctx->stun_delay += cand->stream->ctx->Ta;
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+#ifdef USE_TURN
+static void nr_ice_start_relay_turn_timer_cb(NR_SOCKET s, int how, void *cb_arg)
+ {
+ nr_ice_candidate *cand=cb_arg;
+ int r,_status;
+
+ cand->delay_timer=0;
+
+ if(r=nr_turn_client_allocate(cand->u.relayed.turn, nr_ice_turn_allocated_cb, cb_arg))
+ ABORT(r);
+
+ if(r=nr_ice_socket_register_turn_client(cand->isock, cand->u.relayed.turn,
+ cand->osock, &cand->u.relayed.turn_handle))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ if(_status && (cand->u.relayed.turn->state==NR_TURN_CLIENT_STATE_ALLOCATING)){
+ nr_turn_client_failed(cand->u.relayed.turn);
+ }
+ return;
+ }
+
+static int nr_ice_start_relay_turn(nr_ice_candidate *cand)
+ {
+ int r,_status;
+ assert(!cand->delay_timer);
+ if(r=nr_turn_client_ctx_create(cand->label, cand->isock->sock,
+ cand->u.relayed.server->username,
+ cand->u.relayed.server->password,
+ &cand->stun_server_addr,
+ cand->component->ctx,
+ &cand->u.relayed.turn))
+ ABORT(r);
+
+ if(r=nr_socket_turn_set_ctx(cand->osock, cand->u.relayed.turn))
+ ABORT(r);
+
+ NR_ASYNC_TIMER_SET(cand->stream->ctx->stun_delay,nr_ice_start_relay_turn_timer_cb,cand,&cand->delay_timer);
+ cand->stream->ctx->stun_delay += cand->stream->ctx->Ta;
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+#endif /* USE_TURN */
+
+static void nr_ice_srvrflx_stun_finished_cb(NR_SOCKET sock, int how, void *cb_arg)
+ {
+ int _status;
+ nr_ice_candidate *cand=cb_arg;
+
+ r_log(LOG_ICE,LOG_DEBUG,"ICE(%s)/CAND(%s): %s",cand->ctx->label,cand->label,__FUNCTION__);
+
+ /* Deregister to suppress duplicates */
+ if(cand->u.srvrflx.stun_handle){ /* This test because we might have failed before CB registered */
+ nr_ice_socket_deregister(cand->isock,cand->u.srvrflx.stun_handle);
+ cand->u.srvrflx.stun_handle=0;
+ }
+
+ switch(cand->u.srvrflx.stun->state){
+ /* OK, we should have a mapped address */
+ case NR_STUN_CLIENT_STATE_DONE:
+ /* Copy the address */
+ nr_transport_addr_copy(&cand->addr, &cand->u.srvrflx.stun->results.stun_binding_response.mapped_addr);
+ cand->addr.protocol=cand->base.protocol;
+ nr_transport_addr_fmt_addr_string(&cand->addr);
+ nr_stun_client_ctx_destroy(&cand->u.srvrflx.stun);
+ nr_ice_candidate_mark_done(cand, NR_ICE_CAND_STATE_INITIALIZED);
+ cand=0;
+ break;
+
+ /* This failed, so go to the next STUN server if there is one */
+ case NR_STUN_CLIENT_STATE_FAILED:
+ ABORT(R_NOT_FOUND);
+ break;
+ default:
+ ABORT(R_INTERNAL);
+ }
+ _status = 0;
+ abort:
+ if(_status){
+ nr_ice_candidate_mark_done(cand, NR_ICE_CAND_STATE_FAILED);
+ }
+ }
+
+#ifdef USE_TURN
+static void nr_ice_turn_allocated_cb(NR_SOCKET s, int how, void *cb_arg)
+ {
+ int r,_status;
+ nr_ice_candidate *cand=cb_arg;
+ nr_turn_client_ctx *turn=cand->u.relayed.turn;
+ char *label;
+ nr_transport_addr relay_addr;
+
+ switch(turn->state){
+ /* OK, we should have a mapped address */
+ case NR_TURN_CLIENT_STATE_ALLOCATED:
+ if (r=nr_turn_client_get_relayed_address(turn, &relay_addr))
+ ABORT(r);
+
+ if(r=nr_concat_strings(&label,"turn-relay(",cand->base.as_string,"|",
+ relay_addr.as_string,")",NULL))
+ ABORT(r);
+
+ r_log(LOG_ICE,LOG_DEBUG,"TURN-CLIENT(%s)/CAND(%s): Switching from TURN to RELAY (%s)",cand->u.relayed.turn->label,cand->label,label);
+
+ /* Copy the relayed address into the candidate addr and
+ into the candidate base. Note that we need to keep the
+ ifname in the base. */
+ if (r=nr_transport_addr_copy(&cand->addr, &relay_addr))
+ ABORT(r);
+ if (r=nr_transport_addr_copy_keep_ifname(&cand->base, &relay_addr)) /* Need to keep interface for priority calculation */
+ ABORT(r);
+
+ r_log(LOG_ICE,LOG_DEBUG,"ICE(%s)/CAND(%s): new relay base=%s addr=%s", cand->ctx->label, cand->label, cand->base.as_string, cand->addr.as_string);
+
+ RFREE(cand->label);
+ cand->label=label;
+ nr_ice_candidate_mark_done(cand, NR_ICE_CAND_STATE_INITIALIZED);
+ cand = 0;
+
+ break;
+
+ case NR_TURN_CLIENT_STATE_FAILED:
+ case NR_TURN_CLIENT_STATE_CANCELLED:
+ r_log(NR_LOG_TURN, LOG_WARNING,
+ "ICE-CANDIDATE(%s): nr_turn_allocated_cb called with state %d",
+ cand->label, turn->state);
+ /* This failed, so go to the next TURN server if there is one */
+ ABORT(R_NOT_FOUND);
+ break;
+ default:
+ assert(0); /* should never happen */
+ ABORT(R_INTERNAL);
+ }
+
+ _status=0;
+ abort:
+ if(_status){
+ if (cand) {
+ r_log(NR_LOG_TURN, LOG_WARNING,
+ "ICE-CANDIDATE(%s): nr_turn_allocated_cb failed", cand->label);
+ nr_ice_candidate_mark_done(cand, NR_ICE_CAND_STATE_FAILED);
+ }
+ }
+ }
+#endif /* USE_TURN */
+
+/* Format the candidate attribute as per ICE S 15.1 */
+int nr_ice_format_candidate_attribute(nr_ice_candidate *cand, char *attr, int maxlen, int obfuscate_srflx_addr)
+ {
+ int r,_status;
+ char addr[64];
+ int port;
+ int len;
+ nr_transport_addr *raddr;
+
+ assert(!strcmp(nr_ice_candidate_type_names[HOST], "host"));
+ assert(!strcmp(nr_ice_candidate_type_names[RELAYED], "relay"));
+
+ if (cand->mdns_addr) {
+ /* mdns_addr is NSID_LENGTH which is 39, - 2 for removing the "{" and "}"
+ + 6 for ".local" for a total of 43. */
+ strncpy(addr, cand->mdns_addr, sizeof(addr) - 1);
+ } else {
+ if(r=nr_transport_addr_get_addrstring(&cand->addr,addr,sizeof(addr)))
+ ABORT(r);
+ }
+ if(r=nr_transport_addr_get_port(&cand->addr,&port))
+ ABORT(r);
+ /* https://tools.ietf.org/html/rfc6544#section-4.5 */
+ if (cand->base.protocol==IPPROTO_TCP && cand->tcp_type==TCP_TYPE_ACTIVE)
+ port=9;
+ snprintf(attr,maxlen,"candidate:%s %d %s %u %s %d typ %s",
+ cand->foundation, cand->component_id, cand->addr.protocol==IPPROTO_UDP?"UDP":"TCP",cand->priority, addr, port,
+ nr_ctype_name(cand->type));
+
+ len=strlen(attr); attr+=len; maxlen-=len;
+
+ /* raddr, rport */
+ raddr = (cand->stream->flags &
+ (NR_ICE_CTX_FLAGS_RELAY_ONLY |
+ NR_ICE_CTX_FLAGS_DISABLE_HOST_CANDIDATES)) ?
+ &cand->addr : &cand->base;
+
+ switch(cand->type){
+ case HOST:
+ break;
+ case SERVER_REFLEXIVE:
+ if (obfuscate_srflx_addr) {
+ snprintf(attr,maxlen," raddr 0.0.0.0 rport 0");
+ } else {
+ if(r=nr_transport_addr_get_addrstring(raddr,addr,sizeof(addr)))
+ ABORT(r);
+ if(r=nr_transport_addr_get_port(raddr,&port))
+ ABORT(r);
+ snprintf(attr,maxlen," raddr %s rport %d",addr,port);
+ }
+ break;
+ case PEER_REFLEXIVE:
+ if(r=nr_transport_addr_get_addrstring(raddr,addr,sizeof(addr)))
+ ABORT(r);
+ if(r=nr_transport_addr_get_port(raddr,&port))
+ ABORT(r);
+ snprintf(attr,maxlen," raddr %s rport %d",addr,port);
+ break;
+ case RELAYED:
+ // comes from XorMappedAddress via AllocateResponse
+ if(r=nr_transport_addr_get_addrstring(raddr,addr,sizeof(addr)))
+ ABORT(r);
+ if(r=nr_transport_addr_get_port(raddr,&port))
+ ABORT(r);
+
+ snprintf(attr,maxlen," raddr %s rport %d",addr,port);
+ break;
+ default:
+ assert(0);
+ ABORT(R_INTERNAL);
+ break;
+ }
+
+ if (cand->base.protocol==IPPROTO_TCP && cand->tcp_type){
+ len=strlen(attr);
+ attr+=len;
+ maxlen-=len;
+ snprintf(attr,maxlen," tcptype %s",nr_tcp_type_name(cand->tcp_type));
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_candidate.h b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_candidate.h
new file mode 100644
index 0000000000..40e6545e37
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_candidate.h
@@ -0,0 +1,124 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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 _ice_candidate_h
+#define _ice_candidate_h
+#ifdef __cplusplus
+using namespace std;
+extern "C" {
+#endif /* __cplusplus */
+
+typedef enum {HOST=1, SERVER_REFLEXIVE, PEER_REFLEXIVE, RELAYED, CTYPE_MAX} nr_ice_candidate_type;
+
+struct nr_ice_candidate_ {
+ char *label;
+ char codeword[5];
+ int state;
+ int trickled;
+#define NR_ICE_CAND_STATE_CREATED 1
+#define NR_ICE_CAND_STATE_INITIALIZING 2
+#define NR_ICE_CAND_STATE_INITIALIZED 3
+#define NR_ICE_CAND_STATE_FAILED 4
+#define NR_ICE_CAND_PEER_CANDIDATE_UNPAIRED 9
+#define NR_ICE_CAND_PEER_CANDIDATE_PAIRED 10
+ struct nr_ice_ctx_ *ctx;
+ nr_ice_socket *isock; /* The socket to read from
+ (it contains all other candidates
+ on this socket) */
+ nr_socket *osock; /* The socket to write to */
+ nr_ice_media_stream *stream; /* The media stream this is associated with */
+ nr_ice_component *component; /* The component this is associated with */
+ nr_ice_candidate_type type; /* The type of the candidate (S 4.1.1) */
+ nr_socket_tcp_type tcp_type;
+ UCHAR component_id; /* The component id (S 4.1.2.1) */
+ nr_transport_addr addr; /* The advertised address;
+ JDR calls this the candidate */
+ nr_transport_addr base; /* The base address (S 2.1)*/
+ char *mdns_addr; /* MDNS address, if any */
+ char *foundation; /* Foundation for the candidate (S 4) */
+ UINT4 priority; /* The priority value (S 5.4 */
+ nr_ice_stun_server *stun_server;
+ nr_transport_addr stun_server_addr; /* Resolved STUN server address */
+ void *delay_timer;
+ void *resolver_handle;
+
+ /* Holding data for STUN and TURN */
+ union {
+ struct {
+ nr_stun_client_ctx *stun;
+ void *stun_handle;
+ /* If this is a srflx that is piggybacking on a relay candidate, this is
+ * a back pointer to that relay candidate. */
+ nr_ice_candidate *relay_candidate;
+ } srvrflx;
+ struct {
+ nr_turn_client_ctx *turn;
+ nr_ice_turn_server *server;
+ nr_ice_candidate *srvflx_candidate;
+ nr_socket *turn_sock;
+ void *turn_handle;
+ } relayed;
+ } u;
+
+ NR_async_cb done_cb;
+ void *cb_arg;
+
+ NR_async_cb ready_cb;
+ void *ready_cb_arg;
+ void *ready_cb_timer;
+
+ TAILQ_ENTRY(nr_ice_candidate_) entry_sock;
+ TAILQ_ENTRY(nr_ice_candidate_) entry_comp;
+};
+
+extern char *nr_ice_candidate_type_names[];
+extern char *nr_ice_candidate_tcp_type_names[];
+
+
+int nr_ice_candidate_create(struct nr_ice_ctx_ *ctx,nr_ice_component *component, nr_ice_socket *isock, nr_socket *osock, nr_ice_candidate_type ctype, nr_socket_tcp_type tcp_type, nr_ice_stun_server *stun_server, UCHAR component_id, nr_ice_candidate **candp);
+int nr_ice_candidate_initialize(nr_ice_candidate *cand, NR_async_cb ready_cb, void *cb_arg);
+void nr_ice_candidate_compute_codeword(nr_ice_candidate *cand);
+int nr_ice_candidate_process_stun(nr_ice_candidate *cand, UCHAR *msg, int len, nr_transport_addr *faddr);
+int nr_ice_candidate_destroy(nr_ice_candidate **candp);
+void nr_ice_candidate_stop_gathering(nr_ice_candidate *cand);
+int nr_ice_format_candidate_attribute(nr_ice_candidate *cand, char *attr, int maxlen, int obfuscate_srflx_addr);
+int nr_ice_peer_candidate_from_attribute(nr_ice_ctx *ctx,char *attr,nr_ice_media_stream *stream,nr_ice_candidate **candp);
+int nr_ice_peer_peer_rflx_candidate_create(nr_ice_ctx *ctx,char *label, nr_ice_component *comp,nr_transport_addr *addr, nr_ice_candidate **candp);
+int nr_ice_candidate_compute_priority(nr_ice_candidate *cand);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_candidate_pair.c b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_candidate_pair.c
new file mode 100644
index 0000000000..6a7233963d
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_candidate_pair.c
@@ -0,0 +1,689 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+*/
+
+#include <assert.h>
+#include <string.h>
+#include <nr_api.h>
+#include "async_timer.h"
+#include "ice_ctx.h"
+#include "ice_util.h"
+#include "ice_codeword.h"
+#include "stun.h"
+
+static char *nr_ice_cand_pair_states[]={"UNKNOWN","FROZEN","WAITING","IN_PROGRESS","FAILED","SUCCEEDED","CANCELLED"};
+
+static void nr_ice_candidate_pair_restart_stun_role_change_cb(NR_SOCKET s, int how, void *cb_arg);
+static void nr_ice_candidate_pair_compute_codeword(nr_ice_cand_pair *pair,
+ nr_ice_candidate *lcand, nr_ice_candidate *rcand);
+
+static void nr_ice_candidate_pair_set_priority(nr_ice_cand_pair *pair)
+ {
+ /* Priority computation S 5.7.2 */
+ UINT8 controlling_priority, controlled_priority;
+ if(pair->pctx->controlling)
+ {
+ controlling_priority=pair->local->priority;
+ controlled_priority=pair->remote->priority;
+ }
+ else{
+ controlling_priority=pair->remote->priority;
+ controlled_priority=pair->local->priority;
+ }
+ pair->priority=(MIN(controlling_priority, controlled_priority))<<32 |
+ (MAX(controlling_priority, controlled_priority))<<1 |
+ (controlled_priority > controlling_priority?0:1);
+ }
+
+int nr_ice_candidate_pair_create(nr_ice_peer_ctx *pctx, nr_ice_candidate *lcand,nr_ice_candidate *rcand,nr_ice_cand_pair **pairp)
+ {
+ nr_ice_cand_pair *pair=0;
+ int r,_status;
+ UINT4 RTO;
+ nr_ice_candidate tmpcand;
+ UINT8 t_priority;
+
+ if(!(pair=RCALLOC(sizeof(nr_ice_cand_pair))))
+ ABORT(R_NO_MEMORY);
+
+ pair->pctx=pctx;
+
+ nr_ice_candidate_pair_compute_codeword(pair,lcand,rcand);
+
+ if(r=nr_concat_strings(&pair->as_string,pair->codeword,"|",lcand->addr.as_string,"|",
+ rcand->addr.as_string,"(",lcand->label,"|",rcand->label,")", NULL))
+ ABORT(r);
+
+ nr_ice_candidate_pair_set_state(pctx,pair,NR_ICE_PAIR_STATE_FROZEN);
+ pair->local=lcand;
+ pair->remote=rcand;
+
+ nr_ice_candidate_pair_set_priority(pair);
+
+ /*
+ TODO(bcampen@mozilla.com): Would be nice to log why this candidate was
+ created (initial pair generation, triggered check, and new trickle
+ candidate seem to be the possibilities here).
+ */
+ r_log(LOG_ICE,LOG_INFO,"ICE(%s)/CAND-PAIR(%s): Pairing candidate %s (%x):%s (%x) priority=%llu (%llx)",pctx->ctx->label,pair->codeword,lcand->addr.as_string,lcand->priority,rcand->addr.as_string,rcand->priority,pair->priority,pair->priority);
+
+ /* Foundation */
+ if(r=nr_concat_strings(&pair->foundation,lcand->foundation,"|",
+ rcand->foundation,NULL))
+ ABORT(r);
+
+ /* Compute the RTO per S 16 */
+ RTO = MAX(100, (pctx->ctx->Ta * pctx->waiting_pairs));
+
+ /* Make a bogus candidate to compute a theoretical peer reflexive
+ * priority per S 7.1.1.1 */
+ memcpy(&tmpcand, lcand, sizeof(tmpcand));
+ tmpcand.type = PEER_REFLEXIVE;
+ if (r=nr_ice_candidate_compute_priority(&tmpcand))
+ ABORT(r);
+ t_priority = tmpcand.priority;
+
+ /* Our sending context */
+ if(r=nr_stun_client_ctx_create(pair->as_string,
+ lcand->osock,
+ &rcand->addr,RTO,&pair->stun_client))
+ ABORT(r);
+ if(!(pair->stun_client->params.ice_binding_request.username=r_strdup(rcand->stream->l2r_user)))
+ ABORT(R_NO_MEMORY);
+ if(r=r_data_copy(&pair->stun_client->params.ice_binding_request.password,
+ &rcand->stream->l2r_pass))
+ ABORT(r);
+ /* TODO(ekr@rtfm.com): Do we need to frob this when we change role. Bug 890667 */
+ pair->stun_client->params.ice_binding_request.control = pctx->controlling?
+ NR_ICE_CONTROLLING:NR_ICE_CONTROLLED;
+ pair->stun_client->params.ice_binding_request.priority=t_priority;
+
+ pair->stun_client->params.ice_binding_request.tiebreaker=pctx->tiebreaker;
+
+ *pairp=pair;
+
+ _status=0;
+ abort:
+ if(_status){
+ nr_ice_candidate_pair_destroy(&pair);
+ }
+ return(_status);
+ }
+
+int nr_ice_candidate_pair_destroy(nr_ice_cand_pair **pairp)
+ {
+ nr_ice_cand_pair *pair;
+
+ if(!pairp || !*pairp)
+ return(0);
+
+ pair=*pairp;
+ *pairp=0;
+
+ // record stats back to the ice ctx on destruction
+ if (pair->stun_client) {
+ nr_accumulate_count(&(pair->local->ctx->stats.stun_retransmits), pair->stun_client->retransmit_ct);
+ }
+
+ RFREE(pair->as_string);
+ RFREE(pair->foundation);
+ nr_ice_socket_deregister(pair->local->isock,pair->stun_client_handle);
+ if (pair->stun_client) {
+ RFREE(pair->stun_client->params.ice_binding_request.username);
+ RFREE(pair->stun_client->params.ice_binding_request.password.data);
+ nr_stun_client_ctx_destroy(&pair->stun_client);
+ }
+
+ NR_async_timer_cancel(pair->stun_cb_timer);
+ NR_async_timer_cancel(pair->restart_role_change_cb_timer);
+ NR_async_timer_cancel(pair->restart_nominated_cb_timer);
+
+ RFREE(pair);
+ return(0);
+ }
+
+int nr_ice_candidate_pair_unfreeze(nr_ice_peer_ctx *pctx, nr_ice_cand_pair *pair)
+ {
+ assert(pair->state==NR_ICE_PAIR_STATE_FROZEN);
+
+ nr_ice_candidate_pair_set_state(pctx,pair,NR_ICE_PAIR_STATE_WAITING);
+
+ return(0);
+ }
+
+static void nr_ice_candidate_pair_stun_cb(NR_SOCKET s, int how, void *cb_arg)
+ {
+ int r,_status;
+ nr_ice_cand_pair *pair=cb_arg;
+ nr_ice_cand_pair *actual_pair=0;
+ nr_ice_candidate *cand=0;
+ nr_stun_message *sres;
+ nr_transport_addr *request_src;
+ nr_transport_addr *request_dst;
+ nr_transport_addr *response_src;
+ nr_transport_addr response_dst;
+ nr_stun_message_attribute *attr;
+
+ pair->stun_cb_timer=0;
+
+ r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s)/STREAM(%s)/CAND-PAIR(%s): STUN cb on pair addr = %s",
+ pair->pctx->label,pair->local->stream->label,pair->codeword,pair->as_string);
+
+ /* This ordinarily shouldn't happen, but can if we're
+ doing the second check to confirm nomination.
+ Just bail out */
+ if(pair->state==NR_ICE_PAIR_STATE_SUCCEEDED)
+ goto done;
+
+ switch(pair->stun_client->state){
+ case NR_STUN_CLIENT_STATE_FAILED:
+ sres=pair->stun_client->response;
+ if(sres && nr_stun_message_has_attribute(sres,NR_STUN_ATTR_ERROR_CODE,&attr)&&attr->u.error_code.number==487){
+
+ /*
+ * Flip the controlling bit; subsequent 487s for other pairs will be
+ * ignored, since we abandon their STUN transactions.
+ */
+ nr_ice_peer_ctx_switch_controlling_role(pair->pctx);
+
+ return;
+ }
+ /* Fall through */
+ case NR_STUN_CLIENT_STATE_TIMED_OUT:
+ nr_ice_candidate_pair_set_state(pair->pctx,pair,NR_ICE_PAIR_STATE_FAILED);
+ break;
+ case NR_STUN_CLIENT_STATE_DONE:
+ /* make sure the addresses match up S 7.1.2.2 */
+ response_src=&pair->stun_client->peer_addr;
+ request_dst=&pair->remote->addr;
+ if (nr_transport_addr_cmp(response_src,request_dst,NR_TRANSPORT_ADDR_CMP_MODE_ALL)){
+ r_log(LOG_ICE,LOG_WARNING,"ICE-PEER(%s)/CAND-PAIR(%s): Peer address mismatch %s != %s",pair->pctx->label,pair->codeword,response_src->as_string,request_dst->as_string);
+ nr_ice_candidate_pair_set_state(pair->pctx,pair,NR_ICE_PAIR_STATE_FAILED);
+ break;
+ }
+ request_src=&pair->stun_client->my_addr;
+ nr_socket_getaddr(pair->local->osock,&response_dst);
+ if (nr_transport_addr_cmp(request_src,&response_dst,NR_TRANSPORT_ADDR_CMP_MODE_ALL)){
+ r_log(LOG_ICE,LOG_WARNING,"ICE-PEER(%s)/CAND-PAIR(%s): Local address mismatch %s != %s",pair->pctx->label,pair->codeword,request_src->as_string,response_dst.as_string);
+ nr_ice_candidate_pair_set_state(pair->pctx,pair,NR_ICE_PAIR_STATE_FAILED);
+ break;
+ }
+
+ if(strlen(pair->stun_client->results.ice_binding_response.mapped_addr.as_string)==0){
+ /* we're using the mapped_addr returned by the server to lookup our
+ * candidate, but if the server fails to do that we can't perform
+ * the lookup -- this may be a BUG because if we've gotten here
+ * then the transaction ID check succeeded, and perhaps we should
+ * just assume that it's the server we're talking to and that our
+ * peer is ok, but I'm not sure how that'll interact with the
+ * peer reflexive logic below */
+ r_log(LOG_ICE,LOG_WARNING,"ICE-PEER(%s)/CAND-PAIR(%s): server failed to return mapped address on pair %s", pair->pctx->label,pair->codeword,pair->as_string);
+ nr_ice_candidate_pair_set_state(pair->pctx,pair,NR_ICE_PAIR_STATE_FAILED);
+ break;
+ }
+ else if(!nr_transport_addr_cmp(&pair->local->addr,&pair->stun_client->results.ice_binding_response.mapped_addr,NR_TRANSPORT_ADDR_CMP_MODE_ALL)){
+ nr_ice_candidate_pair_set_state(pair->pctx,pair,NR_ICE_PAIR_STATE_SUCCEEDED);
+ }
+ else if(pair->stun_client->state == NR_STUN_CLIENT_STATE_DONE) {
+ /* OK, this didn't correspond to a pair on the check list, but
+ it probably matches one of our candidates */
+
+ cand=TAILQ_FIRST(&pair->local->component->candidates);
+ while(cand){
+ if(!nr_transport_addr_cmp(&cand->addr,&pair->stun_client->results.ice_binding_response.mapped_addr,NR_TRANSPORT_ADDR_CMP_MODE_ALL)) {
+ r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s): found pre-existing local candidate of type %d for mapped address %s", pair->pctx->label,cand->type,cand->addr.as_string);
+ assert(cand->type != HOST);
+ break;
+ }
+
+ cand=TAILQ_NEXT(cand,entry_comp);
+ }
+
+ if(!cand) {
+ /* OK, nothing found, must be a new peer reflexive */
+ if (pair->local->stream->flags & NR_ICE_CTX_FLAGS_RELAY_ONLY) {
+ /* Any STUN response with a reflexive address in it is unwanted
+ when we'll send on relay only. Bail since cand is used below. */
+ goto done;
+ }
+ if(r=nr_ice_candidate_create(pair->pctx->ctx,
+ pair->local->component,pair->local->isock,pair->local->osock,
+ PEER_REFLEXIVE,pair->local->tcp_type,0,pair->local->component->component_id,&cand))
+ ABORT(r);
+ if(r=nr_transport_addr_copy(&cand->addr,&pair->stun_client->results.ice_binding_response.mapped_addr))
+ ABORT(r);
+ cand->state=NR_ICE_CAND_STATE_INITIALIZED;
+ TAILQ_INSERT_TAIL(&pair->local->component->candidates,cand,entry_comp);
+ } else {
+ /* Check if we have a pair for this candidate already. */
+ if(r=nr_ice_media_stream_find_pair(pair->remote->stream, cand, pair->remote, &actual_pair)) {
+ r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s): no pair exists for %s and %s", pair->pctx->label,cand->addr.as_string, pair->remote->addr.as_string);
+ }
+ }
+
+ if(!actual_pair) {
+ if(r=nr_ice_candidate_pair_create(pair->pctx,cand,pair->remote, &actual_pair))
+ ABORT(r);
+
+ if(r=nr_ice_component_insert_pair(actual_pair->remote->component,actual_pair))
+ ABORT(r);
+
+ /* If the original pair was nominated, make us nominated too. */
+ if(pair->peer_nominated)
+ actual_pair->peer_nominated=1;
+
+ /* Now mark the orig pair failed */
+ nr_ice_candidate_pair_set_state(pair->pctx,pair,NR_ICE_PAIR_STATE_FAILED);
+ }
+
+ assert(actual_pair);
+ nr_ice_candidate_pair_set_state(actual_pair->pctx,actual_pair,NR_ICE_PAIR_STATE_SUCCEEDED);
+ pair=actual_pair;
+
+ }
+
+ /* Should we set nominated? */
+ if(pair->pctx->controlling){
+ if(pair->pctx->ctx->flags & NR_ICE_CTX_FLAGS_AGGRESSIVE_NOMINATION)
+ pair->nominated=1;
+ }
+ else{
+ if(pair->peer_nominated)
+ pair->nominated=1;
+ }
+
+
+ /* increment the number of valid pairs in the component */
+ /* We don't bother to maintain a separate valid list */
+ pair->remote->component->valid_pairs++;
+
+ /* S 7.1.2.2: unfreeze other pairs with the same foundation*/
+ if(r=nr_ice_media_stream_unfreeze_pairs_foundation(pair->remote->stream,pair->foundation))
+ ABORT(r);
+
+ /* Deal with this pair being nominated */
+ if(pair->nominated){
+ if(r=nr_ice_component_nominated_pair(pair->remote->component, pair))
+ ABORT(r);
+ }
+
+ break;
+ default:
+ ABORT(R_INTERNAL);
+ }
+
+ /* If we're controlling but in regular mode, ask the handler
+ if he wants to nominate something and stop... */
+ if(pair->pctx->controlling && !(pair->pctx->ctx->flags & NR_ICE_CTX_FLAGS_AGGRESSIVE_NOMINATION)){
+
+ if(r=nr_ice_component_select_pair(pair->pctx,pair->remote->component)){
+ if(r!=R_NOT_FOUND)
+ ABORT(r);
+ }
+ }
+
+ done:
+ _status=0;
+ abort:
+ if (_status) {
+ // cb doesn't return anything, but we should probably log that we aborted
+ // This also quiets the unused variable warnings.
+ r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s)/STREAM(%s)/CAND-PAIR(%s): STUN cb pair addr = %s abort with status: %d",
+ pair->pctx->label,pair->local->stream->label,pair->codeword,pair->as_string, _status);
+ }
+ return;
+ }
+
+static void nr_ice_candidate_pair_restart(nr_ice_peer_ctx *pctx, nr_ice_cand_pair *pair)
+ {
+ int r,_status;
+ UINT4 mode;
+
+ nr_ice_candidate_pair_set_state(pctx,pair,NR_ICE_PAIR_STATE_IN_PROGRESS);
+
+ /* Start STUN */
+ if(pair->pctx->controlling && (pair->pctx->ctx->flags & NR_ICE_CTX_FLAGS_AGGRESSIVE_NOMINATION))
+ mode=NR_ICE_CLIENT_MODE_USE_CANDIDATE;
+ else
+ mode=NR_ICE_CLIENT_MODE_BINDING_REQUEST;
+
+ nr_stun_client_reset(pair->stun_client);
+
+ if(r=nr_stun_client_start(pair->stun_client,mode,nr_ice_candidate_pair_stun_cb,pair))
+ ABORT(r);
+
+ if ((r=nr_ice_ctx_remember_id(pair->pctx->ctx, pair->stun_client->request))) {
+ /* ignore if this fails (which it shouldn't) because it's only an
+ * optimization and the cleanup routines are not going to do the right
+ * thing if this fails */
+ assert(0);
+ }
+
+ _status=0;
+ abort:
+ if(_status){
+ /* Don't fire the CB, but schedule it to fire ASAP */
+ assert(!pair->stun_cb_timer);
+ NR_ASYNC_TIMER_SET(0,nr_ice_candidate_pair_stun_cb,pair, &pair->stun_cb_timer);
+ _status=0;
+ }
+ }
+
+int nr_ice_candidate_pair_start(nr_ice_peer_ctx *pctx, nr_ice_cand_pair *pair)
+ {
+ int r,_status;
+
+ /* Register the stun ctx for when responses come in*/
+ if(r=nr_ice_socket_register_stun_client(pair->local->isock,pair->stun_client,&pair->stun_client_handle))
+ ABORT(r);
+
+ nr_ice_candidate_pair_restart(pctx, pair);
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+static int nr_ice_candidate_copy_for_triggered_check(nr_ice_cand_pair *pair)
+ {
+ int r,_status;
+ nr_ice_cand_pair *copy;
+
+ if(r=nr_ice_candidate_pair_create(pair->pctx, pair->local, pair->remote, &copy))
+ ABORT(r);
+
+ /* Preserve nomination status */
+ copy->peer_nominated= pair->peer_nominated;
+ copy->nominated = pair->nominated;
+
+ r_log(LOG_ICE,LOG_INFO,"CAND-PAIR(%s): Adding pair to check list and trigger check queue: %s",pair->codeword,pair->as_string);
+ nr_ice_candidate_pair_insert(&pair->remote->stream->check_list,copy);
+ nr_ice_candidate_pair_trigger_check_append(&pair->remote->stream->trigger_check_queue,copy);
+
+ copy->triggered = 1;
+ nr_ice_candidate_pair_set_state(copy->pctx,copy,NR_ICE_PAIR_STATE_WAITING);
+
+ _status=0;
+ abort:
+ return(_status);
+}
+
+int nr_ice_candidate_pair_do_triggered_check(nr_ice_peer_ctx *pctx, nr_ice_cand_pair *pair)
+ {
+ int r,_status;
+
+ if(pair->state==NR_ICE_PAIR_STATE_CANCELLED) {
+ r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s)/CAND_PAIR(%s): Ignoring matching but canceled pair",pctx->label,pair->codeword);
+ return(0);
+ } else if(pair->state==NR_ICE_PAIR_STATE_SUCCEEDED) {
+ r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s)/CAND_PAIR(%s): No new trigger check for succeeded pair",pctx->label,pair->codeword);
+ return(0);
+ } else if (pair->local->stream->obsolete) {
+ r_log(LOG_ICE, LOG_DEBUG,
+ "ICE-PEER(%s)/CAND_PAIR(%s): No new trigger check for pair with "
+ "obsolete stream",
+ pctx->label, pair->codeword);
+ return (0);
+ }
+
+ /* Do not run this logic more than once on a given pair */
+ if(!pair->triggered){
+ r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/CAND-PAIR(%s): triggered check on %s",pctx->label,pair->codeword,pair->as_string);
+
+ pair->triggered=1;
+
+ switch(pair->state){
+ case NR_ICE_PAIR_STATE_FAILED:
+ /* OK, there was a pair, it's just invalid: According to Section
+ * 7.2.1.4, we need to resurrect it */
+ r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/CAND-PAIR(%s): received STUN check on failed pair, resurrecting: %s",pctx->label,pair->codeword,pair->as_string);
+ /* fall through */
+ case NR_ICE_PAIR_STATE_FROZEN:
+ nr_ice_candidate_pair_set_state(pctx,pair,NR_ICE_PAIR_STATE_WAITING);
+ /* fall through even further */
+ case NR_ICE_PAIR_STATE_WAITING:
+ /* Append it additionally to the trigger check queue */
+ r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/CAND-PAIR(%s): Inserting pair to trigger check queue: %s",pctx->label,pair->codeword,pair->as_string);
+ nr_ice_candidate_pair_trigger_check_append(&pair->remote->stream->trigger_check_queue,pair);
+ break;
+ case NR_ICE_PAIR_STATE_IN_PROGRESS:
+ /* Instead of trying to maintain two stun contexts on the same pair,
+ * and handling heterogenous responses and error conditions, we instead
+ * create a second pair that is identical except that it has the
+ * |triggered| bit set. We also cancel the original pair, but it can
+ * still succeed on its own in the special waiting state. */
+ if(r=nr_ice_candidate_copy_for_triggered_check(pair))
+ ABORT(r);
+ nr_ice_candidate_pair_cancel(pair->pctx,pair,1);
+ break;
+ default:
+ /* all states are handled - a new/unknown state should not
+ * automatically enter the start_checks() below */
+ assert(0);
+ break;
+ }
+
+ /* Ensure that the timers are running to start checks on the topmost entry
+ * of the triggered check queue. */
+ if(r=nr_ice_media_stream_start_checks(pair->pctx,pair->remote->stream))
+ ABORT(r);
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+void nr_ice_candidate_pair_cancel(nr_ice_peer_ctx *pctx,nr_ice_cand_pair *pair, int move_to_wait_state)
+ {
+ if(pair->state != NR_ICE_PAIR_STATE_FAILED){
+ /* If it's already running we need to terminate the stun */
+ if(pair->state==NR_ICE_PAIR_STATE_IN_PROGRESS){
+ if(move_to_wait_state) {
+ nr_stun_client_wait(pair->stun_client);
+ } else {
+ nr_stun_client_cancel(pair->stun_client);
+ }
+ }
+ nr_ice_candidate_pair_set_state(pctx,pair,NR_ICE_PAIR_STATE_CANCELLED);
+ }
+ }
+
+int nr_ice_candidate_pair_select(nr_ice_cand_pair *pair)
+ {
+ int r,_status;
+
+ if(!pair){
+ r_log(LOG_ICE,LOG_ERR,"ICE-PAIR: No pair chosen");
+ ABORT(R_BAD_ARGS);
+ }
+
+ if(pair->state!=NR_ICE_PAIR_STATE_SUCCEEDED){
+ r_log(LOG_ICE,LOG_ERR,"ICE-PEER(%s)/CAND-PAIR(%s): tried to install non-succeeded pair, ignoring: %s",pair->pctx->label,pair->codeword,pair->as_string);
+ }
+ else{
+ /* Ok, they chose one */
+ /* 1. Send a new request with nominated. Do it as a scheduled
+ event to avoid reentrancy issues. Only do this if it hasn't
+ happened already (though this shouldn't happen.)
+ */
+ if(!pair->restart_nominated_cb_timer)
+ NR_ASYNC_TIMER_SET(0,nr_ice_candidate_pair_restart_stun_nominated_cb,pair,&pair->restart_nominated_cb_timer);
+
+ /* 2. Tell ourselves this pair is ready */
+ if(r=nr_ice_component_nominated_pair(pair->remote->component, pair))
+ ABORT(r);
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+void nr_ice_candidate_pair_set_state(nr_ice_peer_ctx *pctx, nr_ice_cand_pair *pair, int state)
+ {
+ r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/CAND-PAIR(%s): setting pair to state %s: %s",
+ pctx->label,pair->codeword,nr_ice_cand_pair_states[state],pair->as_string);
+
+ /* NOTE: This function used to reference pctx->state instead of
+ pair->state and the assignment to pair->state was at the top
+ of this function. Because pctx->state was never changed, this seems to have
+ been a typo. The natural logic is "if the state changed
+ decrement the counter" so this implies we should be checking
+ the pair state rather than the pctx->state.
+
+ This didn't cause big problems because waiting_pairs was only
+ used for pacing, so the pacing just was kind of broken.
+
+ This note is here as a reminder until we do more testing
+ and make sure that in fact this was a typo.
+ */
+ if(pair->state!=NR_ICE_PAIR_STATE_WAITING){
+ if(state==NR_ICE_PAIR_STATE_WAITING)
+ pctx->waiting_pairs++;
+ }
+ else{
+ if(state!=NR_ICE_PAIR_STATE_WAITING)
+ pctx->waiting_pairs--;
+
+ assert(pctx->waiting_pairs>=0);
+ }
+ pair->state=state;
+
+
+ if(pair->state==NR_ICE_PAIR_STATE_FAILED ||
+ pair->state==NR_ICE_PAIR_STATE_CANCELLED){
+ nr_ice_component_failed_pair(pair->remote->component, pair);
+ }
+ }
+
+void nr_ice_candidate_pair_dump_state(nr_ice_cand_pair *pair, int log_level)
+ {
+ r_log(LOG_ICE,log_level,"CAND-PAIR(%s): pair %s: state=%s, priority=0x%llx\n",pair->codeword,pair->as_string,nr_ice_cand_pair_states[pair->state],pair->priority);
+ }
+
+
+int nr_ice_candidate_pair_trigger_check_append(nr_ice_cand_pair_head *head,nr_ice_cand_pair *pair)
+ {
+ if(pair->triggered_check_queue_entry.tqe_next ||
+ pair->triggered_check_queue_entry.tqe_prev)
+ return(0);
+
+ TAILQ_INSERT_TAIL(head,pair,triggered_check_queue_entry);
+
+ return(0);
+ }
+
+void nr_ice_candidate_pair_insert(nr_ice_cand_pair_head *head,nr_ice_cand_pair *pair)
+ {
+ nr_ice_cand_pair *c1;
+
+ c1=TAILQ_FIRST(head);
+ while(c1){
+ if(c1->priority < pair->priority){
+ TAILQ_INSERT_BEFORE(c1,pair,check_queue_entry);
+ break;
+ }
+
+ c1=TAILQ_NEXT(c1,check_queue_entry);
+ }
+ if(!c1) TAILQ_INSERT_TAIL(head,pair,check_queue_entry);
+ }
+
+void nr_ice_candidate_pair_restart_stun_nominated_cb(NR_SOCKET s, int how, void *cb_arg)
+ {
+ nr_ice_cand_pair *pair=cb_arg;
+ int r,_status;
+
+ pair->restart_nominated_cb_timer=0;
+
+ r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/STREAM(%s)/CAND-PAIR(%s)/COMP(%d): Restarting pair as nominated: %s",pair->pctx->label,pair->local->stream->label,pair->codeword,pair->remote->component->component_id,pair->as_string);
+
+ nr_stun_client_reset(pair->stun_client);
+
+ if(r=nr_stun_client_start(pair->stun_client,NR_ICE_CLIENT_MODE_USE_CANDIDATE,nr_ice_candidate_pair_stun_cb,pair))
+ ABORT(r);
+
+ if(r=nr_ice_ctx_remember_id(pair->pctx->ctx, pair->stun_client->request))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ if (_status) {
+ // cb doesn't return anything, but we should probably log that we aborted
+ // This also quiets the unused variable warnings.
+ r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s)/STREAM(%s)/CAND-PAIR(%s)/COMP(%d): STUN nominated cb pair as nominated: %s abort with status: %d",
+ pair->pctx->label,pair->local->stream->label,pair->codeword,pair->remote->component->component_id,pair->as_string, _status);
+ }
+ return;
+ }
+
+static void nr_ice_candidate_pair_restart_stun_role_change_cb(NR_SOCKET s, int how, void *cb_arg)
+ {
+ nr_ice_cand_pair *pair=cb_arg;
+
+ pair->restart_role_change_cb_timer=0;
+
+ r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/STREAM(%s)/CAND-PAIR(%s):COMP(%d): Restarting pair as %s: %s",pair->pctx->label,pair->local->stream->label,pair->codeword,pair->remote->component->component_id,pair->pctx->controlling ? "CONTROLLING" : "CONTROLLED",pair->as_string);
+
+ nr_ice_candidate_pair_restart(pair->pctx, pair);
+ }
+
+void nr_ice_candidate_pair_role_change(nr_ice_cand_pair *pair)
+ {
+ pair->stun_client->params.ice_binding_request.control = pair->pctx->controlling ? NR_ICE_CONTROLLING : NR_ICE_CONTROLLED;
+ nr_ice_candidate_pair_set_priority(pair);
+
+ if(pair->state == NR_ICE_PAIR_STATE_IN_PROGRESS) {
+ /* We could try only restarting in-progress pairs when they receive their
+ * 487, but this ends up being simpler, because any extra 487 are dropped.
+ */
+ if(!pair->restart_role_change_cb_timer)
+ NR_ASYNC_TIMER_SET(0,nr_ice_candidate_pair_restart_stun_role_change_cb,pair,&pair->restart_role_change_cb_timer);
+ }
+ }
+
+static void nr_ice_candidate_pair_compute_codeword(nr_ice_cand_pair *pair,
+ nr_ice_candidate *lcand, nr_ice_candidate *rcand)
+ {
+ char as_string[2048];
+
+ snprintf(as_string,
+ sizeof(as_string),
+ "%s|%s(%s|%s)",
+ lcand->addr.as_string,
+ rcand->addr.as_string,
+ lcand->label,
+ rcand->label);
+
+ nr_ice_compute_codeword(as_string,strlen(as_string),pair->codeword);
+ }
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_candidate_pair.h b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_candidate_pair.h
new file mode 100644
index 0000000000..49da1f7802
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_candidate_pair.h
@@ -0,0 +1,101 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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 _ice_candidate_pair_h
+#define _ice_candidate_pair_h
+#ifdef __cplusplus
+using namespace std;
+extern "C" {
+#endif /* __cplusplus */
+
+
+struct nr_ice_cand_pair_ {
+ nr_ice_peer_ctx *pctx;
+ char codeword[5];
+ char *as_string;
+ int state; /* The current state (S 5.7.3) */
+#define NR_ICE_PAIR_STATE_FROZEN 1
+#define NR_ICE_PAIR_STATE_WAITING 2
+#define NR_ICE_PAIR_STATE_IN_PROGRESS 3
+#define NR_ICE_PAIR_STATE_FAILED 4
+#define NR_ICE_PAIR_STATE_SUCCEEDED 5
+#define NR_ICE_PAIR_STATE_CANCELLED 6
+
+ UCHAR peer_nominated; /* The peer sent USE-CANDIDATE
+ on this check */
+ UCHAR nominated; /* Is this nominated or not */
+
+ UCHAR triggered; /* Ignore further trigger check requests */
+
+ UINT8 priority; /* The priority for this pair */
+ nr_ice_candidate *local; /* The local candidate */
+ nr_ice_candidate *remote; /* The remote candidate */
+ char *foundation; /* The combined foundations */
+
+ // for RTCIceCandidatePairStats
+ UINT8 bytes_sent;
+ UINT8 bytes_recvd;
+ struct timeval last_sent;
+ struct timeval last_recvd;
+
+ nr_stun_client_ctx *stun_client; /* STUN context when acting as a client */
+ void *stun_client_handle;
+
+ void *stun_cb_timer;
+ void *restart_role_change_cb_timer;
+ void *restart_nominated_cb_timer;
+
+ TAILQ_ENTRY(nr_ice_cand_pair_) check_queue_entry; /* the check list */
+ TAILQ_ENTRY(nr_ice_cand_pair_) triggered_check_queue_entry; /* the trigger check queue */
+};
+
+int nr_ice_candidate_pair_create(nr_ice_peer_ctx *pctx, nr_ice_candidate *lcand,nr_ice_candidate *rcand,nr_ice_cand_pair **pairp);
+int nr_ice_candidate_pair_unfreeze(nr_ice_peer_ctx *pctx, nr_ice_cand_pair *pair);
+int nr_ice_candidate_pair_start(nr_ice_peer_ctx *pctx, nr_ice_cand_pair *pair);
+void nr_ice_candidate_pair_set_state(nr_ice_peer_ctx *pctx, nr_ice_cand_pair *pair, int state);
+void nr_ice_candidate_pair_dump_state(nr_ice_cand_pair *pair, int log_level);
+void nr_ice_candidate_pair_cancel(nr_ice_peer_ctx *pctx,nr_ice_cand_pair *pair, int move_to_wait_state);
+int nr_ice_candidate_pair_select(nr_ice_cand_pair *pair);
+int nr_ice_candidate_pair_do_triggered_check(nr_ice_peer_ctx *pctx, nr_ice_cand_pair *pair);
+void nr_ice_candidate_pair_insert(nr_ice_cand_pair_head *head,nr_ice_cand_pair *pair);
+int nr_ice_candidate_pair_trigger_check_append(nr_ice_cand_pair_head *head,nr_ice_cand_pair *pair);
+void nr_ice_candidate_pair_restart_stun_nominated_cb(NR_SOCKET s, int how, void *cb_arg);
+int nr_ice_candidate_pair_destroy(nr_ice_cand_pair **pairp);
+void nr_ice_candidate_pair_role_change(nr_ice_cand_pair *pair);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_codeword.h b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_codeword.h
new file mode 100644
index 0000000000..76fce0b69b
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_codeword.h
@@ -0,0 +1,41 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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 _ice_codeword_h
+#define _ice_codeword_h
+
+void nr_ice_compute_codeword(char *buf, int len,char *codeword);
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_component.c b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_component.c
new file mode 100644
index 0000000000..584a9466bc
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_component.c
@@ -0,0 +1,1786 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+*/
+
+#include <string.h>
+#include <assert.h>
+#include <nr_api.h>
+#include <registry.h>
+#include <async_timer.h>
+#include "ice_ctx.h"
+#include "ice_codeword.h"
+#include "stun.h"
+#include "nr_socket_local.h"
+#include "nr_socket_turn.h"
+#include "nr_socket_wrapper.h"
+#include "nr_socket_buffered_stun.h"
+#include "nr_socket_multi_tcp.h"
+#include "ice_reg.h"
+#include "nr_crypto.h"
+#include "r_time.h"
+
+static void nr_ice_component_refresh_consent_cb(NR_SOCKET s, int how, void *cb_arg);
+static int nr_ice_component_stun_server_default_cb(void *cb_arg,nr_stun_server_ctx *stun_ctx,nr_socket *sock, nr_stun_server_request *req, int *dont_free, int *error);
+static int nr_ice_pre_answer_request_destroy(nr_ice_pre_answer_request **parp);
+int nr_ice_component_can_candidate_addr_pair(nr_transport_addr *local, nr_transport_addr *remote);
+int nr_ice_component_can_candidate_tcptype_pair(nr_socket_tcp_type left, nr_socket_tcp_type right);
+void nr_ice_component_consent_calc_consent_timer(nr_ice_component *comp);
+void nr_ice_component_consent_schedule_consent_timer(nr_ice_component *comp);
+int nr_ice_component_refresh_consent(nr_stun_client_ctx *ctx, NR_async_cb finished_cb, void *cb_arg);
+int nr_ice_component_setup_consent(nr_ice_component *comp);
+int nr_ice_pre_answer_enqueue(nr_ice_component *comp, nr_socket *sock, nr_stun_server_request *req, int *dont_free);
+
+/* This function takes ownership of the contents of req (but not req itself) */
+static int nr_ice_pre_answer_request_create(nr_transport_addr *dst, nr_stun_server_request *req, nr_ice_pre_answer_request **parp)
+ {
+ int r, _status;
+ nr_ice_pre_answer_request *par = 0;
+ nr_stun_message_attribute *attr;
+
+ if (!(par = RCALLOC(sizeof(nr_ice_pre_answer_request))))
+ ABORT(R_NO_MEMORY);
+
+ par->req = *req; /* Struct assignment */
+ memset(req, 0, sizeof(*req)); /* Zero contents to avoid confusion */
+
+ if (r=nr_transport_addr_copy(&par->local_addr, dst))
+ ABORT(r);
+ if (!nr_stun_message_has_attribute(par->req.request, NR_STUN_ATTR_USERNAME, &attr))
+ ABORT(R_INTERNAL);
+ if (!(par->username = r_strdup(attr->u.username)))
+ ABORT(R_NO_MEMORY);
+
+ *parp=par;
+ _status=0;
+ abort:
+ if (_status) {
+ /* Erase the request so we don't free it */
+ memset(&par->req, 0, sizeof(nr_stun_server_request));
+ nr_ice_pre_answer_request_destroy(&par);
+ }
+
+ return(_status);
+ }
+
+static int nr_ice_pre_answer_request_destroy(nr_ice_pre_answer_request **parp)
+ {
+ nr_ice_pre_answer_request *par;
+
+ if (!parp || !*parp)
+ return(0);
+
+ par = *parp;
+ *parp = 0;
+
+ nr_stun_message_destroy(&par->req.request);
+ nr_stun_message_destroy(&par->req.response);
+
+ RFREE(par->username);
+ RFREE(par);
+
+ return(0);
+ }
+
+int nr_ice_component_create(nr_ice_media_stream *stream, int component_id, nr_ice_component **componentp)
+ {
+ int _status;
+ nr_ice_component *comp=0;
+
+ if(!(comp=RCALLOC(sizeof(nr_ice_component))))
+ ABORT(R_NO_MEMORY);
+
+ comp->state=NR_ICE_COMPONENT_UNPAIRED;
+ comp->component_id=component_id;
+ comp->stream=stream;
+ comp->ctx=stream->ctx;
+
+ STAILQ_INIT(&comp->sockets);
+ TAILQ_INIT(&comp->candidates);
+ STAILQ_INIT(&comp->pre_answer_reqs);
+
+ STAILQ_INSERT_TAIL(&stream->components,comp,entry);
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_ice_component_destroy(nr_ice_component **componentp)
+ {
+ nr_ice_component *component;
+ nr_ice_socket *s1,*s2;
+ nr_ice_candidate *c1,*c2;
+ nr_ice_pre_answer_request *r1,*r2;
+
+ if(!componentp || !*componentp)
+ return(0);
+
+ component=*componentp;
+ *componentp=0;
+
+ nr_ice_component_consent_destroy(component);
+
+ /* Detach ourselves from the sockets */
+ if (component->local_component){
+ nr_ice_socket *isock=STAILQ_FIRST(&component->local_component->sockets);
+ while(isock){
+ nr_stun_server_remove_client(isock->stun_server, component);
+ isock=STAILQ_NEXT(isock, entry);
+ }
+ }
+
+ /* candidates MUST be destroyed before the sockets so that
+ they can deregister */
+ TAILQ_FOREACH_SAFE(c1, &component->candidates, entry_comp, c2){
+ TAILQ_REMOVE(&component->candidates,c1,entry_comp);
+ nr_ice_candidate_destroy(&c1);
+ }
+
+ STAILQ_FOREACH_SAFE(s1, &component->sockets, entry, s2){
+ STAILQ_REMOVE(&component->sockets,s1,nr_ice_socket_,entry);
+ nr_ice_socket_destroy(&s1);
+ }
+
+ STAILQ_FOREACH_SAFE(r1, &component->pre_answer_reqs, entry, r2){
+ STAILQ_REMOVE(&component->pre_answer_reqs,r1,nr_ice_pre_answer_request_, entry);
+ nr_ice_pre_answer_request_destroy(&r1);
+ }
+
+ RFREE(component);
+ return(0);
+ }
+
+static int nr_ice_component_create_stun_server_ctx(nr_ice_component *component, nr_ice_socket *isock, nr_transport_addr *addr, char *lufrag, Data *pwd)
+ {
+ char label[256];
+ int r,_status;
+
+ /* Create a STUN server context for this socket */
+ snprintf(label, sizeof(label), "server(%s)", addr->as_string);
+ if(r=nr_stun_server_ctx_create(label,&isock->stun_server))
+ ABORT(r);
+
+ /* Add the default STUN credentials so that we can respond before
+ we hear about the peer.*/
+ if(r=nr_stun_server_add_default_client(isock->stun_server, lufrag, pwd, nr_ice_component_stun_server_default_cb, component))
+ ABORT(r);
+
+ /* Do this last; if this function fails, we should not be taking a reference to isock */
+ if(r=nr_ice_socket_register_stun_server(isock,isock->stun_server,&isock->stun_server_handle))
+ ABORT(r);
+
+ _status = 0;
+ abort:
+ return(_status);
+ }
+
+static int nr_ice_component_initialize_udp(struct nr_ice_ctx_ *ctx,nr_ice_component *component, nr_local_addr *addrs, int addr_ct, char *lufrag, Data *pwd)
+ {
+ nr_socket *sock;
+ nr_ice_socket *isock=0;
+ nr_ice_candidate *cand=0;
+ int i;
+ int j;
+ int r,_status;
+
+ if(ctx->flags & NR_ICE_CTX_FLAGS_ONLY_PROXY) {
+ /* No UDP support if we must use a proxy */
+ return 0;
+ }
+
+ /* Now one ice_socket for each address */
+ for(i=0;i<addr_ct;i++){
+ char suppress;
+
+ if(r=NR_reg_get2_char(NR_ICE_REG_SUPPRESS_INTERFACE_PRFX,addrs[i].addr.ifname,&suppress)){
+ if(r!=R_NOT_FOUND)
+ ABORT(r);
+ }
+ else{
+ if(suppress)
+ continue;
+ }
+ r_log(LOG_ICE,LOG_DEBUG,"ICE-STREAM(%s): host address %s",component->stream->label,addrs[i].addr.as_string);
+ if((r=nr_socket_factory_create_socket(ctx->socket_factory,&addrs[i].addr,&sock))){
+ r_log(LOG_ICE,LOG_WARNING,"ICE-STREAM(%s): couldn't create socket for address %s",component->stream->label,addrs[i].addr.as_string);
+ continue;
+ }
+
+ if(r=nr_ice_socket_create(ctx,component,sock,NR_ICE_SOCKET_TYPE_DGRAM,&isock))
+ ABORT(r);
+
+ /* Create a STUN server context for this socket */
+ if ((r=nr_ice_component_create_stun_server_ctx(component,isock,&addrs[i].addr,lufrag,pwd)))
+ ABORT(r);
+
+ /* Make sure we don't leak this. Failures might result in it being
+ * unused, but we hand off references to this in enough places below
+ * that unwinding it all becomes impractical. */
+ STAILQ_INSERT_TAIL(&component->sockets,isock,entry);
+
+ if (!(component->stream->flags & NR_ICE_CTX_FLAGS_RELAY_ONLY)) {
+ /* Create one host candidate */
+ if(r=nr_ice_candidate_create(ctx,component,isock,sock,HOST,0,0,
+ component->component_id,&cand))
+ ABORT(r);
+
+ TAILQ_INSERT_TAIL(&component->candidates,cand,entry_comp);
+ component->candidate_ct++;
+ cand=0;
+
+ /* And a srvrflx candidate for each STUN server */
+ for(j=0;j<component->stream->stun_server_ct;j++){
+ r_log(LOG_ICE,LOG_DEBUG,"ICE-STREAM(%s): Checking STUN server %s %s", component->stream->label, component->stream->stun_servers[j].addr.fqdn, component->stream->stun_servers[j].addr.as_string);
+ /* Skip non-UDP */
+ if (component->stream->stun_servers[j].addr.protocol != IPPROTO_UDP) continue;
+
+ if (nr_transport_addr_check_compatibility(
+ &addrs[i].addr, &component->stream->stun_servers[j].addr)) {
+ r_log(LOG_ICE,LOG_INFO,"ICE-STREAM(%s): Skipping STUN server because of address type mis-match",component->stream->label);
+ continue;
+ }
+
+ /* Ensure id is set (nr_ice_ctx_set_stun_servers does not) */
+ component->stream->stun_servers[j].id = j;
+ if(r=nr_ice_candidate_create(ctx,component,
+ isock,sock,SERVER_REFLEXIVE,0,
+ &component->stream->stun_servers[j],component->component_id,&cand))
+ ABORT(r);
+ TAILQ_INSERT_TAIL(&component->candidates,cand,entry_comp);
+ component->candidate_ct++;
+ cand=0;
+ }
+ }
+ else{
+ r_log(LOG_ICE,LOG_WARNING,"ICE-STREAM(%s): relay only option results in no host candidate for %s",component->stream->label,addrs[i].addr.as_string);
+ }
+
+#ifdef USE_TURN
+ if ((component->stream->flags & NR_ICE_CTX_FLAGS_RELAY_ONLY) &&
+ (component->stream->turn_server_ct == 0)) {
+ r_log(LOG_ICE,LOG_ERR,"ICE-STREAM(%s): relay only option is set without any TURN server configured",component->stream->label);
+ }
+ /* And both a srvrflx and relayed candidate for each TURN server (unless
+ we're in relay-only mode, in which case just the relayed one) */
+ for(j=0;j<component->stream->turn_server_ct;j++){
+ nr_socket *turn_sock;
+ nr_ice_candidate *srvflx_cand=0;
+
+ r_log(LOG_ICE,LOG_DEBUG,"ICE-STREAM(%s): Checking TURN server %s %s", component->stream->label, component->stream->turn_servers[j].turn_server.addr.fqdn, component->stream->turn_servers[j].turn_server.addr.as_string);
+
+ /* Skip non-UDP */
+ if (component->stream->turn_servers[j].turn_server.addr.protocol != IPPROTO_UDP)
+ continue;
+
+ if (nr_transport_addr_check_compatibility(
+ &addrs[i].addr, &component->stream->turn_servers[j].turn_server.addr)) {
+ r_log(LOG_ICE,LOG_INFO,"ICE-STREAM(%s): Skipping TURN server because of address type mis-match",component->stream->label);
+ continue;
+ }
+
+ if (!(component->stream->flags & NR_ICE_CTX_FLAGS_RELAY_ONLY)) {
+ /* Ensure id is set with a unique value */
+ component->stream->turn_servers[j].turn_server.id = j + component->stream->stun_server_ct;
+ /* srvrflx */
+ if(r=nr_ice_candidate_create(ctx,component,
+ isock,sock,SERVER_REFLEXIVE,0,
+ &component->stream->turn_servers[j].turn_server,component->component_id,&cand))
+ ABORT(r);
+ cand->state=NR_ICE_CAND_STATE_INITIALIZING; /* Don't start */
+ cand->done_cb=nr_ice_gather_finished_cb;
+ cand->cb_arg=cand;
+
+ TAILQ_INSERT_TAIL(&component->candidates,cand,entry_comp);
+ component->candidate_ct++;
+ srvflx_cand=cand;
+ cand=0;
+ }
+ /* relayed*/
+ if(r=nr_socket_turn_create(&turn_sock))
+ ABORT(r);
+ if(r=nr_ice_candidate_create(ctx,component,
+ isock,turn_sock,RELAYED,0,
+ &component->stream->turn_servers[j].turn_server,component->component_id,&cand))
+ ABORT(r);
+ if (srvflx_cand) {
+ cand->u.relayed.srvflx_candidate=srvflx_cand;
+ srvflx_cand->u.srvrflx.relay_candidate=cand;
+ }
+ cand->u.relayed.server=&component->stream->turn_servers[j];
+ TAILQ_INSERT_TAIL(&component->candidates,cand,entry_comp);
+ component->candidate_ct++;
+
+ cand=0;
+ }
+#endif /* USE_TURN */
+ }
+
+ _status = 0;
+ abort:
+ return(_status);
+ }
+
+static int nr_ice_component_get_port_from_ephemeral_range(uint16_t *port)
+ {
+ int _status, r;
+ void *buf = port;
+ if(r=nr_crypto_random_bytes(buf, 2))
+ ABORT(r);
+ *port|=49152; /* make it fit into IANA ephemeral port range >= 49152 */
+ _status=0;
+abort:
+ return(_status);
+ }
+
+static int nr_ice_component_create_tcp_host_candidate(struct nr_ice_ctx_ *ctx,
+ nr_ice_component *component, nr_transport_addr *interface_addr, nr_socket_tcp_type tcp_type,
+ int backlog, int so_sock_ct, char *lufrag, Data *pwd, nr_ice_socket **isock)
+ {
+ int r,_status;
+ nr_ice_candidate *cand=0;
+ int tries=3;
+ nr_ice_socket *isock_tmp=0;
+ nr_socket *nrsock=0;
+ nr_transport_addr addr;
+ uint16_t local_port;
+
+ if ((r=nr_transport_addr_copy(&addr,interface_addr)))
+ ABORT(r);
+ addr.protocol=IPPROTO_TCP;
+
+ do{
+ if (!tries--)
+ ABORT(r);
+
+ if((r=nr_ice_component_get_port_from_ephemeral_range(&local_port)))
+ ABORT(r);
+
+ if ((r=nr_transport_addr_set_port(&addr, local_port)))
+ ABORT(r);
+
+ if((r=nr_transport_addr_fmt_addr_string(&addr)))
+ ABORT(r);
+
+ /* It would be better to stop trying if there is error other than
+ port already used, but it'd require significant work to support this. */
+ r=nr_socket_multi_tcp_create(ctx,component,&addr,tcp_type,so_sock_ct,NR_STUN_MAX_MESSAGE_SIZE,&nrsock);
+
+ } while(r);
+
+ if((tcp_type == TCP_TYPE_PASSIVE) && (r=nr_socket_listen(nrsock,backlog)))
+ ABORT(r);
+
+ if((r=nr_ice_socket_create(ctx,component,nrsock,NR_ICE_SOCKET_TYPE_STREAM_TCP,&isock_tmp)))
+ ABORT(r);
+
+ /* nr_ice_socket took ownership of nrsock */
+ nrsock=NULL;
+
+ /* Create a STUN server context for this socket */
+ if ((r=nr_ice_component_create_stun_server_ctx(component,isock_tmp,&addr,lufrag,pwd)))
+ ABORT(r);
+
+ if((r=nr_ice_candidate_create(ctx,component,isock_tmp,isock_tmp->sock,HOST,tcp_type,0,
+ component->component_id,&cand)))
+ ABORT(r);
+
+ if (isock)
+ *isock=isock_tmp;
+
+ TAILQ_INSERT_TAIL(&component->candidates,cand,entry_comp);
+ component->candidate_ct++;
+
+ STAILQ_INSERT_TAIL(&component->sockets,isock_tmp,entry);
+
+ _status=0;
+abort:
+ if (_status) {
+ nr_ice_socket_destroy(&isock_tmp);
+ nr_socket_destroy(&nrsock);
+ }
+ return(_status);
+ }
+
+static int nr_ice_component_initialize_tcp(struct nr_ice_ctx_ *ctx,nr_ice_component *component, nr_local_addr *addrs, int addr_ct, char *lufrag, Data *pwd)
+ {
+ nr_ice_candidate *cand=0;
+ int i;
+ int j;
+ int r,_status;
+ int so_sock_ct=0;
+ int backlog=10;
+ char ice_tcp_disabled=1;
+
+ r_log(LOG_ICE,LOG_DEBUG,"nr_ice_component_initialize_tcp");
+
+ if(r=NR_reg_get_int4(NR_ICE_REG_ICE_TCP_SO_SOCK_COUNT,&so_sock_ct)){
+ if(r!=R_NOT_FOUND)
+ ABORT(r);
+ }
+
+ if(r=NR_reg_get_int4(NR_ICE_REG_ICE_TCP_LISTEN_BACKLOG,&backlog)){
+ if(r!=R_NOT_FOUND)
+ ABORT(r);
+ }
+
+ if ((r=NR_reg_get_char(NR_ICE_REG_ICE_TCP_DISABLE, &ice_tcp_disabled))) {
+ if (r != R_NOT_FOUND)
+ ABORT(r);
+ }
+ if ((component->stream->flags & NR_ICE_CTX_FLAGS_RELAY_ONLY) ||
+ (component->stream->flags & NR_ICE_CTX_FLAGS_ONLY_PROXY)) {
+ r_log(LOG_ICE,LOG_WARNING,"ICE-STREAM(%s): relay/proxy only option results in ICE TCP being disabled",component->stream->label);
+ ice_tcp_disabled = 1;
+ }
+
+ for(i=0;i<addr_ct;i++){
+ char suppress;
+ nr_ice_socket *isock_psv=0;
+ nr_ice_socket *isock_so=0;
+
+ if(r=NR_reg_get2_char(NR_ICE_REG_SUPPRESS_INTERFACE_PRFX,addrs[i].addr.ifname,&suppress)){
+ if(r!=R_NOT_FOUND)
+ ABORT(r);
+ }
+ else if(suppress) {
+ continue;
+ }
+
+ if (!ice_tcp_disabled) {
+ /* passive host candidate */
+ if ((r=nr_ice_component_create_tcp_host_candidate(ctx, component, &addrs[i].addr,
+ TCP_TYPE_PASSIVE, backlog, 0, lufrag, pwd, &isock_psv))) {
+ r_log(LOG_ICE,LOG_WARNING,"ICE-STREAM(%s): failed to create passive TCP host candidate: %d",component->stream->label,r);
+ }
+
+ /* active host candidate */
+ if ((r=nr_ice_component_create_tcp_host_candidate(ctx, component, &addrs[i].addr,
+ TCP_TYPE_ACTIVE, 0, 0, lufrag, pwd, NULL))) {
+ r_log(LOG_ICE,LOG_WARNING,"ICE-STREAM(%s): failed to create active TCP host candidate: %d",component->stream->label,r);
+ }
+
+ /* simultaneous-open host candidate */
+ if (so_sock_ct) {
+ if ((r=nr_ice_component_create_tcp_host_candidate(ctx, component, &addrs[i].addr,
+ TCP_TYPE_SO, 0, so_sock_ct, lufrag, pwd, &isock_so))) {
+ r_log(LOG_ICE,LOG_WARNING,"ICE-STREAM(%s): failed to create simultanous open TCP host candidate: %d",component->stream->label,r);
+ }
+ }
+
+ /* And srvrflx candidates for each STUN server */
+ for(j=0;j<component->stream->stun_server_ct;j++){
+ if (component->stream->stun_servers[j].addr.protocol != IPPROTO_TCP) continue;
+
+ if (isock_psv) {
+ if(r=nr_ice_candidate_create(ctx,component,
+ isock_psv,isock_psv->sock,SERVER_REFLEXIVE,TCP_TYPE_PASSIVE,
+ &component->stream->stun_servers[j],component->component_id,&cand))
+ ABORT(r);
+ TAILQ_INSERT_TAIL(&component->candidates,cand,entry_comp);
+ component->candidate_ct++;
+ cand=0;
+ }
+
+ if (isock_so) {
+ if(r=nr_ice_candidate_create(ctx,component,
+ isock_so,isock_so->sock,SERVER_REFLEXIVE,TCP_TYPE_SO,
+ &component->stream->stun_servers[j],component->component_id,&cand))
+ ABORT(r);
+ TAILQ_INSERT_TAIL(&component->candidates,cand,entry_comp);
+ component->candidate_ct++;
+ cand=0;
+ }
+ }
+ }
+
+#ifdef USE_TURN
+ /* Create a new relayed candidate for each addr/TURN server pair */
+ for(j=0;j<component->stream->turn_server_ct;j++){
+ nr_transport_addr addr;
+ nr_socket *local_sock;
+ nr_socket *buffered_sock;
+ nr_socket *turn_sock;
+ nr_ice_socket *turn_isock;
+
+ r_log(LOG_ICE,LOG_DEBUG,"ICE-STREAM(%s): Checking TURN server %s %s", component->stream->label, component->stream->turn_servers[j].turn_server.addr.fqdn, component->stream->turn_servers[j].turn_server.addr.as_string);
+
+ /* Skip non-TCP */
+ if (component->stream->turn_servers[j].turn_server.addr.protocol != IPPROTO_TCP)
+ continue;
+
+ /* Create relay candidate */
+ if ((r=nr_transport_addr_copy(&addr, &addrs[i].addr)))
+ ABORT(r);
+ addr.protocol = IPPROTO_TCP;
+
+ if (nr_transport_addr_check_compatibility(
+ &addr, &component->stream->turn_servers[j].turn_server.addr)) {
+ r_log(LOG_ICE,LOG_INFO,"ICE-STREAM(%s): Skipping TURN server because of address type mis-match",component->stream->label);
+ continue;
+ }
+
+ if (!ice_tcp_disabled) {
+ /* Use TURN server to get srflx candidates */
+ if (isock_psv) {
+ if(r=nr_ice_candidate_create(ctx,component,
+ isock_psv,isock_psv->sock,SERVER_REFLEXIVE,TCP_TYPE_PASSIVE,
+ &component->stream->turn_servers[j].turn_server,component->component_id,&cand))
+ ABORT(r);
+ TAILQ_INSERT_TAIL(&component->candidates,cand,entry_comp);
+ component->candidate_ct++;
+ cand=0;
+ }
+
+ if (isock_so) {
+ if(r=nr_ice_candidate_create(ctx,component,
+ isock_so,isock_so->sock,SERVER_REFLEXIVE,TCP_TYPE_SO,
+ &component->stream->turn_servers[j].turn_server,component->component_id,&cand))
+ ABORT(r);
+ TAILQ_INSERT_TAIL(&component->candidates,cand,entry_comp);
+ component->candidate_ct++;
+ cand=0;
+ }
+ }
+
+ if (component->stream->turn_servers[j].turn_server.addr.fqdn[0] != 0) {
+ /* If we're going to use TLS, make sure that's recorded */
+ addr.tls = component->stream->turn_servers[j].turn_server.addr.tls;
+ }
+
+ if ((r=nr_transport_addr_fmt_addr_string(&addr)))
+ ABORT(r);
+
+ r_log(LOG_ICE, LOG_DEBUG,
+ "ICE-STREAM(%s): Creating socket for address %s (turn server %s)",
+ component->stream->label, addr.as_string,
+ component->stream->turn_servers[j].turn_server.addr.as_string);
+
+ /* Create a local socket */
+ if((r=nr_socket_factory_create_socket(ctx->socket_factory,&addr,&local_sock))){
+ r_log(LOG_ICE,LOG_DEBUG,"ICE-STREAM(%s): couldn't create socket for address %s",component->stream->label,addr.as_string);
+ continue;
+ }
+
+ r_log(LOG_ICE,LOG_DEBUG,"nr_ice_component_initialize_tcp creating TURN TCP wrappers");
+
+ /* The TCP buffered socket */
+ if((r=nr_socket_buffered_stun_create(local_sock, NR_STUN_MAX_MESSAGE_SIZE, TURN_TCP_FRAMING, &buffered_sock)))
+ ABORT(r);
+
+ /* The TURN socket */
+ if(r=nr_socket_turn_create(&turn_sock))
+ ABORT(r);
+
+ /* Create an ICE socket */
+ if((r=nr_ice_socket_create(ctx, component, buffered_sock, NR_ICE_SOCKET_TYPE_STREAM_TURN, &turn_isock)))
+ ABORT(r);
+
+
+ /* Create a STUN server context for this socket */
+ if ((r=nr_ice_component_create_stun_server_ctx(component,turn_isock,&addr,lufrag,pwd)))
+ ABORT(r);
+
+ /* Make sure we don't leak this. Failures might result in it being
+ * unused, but we hand off references to this in enough places below
+ * that unwinding it all becomes impractical. */
+ STAILQ_INSERT_TAIL(&component->sockets,turn_isock,entry);
+
+ /* Attach ourselves to it */
+ if(r=nr_ice_candidate_create(ctx,component,
+ turn_isock,turn_sock,RELAYED,TCP_TYPE_NONE,
+ &component->stream->turn_servers[j].turn_server,component->component_id,&cand))
+ ABORT(r);
+ cand->u.relayed.srvflx_candidate=NULL;
+ cand->u.relayed.server=&component->stream->turn_servers[j];
+ TAILQ_INSERT_TAIL(&component->candidates,cand,entry_comp);
+ component->candidate_ct++;
+ cand=0;
+ }
+#endif /* USE_TURN */
+ }
+
+ _status = 0;
+ abort:
+ return(_status);
+ }
+
+
+/* Make all the candidates we can make at the beginning */
+int nr_ice_component_initialize(struct nr_ice_ctx_ *ctx,nr_ice_component *component)
+ {
+ int r,_status;
+ nr_local_addr *addrs=ctx->local_addrs;
+ int addr_ct=ctx->local_addr_ct;
+ char *lufrag;
+ char *lpwd;
+ Data pwd;
+ nr_ice_candidate *cand;
+
+ if (component->candidate_ct) {
+ r_log(LOG_ICE,LOG_DEBUG,"ICE(%s): component with id %d already has candidates, probably restarting gathering because of a new stream",ctx->label,component->component_id);
+ return(0);
+ }
+
+ r_log(LOG_ICE,LOG_DEBUG,"ICE(%s): initializing component with id %d",ctx->label,component->component_id);
+
+ if(addr_ct==0){
+ r_log(LOG_ICE,LOG_ERR,"ICE(%s): no local addresses available",ctx->label);
+ ABORT(R_NOT_FOUND);
+ }
+
+ /* Note: we need to recompute these because
+ we have not yet computed the values in the peer media stream.*/
+ lufrag=component->stream->ufrag;
+ assert(lufrag);
+ if (!lufrag)
+ ABORT(R_INTERNAL);
+ lpwd=component->stream->pwd;
+ assert(lpwd);
+ if (!lpwd)
+ ABORT(R_INTERNAL);
+ INIT_DATA(pwd, (UCHAR *)lpwd, strlen(lpwd));
+
+ /* Initialize the UDP candidates */
+ if (r=nr_ice_component_initialize_udp(ctx, component, addrs, addr_ct, lufrag, &pwd))
+ r_log(LOG_ICE,LOG_INFO,"ICE(%s): failed to create UDP candidates with error %d",ctx->label,r);
+ /* And the TCP candidates */
+ if (r=nr_ice_component_initialize_tcp(ctx, component, addrs, addr_ct, lufrag, &pwd))
+ r_log(LOG_ICE,LOG_INFO,"ICE(%s): failed to create TCP candidates with error %d",ctx->label,r);
+
+ /* count the candidates that will be initialized */
+ cand=TAILQ_FIRST(&component->candidates);
+ if(!cand){
+ r_log(LOG_ICE,LOG_ERR,"ICE(%s): couldn't create any valid candidates",ctx->label);
+ ABORT(R_NOT_FOUND);
+ }
+
+ while(cand){
+ ctx->uninitialized_candidates++;
+ cand=TAILQ_NEXT(cand,entry_comp);
+ }
+
+ /* Now initialize all the candidates */
+ cand=TAILQ_FIRST(&component->candidates);
+ while(cand){
+ if(cand->state!=NR_ICE_CAND_STATE_INITIALIZING){
+ nr_ice_candidate_initialize(cand,nr_ice_gather_finished_cb,cand);
+ }
+ cand=TAILQ_NEXT(cand,entry_comp);
+ }
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+void nr_ice_component_stop_gathering(nr_ice_component *component)
+ {
+ nr_ice_candidate *c1,*c2;
+ TAILQ_FOREACH_SAFE(c1, &component->candidates, entry_comp, c2){
+ nr_ice_candidate_stop_gathering(c1);
+ }
+ }
+
+int nr_ice_component_is_done_gathering(nr_ice_component *comp)
+ {
+ nr_ice_candidate *cand=TAILQ_FIRST(&comp->candidates);
+ while(cand){
+ if(cand->state != NR_ICE_CAND_STATE_INITIALIZED &&
+ cand->state != NR_ICE_CAND_STATE_FAILED){
+ return 0;
+ }
+ cand=TAILQ_NEXT(cand,entry_comp);
+ }
+ return 1;
+ }
+
+
+static int nr_ice_any_peer_paired(nr_ice_candidate* cand) {
+ nr_ice_peer_ctx* pctx=STAILQ_FIRST(&cand->ctx->peers);
+ while(pctx && pctx->state == NR_ICE_PEER_STATE_UNPAIRED){
+ /* Is it worth actually looking through the check lists? Probably not. */
+ pctx=STAILQ_NEXT(pctx,entry);
+ }
+ return pctx != NULL;
+}
+
+/*
+ Compare this newly initialized candidate against the other initialized
+ candidates and discard the lower-priority one if they are redundant.
+
+ This algorithm combined with the other algorithms, favors
+ host > srflx > relay
+ */
+int nr_ice_component_maybe_prune_candidate(nr_ice_ctx *ctx, nr_ice_component *comp, nr_ice_candidate *c1, int *was_pruned)
+ {
+ nr_ice_candidate *c2, *tmp = NULL;
+
+ *was_pruned = 0;
+ c2 = TAILQ_FIRST(&comp->candidates);
+ while(c2){
+ if((c1 != c2) &&
+ (c2->state == NR_ICE_CAND_STATE_INITIALIZED) &&
+ !nr_transport_addr_cmp(&c1->base,&c2->base,NR_TRANSPORT_ADDR_CMP_MODE_ALL) &&
+ !nr_transport_addr_cmp(&c1->addr,&c2->addr,NR_TRANSPORT_ADDR_CMP_MODE_ALL)){
+
+ if((c1->type == c2->type) ||
+ (!(ctx->flags & NR_ICE_CTX_FLAGS_DISABLE_HOST_CANDIDATES) &&
+ !(ctx->flags & NR_ICE_CTX_FLAGS_OBFUSCATE_HOST_ADDRESSES) &&
+ ((c1->type==HOST && c2->type == SERVER_REFLEXIVE) ||
+ (c2->type==HOST && c1->type == SERVER_REFLEXIVE)))){
+
+ /*
+ These are redundant. Remove the lower pri one, or if pairing has
+ already occurred, remove the newest one.
+
+ Since this algorithmis run whenever a new candidate
+ is initialized, there should at most one duplicate.
+ */
+ if ((c1->priority <= c2->priority) || nr_ice_any_peer_paired(c2)) {
+ tmp = c1;
+ *was_pruned = 1;
+ }
+ else {
+ tmp = c2;
+ }
+ break;
+ }
+ }
+
+ c2=TAILQ_NEXT(c2,entry_comp);
+ }
+
+ if (tmp) {
+ r_log(LOG_ICE,LOG_DEBUG,"ICE(%s)/CAND(%s): Removing redundant candidate",
+ ctx->label,tmp->label);
+
+ TAILQ_REMOVE(&comp->candidates,tmp,entry_comp);
+ comp->candidate_ct--;
+ TAILQ_REMOVE(&tmp->isock->candidates,tmp,entry_sock);
+
+ nr_ice_candidate_destroy(&tmp);
+ }
+
+ return 0;
+ }
+
+static int nr_ice_component_pair_matches_check(nr_ice_component *comp, nr_ice_cand_pair *pair, nr_transport_addr *local_addr, nr_stun_server_request *req)
+ {
+ if(pair->remote->component->component_id!=comp->component_id)
+ return(0);
+
+ if(nr_transport_addr_cmp(&pair->local->base,local_addr,NR_TRANSPORT_ADDR_CMP_MODE_ALL))
+ return(0);
+
+ if(nr_transport_addr_cmp(&pair->remote->addr,&req->src_addr,NR_TRANSPORT_ADDR_CMP_MODE_ALL))
+ return(0);
+
+ return(1);
+ }
+
+static int nr_ice_component_handle_triggered_check(nr_ice_component *comp, nr_ice_cand_pair *pair, nr_stun_server_request *req, int *error)
+ {
+ nr_stun_message *sreq=req->request;
+ int r=0,_status;
+
+ if(nr_stun_message_has_attribute(sreq,NR_STUN_ATTR_USE_CANDIDATE,0)){
+ if(comp->stream->pctx->controlling){
+ r_log(LOG_ICE,LOG_WARNING,"ICE-PEER(%s)/CAND_PAIR(%s): Peer sent USE-CANDIDATE but is controlled",comp->stream->pctx->label, pair->codeword);
+ }
+ else{
+ /* If this is the first time we've noticed this is nominated...*/
+ pair->peer_nominated=1;
+
+ if(pair->state==NR_ICE_PAIR_STATE_SUCCEEDED && !pair->nominated){
+ pair->nominated=1;
+
+ if(r=nr_ice_component_nominated_pair(pair->remote->component, pair)) {
+ *error=(r==R_NO_MEMORY)?500:400;
+ ABORT(r);
+ }
+ }
+ }
+ }
+
+ /* Note: the RFC says to trigger first and then nominate. But in that case
+ * the canceled trigger pair would get nominated and the cloned trigger pair
+ * would not get the nomination status cloned with it.*/
+ if(r=nr_ice_candidate_pair_do_triggered_check(comp->stream->pctx,pair)) {
+ *error=(r==R_NO_MEMORY)?500:400;
+ ABORT(r);
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+/* Section 7.2.1 */
+static int nr_ice_component_process_incoming_check(nr_ice_component *comp, nr_transport_addr *local_addr, nr_stun_server_request *req, int *error)
+ {
+ nr_ice_cand_pair *pair;
+ nr_ice_candidate *pcand=0;
+ nr_stun_message *sreq=req->request;
+ nr_stun_message_attribute *attr;
+ int r=0,_status;
+ int found_valid=0;
+
+ r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s)/STREAM(%s)/COMP(%d): received request from %s",comp->stream->pctx->label,comp->stream->label,comp->component_id,req->src_addr.as_string);
+
+ if (comp->state == NR_ICE_COMPONENT_DISABLED)
+ ABORT(R_REJECTED);
+
+ /* Check for role conficts (7.2.1.1) */
+ if(comp->stream->pctx->controlling){
+ if(nr_stun_message_has_attribute(sreq,NR_STUN_ATTR_ICE_CONTROLLING,&attr)){
+ /* OK, there is a conflict. Who's right? */
+ r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s): role conflict, both controlling",comp->stream->pctx->label);
+
+ if(attr->u.ice_controlling > comp->stream->pctx->tiebreaker){
+ /* Update the peer ctx. This will propagate to all candidate pairs
+ in the context. */
+ nr_ice_peer_ctx_switch_controlling_role(comp->stream->pctx);
+ }
+ else {
+ /* We are: throw an error */
+ r_log(LOG_ICE,LOG_WARNING,"ICE-PEER(%s): returning 487 role conflict",comp->stream->pctx->label);
+
+ *error=487;
+ ABORT(R_REJECTED);
+ }
+ }
+ }
+ else{
+ if(nr_stun_message_has_attribute(sreq,NR_STUN_ATTR_ICE_CONTROLLED,&attr)){
+ /* OK, there is a conflict. Who's right? */
+ r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s): role conflict, both controlled",comp->stream->pctx->label);
+
+ if(attr->u.ice_controlled < comp->stream->pctx->tiebreaker){
+ /* Update the peer ctx. This will propagate to all candidate pairs
+ in the context. */
+ nr_ice_peer_ctx_switch_controlling_role(comp->stream->pctx);
+ }
+ else {
+ /* We are: throw an error */
+ r_log(LOG_ICE,LOG_WARNING,"ICE-PEER(%s): returning 487 role conflict",comp->stream->pctx->label);
+
+ *error=487;
+ ABORT(R_REJECTED);
+ }
+ }
+ }
+
+ r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s): This STUN request appears to map to local addr %s",comp->stream->pctx->label,local_addr->as_string);
+
+ pair=TAILQ_FIRST(&comp->stream->check_list);
+ while(pair){
+ /* Since triggered checks create duplicate pairs (in this implementation)
+ * we are willing to handle multiple matches here. */
+ if(nr_ice_component_pair_matches_check(comp, pair, local_addr, req)){
+ r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s)/CAND_PAIR(%s): Found a matching pair for received check: %s",comp->stream->pctx->label,pair->codeword,pair->as_string);
+ if(r=nr_ice_component_handle_triggered_check(comp, pair, req, error))
+ ABORT(r);
+ ++found_valid;
+ }
+ pair=TAILQ_NEXT(pair,check_queue_entry);
+ }
+
+ if(!found_valid){
+ /* There were no matching pairs, so we need to create a new peer
+ * reflexive candidate pair. */
+
+ if(!nr_stun_message_has_attribute(sreq,NR_STUN_ATTR_PRIORITY,&attr)){
+ r_log(LOG_ICE,LOG_WARNING,"ICE-PEER(%s): Rejecting stun request without priority",comp->stream->pctx->label);
+ *error=400;
+ ABORT(R_BAD_DATA);
+ }
+
+ /* Find our local component candidate */
+ nr_ice_candidate *cand;
+
+ r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s): no matching pair",comp->stream->pctx->label);
+ cand=TAILQ_FIRST(&comp->local_component->candidates);
+ while(cand){
+ if(!nr_transport_addr_cmp(&cand->addr,local_addr,NR_TRANSPORT_ADDR_CMP_MODE_ALL))
+ break;
+
+ cand=TAILQ_NEXT(cand,entry_comp);
+ }
+
+ /* Well, this really shouldn't happen, but it's an error from the
+ other side, so we just throw an error and keep going */
+ if(!cand){
+ r_log(LOG_ICE,LOG_WARNING,"ICE-PEER(%s): stun request to unknown local address %s, discarding",comp->stream->pctx->label,local_addr->as_string);
+
+ *error=400;
+ ABORT(R_NOT_FOUND);
+ }
+
+ /* Now make a peer reflexive (remote) candidate */
+ if(r=nr_ice_peer_peer_rflx_candidate_create(comp->stream->pctx->ctx,"prflx",comp,&req->src_addr,&pcand)) {
+ *error=(r==R_NO_MEMORY)?500:400;
+ ABORT(r);
+ }
+ pcand->priority=attr->u.priority;
+ pcand->state=NR_ICE_CAND_PEER_CANDIDATE_PAIRED;
+
+ /* Finally, create the candidate pair, insert into the check list, and
+ * apply the incoming check to it. */
+ if(r=nr_ice_candidate_pair_create(comp->stream->pctx,cand,pcand,
+ &pair)) {
+ *error=(r==R_NO_MEMORY)?500:400;
+ ABORT(r);
+ }
+
+ nr_ice_candidate_pair_set_state(pair->pctx,pair,NR_ICE_PAIR_STATE_FROZEN);
+ if(r=nr_ice_component_insert_pair(comp,pair)) {
+ *error=(r==R_NO_MEMORY)?500:400;
+ ABORT(r);
+ }
+
+ /* Do this last, since any call to ABORT will destroy pcand */
+ TAILQ_INSERT_TAIL(&comp->candidates,pcand,entry_comp);
+ pcand=0;
+
+ /* Finally start the trigger check if needed */
+ if(r=nr_ice_component_handle_triggered_check(comp, pair, req, error))
+ ABORT(r);
+ }
+
+ _status=0;
+ abort:
+ if(_status){
+ nr_ice_candidate_destroy(&pcand);
+ assert(*error != 0);
+ if(r!=R_NO_MEMORY) assert(*error != 500);
+ }
+ return(_status);
+ }
+
+static int nr_ice_component_stun_server_cb(void *cb_arg,nr_stun_server_ctx *stun_ctx,nr_socket *sock, nr_stun_server_request *req, int *dont_free, int *error)
+ {
+ nr_ice_component *pcomp=cb_arg;
+ nr_transport_addr local_addr;
+ int r,_status;
+
+ if(pcomp->state==NR_ICE_COMPONENT_FAILED) {
+ *error=400;
+ ABORT(R_REJECTED);
+ }
+
+ if (pcomp->local_component->stream->obsolete) {
+ /* Don't do any triggered check stuff in thiis case. */
+ return 0;
+ }
+
+ /* Find the candidate pair that this maps to */
+ if(r=nr_socket_getaddr(sock,&local_addr)) {
+ *error=500;
+ ABORT(r);
+ }
+
+ if (r=nr_ice_component_process_incoming_check(pcomp, &local_addr, req, error))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_ice_component_service_pre_answer_requests(nr_ice_peer_ctx *pctx, nr_ice_component *pcomp, char *username, int *serviced)
+ {
+ nr_ice_pre_answer_request *r1,*r2;
+ nr_ice_component *comp = pcomp->local_component;
+ int r,_status;
+
+ if (serviced)
+ *serviced = 0;
+
+ r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s)/STREAM(%s)/COMP(%d): looking for pre-answer requests",pctx->label,comp->stream->label,comp->component_id);
+
+ STAILQ_FOREACH_SAFE(r1, &comp->pre_answer_reqs, entry, r2) {
+ if (!strcmp(r1->username, username)) {
+ int error = 0;
+
+ r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s)/STREAM(%s)/COMP(%d): found pre-answer request",pctx->label,comp->stream->label,comp->component_id);
+ r = nr_ice_component_process_incoming_check(pcomp, &r1->local_addr, &r1->req, &error);
+ if (r) {
+ r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/STREAM(%s)/COMP(%d): error processing pre-answer request. Would have returned %d",pctx->label,comp->stream->label,comp->component_id, error);
+ }
+ (*serviced)++;
+ STAILQ_REMOVE(&comp->pre_answer_reqs,r1,nr_ice_pre_answer_request_, entry);
+ nr_ice_pre_answer_request_destroy(&r1);
+ }
+ }
+
+ _status=0;
+ return(_status);
+ }
+
+int nr_ice_component_can_candidate_tcptype_pair(nr_socket_tcp_type left, nr_socket_tcp_type right)
+ {
+ if (left && !right)
+ return(0);
+ if (!left && right)
+ return(0);
+ if (left == TCP_TYPE_ACTIVE && right != TCP_TYPE_PASSIVE)
+ return(0);
+ if (left == TCP_TYPE_SO && right != TCP_TYPE_SO)
+ return(0);
+ if (left == TCP_TYPE_PASSIVE)
+ return(0);
+
+ return(1);
+ }
+
+/* filter out pairings which won't work. */
+int nr_ice_component_can_candidate_addr_pair(nr_transport_addr *local, nr_transport_addr *remote)
+ {
+ if(local->ip_version != remote->ip_version)
+ return(0);
+ if(local->protocol != remote->protocol)
+ return(0);
+ if(nr_transport_addr_is_link_local(local) !=
+ nr_transport_addr_is_link_local(remote))
+ return(0);
+ /* This prevents our ice_unittest (or broken clients) from pairing a
+ * loopback with a host candidate. */
+ if(nr_transport_addr_is_loopback(local) !=
+ nr_transport_addr_is_loopback(remote))
+ return(0);
+
+ return(1);
+ }
+
+int nr_ice_component_pair_candidate(nr_ice_peer_ctx *pctx, nr_ice_component *pcomp, nr_ice_candidate *lcand, int pair_all_remote)
+ {
+ int r, _status;
+ nr_ice_candidate *pcand;
+ nr_ice_cand_pair *pair=0;
+ char codeword[5];
+
+ nr_ice_compute_codeword(lcand->label,strlen(lcand->label),codeword);
+ r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s)/CAND(%s): Pairing local candidate %s",pctx->label,codeword,lcand->label);
+
+ switch(lcand->type){
+ case HOST:
+ break;
+ case SERVER_REFLEXIVE:
+ case PEER_REFLEXIVE:
+ /* Don't actually pair these candidates */
+ goto done;
+ break;
+ case RELAYED:
+ break;
+ default:
+ assert(0);
+ ABORT(R_INTERNAL);
+ break;
+ }
+
+ TAILQ_FOREACH(pcand, &pcomp->candidates, entry_comp){
+ if(!nr_ice_component_can_candidate_addr_pair(&lcand->addr, &pcand->addr))
+ continue;
+ if(!nr_ice_component_can_candidate_tcptype_pair(lcand->tcp_type, pcand->tcp_type))
+ continue;
+
+ /* https://tools.ietf.org/html/draft-ietf-rtcweb-mdns-ice-candidates-03#section-3.3.2 */
+ if(lcand->type == RELAYED && pcand->mdns_addr && strlen(pcand->mdns_addr)) {
+ continue;
+ }
+
+ /*
+ Two modes, depending on |pair_all_remote|
+
+ 1. Pair remote candidates which have not been paired
+ (used in initial pairing or in processing the other side's
+ trickle candidates).
+ 2. Pair any remote candidate (used when processing our own
+ trickle candidates).
+ */
+ if (pair_all_remote || (pcand->state == NR_ICE_CAND_PEER_CANDIDATE_UNPAIRED)) {
+ if (pair_all_remote) {
+ /* When a remote candidate arrives after the start of checking, but
+ * before the gathering of local candidates, it can be in UNPAIRED */
+ pcand->state = NR_ICE_CAND_PEER_CANDIDATE_PAIRED;
+ }
+
+ nr_ice_compute_codeword(pcand->label,strlen(pcand->label),codeword);
+ r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s)/CAND(%s): Pairing with peer candidate %s", pctx->label, codeword, pcand->label);
+
+ if(r=nr_ice_candidate_pair_create(pctx,lcand,pcand,&pair))
+ ABORT(r);
+
+ if(r=nr_ice_component_insert_pair(pcomp, pair))
+ ABORT(r);
+ }
+ }
+
+ done:
+ _status = 0;
+ abort:
+ return(_status);
+ }
+
+int nr_ice_component_pair_candidates(nr_ice_peer_ctx *pctx, nr_ice_component *lcomp,nr_ice_component *pcomp)
+ {
+ nr_ice_candidate *lcand, *pcand;
+ nr_ice_socket *isock;
+ int r,_status;
+
+ r_log(LOG_ICE,LOG_DEBUG,"Pairing candidates======");
+
+ /* Create the candidate pairs */
+ lcand=TAILQ_FIRST(&lcomp->candidates);
+
+ if (!lcand) {
+ /* No local candidates, initialized or not! */
+ ABORT(R_FAILED);
+ }
+
+ while(lcand){
+ if (lcand->state == NR_ICE_CAND_STATE_INITIALIZED) {
+ if ((r = nr_ice_component_pair_candidate(pctx, pcomp, lcand, 0)))
+ ABORT(r);
+ }
+
+ lcand=TAILQ_NEXT(lcand,entry_comp);
+ }
+
+ /* Mark all peer candidates as paired */
+ pcand=TAILQ_FIRST(&pcomp->candidates);
+ while(pcand){
+ pcand->state = NR_ICE_CAND_PEER_CANDIDATE_PAIRED;
+
+ pcand=TAILQ_NEXT(pcand,entry_comp);
+
+ }
+
+ /* Now register the STUN server callback for this component.
+ Note that this is a per-component CB so we only need to
+ do this once.
+ */
+ if (pcomp->state != NR_ICE_COMPONENT_RUNNING) {
+ isock=STAILQ_FIRST(&lcomp->sockets);
+ while(isock){
+ if(r=nr_stun_server_add_client(isock->stun_server,pctx->label,
+ pcomp->stream->r2l_user,&pcomp->stream->r2l_pass,nr_ice_component_stun_server_cb,pcomp)) {
+ ABORT(r);
+ }
+ isock=STAILQ_NEXT(isock,entry);
+ }
+ }
+
+ pcomp->state = NR_ICE_COMPONENT_RUNNING;
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_ice_pre_answer_enqueue(nr_ice_component *comp, nr_socket *sock, nr_stun_server_request *req, int *dont_free)
+ {
+ int r = 0;
+ int _status;
+ nr_ice_pre_answer_request *r1, *r2;
+ nr_transport_addr dst_addr;
+ nr_ice_pre_answer_request *par = 0;
+
+ if (r=nr_socket_getaddr(sock, &dst_addr))
+ ABORT(r);
+
+ STAILQ_FOREACH_SAFE(r1, &comp->pre_answer_reqs, entry, r2) {
+ if (!nr_transport_addr_cmp(&r1->local_addr, &dst_addr,
+ NR_TRANSPORT_ADDR_CMP_MODE_ALL) &&
+ !nr_transport_addr_cmp(&r1->req.src_addr, &req->src_addr,
+ NR_TRANSPORT_ADDR_CMP_MODE_ALL)) {
+ return(0);
+ }
+ }
+
+ if (r=nr_ice_pre_answer_request_create(&dst_addr, req, &par))
+ ABORT(r);
+
+ r_log(LOG_ICE,LOG_DEBUG, "ICE(%s)/STREAM(%s)/COMP(%d): Enqueuing STUN request pre-answer from %s",
+ comp->ctx->label, comp->stream->label, comp->component_id,
+ req->src_addr.as_string);
+
+ *dont_free = 1;
+ STAILQ_INSERT_TAIL(&comp->pre_answer_reqs, par, entry);
+
+ _status=0;
+abort:
+ return(_status);
+ }
+
+/* Fires when we have an incoming candidate that doesn't correspond to an existing
+ remote peer. This is either pre-answer or just spurious. Store it in the
+ component for use when we see the actual answer, at which point we need
+ to do the procedures from S 7.2.1 in nr_ice_component_stun_server_cb.
+ */
+static int nr_ice_component_stun_server_default_cb(void *cb_arg,nr_stun_server_ctx *stun_ctx,nr_socket *sock, nr_stun_server_request *req, int *dont_free, int *error)
+ {
+ int r, _status;
+ nr_ice_component *comp = (nr_ice_component *)cb_arg;
+
+ r_log(LOG_ICE,LOG_DEBUG,"ICE(%s)/STREAM(%s)/COMP(%d): Received STUN request pre-answer from %s",
+ comp->ctx->label, comp->stream->label, comp->component_id,
+ req->src_addr.as_string);
+
+ if (r=nr_ice_pre_answer_enqueue(comp, sock, req, dont_free)) {
+ r_log(LOG_ICE,LOG_ERR,"ICE(%s)/STREAM(%s)/COMP(%d): Failed (%d) to enque pre-answer request from %s",
+ comp->ctx->label, comp->stream->label, comp->component_id, r,
+ req->src_addr.as_string);
+ ABORT(r);
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+#define NR_ICE_CONSENT_TIMER_DEFAULT 5000
+#define NR_ICE_CONSENT_TIMEOUT_DEFAULT 30000
+
+static void nr_ice_component_consent_failed(nr_ice_component *comp)
+ {
+ if (!comp->can_send) {
+ return;
+ }
+
+ r_log(LOG_ICE,LOG_INFO,"ICE(%s)/STREAM(%s)/COMP(%d): Consent refresh failed",
+ comp->ctx->label, comp->stream->label, comp->component_id);
+ comp->can_send = 0;
+
+ if (comp->consent_timeout) {
+ NR_async_timer_cancel(comp->consent_timeout);
+ comp->consent_timeout = 0;
+ }
+ if (comp->consent_timer) {
+ NR_async_timer_cancel(comp->consent_timer);
+ comp->consent_timer = 0;
+ }
+ /* We are turning the consent failure into a ICE component failure to
+ * alert the browser via ICE connection state change about this event. */
+ nr_ice_media_stream_component_failed(comp->stream, comp);
+ }
+
+static void nr_ice_component_consent_timeout_cb(NR_SOCKET s, int how, void *cb_arg)
+ {
+ nr_ice_component *comp=cb_arg;
+
+ comp->consent_timeout = 0;
+
+ r_log(LOG_ICE,LOG_WARNING,"ICE(%s)/STREAM(%s)/COMP(%d): Consent refresh final time out",
+ comp->ctx->label, comp->stream->label, comp->component_id);
+ nr_ice_component_consent_failed(comp);
+ }
+
+
+void nr_ice_component_disconnected(nr_ice_component *comp)
+ {
+ if (!comp->can_send) {
+ return;
+ }
+
+ if (comp->disconnected) {
+ return;
+ }
+
+ r_log(LOG_ICE,LOG_WARNING,"ICE(%s)/STREAM(%s)/COMP(%d): component disconnected",
+ comp->ctx->label, comp->stream->label, comp->component_id);
+ comp->disconnected = 1;
+
+ /* a single disconnected component disconnects the stream */
+ nr_ice_media_stream_set_disconnected(comp->stream, NR_ICE_MEDIA_STREAM_DISCONNECTED);
+ }
+
+static void nr_ice_component_consent_refreshed(nr_ice_component *comp)
+ {
+ uint16_t tval;
+
+ if (!comp->can_send) {
+ return;
+ }
+
+ gettimeofday(&comp->consent_last_seen, 0);
+ r_log(LOG_ICE,LOG_DEBUG,"ICE(%s)/STREAM(%s)/COMP(%d): consent_last_seen is now %lu",
+ comp->ctx->label, comp->stream->label, comp->component_id,
+ comp->consent_last_seen.tv_sec);
+
+ comp->disconnected = 0;
+
+ nr_ice_media_stream_check_if_connected(comp->stream);
+
+ if (comp->consent_timeout)
+ NR_async_timer_cancel(comp->consent_timeout);
+
+ tval = NR_ICE_CONSENT_TIMEOUT_DEFAULT;
+ if (comp->ctx->test_timer_divider)
+ tval = tval / comp->ctx->test_timer_divider;
+
+ NR_ASYNC_TIMER_SET(tval, nr_ice_component_consent_timeout_cb, comp,
+ &comp->consent_timeout);
+ }
+
+static void nr_ice_component_refresh_consent_cb(NR_SOCKET s, int how, void *cb_arg)
+ {
+ nr_ice_component *comp=cb_arg;
+
+ switch (comp->consent_ctx->state) {
+ case NR_STUN_CLIENT_STATE_FAILED:
+ if (comp->consent_ctx->error_code == 403) {
+ r_log(LOG_ICE, LOG_INFO, "ICE(%s)/STREAM(%s)/COMP(%d): Consent revoked by peer",
+ comp->ctx->label, comp->stream->label, comp->component_id);
+ nr_ice_component_consent_failed(comp);
+ }
+ break;
+ case NR_STUN_CLIENT_STATE_DONE:
+ r_log(LOG_ICE, LOG_INFO, "ICE(%s)/STREAM(%s)/COMP(%d): Consent refreshed",
+ comp->ctx->label, comp->stream->label, comp->component_id);
+ nr_ice_component_consent_refreshed(comp);
+ break;
+ case NR_STUN_CLIENT_STATE_TIMED_OUT:
+ r_log(LOG_ICE, LOG_INFO, "ICE(%s)/STREAM(%s)/COMP(%d): A single consent refresh request timed out",
+ comp->ctx->label, comp->stream->label, comp->component_id);
+ nr_ice_component_disconnected(comp);
+ break;
+ default:
+ break;
+ }
+ }
+
+int nr_ice_component_refresh_consent(nr_stun_client_ctx *ctx, NR_async_cb finished_cb, void *cb_arg)
+ {
+ int r,_status;
+
+ nr_stun_client_reset(ctx);
+
+ if (r=nr_stun_client_start(ctx, NR_ICE_CLIENT_MODE_BINDING_REQUEST, finished_cb, cb_arg))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+void nr_ice_component_consent_calc_consent_timer(nr_ice_component *comp)
+ {
+ uint16_t trange, trand, tval;
+
+ trange = NR_ICE_CONSENT_TIMER_DEFAULT * 20 / 100;
+ tval = NR_ICE_CONSENT_TIMER_DEFAULT - trange;
+ if (!nr_crypto_random_bytes((UCHAR*)&trand, sizeof(trand)))
+ tval += (trand % (trange * 2));
+
+ if (comp->ctx->test_timer_divider)
+ tval = tval / comp->ctx->test_timer_divider;
+
+ /* The timeout of the transaction is the maximum time until we send the
+ * next consent request. */
+ comp->consent_ctx->maximum_transmits_timeout_ms = tval;
+ }
+
+static void nr_ice_component_consent_timer_cb(NR_SOCKET s, int how, void *cb_arg)
+ {
+ nr_ice_component *comp=cb_arg;
+ int r;
+
+ if (!comp->consent_ctx) {
+ return;
+ }
+
+ if (comp->consent_timer) {
+ NR_async_timer_cancel(comp->consent_timer);
+ }
+ comp->consent_timer = 0;
+
+ comp->consent_ctx->params.ice_binding_request.username =
+ comp->stream->l2r_user;
+ comp->consent_ctx->params.ice_binding_request.password =
+ comp->stream->l2r_pass;
+ comp->consent_ctx->params.ice_binding_request.control =
+ comp->stream->pctx->controlling?
+ NR_ICE_CONTROLLING:NR_ICE_CONTROLLED;
+ comp->consent_ctx->params.ice_binding_request.tiebreaker =
+ comp->stream->pctx->tiebreaker;
+ comp->consent_ctx->params.ice_binding_request.priority =
+ comp->active->local->priority;
+
+ nr_ice_component_consent_calc_consent_timer(comp);
+
+ if (r=nr_ice_component_refresh_consent(comp->consent_ctx,
+ nr_ice_component_refresh_consent_cb,
+ comp)) {
+ r_log(LOG_ICE,LOG_ERR,"ICE(%s)/STREAM(%s)/COMP(%d): Refresh consent failed with %d",
+ comp->ctx->label, comp->stream->label, comp->component_id, r);
+ }
+
+ nr_ice_component_consent_schedule_consent_timer(comp);
+
+ }
+
+void nr_ice_component_consent_schedule_consent_timer(nr_ice_component *comp)
+ {
+ if (!comp->can_send) {
+ return;
+ }
+
+ NR_ASYNC_TIMER_SET(comp->consent_ctx->maximum_transmits_timeout_ms,
+ nr_ice_component_consent_timer_cb, comp,
+ &comp->consent_timer);
+ }
+
+void nr_ice_component_refresh_consent_now(nr_ice_component *comp)
+ {
+ nr_ice_component_consent_timer_cb(0, 0, comp);
+ }
+
+void nr_ice_component_consent_destroy(nr_ice_component *comp)
+ {
+ if (comp->consent_timer) {
+ NR_async_timer_cancel(comp->consent_timer);
+ comp->consent_timer = 0;
+ }
+ if (comp->consent_timeout) {
+ NR_async_timer_cancel(comp->consent_timeout);
+ comp->consent_timeout = 0;
+ }
+ if (comp->consent_handle) {
+ nr_ice_socket_deregister(comp->active->local->isock,
+ comp->consent_handle);
+ comp->consent_handle = 0;
+ }
+ if (comp->consent_ctx) {
+ nr_stun_client_ctx_destroy(&comp->consent_ctx);
+ comp->consent_ctx = 0;
+ }
+ }
+
+int nr_ice_component_setup_consent(nr_ice_component *comp)
+ {
+ int r,_status;
+
+ r_log(LOG_ICE,LOG_DEBUG,"ICE(%s)/STREAM(%s)/COMP(%d): Setting up refresh consent",
+ comp->ctx->label, comp->stream->label, comp->component_id);
+
+ nr_ice_component_consent_destroy(comp);
+
+ if (r=nr_stun_client_ctx_create("consent", comp->active->local->osock,
+ &comp->active->remote->addr, 0,
+ &comp->consent_ctx))
+ ABORT(r);
+ /* Consent request get send only once. */
+ comp->consent_ctx->maximum_transmits = 1;
+
+ if (r=nr_ice_socket_register_stun_client(comp->active->local->isock,
+ comp->consent_ctx, &comp->consent_handle))
+ ABORT(r);
+
+ comp->can_send = 1;
+ comp->disconnected = 0;
+ nr_ice_component_consent_refreshed(comp);
+
+ nr_ice_component_consent_calc_consent_timer(comp);
+ nr_ice_component_consent_schedule_consent_timer(comp);
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_ice_component_nominated_pair(nr_ice_component *comp, nr_ice_cand_pair *pair)
+ {
+ int r,_status;
+ nr_ice_cand_pair *p2;
+
+ /* Are we changing what the nominated pair is? */
+ if(comp->nominated){
+ if(comp->nominated->priority >= pair->priority)
+ return(0);
+ r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/STREAM(%s)/COMP(%d)/CAND-PAIR(%s): replacing pair %s with CAND-PAIR(%s)",comp->stream->pctx->label,comp->stream->label,comp->component_id,comp->nominated->codeword,comp->nominated->as_string,pair->codeword);
+ /* As consent doesn't hold a reference to its isock this needs to happen
+ * before making the new pair the active one. */
+ nr_ice_component_consent_destroy(comp);
+ }
+
+ /* Set the new nominated pair */
+ r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/STREAM(%s)/COMP(%d)/CAND-PAIR(%s): nominated pair is %s",comp->stream->pctx->label,comp->stream->label,comp->component_id,pair->codeword,pair->as_string);
+ comp->state=NR_ICE_COMPONENT_NOMINATED;
+ comp->nominated=pair;
+ comp->active=pair;
+
+ r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/STREAM(%s)/COMP(%d)/CAND-PAIR(%s): cancelling all pairs but %s",comp->stream->pctx->label,comp->stream->label,comp->component_id,pair->codeword,pair->as_string);
+
+ /* Cancel checks in WAITING and FROZEN per ICE S 8.1.2 */
+ p2=TAILQ_FIRST(&comp->stream->trigger_check_queue);
+ while(p2){
+ if((p2 != pair) &&
+ (p2->remote->component->component_id == comp->component_id)) {
+ assert(p2->state == NR_ICE_PAIR_STATE_WAITING ||
+ p2->state == NR_ICE_PAIR_STATE_CANCELLED);
+ r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/STREAM(%s)/COMP(%d)/CAND-PAIR(%s): cancelling FROZEN/WAITING pair %s in trigger check queue because CAND-PAIR(%s) was nominated.",comp->stream->pctx->label,comp->stream->label,comp->component_id,p2->codeword,p2->as_string,pair->codeword);
+
+ nr_ice_candidate_pair_cancel(pair->pctx,p2,0);
+ }
+
+ p2=TAILQ_NEXT(p2,triggered_check_queue_entry);
+ }
+ p2=TAILQ_FIRST(&comp->stream->check_list);
+ while(p2){
+ if((p2 != pair) &&
+ (p2->remote->component->component_id == comp->component_id) &&
+ ((p2->state == NR_ICE_PAIR_STATE_FROZEN) ||
+ (p2->state == NR_ICE_PAIR_STATE_WAITING))) {
+ r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/STREAM(%s)/COMP(%d)/CAND-PAIR(%s): cancelling FROZEN/WAITING pair %s because CAND-PAIR(%s) was nominated.",comp->stream->pctx->label,comp->stream->label,comp->component_id,p2->codeword,p2->as_string,pair->codeword);
+
+ nr_ice_candidate_pair_cancel(pair->pctx,p2,0);
+ }
+
+ p2=TAILQ_NEXT(p2,check_queue_entry);
+ }
+ r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s)/STREAM(%s)/COMP(%d): cancelling done",comp->stream->pctx->label,comp->stream->label,comp->component_id);
+
+ if(r=nr_ice_component_setup_consent(comp))
+ ABORT(r);
+
+ nr_ice_media_stream_component_nominated(comp->stream,comp);
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+static int nr_ice_component_have_all_pairs_failed(nr_ice_component *comp)
+ {
+ nr_ice_cand_pair *p2;
+
+ p2=TAILQ_FIRST(&comp->stream->check_list);
+ while(p2){
+ if(comp->component_id==p2->local->component_id){
+ switch(p2->state){
+ case NR_ICE_PAIR_STATE_FROZEN:
+ case NR_ICE_PAIR_STATE_WAITING:
+ case NR_ICE_PAIR_STATE_IN_PROGRESS:
+ case NR_ICE_PAIR_STATE_SUCCEEDED:
+ return(0);
+ case NR_ICE_PAIR_STATE_FAILED:
+ case NR_ICE_PAIR_STATE_CANCELLED:
+ /* states that will never be recovered from */
+ break;
+ default:
+ assert(0);
+ break;
+ }
+ }
+
+ p2=TAILQ_NEXT(p2,check_queue_entry);
+ }
+
+ return(1);
+ }
+
+void nr_ice_component_failed_pair(nr_ice_component *comp, nr_ice_cand_pair *pair)
+ {
+ nr_ice_component_check_if_failed(comp);
+ }
+
+void nr_ice_component_check_if_failed(nr_ice_component *comp)
+ {
+ if (comp->state == NR_ICE_COMPONENT_RUNNING) {
+ /* Don't do anything to streams that aren't currently running */
+ r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s)/STREAM(%s)/COMP(%d): Checking whether component needs to be marked failed.",comp->stream->pctx->label,comp->stream->label,comp->component_id);
+
+ if (!comp->stream->pctx->trickle_grace_period_timer &&
+ nr_ice_component_have_all_pairs_failed(comp)) {
+ r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/STREAM(%s)/COMP(%d): All pairs are failed, and grace period has elapsed. Marking component as failed.",comp->stream->pctx->label,comp->stream->label,comp->component_id);
+ nr_ice_media_stream_component_failed(comp->stream,comp);
+ }
+ }
+ }
+
+int nr_ice_component_select_pair(nr_ice_peer_ctx *pctx, nr_ice_component *comp)
+ {
+ nr_ice_cand_pair **pairs=0;
+ int ct=0;
+ nr_ice_cand_pair *pair;
+ int r,_status;
+
+ /* Size the array */
+ pair=TAILQ_FIRST(&comp->stream->check_list);
+ while(pair){
+ if (comp->component_id == pair->local->component_id)
+ ct++;
+
+ pair=TAILQ_NEXT(pair,check_queue_entry);
+ }
+
+ /* Make and fill the array */
+ if(!(pairs=RCALLOC(sizeof(nr_ice_cand_pair *)*ct)))
+ ABORT(R_NO_MEMORY);
+
+ ct=0;
+ pair=TAILQ_FIRST(&comp->stream->check_list);
+ while(pair){
+ if (comp->component_id == pair->local->component_id)
+ pairs[ct++]=pair;
+
+ pair=TAILQ_NEXT(pair,check_queue_entry);
+ }
+
+ if (pctx->handler) {
+ if(r=pctx->handler->vtbl->select_pair(pctx->handler->obj,
+ comp->stream,comp->component_id,pairs,ct))
+ ABORT(r);
+ }
+
+ _status=0;
+ abort:
+ RFREE(pairs);
+ return(_status);
+ }
+
+
+/* Close the underlying sockets for everything but the nominated candidate */
+int nr_ice_component_finalize(nr_ice_component *lcomp, nr_ice_component *rcomp)
+ {
+ nr_ice_socket *isock=0;
+ nr_ice_socket *s1,*s2;
+
+ if(rcomp->state==NR_ICE_COMPONENT_NOMINATED){
+ assert(rcomp->active == rcomp->nominated);
+ isock=rcomp->nominated->local->isock;
+ }
+
+ STAILQ_FOREACH_SAFE(s1, &lcomp->sockets, entry, s2){
+ if(s1!=isock){
+ STAILQ_REMOVE(&lcomp->sockets,s1,nr_ice_socket_,entry);
+ nr_ice_socket_destroy(&s1);
+ }
+ }
+
+ return(0);
+ }
+
+
+int nr_ice_component_insert_pair(nr_ice_component *pcomp, nr_ice_cand_pair *pair)
+ {
+ int _status;
+
+ /* Pairs for peer reflexive are marked SUCCEEDED immediately */
+ if (pair->state != NR_ICE_PAIR_STATE_FROZEN &&
+ pair->state != NR_ICE_PAIR_STATE_SUCCEEDED){
+ assert(0);
+ ABORT(R_BAD_ARGS);
+ }
+
+ /* We do not throw an error after this, because we've inserted the pair. */
+ nr_ice_candidate_pair_insert(&pair->remote->stream->check_list,pair);
+
+ /* Make sure the check timer is running, if the stream was previously
+ * started. We will not start streams just because a pair was created,
+ * unless it is the first pair to be created across all streams. */
+ r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s)/CAND-PAIR(%s): Ensure that check timer is running for new pair %s.",pair->remote->stream->pctx->label, pair->codeword, pair->as_string);
+
+ if(pair->remote->stream->ice_state == NR_ICE_MEDIA_STREAM_CHECKS_ACTIVE ||
+ (pair->remote->stream->ice_state == NR_ICE_MEDIA_STREAM_CHECKS_FROZEN &&
+ !pair->remote->stream->pctx->checks_started)){
+ if(nr_ice_media_stream_start_checks(pair->remote->stream->pctx, pair->remote->stream)) {
+ r_log(LOG_ICE,LOG_WARNING,"ICE-PEER(%s)/CAND-PAIR(%s): Could not restart checks for new pair %s.",pair->remote->stream->pctx->label, pair->codeword, pair->as_string);
+ }
+ }
+
+ _status=0;
+ abort:
+ if (_status) {
+ nr_ice_candidate_pair_destroy(&pair);
+ }
+ return(_status);
+ }
+
+int nr_ice_component_get_default_candidate(nr_ice_component *comp, nr_ice_candidate **candp, int ip_version)
+ {
+ int _status;
+ nr_ice_candidate *cand;
+ nr_ice_candidate *best_cand = NULL;
+
+ /* We have the component. Now find the "best" candidate, making
+ use of the fact that more "reliable" candidate types have
+ higher numbers. So, we sort by type and then priority within
+ type
+ */
+ cand=TAILQ_FIRST(&comp->candidates);
+ while(cand){
+ if (!nr_ice_ctx_hide_candidate(comp->ctx, cand) &&
+ cand->addr.ip_version == ip_version) {
+ if (!best_cand) {
+ best_cand = cand;
+ }
+ else if (best_cand->type < cand->type) {
+ best_cand = cand;
+ } else if (best_cand->type == cand->type &&
+ best_cand->priority < cand->priority) {
+ best_cand = cand;
+ }
+ }
+
+ cand=TAILQ_NEXT(cand,entry_comp);
+ }
+
+ /* No candidates */
+ if (!best_cand)
+ ABORT(R_NOT_FOUND);
+
+ *candp = best_cand;
+
+ _status=0;
+ abort:
+ return(_status);
+
+ }
+
+
+void nr_ice_component_dump_state(nr_ice_component *comp, int log_level)
+ {
+ nr_ice_candidate *cand;
+
+ if (comp->local_component) {
+ r_log(LOG_ICE,log_level,"ICE(%s)/ICE-STREAM(%s): Remote component %d in state %d - dumping candidates",comp->ctx->label,comp->stream->label,comp->component_id,comp->state);
+ } else {
+ r_log(LOG_ICE,log_level,"ICE(%s)/ICE-STREAM(%s): Local component %d - dumping candidates",comp->ctx->label,comp->stream->label,comp->component_id);
+ }
+
+ cand=TAILQ_FIRST(&comp->candidates);
+ while(cand){
+ r_log(LOG_ICE,log_level,"ICE(%s)/ICE-STREAM(%s)/CAND(%s): %s",comp->ctx->label,comp->stream->label,cand->codeword,cand->label);
+ cand=TAILQ_NEXT(cand,entry_comp);
+ }
+ }
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_component.h b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_component.h
new file mode 100644
index 0000000000..0b12a68d58
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_component.h
@@ -0,0 +1,111 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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 _ice_component_h
+#define _ice_component_h
+#ifdef __cplusplus
+using namespace std;
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct nr_ice_pre_answer_request_ {
+ nr_stun_server_request req;
+ char *username;
+ nr_transport_addr local_addr;
+
+ STAILQ_ENTRY(nr_ice_pre_answer_request_) entry;
+} nr_ice_pre_answer_request;
+
+typedef STAILQ_HEAD(nr_ice_pre_answer_request_head_, nr_ice_pre_answer_request_) nr_ice_pre_answer_request_head;
+
+struct nr_ice_component_ {
+ int state;
+#define NR_ICE_COMPONENT_UNPAIRED 0
+#define NR_ICE_COMPONENT_RUNNING 1
+#define NR_ICE_COMPONENT_NOMINATED 2
+#define NR_ICE_COMPONENT_FAILED 3
+#define NR_ICE_COMPONENT_DISABLED 4
+ struct nr_ice_ctx_ *ctx;
+ struct nr_ice_media_stream_ *stream;
+ nr_ice_component *local_component;
+
+ int component_id;
+ nr_ice_socket_head sockets;
+ nr_ice_candidate_head candidates;
+ int candidate_ct;
+ nr_ice_pre_answer_request_head pre_answer_reqs;
+
+ int valid_pairs;
+ struct nr_ice_cand_pair_ *nominated; /* Highest priority nomninated pair */
+ struct nr_ice_cand_pair_ *active;
+
+ nr_stun_client_ctx *consent_ctx;
+ void *consent_timer;
+ void *consent_timeout;
+ void *consent_handle;
+ int can_send;
+ int disconnected;
+ struct timeval consent_last_seen;
+
+ STAILQ_ENTRY(nr_ice_component_)entry;
+};
+
+typedef STAILQ_HEAD(nr_ice_component_head_,nr_ice_component_) nr_ice_component_head;
+
+int nr_ice_component_create(struct nr_ice_media_stream_ *stream, int component_id, nr_ice_component **componentp);
+int nr_ice_component_destroy(nr_ice_component **componentp);
+int nr_ice_component_initialize(struct nr_ice_ctx_ *ctx,nr_ice_component *component);
+void nr_ice_component_stop_gathering(nr_ice_component *component);
+int nr_ice_component_is_done_gathering(nr_ice_component *comp);
+int nr_ice_component_maybe_prune_candidate(nr_ice_ctx *ctx, nr_ice_component *comp, nr_ice_candidate *c1, int *was_pruned);
+int nr_ice_component_pair_candidate(nr_ice_peer_ctx *pctx, nr_ice_component *pcomp, nr_ice_candidate *lcand, int pair_all_remote);
+int nr_ice_component_pair_candidates(nr_ice_peer_ctx *pctx, nr_ice_component *lcomp, nr_ice_component *pcomp);
+int nr_ice_component_service_pre_answer_requests(nr_ice_peer_ctx *pctx, nr_ice_component *pcomp, char *username, int *serviced);
+int nr_ice_component_nominated_pair(nr_ice_component *comp, nr_ice_cand_pair *pair);
+void nr_ice_component_failed_pair(nr_ice_component *comp, nr_ice_cand_pair *pair);
+void nr_ice_component_check_if_failed(nr_ice_component *comp);
+int nr_ice_component_select_pair(nr_ice_peer_ctx *pctx, nr_ice_component *comp);
+int nr_ice_component_set_failed(nr_ice_component *comp);
+int nr_ice_component_finalize(nr_ice_component *lcomp, nr_ice_component *rcomp);
+int nr_ice_component_insert_pair(nr_ice_component *pcomp, nr_ice_cand_pair *pair);
+int nr_ice_component_get_default_candidate(nr_ice_component *comp, nr_ice_candidate **candp, int ip_version);
+void nr_ice_component_consent_destroy(nr_ice_component *comp);
+void nr_ice_component_refresh_consent_now(nr_ice_component *comp);
+void nr_ice_component_disconnected(nr_ice_component *comp);
+void nr_ice_component_dump_state(nr_ice_component *comp, int log_level);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_ctx.c b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_ctx.c
new file mode 100644
index 0000000000..0d498845a4
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_ctx.c
@@ -0,0 +1,1125 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+*/
+
+#include <csi_platform.h>
+#include <assert.h>
+#include <sys/types.h>
+#ifdef WIN32
+#include <winsock2.h>
+#else
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#endif
+#include <sys/queue.h>
+#include <string.h>
+#include <nr_api.h>
+#include <registry.h>
+#include "stun.h"
+#include "ice_ctx.h"
+#include "ice_reg.h"
+#include "nr_crypto.h"
+#include "async_timer.h"
+#include "util.h"
+#include "nr_socket_local.h"
+
+#define ICE_UFRAG_LEN 8
+#define ICE_PWD_LEN 32
+
+int LOG_ICE = 0;
+
+static int nr_ice_random_string(char *str, int len);
+static int nr_ice_fetch_stun_servers(int ct, nr_ice_stun_server **out);
+#ifdef USE_TURN
+static int nr_ice_fetch_turn_servers(int ct, nr_ice_turn_server **out);
+#endif /* USE_TURN */
+static int nr_ice_ctx_pair_new_trickle_candidates(nr_ice_ctx *ctx, nr_ice_candidate *cand);
+static int no_op(void **obj) {
+ return 0;
+}
+
+static nr_socket_factory_vtbl default_socket_factory_vtbl = {
+ nr_socket_local_create,
+ no_op
+};
+
+int nr_ice_fetch_stun_servers(int ct, nr_ice_stun_server **out)
+ {
+ int r,_status;
+ nr_ice_stun_server *servers = 0;
+ int i;
+ NR_registry child;
+ char *addr=0;
+ UINT2 port;
+ in_addr_t addr_int;
+
+ if(!(servers=RCALLOC(sizeof(nr_ice_stun_server)*ct)))
+ ABORT(R_NO_MEMORY);
+
+ for(i=0;i<ct;i++){
+ if(r=NR_reg_get_child_registry(NR_ICE_REG_STUN_SRV_PRFX,i,child))
+ ABORT(r);
+ /* Assume we have a v4 addr for now */
+ if(r=NR_reg_alloc2_string(child,"addr",&addr))
+ ABORT(r);
+ addr_int=inet_addr(addr);
+ if(addr_int==INADDR_NONE){
+ r_log(LOG_ICE,LOG_ERR,"Invalid address %s;",addr);
+ ABORT(R_BAD_ARGS);
+ }
+ if(r=NR_reg_get2_uint2(child,"port",&port)) {
+ if (r != R_NOT_FOUND)
+ ABORT(r);
+ port = 3478;
+ }
+ if (r = nr_ip4_port_to_transport_addr(ntohl(addr_int), port, IPPROTO_UDP,
+ &servers[i].addr))
+ ABORT(r);
+ RFREE(addr);
+ addr=0;
+ }
+
+ *out = servers;
+
+ _status=0;
+ abort:
+ RFREE(addr);
+ if (_status) RFREE(servers);
+ return(_status);
+ }
+
+int nr_ice_ctx_set_stun_servers(nr_ice_ctx *ctx,nr_ice_stun_server *servers,int ct)
+ {
+ int _status;
+
+ if(ctx->stun_servers_cfg){
+ RFREE(ctx->stun_servers_cfg);
+ ctx->stun_servers_cfg=NULL;
+ ctx->stun_server_ct_cfg=0;
+ }
+
+ if (ct) {
+ if(!(ctx->stun_servers_cfg=RCALLOC(sizeof(nr_ice_stun_server)*ct)))
+ ABORT(R_NO_MEMORY);
+
+ memcpy(ctx->stun_servers_cfg,servers,sizeof(nr_ice_stun_server)*ct);
+ ctx->stun_server_ct_cfg = ct;
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_ice_ctx_set_turn_servers(nr_ice_ctx *ctx,nr_ice_turn_server *servers,int ct)
+ {
+ int _status;
+
+ if(ctx->turn_servers_cfg){
+ for (int i = 0; i < ctx->turn_server_ct_cfg; i++) {
+ RFREE(ctx->turn_servers_cfg[i].username);
+ r_data_destroy(&ctx->turn_servers_cfg[i].password);
+ }
+ RFREE(ctx->turn_servers_cfg);
+ ctx->turn_servers_cfg=NULL;
+ ctx->turn_server_ct_cfg=0;
+ }
+
+ if(ct) {
+ if(!(ctx->turn_servers_cfg=RCALLOC(sizeof(nr_ice_turn_server)*ct)))
+ ABORT(R_NO_MEMORY);
+
+ memcpy(ctx->turn_servers_cfg,servers,sizeof(nr_ice_turn_server)*ct);
+ ctx->turn_server_ct_cfg = ct;
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_ice_ctx_copy_turn_servers(nr_ice_ctx *ctx, nr_ice_turn_server *servers, int ct)
+ {
+ int _status, i, r;
+
+ if (r = nr_ice_ctx_set_turn_servers(ctx, servers, ct)) {
+ ABORT(r);
+ }
+
+ // make copies of the username and password so they aren't freed twice
+ for (i = 0; i < ct; ++i) {
+ if (!(ctx->turn_servers_cfg[i].username = r_strdup(servers[i].username))) {
+ ABORT(R_NO_MEMORY);
+ }
+ if (r = r_data_create(&ctx->turn_servers_cfg[i].password,
+ servers[i].password->data,
+ servers[i].password->len)) {
+ ABORT(r);
+ }
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+static int nr_ice_ctx_set_local_addrs(nr_ice_ctx *ctx,nr_local_addr *addrs,int ct)
+ {
+ int _status,i,r;
+
+ if(ctx->local_addrs) {
+ RFREE(ctx->local_addrs);
+ ctx->local_addr_ct=0;
+ ctx->local_addrs=0;
+ }
+
+ if (ct) {
+ if(!(ctx->local_addrs=RCALLOC(sizeof(nr_local_addr)*ct)))
+ ABORT(R_NO_MEMORY);
+
+ for (i=0;i<ct;++i) {
+ if (r=nr_local_addr_copy(ctx->local_addrs+i,addrs+i)) {
+ ABORT(r);
+ }
+ }
+ ctx->local_addr_ct = ct;
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_ice_ctx_set_resolver(nr_ice_ctx *ctx, nr_resolver *resolver)
+ {
+ int _status;
+
+ if (ctx->resolver) {
+ ABORT(R_ALREADY);
+ }
+
+ ctx->resolver = resolver;
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_ice_ctx_set_interface_prioritizer(nr_ice_ctx *ctx, nr_interface_prioritizer *ip)
+ {
+ int _status;
+
+ if (ctx->interface_prioritizer) {
+ ABORT(R_ALREADY);
+ }
+
+ ctx->interface_prioritizer = ip;
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+void nr_ice_ctx_set_socket_factory(nr_ice_ctx *ctx, nr_socket_factory *factory)
+ {
+ nr_socket_factory_destroy(&ctx->socket_factory);
+ ctx->socket_factory = factory;
+ }
+
+#ifdef USE_TURN
+int nr_ice_fetch_turn_servers(int ct, nr_ice_turn_server **out)
+ {
+ int r,_status;
+ nr_ice_turn_server *servers = 0;
+ int i;
+ NR_registry child;
+ char *addr=0;
+ UINT2 port;
+ in_addr_t addr_int;
+ Data data={0};
+
+ if(!(servers=RCALLOC(sizeof(nr_ice_turn_server)*ct)))
+ ABORT(R_NO_MEMORY);
+
+ for(i=0;i<ct;i++){
+ if(r=NR_reg_get_child_registry(NR_ICE_REG_TURN_SRV_PRFX,i,child))
+ ABORT(r);
+ /* Assume we have a v4 addr for now */
+ if(r=NR_reg_alloc2_string(child,"addr",&addr))
+ ABORT(r);
+ addr_int=inet_addr(addr);
+ if(addr_int==INADDR_NONE){
+ r_log(LOG_ICE,LOG_ERR,"Invalid address %s",addr);
+ ABORT(R_BAD_ARGS);
+ }
+ if(r=NR_reg_get2_uint2(child,"port",&port)) {
+ if (r != R_NOT_FOUND)
+ ABORT(r);
+ port = 3478;
+ }
+ if (r = nr_ip4_port_to_transport_addr(ntohl(addr_int), port, IPPROTO_UDP,
+ &servers[i].turn_server.addr))
+ ABORT(r);
+
+
+ if(r=NR_reg_alloc2_string(child,NR_ICE_REG_TURN_SRV_USERNAME,&servers[i].username)){
+ if(r!=R_NOT_FOUND)
+ ABORT(r);
+ }
+
+ if(r=NR_reg_alloc2_data(child,NR_ICE_REG_TURN_SRV_PASSWORD,&data)){
+ if(r!=R_NOT_FOUND)
+ ABORT(r);
+ }
+ else {
+ servers[i].password=RCALLOC(sizeof(*servers[i].password));
+ if(!servers[i].password)
+ ABORT(R_NO_MEMORY);
+ servers[i].password->data = data.data;
+ servers[i].password->len = data.len;
+ data.data=0;
+ }
+
+ RFREE(addr);
+ addr=0;
+ }
+
+ *out = servers;
+
+ _status=0;
+ abort:
+ RFREE(data.data);
+ RFREE(addr);
+ if (_status) RFREE(servers);
+ return(_status);
+ }
+#endif /* USE_TURN */
+
+#define MAXADDRS 100 /* Ridiculously high */
+int nr_ice_ctx_create(char *label, UINT4 flags, nr_ice_ctx **ctxp)
+ {
+ nr_ice_ctx *ctx=0;
+ int r,_status;
+
+ if(r=r_log_register("ice", &LOG_ICE))
+ ABORT(r);
+
+ if(!(ctx=RCALLOC(sizeof(nr_ice_ctx))))
+ ABORT(R_NO_MEMORY);
+
+ ctx->flags=flags;
+
+ if(!(ctx->label=r_strdup(label)))
+ ABORT(R_NO_MEMORY);
+
+ /* Get the STUN servers */
+ if(r=NR_reg_get_child_count(NR_ICE_REG_STUN_SRV_PRFX,
+ (unsigned int *)&ctx->stun_server_ct_cfg)||ctx->stun_server_ct_cfg==0) {
+ r_log(LOG_ICE,LOG_DEBUG,"ICE(%s): No STUN servers specified in nICEr registry", ctx->label);
+ ctx->stun_server_ct_cfg=0;
+ }
+
+ /* 31 is the max for our priority algorithm */
+ if(ctx->stun_server_ct_cfg>31){
+ r_log(LOG_ICE,LOG_WARNING,"ICE(%s): Too many STUN servers specified: max=31", ctx->label);
+ ctx->stun_server_ct_cfg=31;
+ }
+
+ if(ctx->stun_server_ct_cfg>0){
+ if(r=nr_ice_fetch_stun_servers(ctx->stun_server_ct_cfg,&ctx->stun_servers_cfg)){
+ r_log(LOG_ICE,LOG_ERR,"ICE(%s): Couldn't load STUN servers from registry", ctx->label);
+ ctx->stun_server_ct_cfg=0;
+ ABORT(r);
+ }
+ }
+
+#ifdef USE_TURN
+ /* Get the TURN servers */
+ if(r=NR_reg_get_child_count(NR_ICE_REG_TURN_SRV_PRFX,
+ (unsigned int *)&ctx->turn_server_ct_cfg)||ctx->turn_server_ct_cfg==0) {
+ r_log(LOG_ICE,LOG_DEBUG,"ICE(%s): No TURN servers specified in nICEr registry", ctx->label);
+ ctx->turn_server_ct_cfg=0;
+ }
+#else
+ ctx->turn_server_ct_cfg=0;
+#endif /* USE_TURN */
+
+ ctx->local_addrs=0;
+ ctx->local_addr_ct=0;
+
+ /* 31 is the max for our priority algorithm */
+ if((ctx->stun_server_ct_cfg+ctx->turn_server_ct_cfg)>31){
+ r_log(LOG_ICE,LOG_WARNING,"ICE(%s): Too many STUN/TURN servers specified: max=31", ctx->label);
+ ctx->turn_server_ct_cfg=31-ctx->stun_server_ct_cfg;
+ }
+
+#ifdef USE_TURN
+ if(ctx->turn_server_ct_cfg>0){
+ if(r=nr_ice_fetch_turn_servers(ctx->turn_server_ct_cfg,&ctx->turn_servers_cfg)){
+ ctx->turn_server_ct_cfg=0;
+ r_log(LOG_ICE,LOG_ERR,"ICE(%s): Couldn't load TURN servers from registry", ctx->label);
+ ABORT(r);
+ }
+ }
+#endif /* USE_TURN */
+
+
+ ctx->Ta = 20;
+
+ ctx->test_timer_divider = 0;
+
+ if (r=nr_socket_factory_create_int(NULL, &default_socket_factory_vtbl, &ctx->socket_factory))
+ ABORT(r);
+
+ if ((r=NR_reg_get_string((char *)NR_ICE_REG_PREF_FORCE_INTERFACE_NAME, ctx->force_net_interface, sizeof(ctx->force_net_interface)))) {
+ if (r == R_NOT_FOUND) {
+ ctx->force_net_interface[0] = 0;
+ } else {
+ ABORT(r);
+ }
+ }
+
+ ctx->target_for_default_local_address_lookup=0;
+
+ STAILQ_INIT(&ctx->streams);
+ STAILQ_INIT(&ctx->sockets);
+ STAILQ_INIT(&ctx->foundations);
+ STAILQ_INIT(&ctx->peers);
+ STAILQ_INIT(&ctx->ids);
+
+ *ctxp=ctx;
+
+ _status=0;
+ abort:
+ if (_status && ctx) nr_ice_ctx_destroy(&ctx);
+
+ return(_status);
+ }
+
+ void nr_ice_ctx_add_flags(nr_ice_ctx* ctx, UINT4 flags) {
+ ctx->flags |= flags;
+ }
+
+ void nr_ice_ctx_remove_flags(nr_ice_ctx* ctx, UINT4 flags) {
+ ctx->flags &= ~flags;
+ }
+
+ void nr_ice_ctx_destroy(nr_ice_ctx** ctxp) {
+ if (!ctxp || !*ctxp) return;
+
+ nr_ice_ctx* ctx = *ctxp;
+ nr_ice_foundation *f1,*f2;
+ nr_ice_media_stream *s1,*s2;
+ int i;
+ nr_ice_stun_id *id1,*id2;
+
+ ctx->done_cb = 0;
+ ctx->trickle_cb = 0;
+
+ STAILQ_FOREACH_SAFE(s1, &ctx->streams, entry, s2){
+ STAILQ_REMOVE(&ctx->streams,s1,nr_ice_media_stream_,entry);
+ nr_ice_media_stream_destroy(&s1);
+ }
+
+ RFREE(ctx->label);
+
+ RFREE(ctx->stun_servers_cfg);
+
+ RFREE(ctx->local_addrs);
+
+ RFREE(ctx->target_for_default_local_address_lookup);
+
+ for (i = 0; i < ctx->turn_server_ct_cfg; i++) {
+ RFREE(ctx->turn_servers_cfg[i].username);
+ r_data_destroy(&ctx->turn_servers_cfg[i].password);
+ }
+ RFREE(ctx->turn_servers_cfg);
+
+ f1=STAILQ_FIRST(&ctx->foundations);
+ while(f1){
+ f2=STAILQ_NEXT(f1,entry);
+ RFREE(f1);
+ f1=f2;
+ }
+
+ STAILQ_FOREACH_SAFE(id1, &ctx->ids, entry, id2){
+ STAILQ_REMOVE(&ctx->ids,id1,nr_ice_stun_id_,entry);
+ RFREE(id1);
+ }
+
+ nr_resolver_destroy(&ctx->resolver);
+ nr_interface_prioritizer_destroy(&ctx->interface_prioritizer);
+ nr_socket_factory_destroy(&ctx->socket_factory);
+
+ RFREE(ctx);
+
+ *ctxp=0;
+ }
+
+void nr_ice_gather_finished_cb(NR_SOCKET s, int h, void *cb_arg)
+ {
+ int r;
+ nr_ice_candidate *cand=cb_arg;
+ nr_ice_ctx *ctx;
+ nr_ice_media_stream *stream;
+ int component_id;
+
+ assert(cb_arg);
+ if (!cb_arg)
+ return;
+ ctx = cand->ctx;
+ stream = cand->stream;
+ component_id = cand->component_id;
+
+ ctx->uninitialized_candidates--;
+ if (cand->state == NR_ICE_CAND_STATE_FAILED) {
+ r_log(LOG_ICE, LOG_WARNING,
+ "ICE(%s)/CAND(%s): failed to initialize, %d remaining", ctx->label,
+ cand->label, ctx->uninitialized_candidates);
+ } else {
+ r_log(LOG_ICE, LOG_DEBUG, "ICE(%s)/CAND(%s): initialized, %d remaining",
+ ctx->label, cand->label, ctx->uninitialized_candidates);
+ }
+
+ /* Avoid the need for yet another initialization function */
+ if (cand->state == NR_ICE_CAND_STATE_INITIALIZING && cand->type == HOST)
+ cand->state = NR_ICE_CAND_STATE_INITIALIZED;
+
+ if (cand->state == NR_ICE_CAND_STATE_INITIALIZED) {
+ int was_pruned = 0;
+
+ if (r=nr_ice_component_maybe_prune_candidate(ctx, cand->component,
+ cand, &was_pruned)) {
+ r_log(LOG_ICE, LOG_NOTICE, "ICE(%s): Problem pruning candidates",ctx->label);
+ }
+
+ if (was_pruned) {
+ cand = NULL;
+ }
+
+ /* If we are initialized, the candidate wasn't pruned,
+ and we have a trickle ICE callback fire the callback */
+ if (ctx->trickle_cb && cand &&
+ !nr_ice_ctx_hide_candidate(ctx, cand)) {
+ ctx->trickle_cb(ctx->trickle_cb_arg, ctx, cand->stream, cand->component_id, cand);
+
+ if (nr_ice_ctx_pair_new_trickle_candidates(ctx, cand)) {
+ r_log(LOG_ICE,LOG_ERR, "ICE(%s): All could not pair new trickle candidate",ctx->label);
+ /* But continue */
+ }
+ }
+ }
+
+ if (nr_ice_media_stream_is_done_gathering(stream) &&
+ ctx->trickle_cb) {
+ ctx->trickle_cb(ctx->trickle_cb_arg, ctx, stream, component_id, NULL);
+ }
+
+ if(ctx->uninitialized_candidates==0){
+ r_log(LOG_ICE, LOG_INFO, "ICE(%s): All candidates initialized",
+ ctx->label);
+ if (ctx->done_cb) {
+ ctx->done_cb(0,0,ctx->cb_arg);
+ }
+ else {
+ r_log(LOG_ICE, LOG_INFO,
+ "ICE(%s): No done_cb. We were probably destroyed.", ctx->label);
+ }
+ }
+ else {
+ r_log(LOG_ICE,LOG_DEBUG,"ICE(%s): Waiting for %d candidates to be initialized",ctx->label, ctx->uninitialized_candidates);
+ }
+ }
+
+static int nr_ice_ctx_pair_new_trickle_candidates(nr_ice_ctx *ctx, nr_ice_candidate *cand)
+ {
+ int r,_status;
+ nr_ice_peer_ctx *pctx;
+
+ pctx=STAILQ_FIRST(&ctx->peers);
+ while(pctx){
+ if (pctx->state == NR_ICE_PEER_STATE_PAIRED) {
+ r = nr_ice_peer_ctx_pair_new_trickle_candidate(ctx, pctx, cand);
+ if (r)
+ ABORT(r);
+ }
+
+ pctx=STAILQ_NEXT(pctx,entry);
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+/* Get the default address by creating a UDP socket, binding it to a wildcard
+ address, and connecting it to the remote IP. Because this is UDP, no packets
+ are sent. This lets us query the local address assigned to the socket by the
+ kernel.
+
+ If the context's remote address is NULL, then the application wasn't loaded
+ over the network, and we can fall back on connecting to a known public
+ address (namely Google's):
+
+ IPv4: 8.8.8.8
+ IPv6: 2001:4860:4860::8888
+*/
+static int nr_ice_get_default_address(nr_ice_ctx *ctx, int ip_version, nr_transport_addr* addrp)
+ {
+ int r,_status;
+ nr_transport_addr addr, known_remote_addr;
+ nr_transport_addr *remote_addr=ctx->target_for_default_local_address_lookup;
+ nr_socket *sock=0;
+
+ switch(ip_version) {
+ case NR_IPV4:
+ if ((r=nr_str_port_to_transport_addr("0.0.0.0", 0, IPPROTO_UDP, &addr)))
+ ABORT(r);
+ if (!remote_addr || nr_transport_addr_is_loopback(remote_addr)) {
+ if ((r=nr_str_port_to_transport_addr("8.8.8.8", 53, IPPROTO_UDP, &known_remote_addr)))
+ ABORT(r);
+ remote_addr=&known_remote_addr;
+ }
+ break;
+ case NR_IPV6:
+ if ((r=nr_str_port_to_transport_addr("::0", 0, IPPROTO_UDP, &addr)))
+ ABORT(r);
+ if (!remote_addr || nr_transport_addr_is_loopback(remote_addr)) {
+ if ((r=nr_str_port_to_transport_addr("2001:4860:4860::8888", 53, IPPROTO_UDP, &known_remote_addr)))
+ ABORT(r);
+ remote_addr=&known_remote_addr;
+ }
+ break;
+ default:
+ assert(0);
+ ABORT(R_INTERNAL);
+ }
+
+ if ((r=nr_socket_factory_create_socket(ctx->socket_factory, &addr, &sock)))
+ ABORT(r);
+ if ((r=nr_socket_connect(sock, remote_addr)))
+ ABORT(r);
+ if ((r=nr_socket_getaddr(sock, addrp)))
+ ABORT(r);
+
+ r_log(LOG_GENERIC, LOG_DEBUG, "Default address: %s", addrp->as_string);
+
+ _status=0;
+ abort:
+ nr_socket_destroy(&sock);
+ return(_status);
+ }
+
+static int nr_ice_get_default_local_address(nr_ice_ctx *ctx, int ip_version, nr_local_addr* addrs, int addr_ct, nr_local_addr *addrp)
+ {
+ int r,_status;
+ nr_transport_addr default_addr;
+ int i;
+
+ if ((r=nr_ice_get_default_address(ctx, ip_version, &default_addr)))
+ ABORT(r);
+
+ for (i=0; i < addr_ct; ++i) {
+ // if default addr is found in local addrs, copy the more fully
+ // complete local addr to the output arg. Don't need to worry
+ // about comparing ports here.
+ if (!nr_transport_addr_cmp(&default_addr, &addrs[i].addr,
+ NR_TRANSPORT_ADDR_CMP_MODE_ADDR)) {
+ if ((r=nr_local_addr_copy(addrp, &addrs[i])))
+ ABORT(r);
+ break;
+ }
+ }
+
+ // if default addr is not in local addrs, just copy the transport addr
+ // to output arg.
+ if (i == addr_ct) {
+ if ((r=nr_transport_addr_copy(&addrp->addr, &default_addr)))
+ ABORT(r);
+ (void)strlcpy(addrp->addr.ifname, "default route", sizeof(addrp->addr.ifname));
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+/* if handed a IPv4 default_local_addr, looks for IPv6 address on same interface
+ if handed a IPv6 default_local_addr, looks for IPv4 address on same interface
+*/
+static int nr_ice_get_assoc_interface_address(nr_local_addr* default_local_addr,
+ nr_local_addr* local_addrs, int addr_ct,
+ nr_local_addr* assoc_addrp)
+ {
+ int r, _status;
+ int i, ip_version;
+
+ if (!default_local_addr || !local_addrs || !addr_ct) {
+ ABORT(R_BAD_ARGS);
+ }
+
+ /* set _status to R_EOD in case we don't find an associated address */
+ _status = R_EOD;
+
+ /* look for IPv6 if we have IPv4, look for IPv4 if we have IPv6 */
+ ip_version = (NR_IPV4 == default_local_addr->addr.ip_version?NR_IPV6:NR_IPV4);
+
+ for (i=0; i<addr_ct; ++i) {
+ /* if we find the ip_version we're looking for on the matching interface,
+ copy it to assoc_addrp.
+ */
+ if (local_addrs[i].addr.ip_version == ip_version &&
+ !strcmp(local_addrs[i].addr.ifname, default_local_addr->addr.ifname)) {
+ if (r=nr_local_addr_copy(assoc_addrp, &local_addrs[i])) {
+ ABORT(r);
+ }
+ _status = 0;
+ break;
+ }
+ }
+
+ abort:
+ return(_status);
+ }
+
+int nr_ice_set_local_addresses(nr_ice_ctx *ctx,
+ nr_local_addr* stun_addrs, int stun_addr_ct)
+ {
+ int r,_status;
+ nr_local_addr local_addrs[MAXADDRS];
+ nr_local_addr *addrs = 0;
+ int i,addr_ct;
+ nr_local_addr default_addrs[2];
+ int default_addr_ct = 0;
+
+ if (!stun_addrs || !stun_addr_ct) {
+ r_log(LOG_ICE,LOG_ERR,"ICE(%s): no stun addrs provided",ctx->label);
+ ABORT(R_BAD_ARGS);
+ }
+
+ addr_ct = MIN(stun_addr_ct, MAXADDRS);
+ r_log(LOG_ICE, LOG_DEBUG, "ICE(%s): copy %d pre-fetched stun addrs", ctx->label, addr_ct);
+ for (i=0; i<addr_ct; ++i) {
+ if (r=nr_local_addr_copy(&local_addrs[i], &stun_addrs[i])) {
+ ABORT(r);
+ }
+ }
+
+ // removes duplicates and, based on prefs, loopback and link_local addrs
+ if (r=nr_stun_filter_local_addresses(local_addrs, &addr_ct)) {
+ ABORT(r);
+ }
+
+ if (ctx->force_net_interface[0] && addr_ct) {
+ /* Limit us to only addresses on a single interface */
+ int force_addr_ct = 0;
+ for(i=0;i<addr_ct;i++){
+ if (!strcmp(local_addrs[i].addr.ifname, ctx->force_net_interface)) {
+ // copy it down in the array, if needed
+ if (i != force_addr_ct) {
+ if (r=nr_local_addr_copy(&local_addrs[force_addr_ct], &local_addrs[i])) {
+ ABORT(r);
+ }
+ }
+ force_addr_ct++;
+ }
+ }
+ addr_ct = force_addr_ct;
+ }
+
+ r_log(LOG_ICE, LOG_DEBUG,
+ "ICE(%s): use only default local addresses: %s\n",
+ ctx->label,
+ (char*)(ctx->flags & NR_ICE_CTX_FLAGS_ONLY_DEFAULT_ADDRS?"yes":"no"));
+ if ((!addr_ct) || (ctx->flags & NR_ICE_CTX_FLAGS_ONLY_DEFAULT_ADDRS)) {
+ if (ctx->target_for_default_local_address_lookup) {
+ /* Get just the default IPv4 or IPv6 addr */
+ if(!nr_ice_get_default_local_address(
+ ctx, ctx->target_for_default_local_address_lookup->ip_version,
+ local_addrs, addr_ct, &default_addrs[default_addr_ct])) {
+ nr_local_addr *new_addr = &default_addrs[default_addr_ct];
+
+ ++default_addr_ct;
+
+ /* If we have a default target address, check for an associated
+ address on the same interface. For example, if the default
+ target address is IPv6, this will find an associated IPv4
+ address on the same interface.
+ This makes ICE w/ dual stacks work better - Bug 1609124.
+ */
+ if(!nr_ice_get_assoc_interface_address(
+ new_addr, local_addrs, addr_ct,
+ &default_addrs[default_addr_ct])) {
+ ++default_addr_ct;
+ }
+ }
+ } else {
+ /* Get just the default IPv4 and IPv6 addrs */
+ if(!nr_ice_get_default_local_address(ctx, NR_IPV4, local_addrs, addr_ct,
+ &default_addrs[default_addr_ct])) {
+ ++default_addr_ct;
+ }
+ if(!nr_ice_get_default_local_address(ctx, NR_IPV6, local_addrs, addr_ct,
+ &default_addrs[default_addr_ct])) {
+ ++default_addr_ct;
+ }
+ }
+ if (!default_addr_ct) {
+ r_log(LOG_ICE,LOG_ERR,"ICE(%s): failed to find default addresses",ctx->label);
+ ABORT(R_FAILED);
+ }
+ addrs = default_addrs;
+ addr_ct = default_addr_ct;
+ }
+ else {
+ addrs = local_addrs;
+ }
+
+ /* Sort interfaces by preference */
+ if(ctx->interface_prioritizer) {
+ for(i=0;i<addr_ct;i++){
+ if((r=nr_interface_prioritizer_add_interface(ctx->interface_prioritizer,addrs+i)) && (r!=R_ALREADY)) {
+ r_log(LOG_ICE,LOG_ERR,"ICE(%s): unable to add interface ",ctx->label);
+ ABORT(r);
+ }
+ }
+ if(r=nr_interface_prioritizer_sort_preference(ctx->interface_prioritizer)) {
+ r_log(LOG_ICE,LOG_ERR,"ICE(%s): unable to sort interface by preference",ctx->label);
+ ABORT(r);
+ }
+ }
+
+ if (r=nr_ice_ctx_set_local_addrs(ctx,addrs,addr_ct)) {
+ ABORT(r);
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_ice_set_target_for_default_local_address_lookup(nr_ice_ctx *ctx, const char *target_ip, UINT2 target_port)
+ {
+ int r,_status;
+
+ if (ctx->target_for_default_local_address_lookup) {
+ RFREE(ctx->target_for_default_local_address_lookup);
+ ctx->target_for_default_local_address_lookup=0;
+ }
+
+ if (!(ctx->target_for_default_local_address_lookup=RCALLOC(sizeof(nr_transport_addr))))
+ ABORT(R_NO_MEMORY);
+
+ if ((r=nr_str_port_to_transport_addr(target_ip, target_port, IPPROTO_UDP, ctx->target_for_default_local_address_lookup))) {
+ RFREE(ctx->target_for_default_local_address_lookup);
+ ctx->target_for_default_local_address_lookup=0;
+ ABORT(r);
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_ice_gather(nr_ice_ctx *ctx, NR_async_cb done_cb, void *cb_arg)
+ {
+ int r,_status;
+ nr_ice_media_stream *stream;
+ nr_local_addr stun_addrs[MAXADDRS];
+ int stun_addr_ct;
+
+ if (!ctx->local_addrs) {
+ if((r=nr_stun_find_local_addresses(stun_addrs,MAXADDRS,&stun_addr_ct))) {
+ ABORT(r);
+ }
+ if((r=nr_ice_set_local_addresses(ctx,stun_addrs,stun_addr_ct))) {
+ ABORT(r);
+ }
+ }
+
+ if(STAILQ_EMPTY(&ctx->streams)) {
+ r_log(LOG_ICE,LOG_ERR,"ICE(%s): Missing streams to initialize",ctx->label);
+ ABORT(R_BAD_ARGS);
+ }
+
+ r_log(LOG_ICE,LOG_DEBUG,"ICE(%s): Initializing candidates",ctx->label);
+ ctx->done_cb=done_cb;
+ ctx->cb_arg=cb_arg;
+
+ /* Initialize all the media stream/component pairs */
+ stream=STAILQ_FIRST(&ctx->streams);
+ while(stream){
+ if(!stream->obsolete) {
+ if(r=nr_ice_media_stream_initialize(ctx,stream)) {
+ r_log(LOG_ICE,LOG_ERR,"ICE(%s): Failed to initialize a stream; this might not be an unrecoverable error, if this stream will be marked obsolete soon due to an ICE restart.",ctx->label);
+ }
+ }
+
+ stream=STAILQ_NEXT(stream,entry);
+ }
+
+ if(ctx->uninitialized_candidates)
+ ABORT(R_WOULDBLOCK);
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_ice_add_media_stream(nr_ice_ctx *ctx,const char *label,const char *ufrag,const char *pwd,int components, nr_ice_media_stream **streamp)
+ {
+ int r,_status;
+
+ if(r=nr_ice_media_stream_create(ctx,label,ufrag,pwd,components,streamp))
+ ABORT(r);
+
+ STAILQ_INSERT_TAIL(&ctx->streams,*streamp,entry);
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_ice_remove_media_stream(nr_ice_ctx *ctx,nr_ice_media_stream **streamp)
+ {
+ int r,_status;
+ nr_ice_peer_ctx *pctx;
+ nr_ice_media_stream *peer_stream;
+
+ pctx=STAILQ_FIRST(&ctx->peers);
+ while(pctx){
+ if(!nr_ice_peer_ctx_find_pstream(pctx, *streamp, &peer_stream)) {
+ if(r=nr_ice_peer_ctx_remove_pstream(pctx, &peer_stream)) {
+ ABORT(r);
+ }
+ }
+
+ pctx=STAILQ_NEXT(pctx,entry);
+ }
+
+ STAILQ_REMOVE(&ctx->streams,*streamp,nr_ice_media_stream_,entry);
+ if(r=nr_ice_media_stream_destroy(streamp)) {
+ ABORT(r);
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_ice_get_global_attributes(nr_ice_ctx *ctx,char ***attrsp, int *attrctp)
+ {
+ *attrctp=0;
+ *attrsp=0;
+ return(0);
+ }
+
+static int nr_ice_random_string(char *str, int len)
+ {
+ unsigned char bytes[100];
+ size_t needed;
+ int r,_status;
+
+ if(len%2) ABORT(R_BAD_ARGS);
+ needed=len/2;
+
+ if(needed>sizeof(bytes)) ABORT(R_BAD_ARGS);
+
+ if(r=nr_crypto_random_bytes(bytes,needed))
+ ABORT(r);
+
+ if(r=nr_bin2hex(bytes,needed,(unsigned char *)str))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+/* This is incredibly annoying: we now have a datagram but we don't
+ know which peer it's from, and we need to be able to tell the
+ API user. So, offer it to each peer and if one bites, assume
+ the others don't want it
+*/
+int nr_ice_ctx_deliver_packet(nr_ice_ctx *ctx, nr_ice_component *comp, nr_transport_addr *source_addr, UCHAR *data, int len)
+ {
+ nr_ice_peer_ctx *pctx;
+ int r;
+
+ pctx=STAILQ_FIRST(&ctx->peers);
+ while(pctx){
+ r=nr_ice_peer_ctx_deliver_packet_maybe(pctx, comp, source_addr, data, len);
+ if(!r)
+ break;
+
+ pctx=STAILQ_NEXT(pctx,entry);
+ }
+
+ if(!pctx)
+ r_log(LOG_ICE,LOG_WARNING,"ICE(%s): Packet received from %s which doesn't match any known peer",ctx->label,source_addr->as_string);
+
+ return(0);
+ }
+
+int nr_ice_ctx_is_known_id(nr_ice_ctx *ctx, UCHAR id[12])
+ {
+ nr_ice_stun_id *xid;
+
+ xid=STAILQ_FIRST(&ctx->ids);
+ while(xid){
+ if (!memcmp(xid->id, id, 12))
+ return 1;
+
+ xid=STAILQ_NEXT(xid,entry);
+ }
+
+ return 0;
+ }
+
+int nr_ice_ctx_remember_id(nr_ice_ctx *ctx, nr_stun_message *msg)
+{
+ int _status;
+ nr_ice_stun_id *xid;
+
+ xid = RCALLOC(sizeof(*xid));
+ if (!xid)
+ ABORT(R_NO_MEMORY);
+
+ assert(sizeof(xid->id) == sizeof(msg->header.id));
+#if __STDC_VERSION__ >= 201112L
+ _Static_assert(sizeof(xid->id) == sizeof(msg->header.id),"Message ID Size Mismatch");
+#endif
+ memcpy(xid->id, &msg->header.id, sizeof(xid->id));
+
+ STAILQ_INSERT_TAIL(&ctx->ids,xid,entry);
+
+ _status=0;
+ abort:
+ return(_status);
+}
+
+
+/* Clean up some of the resources (mostly file descriptors) used
+ by candidates we didn't choose. Note that this still leaves
+ a fair amount of non-system stuff floating around. This gets
+ cleaned up when you destroy the ICE ctx */
+int nr_ice_ctx_finalize(nr_ice_ctx *ctx, nr_ice_peer_ctx *pctx)
+ {
+ nr_ice_media_stream *lstr,*rstr;
+
+ r_log(LOG_ICE,LOG_DEBUG,"Finalizing ICE ctx %s, peer=%s",ctx->label,pctx->label);
+ /*
+ First find the peer stream, if any
+ */
+ lstr=STAILQ_FIRST(&ctx->streams);
+ while(lstr){
+ rstr=STAILQ_FIRST(&pctx->peer_streams);
+
+ while(rstr){
+ if(rstr->local_stream==lstr)
+ break;
+
+ rstr=STAILQ_NEXT(rstr,entry);
+ }
+
+ nr_ice_media_stream_finalize(lstr,rstr);
+
+ lstr=STAILQ_NEXT(lstr,entry);
+ }
+
+ return(0);
+ }
+
+
+int nr_ice_ctx_set_trickle_cb(nr_ice_ctx *ctx, nr_ice_trickle_candidate_cb cb, void *cb_arg)
+{
+ ctx->trickle_cb = cb;
+ ctx->trickle_cb_arg = cb_arg;
+
+ return 0;
+}
+
+int nr_ice_ctx_hide_candidate(nr_ice_ctx *ctx, nr_ice_candidate *cand)
+ {
+ if (cand->state != NR_ICE_CAND_STATE_INITIALIZED) {
+ return 1;
+ }
+
+ if (ctx->flags & NR_ICE_CTX_FLAGS_DISABLE_HOST_CANDIDATES) {
+ if (cand->type == HOST)
+ return 1;
+ }
+
+ if (cand->stream->obsolete) {
+ return 1;
+ }
+
+ return 0;
+ }
+
+int nr_ice_get_new_ice_ufrag(char** ufrag)
+ {
+ int r,_status;
+ char buf[ICE_UFRAG_LEN+1];
+
+ if(r=nr_ice_random_string(buf,ICE_UFRAG_LEN))
+ ABORT(r);
+ if(!(*ufrag=r_strdup(buf)))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ if(_status) {
+ RFREE(*ufrag);
+ *ufrag = 0;
+ }
+ return(_status);
+ }
+
+int nr_ice_get_new_ice_pwd(char** pwd)
+ {
+ int r,_status;
+ char buf[ICE_PWD_LEN+1];
+
+ if(r=nr_ice_random_string(buf,ICE_PWD_LEN))
+ ABORT(r);
+ if(!(*pwd=r_strdup(buf)))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ if(_status) {
+ RFREE(*pwd);
+ *pwd = 0;
+ }
+ return(_status);
+ }
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_ctx.h b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_ctx.h
new file mode 100644
index 0000000000..8b3081f567
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_ctx.h
@@ -0,0 +1,188 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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 _ice_ctx_h
+#define _ice_ctx_h
+#ifdef __cplusplus
+using namespace std;
+extern "C" {
+#endif /* __cplusplus */
+
+/* Not good practice but making includes simpler */
+#include "transport_addr.h"
+#include "nr_socket.h"
+#include "nr_resolver.h"
+#include "nr_interface_prioritizer.h"
+#include "nr_socket_wrapper.h"
+#include "stun_client_ctx.h"
+#include "stun_server_ctx.h"
+#include "turn_client_ctx.h"
+
+typedef struct nr_ice_foundation_ {
+ int index;
+
+ nr_transport_addr addr;
+ int type;
+ /* ICE spec says that we only compare IP address, not port */
+ nr_transport_addr stun_server_addr;
+
+ STAILQ_ENTRY(nr_ice_foundation_) entry;
+} nr_ice_foundation;
+
+typedef STAILQ_HEAD(nr_ice_foundation_head_,nr_ice_foundation_) nr_ice_foundation_head;
+
+typedef TAILQ_HEAD(nr_ice_candidate_head_,nr_ice_candidate_) nr_ice_candidate_head;
+typedef TAILQ_HEAD(nr_ice_cand_pair_head_,nr_ice_cand_pair_) nr_ice_cand_pair_head;
+typedef struct nr_ice_component_ nr_ice_component;
+typedef struct nr_ice_media_stream_ nr_ice_media_stream;
+typedef struct nr_ice_ctx_ nr_ice_ctx;
+typedef struct nr_ice_peer_ctx_ nr_ice_peer_ctx;
+typedef struct nr_ice_candidate_ nr_ice_candidate;
+typedef struct nr_ice_cand_pair_ nr_ice_cand_pair;
+typedef void (*nr_ice_trickle_candidate_cb) (void *cb_arg,
+ nr_ice_ctx *ctx, nr_ice_media_stream *stream, int component_id,
+ nr_ice_candidate *candidate);
+
+#include "ice_socket.h"
+#include "ice_component.h"
+#include "ice_media_stream.h"
+#include "ice_candidate.h"
+#include "ice_candidate_pair.h"
+#include "ice_handler.h"
+#include "ice_peer_ctx.h"
+
+typedef struct nr_ice_stun_id_ {
+ UCHAR id[12];
+
+ STAILQ_ENTRY(nr_ice_stun_id_) entry;
+} nr_ice_stun_id;
+
+typedef STAILQ_HEAD(nr_ice_stun_id_head_,nr_ice_stun_id_) nr_ice_stun_id_head;
+
+typedef struct nr_ice_stats_ {
+ UINT2 stun_retransmits;
+ UINT2 turn_401s;
+ UINT2 turn_403s;
+ UINT2 turn_438s;
+} nr_ice_stats;
+
+struct nr_ice_ctx_ {
+ UINT4 flags;
+ char *label;
+
+ UINT4 Ta;
+
+ nr_ice_stun_server *stun_servers_cfg; /* The list of stun servers */
+ int stun_server_ct_cfg;
+ nr_ice_turn_server *turn_servers_cfg; /* The list of turn servers */
+ int turn_server_ct_cfg;
+ nr_local_addr *local_addrs; /* The list of available local addresses and corresponding interface information */
+ int local_addr_ct;
+
+ nr_resolver *resolver; /* The resolver to use */
+ nr_interface_prioritizer *interface_prioritizer; /* Priority decision logic */
+ nr_socket_factory *socket_factory;
+
+ nr_ice_foundation_head foundations;
+
+ nr_ice_media_stream_head streams; /* Media streams */
+ int stream_ct;
+ nr_ice_socket_head sockets; /* The sockets we're using */
+ int uninitialized_candidates;
+
+ UINT4 gather_rto;
+ UINT4 stun_delay;
+
+ UINT4 test_timer_divider;
+
+ nr_ice_peer_ctx_head peers;
+ nr_ice_stun_id_head ids;
+
+ NR_async_cb done_cb;
+ void *cb_arg;
+
+ nr_ice_trickle_candidate_cb trickle_cb;
+ void *trickle_cb_arg;
+
+ char force_net_interface[MAXIFNAME];
+ nr_ice_stats stats;
+
+ nr_transport_addr *target_for_default_local_address_lookup;
+};
+
+int nr_ice_ctx_create(char *label, UINT4 flags, nr_ice_ctx **ctxp);
+int nr_ice_ctx_create_with_credentials(char *label, UINT4 flags, char* ufrag, char* pwd, nr_ice_ctx **ctxp);
+#define NR_ICE_CTX_FLAGS_AGGRESSIVE_NOMINATION (1)
+#define NR_ICE_CTX_FLAGS_LITE (1<<1)
+#define NR_ICE_CTX_FLAGS_RELAY_ONLY (1<<2)
+#define NR_ICE_CTX_FLAGS_DISABLE_HOST_CANDIDATES (1<<3)
+#define NR_ICE_CTX_FLAGS_ONLY_DEFAULT_ADDRS (1<<4)
+#define NR_ICE_CTX_FLAGS_ONLY_PROXY (1<<5)
+#define NR_ICE_CTX_FLAGS_OBFUSCATE_HOST_ADDRESSES (1<<6)
+
+void nr_ice_ctx_add_flags(nr_ice_ctx *ctx, UINT4 flags);
+void nr_ice_ctx_remove_flags(nr_ice_ctx *ctx, UINT4 flags);
+void nr_ice_ctx_destroy(nr_ice_ctx** ctxp);
+int nr_ice_set_local_addresses(nr_ice_ctx *ctx, nr_local_addr* stun_addrs, int stun_addr_ct);
+int nr_ice_set_target_for_default_local_address_lookup(nr_ice_ctx *ctx, const char *target_ip, UINT2 target_port);
+int nr_ice_gather(nr_ice_ctx *ctx, NR_async_cb done_cb, void *cb_arg);
+int nr_ice_add_candidate(nr_ice_ctx *ctx, nr_ice_candidate *cand);
+void nr_ice_gather_finished_cb(NR_SOCKET s, int h, void *cb_arg);
+int nr_ice_add_media_stream(nr_ice_ctx *ctx,const char *label,const char *ufrag,const char *pwd,int components, nr_ice_media_stream **streamp);
+int nr_ice_remove_media_stream(nr_ice_ctx *ctx,nr_ice_media_stream **streamp);
+int nr_ice_get_global_attributes(nr_ice_ctx *ctx,char ***attrsp, int *attrctp);
+int nr_ice_ctx_deliver_packet(nr_ice_ctx *ctx, nr_ice_component *comp, nr_transport_addr *source_addr, UCHAR *data, int len);
+int nr_ice_ctx_is_known_id(nr_ice_ctx *ctx, UCHAR id[12]);
+int nr_ice_ctx_remember_id(nr_ice_ctx *ctx, nr_stun_message *msg);
+int nr_ice_ctx_finalize(nr_ice_ctx *ctx, nr_ice_peer_ctx *pctx);
+int nr_ice_ctx_set_stun_servers(nr_ice_ctx *ctx,nr_ice_stun_server *servers, int ct);
+int nr_ice_ctx_set_turn_servers(nr_ice_ctx *ctx,nr_ice_turn_server *servers, int ct);
+int nr_ice_ctx_copy_turn_servers(nr_ice_ctx *ctx, nr_ice_turn_server *servers, int ct);
+int nr_ice_ctx_set_resolver(nr_ice_ctx *ctx, nr_resolver *resolver);
+int nr_ice_ctx_set_interface_prioritizer(nr_ice_ctx *ctx, nr_interface_prioritizer *prioritizer);
+void nr_ice_ctx_set_socket_factory(nr_ice_ctx *ctx, nr_socket_factory *factory);
+int nr_ice_ctx_set_trickle_cb(nr_ice_ctx *ctx, nr_ice_trickle_candidate_cb cb, void *cb_arg);
+int nr_ice_ctx_hide_candidate(nr_ice_ctx *ctx, nr_ice_candidate *cand);
+int nr_ice_get_new_ice_ufrag(char** ufrag);
+int nr_ice_get_new_ice_pwd(char** pwd);
+
+#define NR_ICE_MAX_ATTRIBUTE_SIZE 256
+
+extern int LOG_ICE;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_handler.h b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_handler.h
new file mode 100644
index 0000000000..5a0690adad
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_handler.h
@@ -0,0 +1,84 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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 _ice_h
+#define _ice_h
+#ifdef __cplusplus
+using namespace std;
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct nr_ice_handler_vtbl_ {
+ /* The checks on this media stream are done. The handler needs to
+ select a single pair to proceed with (regular nomination).
+ Once this returns the check starts and the pair can be
+ written on. Use nr_ice_candidate_pair_select() to perform the
+ selection.
+ TODO: !ekr! is this right?
+ */
+ int (*select_pair)(void *obj,nr_ice_media_stream *stream,
+int component_id, nr_ice_cand_pair **potentials,int potential_ct);
+
+ /* This media stream is ready to read/write (aggressive nomination).
+ May be called again if the nominated pair changes due to
+ ICE instability. TODO: !ekr! think about this
+ */
+ int (*stream_ready)(void *obj, nr_ice_media_stream *stream);
+
+ /* This media stream has failed */
+ int (*stream_failed)(void *obj, nr_ice_media_stream *stream);
+
+ /* ICE is connected for this peer ctx */
+ int (*ice_connected)(void *obj, nr_ice_peer_ctx *pctx);
+
+ /* A message was delivered to us */
+ int (*msg_recvd)(void *obj, nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream, int component_id, UCHAR *msg, int len);
+
+ /* ICE has started checking. */
+ int (*ice_checking)(void *obj, nr_ice_peer_ctx *pctx);
+
+ /* ICE detected a (temporary?) disconnect. */
+ int (*ice_disconnected)(void *obj, nr_ice_peer_ctx *pctx);
+} nr_ice_handler_vtbl;
+
+typedef struct nr_ice_handler_ {
+ void *obj;
+ nr_ice_handler_vtbl *vtbl;
+} nr_ice_handler;
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_media_stream.c b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_media_stream.c
new file mode 100644
index 0000000000..62bfbad629
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_media_stream.c
@@ -0,0 +1,1087 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+*/
+
+#include <string.h>
+#include <assert.h>
+#include <nr_api.h>
+#include <r_assoc.h>
+#include <async_timer.h>
+#include "ice_util.h"
+#include "ice_ctx.h"
+
+static char *nr_ice_media_stream_states[]={"INVALID",
+ "UNPAIRED","FROZEN","ACTIVE","CONNECTED","FAILED"
+};
+
+int nr_ice_media_stream_set_state(nr_ice_media_stream *str, int state);
+
+int nr_ice_media_stream_create(nr_ice_ctx *ctx,const char *label,const char *ufrag,const char *pwd,int components, nr_ice_media_stream **streamp)
+ {
+ int r,_status;
+ nr_ice_media_stream *stream=0;
+ nr_ice_component *comp=0;
+ int i;
+
+ if(!(stream=RCALLOC(sizeof(nr_ice_media_stream))))
+ ABORT(R_NO_MEMORY);
+
+ if(!(stream->label=r_strdup(label)))
+ ABORT(R_NO_MEMORY);
+
+ if(!(stream->ufrag=r_strdup(ufrag)))
+ ABORT(R_NO_MEMORY);
+
+ if(!(stream->pwd=r_strdup(pwd)))
+ ABORT(R_NO_MEMORY);
+
+ stream->ctx=ctx;
+
+ STAILQ_INIT(&stream->components);
+ for(i=0;i<components;i++){
+ /* component-id must be > 0, so increment by 1 */
+ if(r=nr_ice_component_create(stream, i+1, &comp))
+ ABORT(r);
+
+ }
+
+ TAILQ_INIT(&stream->check_list);
+ TAILQ_INIT(&stream->trigger_check_queue);
+
+ stream->disconnected = 0;
+ stream->component_ct=components;
+ stream->ice_state = NR_ICE_MEDIA_STREAM_UNPAIRED;
+ stream->obsolete = 0;
+ stream->r2l_user = 0;
+ stream->l2r_user = 0;
+ stream->flags = ctx->flags;
+ if(ctx->stun_server_ct_cfg) {
+ if(!(stream->stun_servers=RCALLOC(sizeof(nr_ice_stun_server)*(ctx->stun_server_ct_cfg))))
+ ABORT(R_NO_MEMORY);
+
+ memcpy(stream->stun_servers,ctx->stun_servers_cfg,sizeof(nr_ice_stun_server)*(ctx->stun_server_ct_cfg));
+ stream->stun_server_ct = ctx->stun_server_ct_cfg;
+ }
+
+ if(ctx->turn_server_ct_cfg) {
+ if(!(stream->turn_servers=RCALLOC(sizeof(nr_ice_turn_server)*(ctx->turn_server_ct_cfg))))
+ ABORT(R_NO_MEMORY);
+
+ for(int i = 0; i < ctx->turn_server_ct_cfg; ++i) {
+ nr_ice_turn_server *dst = &stream->turn_servers[i];
+ nr_ice_turn_server *src = &ctx->turn_servers_cfg[i];
+ memcpy(&dst->turn_server, &src->turn_server, sizeof(nr_ice_stun_server));
+ dst->username = r_strdup(src->username);
+ r_data_create(&dst->password, src->password->data, src->password->len);
+ }
+ stream->turn_server_ct = ctx->turn_server_ct_cfg;
+ }
+
+ r_log(LOG_ICE,LOG_DEBUG,"ICE-STREAM(%s): flags %d",stream->label,stream->flags);
+ *streamp=stream;
+
+ _status=0;
+ abort:
+ if(_status){
+ nr_ice_media_stream_destroy(&stream);
+ }
+ return(_status);
+ }
+
+int nr_ice_media_stream_destroy(nr_ice_media_stream **streamp)
+ {
+ nr_ice_media_stream *stream;
+ nr_ice_component *c1,*c2;
+ nr_ice_cand_pair *p1,*p2;
+ if(!streamp || !*streamp)
+ return(0);
+
+ stream=*streamp;
+ *streamp=0;
+
+ STAILQ_FOREACH_SAFE(c1, &stream->components, entry, c2){
+ STAILQ_REMOVE(&stream->components,c1,nr_ice_component_,entry);
+ nr_ice_component_destroy(&c1);
+ }
+
+ /* Note: all the entries from the trigger check queue are held in here as
+ * well, so we only clean up the super set. */
+ TAILQ_FOREACH_SAFE(p1, &stream->check_list, check_queue_entry, p2){
+ TAILQ_REMOVE(&stream->check_list,p1,check_queue_entry);
+ nr_ice_candidate_pair_destroy(&p1);
+ }
+
+ RFREE(stream->label);
+
+ RFREE(stream->ufrag);
+ RFREE(stream->pwd);
+ RFREE(stream->r2l_user);
+ RFREE(stream->l2r_user);
+ r_data_zfree(&stream->r2l_pass);
+ r_data_zfree(&stream->l2r_pass);
+
+ RFREE(stream->stun_servers);
+ for (int i = 0; i < stream->turn_server_ct; i++) {
+ RFREE(stream->turn_servers[i].username);
+ r_data_destroy(&stream->turn_servers[i].password);
+ }
+ RFREE(stream->turn_servers);
+
+ if(stream->timer)
+ NR_async_timer_cancel(stream->timer);
+
+ RFREE(stream);
+
+ return(0);
+ }
+
+int nr_ice_media_stream_initialize(nr_ice_ctx *ctx, nr_ice_media_stream *stream)
+ {
+ int r,_status;
+ nr_ice_component *comp;
+
+ assert(!stream->obsolete);
+
+ comp=STAILQ_FIRST(&stream->components);
+ while(comp){
+ if(r=nr_ice_component_initialize(ctx,comp))
+ ABORT(r);
+ comp=STAILQ_NEXT(comp,entry);
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+
+int nr_ice_media_stream_get_attributes(nr_ice_media_stream *stream, char ***attrsp, int *attrctp)
+ {
+ int attrct=2;
+ nr_ice_component *comp;
+ char **attrs=0;
+ int index=0;
+ nr_ice_candidate *cand;
+ int r,_status;
+ char *tmp=0;
+
+ *attrctp=0;
+
+ /* First find out how many attributes we need */
+ comp=STAILQ_FIRST(&stream->components);
+ while(comp){
+ if (comp->state != NR_ICE_COMPONENT_DISABLED) {
+ cand = TAILQ_FIRST(&comp->candidates);
+ while(cand){
+ if (!nr_ice_ctx_hide_candidate(stream->ctx, cand)) {
+ ++attrct;
+ }
+
+ cand = TAILQ_NEXT(cand, entry_comp);
+ }
+ }
+ comp=STAILQ_NEXT(comp,entry);
+ }
+
+ /* Make the array we'll need */
+ if(!(attrs=RCALLOC(sizeof(char *)*attrct)))
+ ABORT(R_NO_MEMORY);
+ for(index=0;index<attrct;index++){
+ if(!(attrs[index]=RMALLOC(NR_ICE_MAX_ATTRIBUTE_SIZE)))
+ ABORT(R_NO_MEMORY);
+ }
+
+ index=0;
+ /* Now format the attributes */
+ comp=STAILQ_FIRST(&stream->components);
+ while(comp){
+ if (comp->state != NR_ICE_COMPONENT_DISABLED) {
+ nr_ice_candidate *cand;
+
+ cand=TAILQ_FIRST(&comp->candidates);
+ while(cand){
+ if (!nr_ice_ctx_hide_candidate(stream->ctx, cand)) {
+ assert(index < attrct);
+
+ if (index >= attrct)
+ ABORT(R_INTERNAL);
+
+ if(r=nr_ice_format_candidate_attribute(cand, attrs[index],NR_ICE_MAX_ATTRIBUTE_SIZE, 0))
+ ABORT(r);
+
+ index++;
+ }
+
+ cand=TAILQ_NEXT(cand,entry_comp);
+ }
+ }
+ comp=STAILQ_NEXT(comp,entry);
+ }
+
+ /* Now, ufrag and pwd */
+ if(!(tmp=RMALLOC(100)))
+ ABORT(R_NO_MEMORY);
+ snprintf(tmp,100,"ice-ufrag:%s",stream->ufrag);
+ attrs[index++]=tmp;
+
+ if(!(tmp=RMALLOC(100)))
+ ABORT(R_NO_MEMORY);
+ snprintf(tmp,100,"ice-pwd:%s",stream->pwd);
+ attrs[index++]=tmp;
+
+ *attrsp=attrs;
+ *attrctp=attrct;
+
+ _status=0;
+ abort:
+ if(_status){
+ if(attrs){
+ for(index=0;index<attrct;index++){
+ RFREE(attrs[index]);
+ }
+ RFREE(attrs);
+ }
+ }
+ return(_status);
+ }
+
+/* Get a default candidate per 4.1.4 */
+int nr_ice_media_stream_get_default_candidate(nr_ice_media_stream *stream, int component, nr_ice_candidate **candp)
+ {
+ int r,_status;
+ nr_ice_component *comp;
+
+ comp=STAILQ_FIRST(&stream->components);
+ while(comp){
+ if (comp->component_id == component)
+ break;
+
+ comp=STAILQ_NEXT(comp,entry);
+ }
+
+ if (!comp)
+ ABORT(R_NOT_FOUND);
+
+ /* If there aren't any IPV4 candidates, try IPV6 */
+ if((r=nr_ice_component_get_default_candidate(comp, candp, NR_IPV4)) &&
+ (r=nr_ice_component_get_default_candidate(comp, candp, NR_IPV6))) {
+ ABORT(r);
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+
+int nr_ice_media_stream_pair_candidates(nr_ice_peer_ctx *pctx,nr_ice_media_stream *lstream,nr_ice_media_stream *pstream)
+ {
+ int r,_status;
+ nr_ice_component *pcomp,*lcomp;
+
+ pcomp=STAILQ_FIRST(&pstream->components);
+ lcomp=STAILQ_FIRST(&lstream->components);
+ while(pcomp){
+ if ((lcomp->state != NR_ICE_COMPONENT_DISABLED) &&
+ (pcomp->state != NR_ICE_COMPONENT_DISABLED)) {
+ if(r=nr_ice_component_pair_candidates(pctx,lcomp,pcomp))
+ ABORT(r);
+ }
+
+ lcomp=STAILQ_NEXT(lcomp,entry);
+ pcomp=STAILQ_NEXT(pcomp,entry);
+ };
+
+ if (pstream->ice_state == NR_ICE_MEDIA_STREAM_UNPAIRED) {
+ nr_ice_media_stream_set_state(pstream, NR_ICE_MEDIA_STREAM_CHECKS_FROZEN);
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_ice_media_stream_service_pre_answer_requests(nr_ice_peer_ctx *pctx, nr_ice_media_stream *lstream, nr_ice_media_stream *pstream, int *serviced)
+ {
+ nr_ice_component *pcomp;
+ int r,_status;
+ char *user = 0;
+
+ if (serviced)
+ *serviced = 0;
+
+ pcomp=STAILQ_FIRST(&pstream->components);
+ while(pcomp){
+ int serviced_inner=0;
+
+ /* Flush all the pre-answer requests */
+ if(r=nr_ice_component_service_pre_answer_requests(pctx, pcomp, pstream->r2l_user, &serviced_inner))
+ ABORT(r);
+ if (serviced)
+ *serviced += serviced_inner;
+
+ pcomp=STAILQ_NEXT(pcomp,entry);
+ }
+
+ _status=0;
+ abort:
+ RFREE(user);
+ return(_status);
+ }
+
+/* S 5.8 -- run the first pair from the triggered check queue (even after
+ * checks have completed S 8.1.2) or run the highest priority WAITING pair or
+ * if not available FROZEN pair from the check queue */
+static void nr_ice_media_stream_check_timer_cb(NR_SOCKET s, int h, void *cb_arg)
+ {
+ int r,_status;
+ nr_ice_media_stream *stream=cb_arg;
+ nr_ice_cand_pair *pair = 0;
+ int timer_multiplier=stream->pctx->active_streams ? stream->pctx->active_streams : 1;
+ int timer_val=stream->pctx->ctx->Ta*timer_multiplier;
+
+ assert(timer_val>0);
+
+ r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s): check timer expired for media stream %s",stream->pctx->label,stream->label);
+ stream->timer=0;
+
+ /* The trigger check queue has the highest priority */
+ pair=TAILQ_FIRST(&stream->trigger_check_queue);
+ while(pair){
+ if(pair->state==NR_ICE_PAIR_STATE_WAITING){
+ /* Remove the pair from he trigger check queue */
+ r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s): Removing pair from trigger check queue %s",stream->pctx->label,pair->as_string);
+ TAILQ_REMOVE(&stream->trigger_check_queue,pair,triggered_check_queue_entry);
+ break;
+ }
+ pair=TAILQ_NEXT(pair,triggered_check_queue_entry);
+ }
+
+ if (stream->ice_state != NR_ICE_MEDIA_STREAM_CHECKS_CONNECTED) {
+ if(!pair){
+ /* Find the highest priority WAITING check and move it to RUNNING */
+ pair=TAILQ_FIRST(&stream->check_list);
+ while(pair){
+ if(pair->state==NR_ICE_PAIR_STATE_WAITING)
+ break;
+ pair=TAILQ_NEXT(pair,check_queue_entry);
+ }
+ }
+
+ /* Hmmm... No WAITING. Let's look for FROZEN */
+ if(!pair){
+ pair=TAILQ_FIRST(&stream->check_list);
+
+ while(pair){
+ if(pair->state==NR_ICE_PAIR_STATE_FROZEN){
+ if(r=nr_ice_candidate_pair_unfreeze(stream->pctx,pair))
+ ABORT(r);
+ break;
+ }
+ pair=TAILQ_NEXT(pair,check_queue_entry);
+ }
+ }
+ }
+
+ if(pair){
+ nr_ice_candidate_pair_start(pair->pctx,pair); /* Ignore failures */
+ NR_ASYNC_TIMER_SET(timer_val,nr_ice_media_stream_check_timer_cb,cb_arg,&stream->timer);
+ }
+ else {
+ r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s): no FROZEN/WAITING pairs for %s",stream->pctx->label,stream->label);
+ }
+
+ _status=0;
+ abort:
+ if (_status) {
+ // cb doesn't return anything, but we should probably log that we aborted
+ // This also quiets the unused variable warnings.
+ r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s): check timer cb for media stream %s abort with status: %d",
+ stream->pctx->label,stream->label, _status);
+ }
+ return;
+ }
+
+/* Start checks for this media stream (aka check list) */
+int nr_ice_media_stream_start_checks(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream)
+ {
+ int r,_status;
+
+ /* Don't start the check timer if the stream is failed */
+ if (stream->ice_state == NR_ICE_MEDIA_STREAM_CHECKS_FAILED) {
+ assert(0);
+ ABORT(R_INTERNAL);
+ }
+
+ if (stream->local_stream->obsolete) {
+ assert(0);
+ ABORT(R_INTERNAL);
+ }
+
+ /* Even if the stream is completed already remote can still create a new
+ * triggered check request which needs to fire, but not change our stream
+ * state. */
+ if (stream->ice_state != NR_ICE_MEDIA_STREAM_CHECKS_CONNECTED) {
+ if(r=nr_ice_media_stream_set_state(stream,NR_ICE_MEDIA_STREAM_CHECKS_ACTIVE)) {
+ ABORT(r);
+ }
+ }
+
+ if (!stream->timer) {
+ r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/ICE-STREAM(%s): Starting check timer for stream.",pctx->label,stream->label);
+ nr_ice_media_stream_check_timer_cb(0,0,stream);
+ }
+
+ nr_ice_peer_ctx_stream_started_checks(pctx, stream);
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+/* Start checks for this media stream (aka check list) S 5.7 */
+int nr_ice_media_stream_unfreeze_pairs(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream)
+ {
+ int r,_status;
+ r_assoc *assoc=0;
+ nr_ice_cand_pair *pair=0;
+
+ /* Already seen assoc */
+ if(r=r_assoc_create(&assoc,r_assoc_crc32_hash_compute,5))
+ ABORT(r);
+
+ /* S 5.7.4. Set the highest priority pairs in each foundation to WAITING */
+ pair=TAILQ_FIRST(&stream->check_list);
+ while(pair){
+ void *v;
+
+ if(r=r_assoc_fetch(assoc,pair->foundation,strlen(pair->foundation),&v)){
+ if(r!=R_NOT_FOUND)
+ ABORT(r);
+ if(r=nr_ice_candidate_pair_unfreeze(pctx,pair))
+ ABORT(r);
+
+ if(r=r_assoc_insert(assoc,pair->foundation,strlen(pair->foundation),
+ 0,0,0,R_ASSOC_NEW))
+ ABORT(r);
+ }
+
+ /* Already exists... fall through */
+ pair=TAILQ_NEXT(pair,check_queue_entry);
+ }
+
+ _status=0;
+ abort:
+ r_assoc_destroy(&assoc);
+ return(_status);
+ }
+
+static int nr_ice_media_stream_unfreeze_pairs_match(nr_ice_media_stream *stream, char *foundation)
+ {
+ nr_ice_cand_pair *pair;
+ int r,_status;
+ int unfroze=0;
+
+ pair=TAILQ_FIRST(&stream->check_list);
+ while(pair){
+ if(pair->state==NR_ICE_PAIR_STATE_FROZEN &&
+ !strcmp(foundation,pair->foundation)){
+ if(r=nr_ice_candidate_pair_unfreeze(stream->pctx,pair))
+ ABORT(r);
+ unfroze++;
+ }
+ pair=TAILQ_NEXT(pair,check_queue_entry);
+ }
+
+ if(!unfroze)
+ return(R_NOT_FOUND);
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+/* S 7.1.2.2 */
+int nr_ice_media_stream_unfreeze_pairs_foundation(nr_ice_media_stream *stream, char *foundation)
+ {
+ int r,_status;
+ nr_ice_media_stream *str;
+
+ /* 1. Unfreeze all frozen pairs with the same foundation
+ in this stream */
+ if(r=nr_ice_media_stream_unfreeze_pairs_match(stream,foundation)){
+ if(r!=R_NOT_FOUND)
+ ABORT(r);
+ }
+
+ /* Now go through the check lists for the other streams */
+ str=STAILQ_FIRST(&stream->pctx->peer_streams);
+ while(str){
+ if(str!=stream && !str->local_stream->obsolete){
+ switch(str->ice_state){
+ case NR_ICE_MEDIA_STREAM_CHECKS_ACTIVE:
+ /* Unfreeze matching pairs */
+ if(r=nr_ice_media_stream_unfreeze_pairs_match(str,foundation)){
+ if(r!=R_NOT_FOUND)
+ ABORT(r);
+ }
+ break;
+ case NR_ICE_MEDIA_STREAM_CHECKS_FROZEN:
+ /* Unfreeze matching pairs if any */
+ r=nr_ice_media_stream_unfreeze_pairs_match(str,foundation);
+ if(r){
+ if(r!=R_NOT_FOUND)
+ ABORT(r);
+
+ /* OK, no matching pairs: execute the algorithm from 5.7
+ for this stream */
+ if(r=nr_ice_media_stream_unfreeze_pairs(str->pctx,str))
+ ABORT(r);
+ }
+ if(r=nr_ice_media_stream_start_checks(str->pctx,str))
+ ABORT(r);
+
+ break;
+ default:
+ break;
+ }
+ }
+
+ str=STAILQ_NEXT(str,entry);
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+
+void nr_ice_media_stream_dump_state(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream, int log_level)
+ {
+ nr_ice_cand_pair *pair;
+ nr_ice_component *comp;
+
+ if(stream->local_stream){
+ /* stream has a corresponding local_stream */
+ nr_ice_media_stream_dump_state(stream->local_stream->pctx,stream->local_stream, log_level);
+ r_log(LOG_ICE,log_level,"ICE-PEER(%s)/STREAM(%s): state dump", stream->pctx->label, stream->label);
+ } else {
+ r_log(LOG_ICE,log_level,"ICE(%s)/STREAM(%s): state dump", stream->ctx->label, stream->label);
+ }
+
+ pair=TAILQ_FIRST(&stream->check_list);
+ while(pair){
+ nr_ice_candidate_pair_dump_state(pair, log_level);
+ pair=TAILQ_NEXT(pair,check_queue_entry);
+ }
+
+ comp=STAILQ_FIRST(&stream->components);
+ while(comp){
+ nr_ice_component_dump_state(comp, log_level);
+ comp=STAILQ_NEXT(comp,entry);
+ }
+ }
+
+int nr_ice_media_stream_set_state(nr_ice_media_stream *str, int state)
+ {
+ /* Make no-change a no-op */
+ if (state == str->ice_state)
+ return 0;
+
+ assert((size_t)state < sizeof(nr_ice_media_stream_states)/sizeof(char *));
+ assert((size_t)str->ice_state < sizeof(nr_ice_media_stream_states)/sizeof(char *));
+
+ r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s): stream %s state %s->%s",
+ str->pctx->label,str->label,
+ nr_ice_media_stream_states[str->ice_state],
+ nr_ice_media_stream_states[state]);
+
+ if(state == NR_ICE_MEDIA_STREAM_CHECKS_ACTIVE)
+ str->pctx->active_streams++;
+ if(str->ice_state == NR_ICE_MEDIA_STREAM_CHECKS_ACTIVE)
+ str->pctx->active_streams--;
+
+ r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s): %d active streams",
+ str->pctx->label, str->pctx->active_streams);
+
+ str->ice_state=state;
+ if (state == NR_ICE_MEDIA_STREAM_CHECKS_FAILED) {
+ nr_ice_media_stream_dump_state(str->pctx,str,LOG_ERR);
+ }
+
+ return(0);
+ }
+
+void nr_ice_media_stream_stop_checking(nr_ice_media_stream *str)
+ {
+ nr_ice_cand_pair *p;
+ nr_ice_component *comp;
+
+ /* Cancel candidate pairs */
+ p=TAILQ_FIRST(&str->check_list);
+ while(p){
+ nr_ice_candidate_pair_cancel(p->pctx,p,0);
+ p=TAILQ_NEXT(p,check_queue_entry);
+ }
+
+ if(str->timer) {
+ NR_async_timer_cancel(str->timer);
+ str->timer = 0;
+ }
+
+ /* Cancel consent timers in case it is running already */
+ comp=STAILQ_FIRST(&str->components);
+ while(comp){
+ nr_ice_component_consent_destroy(comp);
+ comp=STAILQ_NEXT(comp,entry);
+ }
+ }
+
+void nr_ice_media_stream_set_obsolete(nr_ice_media_stream *str)
+ {
+ nr_ice_component *c1,*c2;
+ str->obsolete = 1;
+
+ STAILQ_FOREACH_SAFE(c1, &str->components, entry, c2){
+ nr_ice_component_stop_gathering(c1);
+ }
+
+ nr_ice_media_stream_stop_checking(str);
+ }
+
+int nr_ice_media_stream_is_done_gathering(nr_ice_media_stream *str)
+ {
+ nr_ice_component *comp;
+ comp=STAILQ_FIRST(&str->components);
+ while(comp){
+ if(!nr_ice_component_is_done_gathering(comp)) {
+ return 0;
+ }
+ comp=STAILQ_NEXT(comp,entry);
+ }
+ return 1;
+ }
+
+void nr_ice_media_stream_refresh_consent_all(nr_ice_media_stream *stream)
+ {
+ nr_ice_component *comp;
+
+ comp=STAILQ_FIRST(&stream->components);
+ while(comp){
+ if(comp->disconnected) {
+ nr_ice_component_refresh_consent_now(comp);
+ }
+
+ comp=STAILQ_NEXT(comp,entry);
+ }
+ }
+
+void nr_ice_media_stream_disconnect_all_components(nr_ice_media_stream *stream)
+ {
+ nr_ice_component *comp;
+
+ comp=STAILQ_FIRST(&stream->components);
+ while(comp){
+ comp->disconnected = 1;
+
+ comp=STAILQ_NEXT(comp,entry);
+ }
+ }
+
+void nr_ice_media_stream_set_disconnected(nr_ice_media_stream *stream, int disconnected)
+ {
+ if (stream->disconnected == disconnected) {
+ return;
+ }
+
+ if (stream->ice_state != NR_ICE_MEDIA_STREAM_CHECKS_CONNECTED) {
+ return;
+ }
+ stream->disconnected = disconnected;
+
+ if (disconnected == NR_ICE_MEDIA_STREAM_DISCONNECTED) {
+ if (!stream->local_stream->obsolete) {
+ nr_ice_peer_ctx_disconnected(stream->pctx);
+ }
+ } else {
+ nr_ice_peer_ctx_check_if_connected(stream->pctx);
+ }
+ }
+
+int nr_ice_media_stream_check_if_connected(nr_ice_media_stream *stream)
+ {
+ nr_ice_component *comp;
+
+ comp=STAILQ_FIRST(&stream->components);
+ while(comp){
+ if((comp->state != NR_ICE_COMPONENT_DISABLED) &&
+ (comp->local_component->state != NR_ICE_COMPONENT_DISABLED) &&
+ comp->disconnected)
+ break;
+
+ comp=STAILQ_NEXT(comp,entry);
+ }
+
+ /* At least one disconnected component */
+ if(comp)
+ goto done;
+
+ nr_ice_media_stream_set_disconnected(stream, NR_ICE_MEDIA_STREAM_CONNECTED);
+
+ done:
+ return(0);
+ }
+
+/* S OK, this component has a nominated. If every component has a nominated,
+ the stream is ready */
+void nr_ice_media_stream_component_nominated(nr_ice_media_stream *stream,nr_ice_component *component)
+ {
+ nr_ice_component *comp;
+
+ comp=STAILQ_FIRST(&stream->components);
+ while(comp){
+ if((comp->state != NR_ICE_COMPONENT_DISABLED) &&
+ (comp->local_component->state != NR_ICE_COMPONENT_DISABLED) &&
+ !comp->nominated)
+ break;
+
+ comp=STAILQ_NEXT(comp,entry);
+ }
+
+ /* At least one un-nominated component */
+ if(comp)
+ return;
+
+ /* All done... */
+ r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s)/ICE-STREAM(%s): all active components have nominated candidate pairs",stream->pctx->label,stream->label);
+ nr_ice_media_stream_set_state(stream,NR_ICE_MEDIA_STREAM_CHECKS_CONNECTED);
+
+ /* Cancel our timer */
+ if(stream->timer){
+ NR_async_timer_cancel(stream->timer);
+ stream->timer=0;
+ }
+
+ if (stream->pctx->handler && !stream->local_stream->obsolete) {
+ stream->pctx->handler->vtbl->stream_ready(stream->pctx->handler->obj,stream->local_stream);
+ }
+
+ /* Now tell the peer_ctx that we're connected */
+ nr_ice_peer_ctx_check_if_connected(stream->pctx);
+ }
+
+void nr_ice_media_stream_component_failed(nr_ice_media_stream *stream,nr_ice_component *component)
+ {
+ component->state=NR_ICE_COMPONENT_FAILED;
+
+ /* at least one component failed in this media stream, so the entire
+ * media stream is marked failed */
+
+ nr_ice_media_stream_set_state(stream,NR_ICE_MEDIA_STREAM_CHECKS_FAILED);
+
+ nr_ice_media_stream_stop_checking(stream);
+
+ if (stream->pctx->handler && !stream->local_stream->obsolete) {
+ stream->pctx->handler->vtbl->stream_failed(stream->pctx->handler->obj,stream->local_stream);
+ }
+
+ /* Now tell the peer_ctx that we've failed */
+ nr_ice_peer_ctx_check_if_connected(stream->pctx);
+ }
+
+int nr_ice_media_stream_get_best_candidate(nr_ice_media_stream *str, int component, nr_ice_candidate **candp)
+ {
+ nr_ice_candidate *cand;
+ nr_ice_candidate *best_cand=0;
+ nr_ice_component *comp;
+ int r,_status;
+
+ if(r=nr_ice_media_stream_find_component(str,component,&comp))
+ ABORT(r);
+
+ cand=TAILQ_FIRST(&comp->candidates);
+ while(cand){
+ if(cand->state==NR_ICE_CAND_STATE_INITIALIZED){
+ if(!best_cand || (cand->priority>best_cand->priority))
+ best_cand=cand;
+
+ }
+ cand=TAILQ_NEXT(cand,entry_comp);
+ }
+
+ if(!best_cand)
+ ABORT(R_NOT_FOUND);
+
+ *candp=best_cand;
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+
+/* OK, we have the stream the user created, but that reflects the base
+ ICE ctx, not the peer_ctx. So, find the related stream in the pctx,
+ and then find the component */
+int nr_ice_media_stream_find_component(nr_ice_media_stream *str, int comp_id, nr_ice_component **compp)
+ {
+ int _status;
+ nr_ice_component *comp;
+
+ comp=STAILQ_FIRST(&str->components);
+ while(comp){
+ if(comp->component_id==comp_id)
+ break;
+
+ comp=STAILQ_NEXT(comp,entry);
+ }
+ if(!comp)
+ ABORT(R_NOT_FOUND);
+
+ *compp=comp;
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_ice_media_stream_send(nr_ice_peer_ctx *pctx, nr_ice_media_stream *str, int component, UCHAR *data, int len)
+ {
+ int r,_status;
+ nr_ice_component *comp;
+
+ /* First find the peer component */
+ if(r=nr_ice_peer_ctx_find_component(pctx, str, component, &comp))
+ ABORT(r);
+
+ /* Do we have an active pair yet? We should... */
+ if(!comp->active)
+ ABORT(R_NOT_FOUND);
+
+ /* Does fresh ICE consent exist? */
+ if(!comp->can_send)
+ ABORT(R_FAILED);
+
+ /* OK, write to that pair, which means:
+ 1. Use the socket on our local side.
+ 2. Use the address on the remote side
+ */
+ if(r=nr_socket_sendto(comp->active->local->osock,data,len,0,
+ &comp->active->remote->addr)) {
+ if ((r==R_IO_ERROR) || (r==R_EOD)) {
+ nr_ice_component_disconnected(comp);
+ }
+ ABORT(r);
+ }
+
+ // accumulate the sent bytes for the active candidate pair
+ comp->active->bytes_sent += len;
+ gettimeofday(&comp->active->last_sent, 0);
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+/* Returns R_REJECTED if the component is unpaired or has been disabled. */
+int nr_ice_media_stream_get_active(nr_ice_peer_ctx *pctx, nr_ice_media_stream *str, int component, nr_ice_candidate **local, nr_ice_candidate **remote)
+ {
+ int r,_status;
+ nr_ice_component *comp;
+
+ /* First find the peer component */
+ if(r=nr_ice_peer_ctx_find_component(pctx, str, component, &comp))
+ ABORT(r);
+
+ if (comp->state == NR_ICE_COMPONENT_UNPAIRED ||
+ comp->state == NR_ICE_COMPONENT_DISABLED)
+ ABORT(R_REJECTED);
+
+ if(!comp->active)
+ ABORT(R_NOT_FOUND);
+
+ if (local) *local = comp->active->local;
+ if (remote) *remote = comp->active->remote;
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_ice_media_stream_addrs(nr_ice_peer_ctx *pctx, nr_ice_media_stream *str, int component, nr_transport_addr *local, nr_transport_addr *remote)
+ {
+ int r,_status;
+ nr_ice_component *comp;
+
+ /* First find the peer component */
+ if(r=nr_ice_peer_ctx_find_component(pctx, str, component, &comp))
+ ABORT(r);
+
+ /* Do we have an active pair yet? We should... */
+ if(!comp->active)
+ ABORT(R_BAD_ARGS);
+
+ /* Use the socket on our local side */
+ if(r=nr_socket_getaddr(comp->active->local->osock,local))
+ ABORT(r);
+
+ /* Use the address on the remote side */
+ if(r=nr_transport_addr_copy(remote,&comp->active->remote->addr))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+
+
+int nr_ice_media_stream_finalize(nr_ice_media_stream *lstr,nr_ice_media_stream *rstr)
+ {
+ nr_ice_component *lcomp,*rcomp;
+
+ r_log(LOG_ICE,LOG_DEBUG,"Finalizing media stream %s, peer=%s",lstr->label,
+ rstr?rstr->label:"NONE");
+
+ lcomp=STAILQ_FIRST(&lstr->components);
+ if(rstr)
+ rcomp=STAILQ_FIRST(&rstr->components);
+ else
+ rcomp=0;
+
+ while(lcomp){
+ nr_ice_component_finalize(lcomp,rcomp);
+
+ lcomp=STAILQ_NEXT(lcomp,entry);
+ if(rcomp){
+ rcomp=STAILQ_NEXT(rcomp,entry);
+ }
+ }
+
+ return(0);
+ }
+
+int nr_ice_media_stream_pair_new_trickle_candidate(nr_ice_peer_ctx *pctx, nr_ice_media_stream *pstream, nr_ice_candidate *cand)
+ {
+ int r,_status;
+ nr_ice_component *comp;
+
+ if ((r=nr_ice_media_stream_find_component(pstream, cand->component_id, &comp)))
+ ABORT(R_NOT_FOUND);
+
+ if (r=nr_ice_component_pair_candidate(pctx, comp, cand, 1))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_ice_media_stream_get_consent_status(nr_ice_media_stream *stream, int
+component_id, int *can_send, struct timeval *ts)
+ {
+ int r,_status;
+ nr_ice_component *comp;
+
+ if ((r=nr_ice_media_stream_find_component(stream, component_id, &comp)))
+ ABORT(r);
+
+ *can_send = comp->can_send;
+ ts->tv_sec = comp->consent_last_seen.tv_sec;
+ ts->tv_usec = comp->consent_last_seen.tv_usec;
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_ice_media_stream_disable_component(nr_ice_media_stream *stream, int component_id)
+ {
+ int r,_status;
+ nr_ice_component *comp;
+
+ if (stream->ice_state != NR_ICE_MEDIA_STREAM_UNPAIRED)
+ ABORT(R_FAILED);
+
+ if ((r=nr_ice_media_stream_find_component(stream, component_id, &comp)))
+ ABORT(r);
+
+ /* Can only disable before pairing */
+ if (comp->state != NR_ICE_COMPONENT_UNPAIRED &&
+ comp->state != NR_ICE_COMPONENT_DISABLED)
+ ABORT(R_FAILED);
+
+ comp->state = NR_ICE_COMPONENT_DISABLED;
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+void nr_ice_media_stream_role_change(nr_ice_media_stream *stream)
+ {
+ nr_ice_cand_pair *pair,*temp_pair;
+ /* Changing role causes candidate pair priority to change, which requires
+ * re-sorting the check list. */
+ nr_ice_cand_pair_head old_checklist;
+
+ /* Move check_list to old_checklist (not POD, have to do the hard way) */
+ TAILQ_INIT(&old_checklist);
+ TAILQ_FOREACH_SAFE(pair,&stream->check_list,check_queue_entry,temp_pair) {
+ TAILQ_REMOVE(&stream->check_list,pair,check_queue_entry);
+ TAILQ_INSERT_TAIL(&old_checklist,pair,check_queue_entry);
+ }
+
+ /* Re-insert into the check list */
+ TAILQ_FOREACH_SAFE(pair,&old_checklist,check_queue_entry,temp_pair) {
+ TAILQ_REMOVE(&old_checklist,pair,check_queue_entry);
+ nr_ice_candidate_pair_role_change(pair);
+ nr_ice_candidate_pair_insert(&stream->check_list,pair);
+ }
+ }
+
+int nr_ice_media_stream_find_pair(nr_ice_media_stream *str, nr_ice_candidate *lcand, nr_ice_candidate *rcand, nr_ice_cand_pair **pair)
+ {
+ nr_ice_cand_pair_head *head = &str->check_list;
+ nr_ice_cand_pair *c1;
+
+ c1=TAILQ_FIRST(head);
+ while(c1){
+ if(c1->local == lcand &&
+ c1->remote == rcand) {
+ *pair=c1;
+ return(0);
+ }
+
+ c1=TAILQ_NEXT(c1,check_queue_entry);
+ }
+
+ return(R_NOT_FOUND);
+ }
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_media_stream.h b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_media_stream.h
new file mode 100644
index 0000000000..99f906c100
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_media_stream.h
@@ -0,0 +1,146 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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 _ice_media_stream_h
+#define _ice_media_stream_h
+#ifdef __cplusplus
+using namespace std;
+extern "C" {
+#endif /* __cplusplus */
+
+#include "transport_addr.h"
+
+typedef struct nr_ice_stun_server_ {
+ nr_transport_addr addr;
+ int id;
+} nr_ice_stun_server;
+
+typedef struct nr_ice_turn_server_ {
+ nr_ice_stun_server turn_server;
+ char *username;
+ Data *password;
+} nr_ice_turn_server;
+
+struct nr_ice_media_stream_ {
+ char *label;
+ struct nr_ice_ctx_ *ctx;
+ struct nr_ice_peer_ctx_ *pctx;
+
+ struct nr_ice_media_stream_ *local_stream; /* used when this is a peer */
+ int component_ct;
+ nr_ice_component_head components;
+
+ char *ufrag; /* ICE username */
+ char *pwd; /* ICE password */
+ char *r2l_user; /* The username for incoming requests */
+ char *l2r_user; /* The username for outgoing requests */
+ Data r2l_pass; /* The password for incoming requests */
+ Data l2r_pass; /* The password for outcoming requests */
+ int ice_state;
+ /* The stream is being replaced by another, so it will not continue any ICE
+ * processing. If this stream is connected already, traffic can continue to
+ * flow for a limited time while the new stream gets ready. */
+ int obsolete;
+
+#define NR_ICE_MEDIA_STREAM_UNPAIRED 1
+#define NR_ICE_MEDIA_STREAM_CHECKS_FROZEN 2
+#define NR_ICE_MEDIA_STREAM_CHECKS_ACTIVE 3
+#define NR_ICE_MEDIA_STREAM_CHECKS_CONNECTED 4
+#define NR_ICE_MEDIA_STREAM_CHECKS_FAILED 5
+
+ int disconnected;
+
+#define NR_ICE_MEDIA_STREAM_CONNECTED 0
+#define NR_ICE_MEDIA_STREAM_DISCONNECTED 1
+
+ /* Copy of flags field from nr_ice_ctx */
+ int flags;
+
+ /* Copy of STUN/TURN servers from nr_ice_ctx */
+ nr_ice_stun_server *stun_servers; /* The list of stun servers */
+ int stun_server_ct;
+ nr_ice_turn_server *turn_servers; /* The list of turn servers */
+ int turn_server_ct;
+
+ nr_ice_cand_pair_head check_list;
+ nr_ice_cand_pair_head trigger_check_queue;
+ void *timer; /* Check list periodic timer */
+
+/* nr_ice_cand_pair_head valid_list; */
+
+ STAILQ_ENTRY(nr_ice_media_stream_) entry;
+};
+
+typedef STAILQ_HEAD(nr_ice_media_stream_head_,nr_ice_media_stream_) nr_ice_media_stream_head;
+
+int nr_ice_media_stream_create(struct nr_ice_ctx_ *ctx,const char *label,const char *ufrag,const char *pwd,int components, nr_ice_media_stream **streamp);
+int nr_ice_media_stream_destroy(nr_ice_media_stream **streamp);
+int nr_ice_media_stream_finalize(nr_ice_media_stream *lstr,nr_ice_media_stream *rstr);
+int nr_ice_media_stream_initialize(struct nr_ice_ctx_ *ctx, nr_ice_media_stream *stream);
+int nr_ice_media_stream_get_attributes(nr_ice_media_stream *stream, char ***attrsp,int *attrctp);
+int nr_ice_media_stream_get_default_candidate(nr_ice_media_stream *stream, int component, nr_ice_candidate **candp);
+int nr_ice_media_stream_pair_candidates(nr_ice_peer_ctx *pctx,nr_ice_media_stream *lstream,nr_ice_media_stream *pstream);
+int nr_ice_media_stream_start_checks(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream);
+int nr_ice_media_stream_service_pre_answer_requests(nr_ice_peer_ctx *pctx,nr_ice_media_stream *lstream,nr_ice_media_stream *pstream, int *serviced);
+int nr_ice_media_stream_unfreeze_pairs(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream);
+int nr_ice_media_stream_unfreeze_pairs_foundation(nr_ice_media_stream *stream, char *foundation);
+void nr_ice_media_stream_dump_state(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream, int log_level);
+void nr_ice_media_stream_component_nominated(nr_ice_media_stream *stream,nr_ice_component *component);
+void nr_ice_media_stream_component_failed(nr_ice_media_stream *stream,nr_ice_component *component);
+void nr_ice_media_stream_refresh_consent_all(nr_ice_media_stream *stream);
+void nr_ice_media_stream_disconnect_all_components(nr_ice_media_stream *stream);
+void nr_ice_media_stream_set_disconnected(nr_ice_media_stream *stream, int disconnected);
+int nr_ice_media_stream_check_if_connected(nr_ice_media_stream *stream);
+int nr_ice_media_stream_set_state(nr_ice_media_stream *str, int state);
+void nr_ice_media_stream_stop_checking(nr_ice_media_stream *str);
+void nr_ice_media_stream_set_obsolete(nr_ice_media_stream *str);
+int nr_ice_media_stream_is_done_gathering(nr_ice_media_stream *str);
+int nr_ice_media_stream_get_best_candidate(nr_ice_media_stream *str, int component, nr_ice_candidate **candp);
+int nr_ice_media_stream_send(nr_ice_peer_ctx *pctx, nr_ice_media_stream *str, int component, UCHAR *data, int len);
+int nr_ice_media_stream_get_active(nr_ice_peer_ctx *pctx, nr_ice_media_stream *str, int component, nr_ice_candidate **local, nr_ice_candidate **remote);
+int nr_ice_media_stream_find_component(nr_ice_media_stream *str, int comp_id, nr_ice_component **compp);
+int nr_ice_media_stream_find_pair(nr_ice_media_stream *str, nr_ice_candidate *local, nr_ice_candidate *remote, nr_ice_cand_pair **pair);
+int nr_ice_media_stream_addrs(nr_ice_peer_ctx *pctx, nr_ice_media_stream *str, int component, nr_transport_addr *local, nr_transport_addr *remote);
+int
+nr_ice_peer_ctx_parse_media_stream_attribute(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream, char *attr);
+int nr_ice_media_stream_get_consent_status(nr_ice_media_stream *stream, int component_id, int *can_send, struct timeval *ts);
+int nr_ice_media_stream_disable_component(nr_ice_media_stream *stream, int component_id);
+int nr_ice_media_stream_pair_new_trickle_candidate(nr_ice_peer_ctx *pctx, nr_ice_media_stream *pstream, nr_ice_candidate *cand);
+void nr_ice_media_stream_role_change(nr_ice_media_stream *stream);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_parser.c b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_parser.c
new file mode 100644
index 0000000000..25cda3364a
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_parser.c
@@ -0,0 +1,564 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+*/
+
+#include <csi_platform.h>
+#include <sys/types.h>
+#ifdef WIN32
+#include <winsock2.h>
+#else
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <strings.h>
+#endif
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include "nr_api.h"
+#include "ice_ctx.h"
+#include "ice_candidate.h"
+#include "ice_reg.h"
+
+static void
+skip_whitespace(char **str)
+{
+ char *c = *str;
+ while (*c == ' ')
+ ++c;
+
+ *str = c;
+}
+
+static void
+fast_forward(char **str, int skip)
+{
+ char *c = *str;
+ while (*c != '\0' && skip-- > 0)
+ ++c;
+
+ *str = c;
+}
+
+static void
+skip_to_past_space(char **str)
+{
+ char *c = *str;
+ while (*c != ' ' && *c != '\0')
+ ++c;
+
+ *str = c;
+
+ skip_whitespace(str);
+}
+
+static int
+grab_token(char **str, char **out)
+{
+ int _status;
+ char *c = *str;
+ int len;
+ char *tmp;
+
+ while (*c != ' ' && *c != '\0')
+ ++c;
+
+ len = c - *str;
+
+ tmp = RMALLOC(len + 1);
+ if (!tmp)
+ ABORT(R_NO_MEMORY);
+
+ memcpy(tmp, *str, len);
+ tmp[len] = '\0';
+
+ *str = c;
+ *out = tmp;
+
+ _status = 0;
+abort:
+ return _status;
+}
+
+int
+nr_ice_peer_candidate_from_attribute(nr_ice_ctx *ctx,char *orig,nr_ice_media_stream *stream,nr_ice_candidate **candp)
+{
+ int r,_status;
+ char* str = orig;
+ nr_ice_candidate *cand;
+ char *connection_address=0;
+ unsigned int port;
+ int i;
+ unsigned int component_id;
+ char *rel_addr=0;
+ unsigned char transport;
+
+ if(!(cand=RCALLOC(sizeof(nr_ice_candidate))))
+ ABORT(R_NO_MEMORY);
+
+ if(!(cand->label=r_strdup(orig)))
+ ABORT(R_NO_MEMORY);
+
+ cand->ctx=ctx;
+ cand->isock=0;
+ cand->state=NR_ICE_CAND_PEER_CANDIDATE_UNPAIRED;
+ cand->stream=stream;
+ skip_whitespace(&str);
+
+ /* Skip a= if present */
+ if (!strncmp(str, "a=", 2))
+ str += 2;
+
+ /* Candidate attr */
+ if (strncasecmp(str, "candidate:", 10))
+ ABORT(R_BAD_DATA);
+
+ fast_forward(&str, 10);
+ if (*str == '\0')
+ ABORT(R_BAD_DATA);
+
+ skip_whitespace(&str);
+ if (*str == '\0')
+ ABORT(R_BAD_DATA);
+
+ /* Foundation */
+ if ((r=grab_token(&str, &cand->foundation)))
+ ABORT(r);
+
+ if (*str == '\0')
+ ABORT(R_BAD_DATA);
+
+ skip_whitespace(&str);
+ if (*str == '\0')
+ ABORT(R_BAD_DATA);
+
+ /* component */
+ if (sscanf(str, "%u", &component_id) != 1)
+ ABORT(R_BAD_DATA);
+
+ if (component_id < 1 || component_id > 256)
+ ABORT(R_BAD_DATA);
+
+ cand->component_id = (UCHAR)component_id;
+
+ skip_to_past_space(&str);
+ if (*str == '\0')
+ ABORT(R_BAD_DATA);
+
+ /* Protocol */
+ if (!strncasecmp(str, "UDP", 3))
+ transport=IPPROTO_UDP;
+ else if (!strncasecmp(str, "TCP", 3))
+ transport=IPPROTO_TCP;
+ else
+ ABORT(R_BAD_DATA);
+
+ fast_forward(&str, 3);
+ if (*str == '\0')
+ ABORT(R_BAD_DATA);
+
+ skip_whitespace(&str);
+ if (*str == '\0')
+ ABORT(R_BAD_DATA);
+
+ /* priority */
+ if (sscanf(str, "%u", &cand->priority) != 1)
+ ABORT(R_BAD_DATA);
+
+ if (cand->priority < 1)
+ ABORT(R_BAD_DATA);
+
+ skip_to_past_space(&str);
+ if (*str == '\0')
+ ABORT(R_BAD_DATA);
+
+ /* Peer address/port */
+ if ((r=grab_token(&str, &connection_address)))
+ ABORT(r);
+
+ if (*str == '\0')
+ ABORT(R_BAD_DATA);
+
+ skip_whitespace(&str);
+ if (*str == '\0')
+ ABORT(R_BAD_DATA);
+
+ if (sscanf(str, "%u", &port) != 1)
+ ABORT(R_BAD_DATA);
+
+ if (port < 1 || port > 0x0FFFF)
+ ABORT(R_BAD_DATA);
+
+ if ((r=nr_str_port_to_transport_addr(connection_address,port,transport,&cand->addr)))
+ ABORT(r);
+
+ skip_to_past_space(&str);
+ if (*str == '\0')
+ ABORT(R_BAD_DATA);
+
+ /* Type */
+ if (strncasecmp("typ", str, 3))
+ ABORT(R_BAD_DATA);
+
+ fast_forward(&str, 3);
+ if (*str == '\0')
+ ABORT(R_BAD_DATA);
+
+ skip_whitespace(&str);
+ if (*str == '\0')
+ ABORT(R_BAD_DATA);
+
+ assert(nr_ice_candidate_type_names[0] == 0);
+
+ for (i = 1; nr_ice_candidate_type_names[i]; ++i) {
+ if(!strncasecmp(nr_ice_candidate_type_names[i], str, strlen(nr_ice_candidate_type_names[i]))) {
+ cand->type=i;
+ break;
+ }
+ }
+ if (nr_ice_candidate_type_names[i] == 0)
+ ABORT(R_BAD_DATA);
+
+ fast_forward(&str, strlen(nr_ice_candidate_type_names[i]));
+
+ /* Look for the other side's raddr, rport */
+ /* raddr, rport */
+ switch (cand->type) {
+ case HOST:
+ break;
+ case SERVER_REFLEXIVE:
+ case PEER_REFLEXIVE:
+ case RELAYED:
+
+ skip_whitespace(&str);
+ if (*str == '\0')
+ ABORT(R_BAD_DATA);
+
+ if (strncasecmp("raddr", str, 5))
+ ABORT(R_BAD_DATA);
+
+ fast_forward(&str, 5);
+ if (*str == '\0')
+ ABORT(R_BAD_DATA);
+
+ skip_whitespace(&str);
+ if (*str == '\0')
+ ABORT(R_BAD_DATA);
+
+ if ((r=grab_token(&str, &rel_addr)))
+ ABORT(r);
+
+ if (*str == '\0')
+ ABORT(R_BAD_DATA);
+
+ skip_whitespace(&str);
+ if (*str == '\0')
+ ABORT(R_BAD_DATA);
+
+ if (strncasecmp("rport", str, 5))
+ ABORT(R_BAD_DATA);
+
+ fast_forward(&str, 5);
+ if (*str == '\0')
+ ABORT(R_BAD_DATA);
+
+ skip_whitespace(&str);
+ if (*str == '\0')
+ ABORT(R_BAD_DATA);
+
+ if (sscanf(str, "%u", &port) != 1)
+ ABORT(R_BAD_DATA);
+
+ if (port > 0x0FFFF)
+ ABORT(R_BAD_DATA);
+
+ if ((r=nr_str_port_to_transport_addr(rel_addr,port,transport,&cand->base)))
+ ABORT(r);
+
+ skip_to_past_space(&str);
+ /* it's expected to be at EOD at this point */
+
+ break;
+ default:
+ ABORT(R_INTERNAL);
+ break;
+ }
+
+ skip_whitespace(&str);
+
+ if (transport == IPPROTO_TCP && cand->type != RELAYED) {
+ /* Parse tcptype extension per RFC 6544 S 4.5 */
+ if (strncasecmp("tcptype ", str, 8))
+ ABORT(R_BAD_DATA);
+
+ fast_forward(&str, 8);
+ skip_whitespace(&str);
+
+ for (i = 1; nr_ice_candidate_tcp_type_names[i]; ++i) {
+ if(!strncasecmp(nr_ice_candidate_tcp_type_names[i], str, strlen(nr_ice_candidate_tcp_type_names[i]))) {
+ cand->tcp_type=i;
+ fast_forward(&str, strlen(nr_ice_candidate_tcp_type_names[i]));
+ break;
+ }
+ }
+
+ if (cand->tcp_type == 0)
+ ABORT(R_BAD_DATA);
+
+ if (*str && *str != ' ')
+ ABORT(R_BAD_DATA);
+ }
+ /* Ignore extensions per RFC 5245 S 15.1 */
+#if 0
+ /* This used to be an assert, but we don't want to exit on invalid
+ remote data */
+ if (strlen(str) != 0) {
+ ABORT(R_BAD_DATA);
+ }
+#endif
+
+ nr_ice_candidate_compute_codeword(cand);
+
+ *candp=cand;
+
+ _status=0;
+ abort:
+ if (_status){
+ r_log(LOG_ICE,LOG_WARNING,"ICE(%s): Error parsing attribute: %s",ctx->label,orig);
+ nr_ice_candidate_destroy(&cand);
+ }
+
+ RFREE(connection_address);
+ RFREE(rel_addr);
+ return(_status);
+}
+
+
+int
+nr_ice_peer_ctx_parse_media_stream_attribute(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream, char *attr)
+{
+ int r,_status;
+ char *orig = 0;
+ char *str;
+
+ orig = str = attr;
+
+ if (!strncasecmp(str, "ice-ufrag:", 10)) {
+ fast_forward(&str, 10);
+ if (*str == '\0')
+ ABORT(R_BAD_DATA);
+
+ skip_whitespace(&str);
+ if (*str == '\0')
+ ABORT(R_BAD_DATA);
+
+ RFREE(stream->ufrag);
+ if ((r=grab_token(&str, &stream->ufrag)))
+ ABORT(r);
+ }
+ else if (!strncasecmp(str, "ice-pwd:", 8)) {
+ fast_forward(&str, 8);
+ if (*str == '\0')
+ ABORT(R_BAD_DATA);
+
+ skip_whitespace(&str);
+ if (*str == '\0')
+ ABORT(R_BAD_DATA);
+
+ RFREE(stream->pwd);
+ if ((r=grab_token(&str, &stream->pwd)))
+ ABORT(r);
+ }
+ else {
+ ABORT(R_BAD_DATA);
+ }
+
+ skip_whitespace(&str);
+
+ /* RFC 5245 grammar doesn't have an extension point for ice-pwd or
+ ice-ufrag: if there's anything left on the line, we treat it as bad. */
+ if (str[0] != '\0') {
+ ABORT(R_BAD_DATA);
+ }
+
+ _status=0;
+ abort:
+ if (_status) {
+ if (orig)
+ r_log(LOG_ICE,LOG_WARNING,"ICE-PEER(%s): Error parsing attribute: %s",pctx->label,orig);
+ }
+
+ return(_status);
+}
+
+int
+nr_ice_peer_ctx_parse_global_attributes(nr_ice_peer_ctx *pctx, char **attrs, int attr_ct)
+{
+ int r,_status;
+ int i;
+ char *orig = 0;
+ char *str;
+ char *component_id = 0;
+ char *connection_address = 0;
+ unsigned int port;
+ in_addr_t addr;
+ char *ice_option_tag = 0;
+
+ for(i=0;i<attr_ct;i++){
+ orig = str = attrs[i];
+
+ component_id = 0;
+ connection_address = 0;
+ ice_option_tag = 0;
+
+ if (!strncasecmp(str, "remote-candidates:", 18)) {
+ fast_forward(&str, 18);
+ skip_whitespace(&str);
+
+ while (*str != '\0') {
+ if ((r=grab_token(&str, &component_id)))
+ ABORT(r);
+
+ if (*str == '\0')
+ ABORT(R_BAD_DATA);
+
+ skip_whitespace(&str);
+ if (*str == '\0')
+ ABORT(R_BAD_DATA);
+
+ if ((r=grab_token(&str, &connection_address)))
+ ABORT(r);
+
+ if (*str == '\0')
+ ABORT(R_BAD_DATA);
+
+ addr = inet_addr(connection_address);
+ if (addr == INADDR_NONE)
+ ABORT(R_BAD_DATA);
+
+ skip_whitespace(&str);
+ if (*str == '\0')
+ ABORT(R_BAD_DATA);
+
+ if (sscanf(str, "%u", &port) != 1)
+ ABORT(R_BAD_DATA);
+
+ if (port < 1 || port > 0x0FFFF)
+ ABORT(R_BAD_DATA);
+
+ skip_to_past_space(&str);
+
+#if 0
+ /* TODO: !nn! just drop on the floor for now, later put somewhere */
+ /* Assume v4 for now */
+ if(r=nr_ip4_port_to_transport_addr(ntohl(addr),port,IPPROTO_UDP,&candidate->base))
+ ABORT(r);
+
+ TAILQ_INSERT_TAIL(head, elm, field);
+#endif
+
+ component_id = 0; /* prevent free */
+ RFREE(connection_address);
+ connection_address = 0; /* prevent free */
+ }
+ }
+ else if (!strncasecmp(str, "ice-lite", 8)) {
+ pctx->peer_lite = 1;
+ pctx->controlling = 1;
+
+ fast_forward(&str, 8);
+ }
+ else if (!strncasecmp(str, "ice-mismatch", 12)) {
+ pctx->peer_ice_mismatch = 1;
+
+ fast_forward(&str, 12);
+ }
+ else if (!strncasecmp(str, "ice-ufrag:", 10)) {
+ fast_forward(&str, 10);
+ if (*str == '\0')
+ ABORT(R_BAD_DATA);
+
+ skip_whitespace(&str);
+ if (*str == '\0')
+ ABORT(R_BAD_DATA);
+ }
+ else if (!strncasecmp(str, "ice-pwd:", 8)) {
+ fast_forward(&str, 8);
+ if (*str == '\0')
+ ABORT(R_BAD_DATA);
+
+ skip_whitespace(&str);
+ if (*str == '\0')
+ ABORT(R_BAD_DATA);
+ }
+ else if (!strncasecmp(str, "ice-options:", 12)) {
+ fast_forward(&str, 12);
+ skip_whitespace(&str);
+
+ while (*str != '\0') {
+ if ((r=grab_token(&str, &ice_option_tag)))
+ ABORT(r);
+
+ skip_whitespace(&str);
+
+ //TODO: for now, just throw away; later put somewhere
+ RFREE(ice_option_tag);
+
+ ice_option_tag = 0; /* prevent free */
+ }
+ }
+ else {
+ ABORT(R_BAD_DATA);
+ }
+
+ skip_whitespace(&str);
+
+ /* RFC 5245 grammar doesn't have an extension point for any of the
+ preceding attributes: if there's anything left on the line, we
+ treat it as bad data. */
+ if (str[0] != '\0') {
+ ABORT(R_BAD_DATA);
+ }
+ }
+
+ _status=0;
+ abort:
+ if (_status) {
+ if (orig)
+ r_log(LOG_ICE,LOG_WARNING,"ICE-PEER(%s): Error parsing attribute: %s",pctx->label,orig);
+ }
+
+ RFREE(connection_address);
+ RFREE(component_id);
+ RFREE(ice_option_tag);
+ return(_status);
+}
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_peer_ctx.c b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_peer_ctx.c
new file mode 100644
index 0000000000..0bf97eb984
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_peer_ctx.c
@@ -0,0 +1,875 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+*/
+
+#include <string.h>
+#include <assert.h>
+#include <registry.h>
+#include <nr_api.h>
+#include "ice_ctx.h"
+#include "ice_peer_ctx.h"
+#include "ice_media_stream.h"
+#include "ice_util.h"
+#include "nr_crypto.h"
+#include "async_timer.h"
+#include "ice_reg.h"
+
+static void nr_ice_peer_ctx_parse_stream_attributes_int(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream, nr_ice_media_stream *pstream, char **attrs, int attr_ct);
+static int nr_ice_ctx_parse_candidate(nr_ice_peer_ctx *pctx, nr_ice_media_stream *pstream, char *candidate, int trickled, const char *mdns_addr);
+static void nr_ice_peer_ctx_start_trickle_timer(nr_ice_peer_ctx *pctx);
+
+int nr_ice_peer_ctx_create(nr_ice_ctx *ctx, nr_ice_handler *handler,char *label, nr_ice_peer_ctx **pctxp)
+ {
+ int r,_status;
+ nr_ice_peer_ctx *pctx=0;
+
+ if(!(pctx=RCALLOC(sizeof(nr_ice_peer_ctx))))
+ ABORT(R_NO_MEMORY);
+
+ pctx->state = NR_ICE_PEER_STATE_UNPAIRED;
+
+ if(!(pctx->label=r_strdup(label)))
+ ABORT(R_NO_MEMORY);
+
+ pctx->ctx=ctx;
+ pctx->handler=handler;
+
+ /* Decide controlling vs. controlled */
+ if(ctx->flags & NR_ICE_CTX_FLAGS_LITE){
+ pctx->controlling=0;
+ } else {
+ pctx->controlling=1;
+ }
+ if(r=nr_crypto_random_bytes((UCHAR *)&pctx->tiebreaker,8))
+ ABORT(r);
+
+ STAILQ_INIT(&pctx->peer_streams);
+
+ STAILQ_INSERT_TAIL(&ctx->peers,pctx,entry);
+
+ *pctxp=pctx;
+
+ _status = 0;
+ abort:
+ if(_status){
+ nr_ice_peer_ctx_destroy(&pctx);
+ }
+ return(_status);
+ }
+
+
+
+int nr_ice_peer_ctx_parse_stream_attributes(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream, char **attrs, int attr_ct)
+ {
+ nr_ice_media_stream *pstream=0;
+ nr_ice_component *comp,*comp2;
+ char *lufrag,*rufrag;
+ char *lpwd,*rpwd;
+ int r,_status;
+
+ /*
+ Note: use component_ct from our own stream since components other
+ than this offered by the other side are unusable */
+ if(r=nr_ice_media_stream_create(pctx->ctx,stream->label,"","",stream->component_ct,&pstream))
+ ABORT(r);
+
+ /* Match up the local and remote components */
+ comp=STAILQ_FIRST(&stream->components);
+ comp2=STAILQ_FIRST(&pstream->components);
+ while(comp){
+ comp2->local_component=comp;
+
+ comp=STAILQ_NEXT(comp,entry);
+ comp2=STAILQ_NEXT(comp2,entry);
+ }
+
+ pstream->local_stream=stream;
+ pstream->pctx=pctx;
+
+ nr_ice_peer_ctx_parse_stream_attributes_int(pctx,stream,pstream,attrs,attr_ct);
+
+ /* Now that we have the ufrag and password, compute all the username/password
+ pairs */
+ lufrag=stream->ufrag;
+ lpwd=stream->pwd;
+ assert(lufrag);
+ assert(lpwd);
+ rufrag=pstream->ufrag;
+ rpwd=pstream->pwd;
+ if (!rufrag || !rpwd)
+ ABORT(R_BAD_DATA);
+
+ if(r=nr_concat_strings(&pstream->r2l_user,lufrag,":",rufrag,NULL))
+ ABORT(r);
+ if(r=nr_concat_strings(&pstream->l2r_user,rufrag,":",lufrag,NULL))
+ ABORT(r);
+ if(r=r_data_make(&pstream->r2l_pass, (UCHAR *)lpwd, strlen(lpwd)))
+ ABORT(r);
+ if(r=r_data_make(&pstream->l2r_pass, (UCHAR *)rpwd, strlen(rpwd)))
+ ABORT(r);
+
+ STAILQ_INSERT_TAIL(&pctx->peer_streams,pstream,entry);
+ pstream=0;
+
+ _status=0;
+ abort:
+ if (_status) {
+ nr_ice_media_stream_destroy(&pstream);
+ }
+ return(_status);
+ }
+
+static void nr_ice_peer_ctx_parse_stream_attributes_int(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream, nr_ice_media_stream *pstream, char **attrs, int attr_ct)
+ {
+ int r;
+ int i;
+
+ for(i=0;i<attr_ct;i++){
+ if(!strncmp(attrs[i],"ice-",4)){
+ if(r=nr_ice_peer_ctx_parse_media_stream_attribute(pctx,pstream,attrs[i])) {
+ r_log(LOG_ICE,LOG_WARNING,"ICE(%s): peer (%s) specified bogus ICE attribute",pctx->ctx->label,pctx->label);
+ continue;
+ }
+ }
+ else if (!strncmp(attrs[i],"candidate",9)){
+ if(r=nr_ice_ctx_parse_candidate(pctx,pstream,attrs[i],0,0)) {
+ r_log(LOG_ICE,LOG_WARNING,"ICE(%s): peer (%s) specified bogus candidate",pctx->ctx->label,pctx->label);
+ continue;
+ }
+ }
+ else {
+ r_log(LOG_ICE,LOG_WARNING,"ICE(%s): peer (%s) specified bogus attribute: %s",pctx->ctx->label,pctx->label,attrs[i]);
+ }
+ }
+
+ /* Doesn't fail because we just skip errors */
+ }
+
+static int nr_ice_ctx_parse_candidate(nr_ice_peer_ctx *pctx, nr_ice_media_stream *pstream, char *candidate, int trickled, const char *mdns_addr)
+ {
+ nr_ice_candidate *cand=0;
+ nr_ice_component *comp;
+ int j;
+ int r, _status;
+
+ if(r=nr_ice_peer_candidate_from_attribute(pctx->ctx,candidate,pstream,&cand))
+ ABORT(r);
+
+ /* set the trickled flag on the candidate */
+ cand->trickled = trickled;
+
+ if (mdns_addr) {
+ cand->mdns_addr = r_strdup(mdns_addr);
+ if (!cand->mdns_addr) {
+ ABORT(R_NO_MEMORY);
+ }
+ }
+
+ /* Not the fastest way to find a component, but it's what we got */
+ j=1;
+ for(comp=STAILQ_FIRST(&pstream->components);comp;comp=STAILQ_NEXT(comp,entry)){
+ if(j==cand->component_id)
+ break;
+
+ j++;
+ }
+
+ if(!comp){
+ /* Very common for the answerer when it uses rtcp-mux */
+ r_log(LOG_ICE,LOG_INFO,"ICE(%s): peer (%s) no such component for candidate %s",pctx->ctx->label,pctx->label, candidate);
+ ABORT(R_REJECTED);
+ }
+
+ if (comp->state == NR_ICE_COMPONENT_DISABLED) {
+ r_log(LOG_ICE,LOG_WARNING,"Peer offered candidate for disabled remote component: %s", candidate);
+ ABORT(R_BAD_DATA);
+ }
+ if (comp->local_component->state == NR_ICE_COMPONENT_DISABLED) {
+ r_log(LOG_ICE,LOG_WARNING,"Peer offered candidate for disabled local component: %s", candidate);
+ ABORT(R_BAD_DATA);
+ }
+
+ cand->component=comp;
+
+ TAILQ_INSERT_TAIL(&comp->candidates,cand,entry_comp);
+
+ r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s)/CAND(%s): creating peer candidate",
+ pctx->label,cand->label);
+
+ _status=0;
+ abort:
+ if (_status) {
+ nr_ice_candidate_destroy(&cand);
+ }
+ return(_status);
+ }
+
+int nr_ice_peer_ctx_find_pstream(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream, nr_ice_media_stream **pstreamp)
+ {
+ int _status;
+ nr_ice_media_stream *pstream;
+
+ /* Because we don't have forward pointers, iterate through all the
+ peer streams to find one that matches us */
+ pstream=STAILQ_FIRST(&pctx->peer_streams);
+
+ if(!pstream) {
+ /* No peer streams at all, presumably because they do not exist yet.
+ * Don't log a warning here. */
+ ABORT(R_NOT_FOUND);
+ }
+
+ while(pstream) {
+ if (pstream->local_stream == stream)
+ break;
+
+ pstream = STAILQ_NEXT(pstream, entry);
+ }
+
+ if (!pstream) {
+ r_log(LOG_ICE,LOG_WARNING,"ICE(%s): peer (%s) has no stream matching stream %s",pctx->ctx->label,pctx->label,stream->label);
+ ABORT(R_NOT_FOUND);
+ }
+
+ *pstreamp = pstream;
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_ice_peer_ctx_remove_pstream(nr_ice_peer_ctx *pctx, nr_ice_media_stream **pstreamp)
+ {
+ int r,_status;
+
+ STAILQ_REMOVE(&pctx->peer_streams,*pstreamp,nr_ice_media_stream_,entry);
+
+ if(r=nr_ice_media_stream_destroy(pstreamp)) {
+ ABORT(r);
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_ice_peer_ctx_parse_trickle_candidate(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream, char *candidate, const char *mdns_addr)
+ {
+ nr_ice_media_stream *pstream;
+ int r,_status;
+ int needs_pairing = 0;
+
+ if (stream->obsolete) {
+ return 0;
+ }
+
+ r_log(LOG_ICE,LOG_DEBUG,"ICE(%s): peer (%s) parsing trickle ICE candidate %s",pctx->ctx->label,pctx->label,candidate);
+ r = nr_ice_peer_ctx_find_pstream(pctx, stream, &pstream);
+ if (r)
+ ABORT(r);
+
+ switch(pstream->ice_state) {
+ case NR_ICE_MEDIA_STREAM_UNPAIRED:
+ break;
+ case NR_ICE_MEDIA_STREAM_CHECKS_FROZEN:
+ case NR_ICE_MEDIA_STREAM_CHECKS_ACTIVE:
+ needs_pairing = 1;
+ break;
+ default:
+ r_log(LOG_ICE,LOG_ERR,"ICE(%s): peer (%s), stream(%s) tried to trickle ICE in inappropriate state %d",pctx->ctx->label,pctx->label,stream->label,pstream->ice_state);
+ ABORT(R_ALREADY);
+ break;
+ }
+
+ if(r=nr_ice_ctx_parse_candidate(pctx,pstream,candidate,1,mdns_addr)){
+ ABORT(r);
+ }
+
+ /* If ICE is running (i.e., we are in FROZEN or ACTIVE states)
+ then we need to pair this new candidate. For now we
+ just re-pair the stream which is inefficient but still
+ fine because we suppress duplicate pairing */
+ if (needs_pairing) {
+ /* Start the remote trickle grace timeout if it hasn't been started by
+ another trickled candidate or from the SDP. */
+ if (!pctx->trickle_grace_period_timer) {
+ nr_ice_peer_ctx_start_trickle_timer(pctx);
+ }
+
+ if(r=nr_ice_media_stream_pair_candidates(pctx, stream, pstream)) {
+ r_log(LOG_ICE,LOG_ERR,"ICE(%s): peer (%s), stream(%s) failed to pair trickle ICE candidates",pctx->ctx->label,pctx->label,stream->label);
+ ABORT(r);
+ }
+
+ /* Start checks if this stream is not checking yet or if it has checked
+ all the available candidates but not had a completed check for all
+ components.
+
+ Note that this is not compliant with RFC 5245, but consistent with
+ the libjingle trickle ICE behavior. Note that we will not restart
+ checks if either (a) the stream has failed or (b) all components
+ have a successful pair because the switch statement above jumps
+ will in both states.
+
+ TODO(ekr@rtfm.com): restart checks.
+ TODO(ekr@rtfm.com): update when the trickle ICE RFC is published
+ */
+ if (!pstream->timer) {
+ if(r=nr_ice_media_stream_start_checks(pctx, pstream)) {
+ r_log(LOG_ICE,LOG_ERR,"ICE(%s): peer (%s), stream(%s) failed to start checks",pctx->ctx->label,pctx->label,stream->label);
+ ABORT(r);
+ }
+ }
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+
+ }
+
+
+static void nr_ice_peer_ctx_trickle_wait_cb(NR_SOCKET s, int how, void *cb_arg)
+ {
+ nr_ice_peer_ctx *pctx=cb_arg;
+ nr_ice_media_stream *stream;
+ nr_ice_component *comp;
+
+ pctx->trickle_grace_period_timer=0;
+
+ r_log(LOG_ICE,LOG_INFO,"ICE(%s): peer (%s) Trickle grace period is over; marking every component with only failed pairs as failed.",pctx->ctx->label,pctx->label);
+
+ stream=STAILQ_FIRST(&pctx->peer_streams);
+ while(stream){
+ comp=STAILQ_FIRST(&stream->components);
+ while(comp){
+ nr_ice_component_check_if_failed(comp);
+ comp=STAILQ_NEXT(comp,entry);
+ }
+ stream=STAILQ_NEXT(stream,entry);
+ }
+ }
+
+static void nr_ice_peer_ctx_start_trickle_timer(nr_ice_peer_ctx *pctx)
+ {
+ UINT4 grace_period_timeout=0;
+
+ if(pctx->trickle_grace_period_timer) {
+ NR_async_timer_cancel(pctx->trickle_grace_period_timer);
+ pctx->trickle_grace_period_timer=0;
+ }
+
+ NR_reg_get_uint4(NR_ICE_REG_TRICKLE_GRACE_PERIOD,&grace_period_timeout);
+
+ if (grace_period_timeout) {
+ r_log(LOG_ICE,LOG_INFO,"ICE(%s): peer (%s) starting grace period timer for %u ms",pctx->ctx->label,pctx->label, grace_period_timeout);
+ /* If we're doing trickle, we need to allow a grace period for new
+ * trickle candidates to arrive in case the pairs we have fail quickly. */
+ NR_ASYNC_TIMER_SET(grace_period_timeout,nr_ice_peer_ctx_trickle_wait_cb,pctx,&pctx->trickle_grace_period_timer);
+ }
+ }
+
+int nr_ice_peer_ctx_pair_candidates(nr_ice_peer_ctx *pctx)
+ {
+ nr_ice_media_stream *stream;
+ int r,_status;
+
+ if(pctx->peer_lite && !pctx->controlling) {
+ if(pctx->ctx->flags & NR_ICE_CTX_FLAGS_LITE){
+ r_log(LOG_ICE,LOG_ERR,"Both sides are ICE-Lite");
+ ABORT(R_BAD_DATA);
+ }
+ nr_ice_peer_ctx_switch_controlling_role(pctx);
+ }
+
+ r_log(LOG_ICE,LOG_DEBUG,"ICE(%s): peer (%s) pairing candidates",pctx->ctx->label,pctx->label);
+
+ if(STAILQ_EMPTY(&pctx->peer_streams)) {
+ r_log(LOG_ICE,LOG_ERR,"ICE(%s): peer (%s) received no media stream attributes",pctx->ctx->label,pctx->label);
+ ABORT(R_FAILED);
+ }
+
+ /* Set this first; if we fail partway through, we do not want to end
+ * up in UNPAIRED after creating some pairs. */
+ pctx->state = NR_ICE_PEER_STATE_PAIRED;
+
+ stream=STAILQ_FIRST(&pctx->peer_streams);
+ while(stream){
+ if(!stream->local_stream->obsolete) {
+ if(r=nr_ice_media_stream_pair_candidates(pctx, stream->local_stream,
+ stream))
+ ABORT(r);
+ }
+
+ stream=STAILQ_NEXT(stream,entry);
+ }
+
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+
+int nr_ice_peer_ctx_pair_new_trickle_candidate(nr_ice_ctx *ctx, nr_ice_peer_ctx *pctx, nr_ice_candidate *cand)
+ {
+ int r, _status;
+ nr_ice_media_stream *pstream;
+
+ r_log(LOG_ICE,LOG_ERR,"ICE(%s): peer (%s) pairing local trickle ICE candidate %s",pctx->ctx->label,pctx->label,cand->label);
+ if ((r = nr_ice_peer_ctx_find_pstream(pctx, cand->stream, &pstream)))
+ ABORT(r);
+
+ /* Start the remote trickle grace timeout if it hasn't been started
+ already. */
+ if (!pctx->trickle_grace_period_timer) {
+ nr_ice_peer_ctx_start_trickle_timer(pctx);
+ }
+
+ if ((r = nr_ice_media_stream_pair_new_trickle_candidate(pctx, pstream, cand)))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ return _status;
+ }
+
+int nr_ice_peer_ctx_disable_component(nr_ice_peer_ctx *pctx, nr_ice_media_stream *lstream, int component_id)
+ {
+ int r, _status;
+ nr_ice_media_stream *pstream;
+ nr_ice_component *component;
+
+ if ((r=nr_ice_peer_ctx_find_pstream(pctx, lstream, &pstream)))
+ ABORT(r);
+
+ /* We shouldn't be calling this after we have started pairing */
+ if (pstream->ice_state != NR_ICE_MEDIA_STREAM_UNPAIRED)
+ ABORT(R_FAILED);
+
+ if ((r=nr_ice_media_stream_find_component(pstream, component_id,
+ &component)))
+ ABORT(r);
+
+ component->state = NR_ICE_COMPONENT_DISABLED;
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+ void nr_ice_peer_ctx_destroy(nr_ice_peer_ctx** pctxp) {
+ if (!pctxp || !*pctxp) return;
+
+ nr_ice_peer_ctx* pctx = *pctxp;
+ nr_ice_media_stream *str1,*str2;
+
+ /* Stop calling the handler */
+ pctx->handler = 0;
+
+ NR_async_timer_cancel(pctx->connected_cb_timer);
+ RFREE(pctx->label);
+
+ STAILQ_FOREACH_SAFE(str1, &pctx->peer_streams, entry, str2){
+ STAILQ_REMOVE(&pctx->peer_streams,str1,nr_ice_media_stream_,entry);
+ nr_ice_media_stream_destroy(&str1);
+ }
+ assert(pctx->ctx);
+ if (pctx->ctx)
+ STAILQ_REMOVE(&pctx->ctx->peers, pctx, nr_ice_peer_ctx_, entry);
+
+ if(pctx->trickle_grace_period_timer) {
+ NR_async_timer_cancel(pctx->trickle_grace_period_timer);
+ pctx->trickle_grace_period_timer=0;
+ }
+
+ RFREE(pctx);
+
+ *pctxp=0;
+ }
+
+/* Start the checks for the first media stream (S 5.7)
+ The rest remain FROZEN */
+int nr_ice_peer_ctx_start_checks(nr_ice_peer_ctx *pctx)
+ {
+ return nr_ice_peer_ctx_start_checks2(pctx, 0);
+ }
+
+/* Start checks for some media stream.
+
+ If allow_non_first == 0, then we only look at the first stream,
+ which is 5245-complaint.
+
+ If allow_non_first == 1 then we find the first non-empty stream
+ This is not compliant with RFC 5245 but is necessary to make trickle ICE
+ work plausibly
+*/
+int nr_ice_peer_ctx_start_checks2(nr_ice_peer_ctx *pctx, int allow_non_first)
+ {
+ int r,_status;
+ nr_ice_media_stream *stream;
+ int started = 0;
+
+ /* Ensure that grace period timer is running. We might cancel this if we
+ * didn't actually start any pairs. */
+ nr_ice_peer_ctx_start_trickle_timer(pctx);
+
+ /* Might have added some streams */
+ pctx->reported_connected = 0;
+ NR_async_timer_cancel(pctx->connected_cb_timer);
+ pctx->connected_cb_timer = 0;
+ pctx->checks_started = 0;
+
+ nr_ice_peer_ctx_check_if_connected(pctx);
+
+ if (pctx->reported_connected) {
+ r_log(LOG_ICE,LOG_ERR,"ICE(%s): peer (%s) in %s all streams were done",pctx->ctx->label,pctx->label,__FUNCTION__);
+ return (0);
+ }
+
+ stream=STAILQ_FIRST(&pctx->peer_streams);
+ if(!stream)
+ ABORT(R_FAILED);
+
+ while (stream) {
+ if(!stream->local_stream->obsolete) {
+ assert(stream->ice_state != NR_ICE_MEDIA_STREAM_UNPAIRED);
+
+ if (stream->ice_state == NR_ICE_MEDIA_STREAM_CHECKS_FROZEN) {
+ if(!TAILQ_EMPTY(&stream->check_list))
+ break;
+
+ if(!allow_non_first){
+ /* This test applies if:
+
+ 1. allow_non_first is 0 (i.e., non-trickle ICE)
+ 2. the first stream has an empty check list.
+
+ But in the non-trickle ICE case, the other side should have provided
+ some candidates or ICE is pretty much not going to work and we're
+ just going to fail. Hence R_FAILED as opposed to R_NOT_FOUND and
+ immediate termination here.
+ */
+ r_log(LOG_ICE,LOG_ERR,"ICE(%s): peer (%s) first stream has empty check list",pctx->ctx->label,pctx->label);
+ ABORT(R_FAILED);
+ }
+ }
+ }
+
+ stream=STAILQ_NEXT(stream, entry);
+ }
+
+ if (!stream) {
+ /*
+ We fail above if we aren't doing trickle, and this is not all that
+ unusual in the trickle case.
+ */
+ r_log(LOG_ICE,LOG_NOTICE,"ICE(%s): peer (%s) no streams with non-empty check lists",pctx->ctx->label,pctx->label);
+ }
+ else if (stream->ice_state == NR_ICE_MEDIA_STREAM_CHECKS_FROZEN) {
+ if(r=nr_ice_media_stream_unfreeze_pairs(pctx,stream))
+ ABORT(r);
+ if(r=nr_ice_media_stream_start_checks(pctx,stream))
+ ABORT(r);
+ ++started;
+ }
+
+ stream=STAILQ_FIRST(&pctx->peer_streams);
+ while (stream) {
+ int serviced = 0;
+ if (r=nr_ice_media_stream_service_pre_answer_requests(pctx, stream->local_stream, stream, &serviced))
+ ABORT(r);
+
+ if (serviced) {
+ ++started;
+ }
+ else {
+ r_log(LOG_ICE,LOG_NOTICE,"ICE(%s): peer (%s) no streams with pre-answer requests",pctx->ctx->label,pctx->label);
+ }
+
+
+ stream=STAILQ_NEXT(stream, entry);
+ }
+
+ if (!started && pctx->ctx->uninitialized_candidates) {
+ r_log(LOG_ICE,LOG_INFO,"ICE(%s): peer (%s) no checks to start, but gathering is not done yet, cancelling grace period timer",pctx->ctx->label,pctx->label);
+ /* Never mind on the grace period timer */
+ NR_async_timer_cancel(pctx->trickle_grace_period_timer);
+ pctx->trickle_grace_period_timer=0;
+ ABORT(R_NOT_FOUND);
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+void nr_ice_peer_ctx_stream_started_checks(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream)
+ {
+ if (!pctx->checks_started) {
+ r_log(LOG_ICE,LOG_NOTICE,"ICE(%s): peer (%s) is now checking",pctx->ctx->label,pctx->label);
+ pctx->checks_started = 1;
+ if (pctx->handler && pctx->handler->vtbl->ice_checking) {
+ pctx->handler->vtbl->ice_checking(pctx->handler->obj, pctx);
+ }
+ }
+ }
+
+void nr_ice_peer_ctx_dump_state(nr_ice_peer_ctx *pctx, int log_level)
+ {
+ nr_ice_media_stream *stream;
+
+ r_log(LOG_ICE,log_level,"PEER %s STATE DUMP",pctx->label);
+ r_log(LOG_ICE,log_level,"==========================================");
+ stream=STAILQ_FIRST(&pctx->peer_streams);
+ while(stream){
+ nr_ice_media_stream_dump_state(pctx,stream,log_level);
+ }
+ r_log(LOG_ICE,log_level,"==========================================");
+ }
+
+void nr_ice_peer_ctx_refresh_consent_all_streams(nr_ice_peer_ctx *pctx)
+ {
+ nr_ice_media_stream *str;
+
+ r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s): refreshing consent on all streams",pctx->label);
+
+ str=STAILQ_FIRST(&pctx->peer_streams);
+ while(str) {
+ nr_ice_media_stream_refresh_consent_all(str);
+ str=STAILQ_NEXT(str,entry);
+ }
+ }
+
+void nr_ice_peer_ctx_disconnected(nr_ice_peer_ctx *pctx)
+ {
+ if (pctx->reported_connected &&
+ pctx->handler &&
+ pctx->handler->vtbl->ice_disconnected) {
+ pctx->handler->vtbl->ice_disconnected(pctx->handler->obj, pctx);
+
+ pctx->reported_connected = 0;
+ }
+ }
+
+void nr_ice_peer_ctx_disconnect_all_streams(nr_ice_peer_ctx *pctx)
+ {
+ nr_ice_media_stream *str;
+
+ r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s): disconnecting all streams",pctx->label);
+
+ str=STAILQ_FIRST(&pctx->peer_streams);
+ while(str) {
+ nr_ice_media_stream_disconnect_all_components(str);
+
+ /* The first stream to be disconnected will cause the peer ctx to signal
+ the disconnect up. */
+ nr_ice_media_stream_set_disconnected(str, NR_ICE_MEDIA_STREAM_DISCONNECTED);
+
+ str=STAILQ_NEXT(str,entry);
+ }
+ }
+
+void nr_ice_peer_ctx_connected(nr_ice_peer_ctx *pctx)
+ {
+ /* Fire the handler callback to say we're done */
+ if (pctx->reported_connected &&
+ pctx->handler &&
+ pctx->handler->vtbl->ice_connected) {
+ pctx->handler->vtbl->ice_connected(pctx->handler->obj, pctx);
+ }
+ }
+
+static void nr_ice_peer_ctx_fire_connected(NR_SOCKET s, int how, void *cb_arg)
+ {
+ nr_ice_peer_ctx *pctx=cb_arg;
+
+ pctx->connected_cb_timer=0;
+
+ nr_ice_peer_ctx_connected(pctx);
+ }
+
+/* Examine all the streams to see if we're
+ maybe miraculously connected */
+void nr_ice_peer_ctx_check_if_connected(nr_ice_peer_ctx *pctx)
+ {
+ nr_ice_media_stream *str;
+ int failed=0;
+ int succeeded=0;
+
+ str=STAILQ_FIRST(&pctx->peer_streams);
+ while(str){
+ if (!str->local_stream->obsolete){
+ if(str->ice_state==NR_ICE_MEDIA_STREAM_CHECKS_CONNECTED){
+ succeeded++;
+ }
+ else if(str->ice_state==NR_ICE_MEDIA_STREAM_CHECKS_FAILED){
+ failed++;
+ }
+ else{
+ break;
+ }
+ }
+ str=STAILQ_NEXT(str,entry);
+ }
+
+ if(str)
+ return; /* Something isn't done */
+
+ /* OK, we're finished, one way or another */
+ r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s): all checks completed success=%d fail=%d",pctx->label,succeeded,failed);
+
+ /* Make sure grace period timer is cancelled */
+ if(pctx->trickle_grace_period_timer) {
+ r_log(LOG_ICE,LOG_INFO,"ICE(%s): peer (%s) cancelling grace period timer",pctx->ctx->label,pctx->label);
+ NR_async_timer_cancel(pctx->trickle_grace_period_timer);
+ pctx->trickle_grace_period_timer=0;
+ }
+
+ /* Schedule a connected notification for the first connected event.
+ IMPORTANT: This is done in a callback because we expect destructors
+ of various kinds to be fired from here */
+ if (!pctx->reported_connected) {
+ pctx->reported_connected = 1;
+ assert(!pctx->connected_cb_timer);
+ NR_ASYNC_TIMER_SET(0,nr_ice_peer_ctx_fire_connected,pctx,&pctx->connected_cb_timer);
+ }
+ }
+
+
+/* Given a component in the main ICE ctx, find the relevant component in
+ the peer_ctx */
+int nr_ice_peer_ctx_find_component(nr_ice_peer_ctx *pctx, nr_ice_media_stream *str, int component_id, nr_ice_component **compp)
+ {
+ nr_ice_media_stream *pstr;
+ int r,_status;
+
+ pstr=STAILQ_FIRST(&pctx->peer_streams);
+ while(pstr){
+ if(pstr->local_stream==str)
+ break;
+
+ pstr=STAILQ_NEXT(pstr,entry);
+ }
+ if(!pstr)
+ ABORT(R_BAD_ARGS);
+
+ if(r=nr_ice_media_stream_find_component(pstr,component_id,compp))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+/*
+ This packet may be for us.
+
+ 1. Find the matching peer component
+ 2. Examine the packet source address to see if it matches
+ one of the peer candidates.
+ 3. Fire the relevant callback handler if there is a match
+
+ Return 0 if match, R_REJECTED if no match, other errors
+ if we can't even find the component or something like that.
+*/
+
+int nr_ice_peer_ctx_deliver_packet_maybe(nr_ice_peer_ctx *pctx, nr_ice_component *comp, nr_transport_addr *source_addr, UCHAR *data, int len)
+ {
+ nr_ice_component *peer_comp;
+ nr_ice_candidate *cand;
+ int r,_status;
+
+ if(r=nr_ice_peer_ctx_find_component(pctx, comp->stream, comp->component_id,
+ &peer_comp))
+ ABORT(r);
+
+ /* OK, we've found the component, now look for matches */
+ cand=TAILQ_FIRST(&peer_comp->candidates);
+ while(cand){
+ if(!nr_transport_addr_cmp(source_addr,&cand->addr,
+ NR_TRANSPORT_ADDR_CMP_MODE_ALL))
+ break;
+
+ cand=TAILQ_NEXT(cand,entry_comp);
+ }
+
+ if(!cand)
+ ABORT(R_REJECTED);
+
+ // accumulate the received bytes for the active candidate pair
+ if (peer_comp->active) {
+ peer_comp->active->bytes_recvd += len;
+ gettimeofday(&peer_comp->active->last_recvd, 0);
+ }
+
+ /* OK, there's a match. Call the handler */
+
+ if (pctx->handler) {
+ r_log(LOG_ICE,LOG_DEBUG,"ICE-PEER(%s): Delivering data", pctx->label);
+
+ pctx->handler->vtbl->msg_recvd(pctx->handler->obj,
+ pctx,comp->stream,comp->component_id,data,len);
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+void nr_ice_peer_ctx_switch_controlling_role(nr_ice_peer_ctx *pctx)
+ {
+ int controlling = !(pctx->controlling);
+ if(pctx->controlling_conflict_resolved) {
+ r_log(LOG_ICE,LOG_WARNING,"ICE(%s): peer (%s) %s called more than once; "
+ "this probably means the peer is confused. Not switching roles.",
+ pctx->ctx->label,pctx->label,__FUNCTION__);
+ return;
+ }
+
+ r_log(LOG_ICE,LOG_INFO,"ICE-PEER(%s): detected "
+ "role conflict. Switching to %s",
+ pctx->label,
+ controlling ? "controlling" : "controlled");
+
+ pctx->controlling = controlling;
+ pctx->controlling_conflict_resolved = 1;
+
+ if(pctx->state == NR_ICE_PEER_STATE_PAIRED) {
+ /* We have formed candidate pairs. We need to inform them. */
+ nr_ice_media_stream *pstream=STAILQ_FIRST(&pctx->peer_streams);
+ while(pstream) {
+ nr_ice_media_stream_role_change(pstream);
+ pstream = STAILQ_NEXT(pstream, entry);
+ }
+ }
+ }
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_peer_ctx.h b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_peer_ctx.h
new file mode 100644
index 0000000000..ec73d96a03
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_peer_ctx.h
@@ -0,0 +1,101 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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 _ice_peer_ctx_h
+#define _ice_peer_ctx_h
+#ifdef __cplusplus
+using namespace std;
+extern "C" {
+#endif /* __cplusplus */
+
+struct nr_ice_peer_ctx_ {
+ int state;
+#define NR_ICE_PEER_STATE_UNPAIRED 1
+#define NR_ICE_PEER_STATE_PAIRED 2
+
+ char *label;
+ nr_ice_ctx *ctx;
+ nr_ice_handler *handler;
+
+ UCHAR controlling; /* 1 for controlling, 0 for controlled */
+ UCHAR controlling_conflict_resolved;
+ UINT8 tiebreaker;
+
+ int peer_lite;
+ int peer_ice_mismatch;
+
+ nr_ice_media_stream_head peer_streams;
+ int active_streams;
+ int waiting_pairs;
+ UCHAR checks_started;
+
+ void *connected_cb_timer;
+ UCHAR reported_connected;
+ void *trickle_grace_period_timer;
+
+ STAILQ_ENTRY(nr_ice_peer_ctx_) entry;
+};
+
+typedef STAILQ_HEAD(nr_ice_peer_ctx_head_, nr_ice_peer_ctx_) nr_ice_peer_ctx_head;
+
+int nr_ice_peer_ctx_create(nr_ice_ctx *ctx, nr_ice_handler *handler,char *label, nr_ice_peer_ctx **pctxp);
+void nr_ice_peer_ctx_destroy(nr_ice_peer_ctx** pctxp);
+int nr_ice_peer_ctx_parse_stream_attributes(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream, char **attrs, int attr_ct);
+int nr_ice_peer_ctx_find_pstream(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream, nr_ice_media_stream **pstreamp);
+int nr_ice_peer_ctx_remove_pstream(nr_ice_peer_ctx *pctx, nr_ice_media_stream **pstreamp);
+int nr_ice_peer_ctx_parse_trickle_candidate(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream, char *cand, const char *mdns_addr);
+
+int nr_ice_peer_ctx_pair_candidates(nr_ice_peer_ctx *pctx);
+int nr_ice_peer_ctx_parse_global_attributes(nr_ice_peer_ctx *pctx, char **attrs, int attr_ct);
+int nr_ice_peer_ctx_start_checks(nr_ice_peer_ctx *pctx);
+int nr_ice_peer_ctx_start_checks2(nr_ice_peer_ctx *pctx, int allow_non_first);
+void nr_ice_peer_ctx_stream_started_checks(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream);
+void nr_ice_peer_ctx_refresh_consent_all_streams(nr_ice_peer_ctx *pctx);
+void nr_ice_peer_ctx_disconnect_all_streams(nr_ice_peer_ctx *pctx);
+void nr_ice_peer_ctx_disconnected(nr_ice_peer_ctx *pctx);
+void nr_ice_peer_ctx_connected(nr_ice_peer_ctx *pctx);
+void nr_ice_peer_ctx_dump_state(nr_ice_peer_ctx *pctx, int log_level);
+int nr_ice_peer_ctx_log_state(nr_ice_peer_ctx *pctx);
+void nr_ice_peer_ctx_check_if_connected(nr_ice_peer_ctx *pctx);
+int nr_ice_peer_ctx_find_component(nr_ice_peer_ctx *pctx, nr_ice_media_stream *str, int component_id, nr_ice_component **compp);
+int nr_ice_peer_ctx_deliver_packet_maybe(nr_ice_peer_ctx *pctx, nr_ice_component *comp, nr_transport_addr *source_addr, UCHAR *data, int len);
+int nr_ice_peer_ctx_disable_component(nr_ice_peer_ctx *pctx, nr_ice_media_stream *lstream, int component_id);
+int nr_ice_peer_ctx_pair_new_trickle_candidate(nr_ice_ctx *ctx, nr_ice_peer_ctx *pctx, nr_ice_candidate *cand);
+void nr_ice_peer_ctx_switch_controlling_role(nr_ice_peer_ctx *pctx);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_reg.h b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_reg.h
new file mode 100644
index 0000000000..3acc02360a
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_reg.h
@@ -0,0 +1,81 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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 _ice_reg_h
+#define _ice_reg_h
+#ifdef __cplusplus
+using namespace std;
+extern "C" {
+#endif /* __cplusplus */
+
+#define NR_ICE_REG_PREF_TYPE_HOST "ice.pref.type.host"
+#define NR_ICE_REG_PREF_TYPE_RELAYED "ice.pref.type.relayed"
+#define NR_ICE_REG_PREF_TYPE_SRV_RFLX "ice.pref.type.srv_rflx"
+#define NR_ICE_REG_PREF_TYPE_PEER_RFLX "ice.pref.type.peer_rflx"
+#define NR_ICE_REG_PREF_TYPE_HOST_TCP "ice.pref.type.host_tcp"
+#define NR_ICE_REG_PREF_TYPE_RELAYED_TCP "ice.pref.type.relayed_tcp"
+#define NR_ICE_REG_PREF_TYPE_SRV_RFLX_TCP "ice.pref.type.srv_rflx_tcp"
+#define NR_ICE_REG_PREF_TYPE_PEER_RFLX_TCP "ice.pref.type.peer_rflx_tcp"
+
+#define NR_ICE_REG_PREF_INTERFACE_PRFX "ice.pref.interface"
+#define NR_ICE_REG_SUPPRESS_INTERFACE_PRFX "ice.suppress.interface"
+
+#define NR_ICE_REG_STUN_SRV_PRFX "ice.stun.server"
+#define NR_ICE_REG_STUN_SRV_ADDR "addr"
+#define NR_ICE_REG_STUN_SRV_PORT "port"
+
+#define NR_ICE_REG_TURN_SRV_PRFX "ice.turn.server"
+#define NR_ICE_REG_TURN_SRV_ADDR "addr"
+#define NR_ICE_REG_TURN_SRV_PORT "port"
+#define NR_ICE_REG_TURN_SRV_BANDWIDTH "bandwidth"
+#define NR_ICE_REG_TURN_SRV_LIFETIME "lifetime"
+#define NR_ICE_REG_TURN_SRV_USERNAME "username"
+#define NR_ICE_REG_TURN_SRV_PASSWORD "password"
+
+#define NR_ICE_REG_ICE_TCP_DISABLE "ice.tcp.disable"
+#define NR_ICE_REG_ICE_TCP_SO_SOCK_COUNT "ice.tcp.so_sock_count"
+#define NR_ICE_REG_ICE_TCP_LISTEN_BACKLOG "ice.tcp.listen_backlog"
+
+#define NR_ICE_REG_KEEPALIVE_TIMER "ice.keepalive_timer"
+
+#define NR_ICE_REG_TRICKLE_GRACE_PERIOD "ice.trickle_grace_period"
+#define NR_ICE_REG_PREF_FORCE_INTERFACE_NAME "ice.forced_interface_name"
+#define NR_ICE_REG_USE_NR_RESOLVER_FOR_TCP "ice.tcp.use_nr_resolver"
+#define NR_ICE_REG_USE_NR_RESOLVER_FOR_UDP "ice.udp.use_nr_resolver"
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_socket.c b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_socket.c
new file mode 100644
index 0000000000..a6c8513300
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_socket.c
@@ -0,0 +1,404 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+*/
+
+#include <assert.h>
+#include <string.h>
+#include "nr_api.h"
+#include "ice_ctx.h"
+#include "stun.h"
+#include "nr_socket_buffered_stun.h"
+#include "nr_socket_multi_tcp.h"
+
+static void nr_ice_socket_readable_cb(NR_SOCKET s, int how, void *cb_arg)
+ {
+ int r;
+ nr_ice_stun_ctx *sc1,*sc2;
+ nr_ice_socket *sock=cb_arg;
+ UCHAR buf[9216];
+ char string[256];
+ nr_transport_addr addr;
+ int len;
+ size_t len_s;
+ int is_stun;
+ int is_req;
+ int is_ind;
+ int processed_indication=0;
+
+ nr_socket *stun_srv_sock=sock->sock;
+
+ r_log(LOG_ICE,LOG_DEBUG,"ICE(%s): Socket ready to read",sock->ctx->label);
+
+ if(r=nr_socket_recvfrom(sock->sock,buf,sizeof(buf),&len_s,0,&addr)){
+ if (r != R_WOULDBLOCK && (sock->type != NR_ICE_SOCKET_TYPE_DGRAM)) {
+ /* Report this error upward. Bug 946423 */
+ r_log(LOG_ICE,LOG_ERR,"ICE(%s): Error %d on reliable socket(%p). Abandoning.",sock->ctx->label, r, s);
+ nr_ice_socket_failed(sock);
+ return;
+ }
+ }
+
+ if (sock->type != NR_ICE_SOCKET_TYPE_STREAM_TCP) {
+ r_log(LOG_ICE,LOG_DEBUG,"ICE(%s): rearming",sock->ctx->label);
+ NR_ASYNC_WAIT(s,how,nr_ice_socket_readable_cb,cb_arg);
+ }
+
+ if (r) {
+ return;
+ }
+
+ /* Deal with the fact that sizeof(int) and sizeof(size_t) may not
+ be the same */
+ if (len_s > (size_t)INT_MAX)
+ return;
+
+ len = (int)len_s;
+
+#ifdef USE_TURN
+ re_process:
+#endif /* USE_TURN */
+ r_log(LOG_ICE,LOG_DEBUG,"ICE(%s): Read %d bytes %sfrom %s",sock->ctx->label,len,(processed_indication ? "relayed " : ""),addr.as_string);
+
+ /* First question: is this STUN or not? */
+ is_stun=nr_is_stun_message(buf,len);
+
+ if(is_stun){
+ is_req=nr_is_stun_request_message(buf,len);
+ is_ind=is_req?0:nr_is_stun_indication_message(buf,len);
+
+ snprintf(string, sizeof(string)-1, "ICE(%s): Message is STUN (%s)",sock->ctx->label,
+ is_req ? "request" : (is_ind ? "indication" : "other"));
+ r_dump(NR_LOG_STUN, LOG_DEBUG, string, (char*)buf, len);
+
+
+ /* We need to offer it to all of our stun contexts
+ to see who bites */
+ sc1=TAILQ_FIRST(&sock->stun_ctxs);
+ while(sc1){
+ sc2=TAILQ_NEXT(sc1,entry);
+
+ r=-1;
+ switch(sc1->type){
+ /* This has been deleted, prune... */
+ case NR_ICE_STUN_NONE:
+ TAILQ_REMOVE(&sock->stun_ctxs,sc1,entry);
+ RFREE(sc1);
+ break;
+
+ case NR_ICE_STUN_CLIENT:
+ if(!(is_req||is_ind)){
+ r=nr_stun_client_process_response(sc1->u.client,buf,len,&addr);
+ }
+ break;
+
+ case NR_ICE_STUN_SERVER:
+ if(is_req){
+ r=nr_stun_server_process_request(sc1->u.server,stun_srv_sock,(char *)buf,len,&addr,NR_STUN_AUTH_RULE_SHORT_TERM);
+ }
+ break;
+#ifdef USE_TURN
+ case NR_ICE_TURN_CLIENT:
+ /* data indications are ok, so don't ignore those */
+ /* Check that this is from the right TURN server address. Else
+ skip */
+ if (nr_transport_addr_cmp(
+ &sc1->u.turn_client.turn_client->turn_server_addr,
+ &addr, NR_TRANSPORT_ADDR_CMP_MODE_ALL))
+ break;
+
+ if(!is_req){
+ if(!is_ind)
+ r=nr_turn_client_process_response(sc1->u.turn_client.turn_client,buf,len,&addr);
+ else{
+ nr_transport_addr n_addr;
+ size_t n_len;
+
+ if (processed_indication) {
+ /* Don't allow recursively wrapped indications */
+ r_log(LOG_ICE, LOG_WARNING,
+ "ICE(%s): discarding recursively wrapped indication",
+ sock->ctx->label);
+ break;
+ }
+ /* This is a bit of a hack. If it's a data indication, strip
+ off the TURN framing and re-enter. This works because
+ all STUN processing is on the same physical socket.
+ We don't care about other kinds of indication */
+ r=nr_turn_client_parse_data_indication(
+ sc1->u.turn_client.turn_client, &addr,
+ buf, len, buf, &n_len, len, &n_addr);
+ if(!r){
+ r_log(LOG_ICE,LOG_DEBUG,"Unwrapped a data indication.");
+ len=n_len;
+ nr_transport_addr_copy(&addr,&n_addr);
+ stun_srv_sock=sc1->u.turn_client.turn_sock;
+ processed_indication=1;
+ goto re_process;
+ }
+ }
+ }
+ break;
+#endif /* USE_TURN */
+
+ default:
+ assert(0); /* Can't happen */
+ return;
+ }
+ if(!r) {
+ break;
+ }
+
+ sc1=sc2;
+ }
+ if(!sc1){
+ if (nr_ice_ctx_is_known_id(sock->ctx,((nr_stun_message_header*)buf)->id.octet))
+ r_log(LOG_ICE,LOG_DEBUG,"ICE(%s): Message is a retransmit",sock->ctx->label);
+ else
+ r_log(LOG_ICE,LOG_NOTICE,"ICE(%s): Message does not correspond to any registered stun ctx",sock->ctx->label);
+ }
+ }
+ else{
+ r_log(LOG_ICE,LOG_DEBUG,"ICE(%s): Message is not STUN",sock->ctx->label);
+
+ nr_ice_ctx_deliver_packet(sock->ctx, sock->component, &addr, buf, len);
+ }
+
+ return;
+ }
+
+int nr_ice_socket_create(nr_ice_ctx *ctx,nr_ice_component *comp, nr_socket *nsock, int type, nr_ice_socket **sockp)
+ {
+ nr_ice_socket *sock=0;
+ NR_SOCKET fd;
+ nr_transport_addr addr;
+ int r,_status;
+
+ if(!(sock=RCALLOC(sizeof(nr_ice_socket))))
+ ABORT(R_NO_MEMORY);
+
+ sock->sock=nsock;
+ sock->ctx=ctx;
+ sock->component=comp;
+
+ if(r=nr_socket_getaddr(nsock, &addr))
+ ABORT(r);
+
+ if (type == NR_ICE_SOCKET_TYPE_DGRAM) {
+ assert(addr.protocol == IPPROTO_UDP);
+ }
+ else {
+ assert(addr.protocol == IPPROTO_TCP);
+ }
+ sock->type=type;
+
+ TAILQ_INIT(&sock->candidates);
+ TAILQ_INIT(&sock->stun_ctxs);
+
+ if (sock->type == NR_ICE_SOCKET_TYPE_DGRAM){
+ if((r=nr_socket_getfd(nsock,&fd)))
+ ABORT(r);
+ NR_ASYNC_WAIT(fd,NR_ASYNC_WAIT_READ,nr_ice_socket_readable_cb,sock);
+ }
+ else if (sock->type == NR_ICE_SOCKET_TYPE_STREAM_TURN) {
+ /* some OS's (e.g. Linux) don't like to see un-connected TCP sockets in
+ * the poll socket set. */
+ nr_socket_buffered_stun_set_readable_cb(nsock,nr_ice_socket_readable_cb,sock);
+ }
+ else if (sock->type == NR_ICE_SOCKET_TYPE_STREAM_TCP) {
+ /* in this case we can't hook up using NR_ASYNC_WAIT, because nr_socket_multi_tcp
+ consists of multiple nr_sockets and file descriptors. */
+ if((r=nr_socket_multi_tcp_set_readable_cb(nsock,nr_ice_socket_readable_cb,sock)))
+ ABORT(r);
+ }
+
+ *sockp=sock;
+
+ _status=0;
+ abort:
+ if(_status) RFREE(sock);
+ return(_status);
+ }
+
+
+int nr_ice_socket_destroy(nr_ice_socket **isockp)
+ {
+ nr_ice_stun_ctx *s1,*s2;
+ nr_ice_socket *isock;
+
+ if(!isockp || !*isockp)
+ return(0);
+
+ isock=*isockp;
+ *isockp=0;
+
+ /* Close the socket */
+ nr_ice_socket_close(isock);
+
+ /* The STUN server */
+ nr_stun_server_ctx_destroy(&isock->stun_server);
+
+ /* Now clean up the STUN ctxs */
+ TAILQ_FOREACH_SAFE(s1, &isock->stun_ctxs, entry, s2){
+ TAILQ_REMOVE(&isock->stun_ctxs, s1, entry);
+ RFREE(s1);
+ }
+
+ RFREE(isock);
+
+ return(0);
+ }
+
+int nr_ice_socket_close(nr_ice_socket *isock)
+ {
+#ifdef NR_SOCKET_IS_VOID_PTR
+ NR_SOCKET fd=NULL;
+ NR_SOCKET no_socket = NULL;
+#else
+ NR_SOCKET fd=-1;
+ NR_SOCKET no_socket = -1;
+#endif
+
+ if (!isock||!isock->sock)
+ return(0);
+
+ if (isock->type != NR_ICE_SOCKET_TYPE_STREAM_TCP){
+ nr_socket_getfd(isock->sock,&fd);
+ assert(isock->sock!=0);
+ if(fd != no_socket){
+ NR_ASYNC_CANCEL(fd,NR_ASYNC_WAIT_READ);
+ NR_ASYNC_CANCEL(fd,NR_ASYNC_WAIT_WRITE);
+ }
+ }
+ nr_socket_destroy(&isock->sock);
+
+ return(0);
+ }
+
+int nr_ice_socket_register_stun_client(nr_ice_socket *sock, nr_stun_client_ctx *srv,void **handle)
+ {
+ nr_ice_stun_ctx *sc=0;
+ int _status;
+
+ if(!(sc=RCALLOC(sizeof(nr_ice_stun_ctx))))
+ ABORT(R_NO_MEMORY);
+
+ sc->type=NR_ICE_STUN_CLIENT;
+ sc->u.client=srv;
+
+ TAILQ_INSERT_TAIL(&sock->stun_ctxs,sc,entry);
+
+ *handle=sc;
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_ice_socket_register_stun_server(nr_ice_socket *sock, nr_stun_server_ctx *srv,void **handle)
+ {
+ nr_ice_stun_ctx *sc=0;
+ int _status;
+
+ if(!(sc=RCALLOC(sizeof(nr_ice_stun_ctx))))
+ ABORT(R_NO_MEMORY);
+
+ sc->type=NR_ICE_STUN_SERVER;
+ sc->u.server=srv;
+
+ TAILQ_INSERT_TAIL(&sock->stun_ctxs,sc,entry);
+
+ *handle=sc;
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_ice_socket_register_turn_client(nr_ice_socket *sock, nr_turn_client_ctx *srv,
+ nr_socket *turn_socket, void **handle)
+ {
+ nr_ice_stun_ctx *sc=0;
+ int _status;
+
+ if(!(sc=RCALLOC(sizeof(nr_ice_stun_ctx))))
+ ABORT(R_NO_MEMORY);
+
+ sc->type=NR_ICE_TURN_CLIENT;
+ sc->u.turn_client.turn_client=srv;
+ sc->u.turn_client.turn_sock=turn_socket;
+
+ TAILQ_INSERT_TAIL(&sock->stun_ctxs,sc,entry);
+
+ *handle=sc;
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+/* Just mark it deregistered. Don't delete it now because it's not safe
+ in the CB, which is where this is likely to be called */
+int nr_ice_socket_deregister(nr_ice_socket *sock, void *handle)
+ {
+ nr_ice_stun_ctx *sc=handle;
+
+ if(!sc)
+ return(0);
+
+ sc->type=NR_ICE_STUN_NONE;
+
+ return(0);
+ }
+
+void nr_ice_socket_failed(nr_ice_socket *sock)
+ {
+ nr_ice_stun_ctx *s1,*s2;
+ TAILQ_FOREACH_SAFE(s1, &sock->stun_ctxs, entry, s2){
+ switch (s1->type) {
+ case NR_ICE_STUN_NONE:
+ break;
+ case NR_ICE_STUN_CLIENT:
+ nr_stun_client_failed(s1->u.client);
+ break;
+ case NR_ICE_STUN_SERVER:
+ /* Nothing to do here? */
+ break;
+#ifdef USE_TURN
+ case NR_ICE_TURN_CLIENT:
+ nr_turn_client_failed(s1->u.turn_client.turn_client);
+ break;
+#endif
+ default:
+ assert(0);
+ }
+ }
+ }
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_socket.h b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_socket.h
new file mode 100644
index 0000000000..9b73a2690e
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/ice/ice_socket.h
@@ -0,0 +1,98 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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 _ice_socket_h
+#define _ice_socket_h
+#ifdef __cplusplus
+using namespace std;
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct nr_ice_stun_ctx_ {
+ int type;
+#define NR_ICE_STUN_NONE 0 /* Deregistered */
+#define NR_ICE_STUN_CLIENT 1
+#define NR_ICE_STUN_SERVER 2
+#define NR_ICE_TURN_CLIENT 3
+
+ union {
+ nr_stun_client_ctx *client;
+ nr_stun_server_ctx *server;
+ struct {
+ nr_turn_client_ctx *turn_client;
+ nr_socket *turn_sock; /* The nr_socket_turn wrapped around
+ turn_client */
+ } turn_client;
+ } u;
+
+ TAILQ_ENTRY(nr_ice_stun_ctx_) entry;
+} nr_ice_stun_ctx;
+
+
+typedef struct nr_ice_socket_ {
+ int type;
+#define NR_ICE_SOCKET_TYPE_DGRAM 1
+#define NR_ICE_SOCKET_TYPE_STREAM_TURN 2
+#define NR_ICE_SOCKET_TYPE_STREAM_TCP 3
+
+ nr_socket *sock;
+ nr_ice_ctx *ctx;
+
+ nr_ice_candidate_head candidates;
+ nr_ice_component *component;
+
+ TAILQ_HEAD(nr_ice_stun_ctx_head_,nr_ice_stun_ctx_) stun_ctxs;
+
+ nr_stun_server_ctx *stun_server;
+ void *stun_server_handle;
+
+ STAILQ_ENTRY(nr_ice_socket_) entry;
+} nr_ice_socket;
+
+typedef STAILQ_HEAD(nr_ice_socket_head_,nr_ice_socket_) nr_ice_socket_head;
+
+int nr_ice_socket_create(struct nr_ice_ctx_ *ctx, struct nr_ice_component_ *comp, nr_socket *nsock, int type, nr_ice_socket **sockp);
+int nr_ice_socket_destroy(nr_ice_socket **isock);
+int nr_ice_socket_close(nr_ice_socket *isock);
+int nr_ice_socket_register_stun_client(nr_ice_socket *sock, nr_stun_client_ctx *srv,void **handle);
+int nr_ice_socket_register_stun_server(nr_ice_socket *sock, nr_stun_server_ctx *srv,void **handle);
+int nr_ice_socket_register_turn_client(nr_ice_socket *sock, nr_turn_client_ctx *srv,nr_socket *turn_socket, void **handle);
+int nr_ice_socket_deregister(nr_ice_socket *sock, void *handle);
+void nr_ice_socket_failed(nr_ice_socket *sock);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/net/local_addr.c b/dom/media/webrtc/transport/third_party/nICEr/src/net/local_addr.c
new file mode 100644
index 0000000000..a0896d5e9d
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/net/local_addr.c
@@ -0,0 +1,70 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+Copyright (c) 2013, Mozilla
+
+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 Adobe Systems, Network Resonance, Mozilla 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
+OWNER 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.
+*/
+
+#include <nr_api.h>
+#include <string.h>
+#include "local_addr.h"
+
+int nr_local_addr_copy(nr_local_addr *to, nr_local_addr *from)
+ {
+ int r,_status;
+
+ if (r=nr_transport_addr_copy(&(to->addr), &(from->addr))) {
+ ABORT(r);
+ }
+ to->interface = from->interface;
+ to->flags = from->flags;
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_local_addr_fmt_info_string(nr_local_addr *addr, char *buf, int len)
+ {
+ int addr_type = addr->interface.type;
+ const char *vpn = (addr_type & NR_INTERFACE_TYPE_VPN) ? "VPN on " : "";
+
+ const char *type = (addr_type & NR_INTERFACE_TYPE_WIRED) ? "wired" :
+ (addr_type & NR_INTERFACE_TYPE_WIFI) ? "wifi" :
+ (addr_type & NR_INTERFACE_TYPE_MOBILE) ? "mobile" :
+ "unknown";
+
+ snprintf(buf, len, "%s%s, estimated speed: %d kbps %s",
+ vpn, type, addr->interface.estimated_speed,
+ (addr->flags & NR_ADDR_FLAG_TEMPORARY ? "temporary" : ""));
+ buf[len - 1] = '\0';
+ return (0);
+ }
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/net/local_addr.h b/dom/media/webrtc/transport/third_party/nICEr/src/net/local_addr.h
new file mode 100644
index 0000000000..fb963e1115
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/net/local_addr.h
@@ -0,0 +1,62 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+Copyright (c) 2013, Mozilla
+
+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 Adobe Systems, Network Resonance, Mozilla 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
+OWNER 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 _local_addr_h
+#define _local_addr_h
+
+#include "transport_addr.h"
+
+typedef struct nr_interface_ {
+ int type;
+#define NR_INTERFACE_TYPE_UNKNOWN 0
+#define NR_INTERFACE_TYPE_WIRED 1
+#define NR_INTERFACE_TYPE_WIFI 1 << 1
+#define NR_INTERFACE_TYPE_MOBILE 1 << 2
+#define NR_INTERFACE_TYPE_VPN 1 << 3
+#define NR_INTERFACE_TYPE_TEREDO 1 << 4
+ int estimated_speed; /* Speed in kbps */
+} nr_interface;
+
+typedef struct nr_local_addr_ {
+ nr_transport_addr addr;
+ nr_interface interface;
+#define NR_ADDR_FLAG_TEMPORARY 0x1
+ int flags;
+} nr_local_addr;
+
+int nr_local_addr_copy(nr_local_addr *to, nr_local_addr *from);
+int nr_local_addr_fmt_info_string(nr_local_addr *addr, char *buf, int len);
+
+#endif
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/net/nr_interface_prioritizer.c b/dom/media/webrtc/transport/third_party/nICEr/src/net/nr_interface_prioritizer.c
new file mode 100644
index 0000000000..75e5f95467
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/net/nr_interface_prioritizer.c
@@ -0,0 +1,88 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+Copyright (c) 2013, Mozilla
+
+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 Adobe Systems, Network Resonance, Mozilla 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
+OWNER 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.
+*/
+
+#include "nr_api.h"
+#include "nr_interface_prioritizer.h"
+#include "transport_addr.h"
+
+int nr_interface_prioritizer_create_int(void *obj,
+ nr_interface_prioritizer_vtbl *vtbl,nr_interface_prioritizer **ifpp)
+ {
+ int _status;
+ nr_interface_prioritizer *ifp=0;
+
+ if(!(ifp=RCALLOC(sizeof(nr_interface_prioritizer))))
+ ABORT(R_NO_MEMORY);
+
+ ifp->obj = obj;
+ ifp->vtbl = vtbl;
+
+ *ifpp = ifp;
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_interface_prioritizer_destroy(nr_interface_prioritizer **ifpp)
+ {
+ nr_interface_prioritizer *ifp;
+
+ if (!ifpp || !*ifpp)
+ return(0);
+
+ ifp = *ifpp;
+ *ifpp = 0;
+ ifp->vtbl->destroy(&ifp->obj);
+ RFREE(ifp);
+ return(0);
+ }
+
+int nr_interface_prioritizer_add_interface(nr_interface_prioritizer *ifp,
+ nr_local_addr *addr)
+ {
+ return ifp->vtbl->add_interface(ifp->obj, addr);
+ }
+
+int nr_interface_prioritizer_get_priority(nr_interface_prioritizer *ifp,
+ const char *key, UCHAR *interface_preference)
+ {
+ return ifp->vtbl->get_priority(ifp->obj,key,interface_preference);
+ }
+
+int nr_interface_prioritizer_sort_preference(nr_interface_prioritizer *ifp)
+ {
+ return ifp->vtbl->sort_preference(ifp->obj);
+ }
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/net/nr_interface_prioritizer.h b/dom/media/webrtc/transport/third_party/nICEr/src/net/nr_interface_prioritizer.h
new file mode 100644
index 0000000000..c8a36526cc
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/net/nr_interface_prioritizer.h
@@ -0,0 +1,66 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+Copyright (c) 2013, Mozilla
+
+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 Adobe Systems, Network Resonance, Mozilla 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
+OWNER 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 _nr_interface_prioritizer
+#define _nr_interface_prioritizer
+
+#include "transport_addr.h"
+#include "local_addr.h"
+
+typedef struct nr_interface_prioritizer_vtbl_ {
+ int (*add_interface)(void *obj, nr_local_addr *iface);
+ int (*get_priority)(void *obj, const char *key, UCHAR *pref);
+ int (*sort_preference)(void *obj);
+ int (*destroy)(void **obj);
+} nr_interface_prioritizer_vtbl;
+
+typedef struct nr_interface_prioritizer_ {
+ void *obj;
+ nr_interface_prioritizer_vtbl *vtbl;
+} nr_interface_prioritizer;
+
+int nr_interface_prioritizer_create_int(void *obj, nr_interface_prioritizer_vtbl *vtbl,
+ nr_interface_prioritizer **prioritizer);
+
+int nr_interface_prioritizer_destroy(nr_interface_prioritizer **prioritizer);
+
+int nr_interface_prioritizer_add_interface(nr_interface_prioritizer *prioritizer,
+ nr_local_addr *addr);
+
+int nr_interface_prioritizer_get_priority(nr_interface_prioritizer *prioritizer,
+ const char *key, UCHAR *interface_preference);
+
+int nr_interface_prioritizer_sort_preference(nr_interface_prioritizer *prioritizer);
+#endif
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/net/nr_resolver.c b/dom/media/webrtc/transport/third_party/nICEr/src/net/nr_resolver.c
new file mode 100644
index 0000000000..4dbf1bbe91
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/net/nr_resolver.c
@@ -0,0 +1,85 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+Copyright (c) 2013, Mozilla
+
+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 Adobe Systems, Network Resonance, Mozilla 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
+OWNER 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.
+*/
+
+#include <nr_api.h>
+#include "nr_resolver.h"
+
+int nr_resolver_create_int(void *obj, nr_resolver_vtbl *vtbl, nr_resolver **resolverp)
+{
+ int _status;
+ nr_resolver *resolver=0;
+
+ if (!(resolver=RCALLOC(sizeof(nr_resolver))))
+ ABORT(R_NO_MEMORY);
+
+ resolver->obj=obj;
+ resolver->vtbl=vtbl;
+
+ *resolverp=resolver;
+ _status=0;
+abort:
+ return(_status);
+}
+
+int nr_resolver_destroy(nr_resolver **resolverp)
+{
+ nr_resolver *resolver;
+
+ if(!resolverp || !*resolverp)
+ return(0);
+
+ resolver=*resolverp;
+ *resolverp=0;
+
+ resolver->vtbl->destroy(&resolver->obj);
+
+ RFREE(resolver);
+
+ return(0);
+}
+
+int nr_resolver_resolve(nr_resolver *resolver,
+ nr_resolver_resource *resource,
+ int (*cb)(void *cb_arg, nr_transport_addr *addr),
+ void *cb_arg,
+ void **handle)
+{
+ return resolver->vtbl->resolve(resolver->obj, resource, cb, cb_arg, handle);
+}
+
+int nr_resolver_cancel(nr_resolver *resolver, void *handle)
+{
+ return resolver->vtbl->cancel(resolver->obj, handle);
+}
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/net/nr_resolver.h b/dom/media/webrtc/transport/third_party/nICEr/src/net/nr_resolver.h
new file mode 100644
index 0000000000..376ba9998b
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/net/nr_resolver.h
@@ -0,0 +1,96 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+Copyright (c) 2013, Mozilla
+
+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 Adobe Systems, Network Resonance, Mozilla 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
+OWNER 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 _nr_resolver_h
+#define _nr_resolver_h
+
+#include "transport_addr.h"
+
+#define NR_RESOLVE_PROTOCOL_STUN 1
+#define NR_RESOLVE_PROTOCOL_TURN 2
+
+typedef struct nr_resolver_resource_ {
+ const char *domain_name;
+ UINT2 port;
+ int stun_turn;
+ UCHAR transport_protocol;
+ UCHAR address_family;
+} nr_resolver_resource;
+
+typedef struct nr_resolver_vtbl_ {
+ int (*destroy)(void **obj);
+ int (*resolve)(void *obj,
+ nr_resolver_resource *resource,
+ int (*cb)(void *cb_arg, nr_transport_addr *addr),
+ void *cb_arg,
+ void **handle);
+ int (*cancel)(void *obj, void *handle);
+} nr_resolver_vtbl;
+
+typedef struct nr_resolver_ {
+ void *obj;
+ nr_resolver_vtbl *vtbl;
+} nr_resolver;
+
+
+/*
+ The convention here is that the provider of this interface
+ must generate a void *obj, and a vtbl and then call
+ nr_resolver_create_int() to allocate the generic wrapper
+ object.
+
+ The vtbl must contain implementations for all the functions
+ listed.
+
+ The nr_resolver_destroy() function (and hence vtbl->destroy)
+ will be called when the consumer of the resolver is done
+ with it. That is the signal that it is safe to clean up
+ the resources associated with obj. No other function will
+ be called afterwards.
+*/
+int nr_resolver_create_int(void *obj, nr_resolver_vtbl *vtbl,
+ nr_resolver **resolverp);
+int nr_resolver_destroy(nr_resolver **resolverp);
+
+/* Request resolution of a domain */
+int nr_resolver_resolve(nr_resolver *resolver,
+ nr_resolver_resource *resource,
+ int (*cb)(void *cb_arg, nr_transport_addr *addr),
+ void *cb_arg,
+ void **handle);
+
+/* Cancel a requested resolution. No callback will fire. */
+int nr_resolver_cancel(nr_resolver *resolver, void *handle);
+#endif
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/net/nr_socket.c b/dom/media/webrtc/transport/third_party/nICEr/src/net/nr_socket.c
new file mode 100644
index 0000000000..c9867610a6
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/net/nr_socket.c
@@ -0,0 +1,187 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+*/
+
+#include <assert.h>
+#include <nr_api.h>
+#include "nr_socket.h"
+#include "local_addr.h"
+
+#define CHECK_DEFINED(f) assert(sock->vtbl->f); if (!sock->vtbl->f) ERETURN(R_INTERNAL);
+int nr_socket_create_int(void *obj, nr_socket_vtbl *vtbl, nr_socket **sockp)
+ {
+ int _status;
+ nr_socket *sock=0;
+
+ if(!(sock=RCALLOC(sizeof(nr_socket))))
+ ABORT(R_NO_MEMORY);
+
+ assert(vtbl->version >= 1 && vtbl->version <= 2);
+ if (vtbl->version < 1 || vtbl->version > 2)
+ ABORT(R_INTERNAL);
+
+ sock->obj=obj;
+ sock->vtbl=vtbl;
+
+ *sockp=sock;
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_socket_destroy(nr_socket **sockp)
+ {
+ nr_socket *sock;
+
+ if(!sockp || !*sockp)
+ return(0);
+
+
+ sock=*sockp;
+ *sockp=0;
+
+ CHECK_DEFINED(destroy);
+
+ assert(sock->vtbl);
+ if (sock->vtbl)
+ sock->vtbl->destroy(&sock->obj);
+
+ RFREE(sock);
+
+ return(0);
+ }
+
+int nr_socket_sendto(nr_socket *sock,const void *msg, size_t len, int flags,
+ const nr_transport_addr *addr)
+ {
+ CHECK_DEFINED(ssendto);
+ return sock->vtbl->ssendto(sock->obj,msg,len,flags,addr);
+ }
+
+int nr_socket_recvfrom(nr_socket *sock,void * restrict buf, size_t maxlen,
+ size_t *len, int flags, nr_transport_addr *addr)
+ {
+ CHECK_DEFINED(srecvfrom);
+ return sock->vtbl->srecvfrom(sock->obj, buf, maxlen, len, flags, addr);
+ }
+
+int nr_socket_getfd(nr_socket *sock, NR_SOCKET *fd)
+ {
+ CHECK_DEFINED(getfd);
+ return sock->vtbl->getfd(sock->obj, fd);
+ }
+
+int nr_socket_getaddr(nr_socket *sock, nr_transport_addr *addrp)
+ {
+ CHECK_DEFINED(getaddr);
+ return sock->vtbl->getaddr(sock->obj, addrp);
+ }
+
+int nr_socket_close(nr_socket *sock)
+ {
+ CHECK_DEFINED(close);
+ return sock->vtbl->close(sock->obj);
+ }
+
+int nr_socket_connect(nr_socket *sock, const nr_transport_addr *addr)
+ {
+ CHECK_DEFINED(connect);
+ return sock->vtbl->connect(sock->obj, addr);
+ }
+
+int nr_socket_write(nr_socket *sock,const void *msg, size_t len, size_t *written, int flags)
+ {
+ CHECK_DEFINED(swrite);
+ return sock->vtbl->swrite(sock->obj, msg, len, written);
+ }
+
+
+int nr_socket_read(nr_socket *sock,void * restrict buf, size_t maxlen,
+ size_t *len, int flags)
+ {
+ CHECK_DEFINED(sread);
+ return sock->vtbl->sread(sock->obj, buf, maxlen, len);
+ }
+
+int nr_socket_listen(nr_socket *sock, int backlog)
+ {
+ assert(sock->vtbl->version >=2 );
+ CHECK_DEFINED(listen);
+ return sock->vtbl->listen(sock->obj, backlog);
+ }
+
+int nr_socket_accept(nr_socket *sock, nr_transport_addr *addrp, nr_socket **sockp)
+{
+ assert(sock->vtbl->version >= 2);
+ CHECK_DEFINED(accept);
+ return sock->vtbl->accept(sock->obj, addrp, sockp);
+}
+
+
+int nr_socket_factory_create_int(void *obj,
+ nr_socket_factory_vtbl *vtbl, nr_socket_factory **factorypp)
+ {
+ int _status;
+ nr_socket_factory *factoryp=0;
+
+ if(!(factoryp=RCALLOC(sizeof(nr_socket_factory))))
+ ABORT(R_NO_MEMORY);
+
+ factoryp->obj = obj;
+ factoryp->vtbl = vtbl;
+
+ *factorypp = factoryp;
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_socket_factory_destroy(nr_socket_factory **factorypp)
+ {
+ nr_socket_factory *factoryp;
+
+ if (!factorypp || !*factorypp)
+ return (0);
+
+ factoryp = *factorypp;
+ *factorypp = NULL;
+ factoryp->vtbl->destroy(&factoryp->obj);
+ RFREE(factoryp);
+ return (0);
+ }
+
+int nr_socket_factory_create_socket(nr_socket_factory *factory, nr_transport_addr *addr, nr_socket **sockp)
+ {
+ return factory->vtbl->create_socket(factory->obj, addr, sockp);
+ }
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/net/nr_socket.h b/dom/media/webrtc/transport/third_party/nICEr/src/net/nr_socket.h
new file mode 100644
index 0000000000..777837f6cc
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/net/nr_socket.h
@@ -0,0 +1,123 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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 _nr_socket_h
+#define _nr_socket_h
+
+#include <sys/types.h>
+#ifdef WIN32
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#else
+#include <sys/socket.h>
+#endif
+
+#include "transport_addr.h"
+#include "csi_platform.h"
+
+#ifdef __cplusplus
+#define restrict
+#elif defined(WIN32)
+/* Undef before defining to avoid a compiler warning */
+#undef restrict
+#define restrict __restrict
+#endif
+
+typedef enum {
+ TCP_TYPE_NONE=0,
+ TCP_TYPE_ACTIVE,
+ TCP_TYPE_PASSIVE,
+ TCP_TYPE_SO,
+ TCP_TYPE_MAX
+} nr_socket_tcp_type;
+
+typedef struct nr_socket_ nr_socket;
+
+typedef struct nr_socket_vtbl_ {
+ UINT4 version; /* Currently 2 */
+ int (*destroy)(void **obj);
+ int (*ssendto)(void *obj,const void *msg, size_t len, int flags,
+ const nr_transport_addr *addr);
+ int (*srecvfrom)(void *obj,void * restrict buf, size_t maxlen, size_t *len, int flags,
+ nr_transport_addr *addr);
+ int (*getfd)(void *obj, NR_SOCKET *fd);
+ int (*getaddr)(void *obj, nr_transport_addr *addrp);
+ int (*connect)(void *obj, const nr_transport_addr *addr);
+ int (*swrite)(void *obj,const void *msg, size_t len, size_t *written);
+ int (*sread)(void *obj,void * restrict buf, size_t maxlen, size_t *len);
+ int (*close)(void *obj);
+
+ /* available since version 2 */
+ int (*listen)(void *obj, int backlog);
+ int (*accept)(void *obj, nr_transport_addr *addrp, nr_socket **sockp);
+} nr_socket_vtbl;
+
+
+struct nr_socket_ {
+ void *obj;
+ nr_socket_vtbl *vtbl;
+};
+
+typedef struct nr_socket_factory_vtbl_ {
+ int (*create_socket)(void *obj, nr_transport_addr *addr, nr_socket **sockp);
+ int (*destroy)(void **obj);
+} nr_socket_factory_vtbl;
+
+typedef struct nr_socket_factory_ {
+ void *obj;
+ nr_socket_factory_vtbl *vtbl;
+} nr_socket_factory;
+
+/* To be called by constructors */
+int nr_socket_create_int(void *obj, nr_socket_vtbl *vtbl, nr_socket **sockp);
+int nr_socket_destroy(nr_socket **sockp);
+int nr_socket_sendto(nr_socket *sock,const void *msg, size_t len,
+ int flags, const nr_transport_addr *addr);
+int nr_socket_recvfrom(nr_socket *sock,void * restrict buf, size_t maxlen,
+ size_t *len, int flags, nr_transport_addr *addr);
+int nr_socket_getfd(nr_socket *sock, NR_SOCKET *fd);
+int nr_socket_getaddr(nr_socket *sock, nr_transport_addr *addrp);
+int nr_socket_close(nr_socket *sock);
+int nr_socket_connect(nr_socket *sock, const nr_transport_addr *addr);
+int nr_socket_write(nr_socket *sock,const void *msg, size_t len, size_t *written, int flags);
+int nr_socket_read(nr_socket *sock, void * restrict buf, size_t maxlen, size_t *len, int flags);
+int nr_socket_listen(nr_socket *sock, int backlog);
+int nr_socket_accept(nr_socket *sock, nr_transport_addr *addrp, nr_socket **sockp);
+
+int nr_socket_factory_create_int(void *obj, nr_socket_factory_vtbl *vtbl, nr_socket_factory **factorypp);
+int nr_socket_factory_destroy(nr_socket_factory **factoryp);
+int nr_socket_factory_create_socket(nr_socket_factory *factory, nr_transport_addr *addr, nr_socket **sockp);
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/net/nr_socket_local.h b/dom/media/webrtc/transport/third_party/nICEr/src/net/nr_socket_local.h
new file mode 100644
index 0000000000..a2f813ff66
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/net/nr_socket_local.h
@@ -0,0 +1,41 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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 _nr_socket_local_h
+#define _nr_socket_local_h
+
+int nr_socket_local_create(void *obj, nr_transport_addr *addr, nr_socket **sockp);
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/net/nr_socket_multi_tcp.c b/dom/media/webrtc/transport/third_party/nICEr/src/net/nr_socket_multi_tcp.c
new file mode 100644
index 0000000000..9b2489b214
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/net/nr_socket_multi_tcp.c
@@ -0,0 +1,642 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+Copyright (c) 2014, Mozilla
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+*/
+
+#include <assert.h>
+#include <sys/types.h>
+
+#include "nr_api.h"
+#include "ice_ctx.h"
+#include "nr_socket.h"
+#include "nr_socket_local.h"
+#include "nr_socket_multi_tcp.h"
+#include "nr_socket_buffered_stun.h"
+#include "async_timer.h"
+
+typedef struct nr_tcp_socket_ctx_ {
+ nr_socket * inner;
+ nr_transport_addr remote_addr;
+ int is_framed;
+
+ TAILQ_ENTRY(nr_tcp_socket_ctx_) entry;
+} nr_tcp_socket_ctx;
+
+typedef TAILQ_HEAD(nr_tcp_socket_head_,nr_tcp_socket_ctx_) nr_tcp_socket_head;
+
+static void nr_tcp_socket_readable_cb(NR_SOCKET s, int how, void *arg);
+
+static int nr_tcp_socket_ctx_destroy(nr_tcp_socket_ctx **objp)
+ {
+ nr_tcp_socket_ctx *sock;
+
+ if (!objp || !*objp)
+ return(0);
+
+ sock=*objp;
+ *objp=0;
+
+ nr_socket_destroy(&sock->inner);
+
+ RFREE(sock);
+
+ return(0);
+ }
+
+/* This takes ownership of nrsock whether it fails or not. */
+static int nr_tcp_socket_ctx_create(nr_socket *nrsock, int is_framed,
+ int max_pending, nr_tcp_socket_ctx **sockp)
+ {
+ int r, _status;
+ nr_tcp_socket_ctx *sock = 0;
+ nr_socket *tcpsock;
+
+ if (!(sock = RCALLOC(sizeof(nr_tcp_socket_ctx)))) {
+ nr_socket_destroy(&nrsock);
+ ABORT(R_NO_MEMORY);
+ }
+
+ if ((r=nr_socket_buffered_stun_create(nrsock, max_pending, is_framed ? ICE_TCP_FRAMING : TURN_TCP_FRAMING, &tcpsock))){
+ nr_socket_destroy(&nrsock);
+ ABORT(r);
+ }
+
+ sock->inner=tcpsock;
+ sock->is_framed=is_framed;
+
+ if ((r=nr_ip4_port_to_transport_addr(ntohl(INADDR_ANY), 0, IPPROTO_TCP, &sock->remote_addr)))
+ ABORT(r);
+
+ *sockp=sock;
+
+ _status=0;
+abort:
+ if (_status) {
+ r_log(LOG_ICE,LOG_DEBUG,"%s:%d function %s failed with error %d",__FILE__,__LINE__,__FUNCTION__,_status);
+ nr_tcp_socket_ctx_destroy(&sock);
+ }
+ return(_status);
+ }
+
+static int nr_tcp_socket_ctx_initialize(nr_tcp_socket_ctx *tcpsock,
+ const nr_transport_addr *addr, void* cb_arg)
+ {
+ int r, _status;
+ NR_SOCKET fd;
+
+ if ((r=nr_transport_addr_copy(&tcpsock->remote_addr, addr)))
+ ABORT(r);
+ if ((r=nr_socket_getfd(tcpsock->inner, &fd)))
+ ABORT(r);
+ NR_ASYNC_WAIT(fd, NR_ASYNC_WAIT_READ, nr_tcp_socket_readable_cb, cb_arg);
+
+ _status=0;
+ abort:
+ if (_status)
+ r_log(LOG_ICE,LOG_DEBUG,"%s:%d function %s(addr:%s) failed with error %d",__FILE__,__LINE__,__FUNCTION__,addr->as_string,_status);
+ return(_status);
+ }
+
+typedef struct nr_socket_multi_tcp_ {
+ nr_ice_ctx *ctx;
+ nr_socket *listen_socket;
+ nr_tcp_socket_head sockets;
+ nr_socket_tcp_type tcp_type;
+ nr_transport_addr addr;
+ NR_async_cb readable_cb;
+ void *readable_cb_arg;
+ int max_pending;
+} nr_socket_multi_tcp;
+
+static int nr_socket_multi_tcp_destroy(void **objp);
+static int nr_socket_multi_tcp_sendto(void *obj,const void *msg, size_t len,
+ int flags, const nr_transport_addr *to);
+static int nr_socket_multi_tcp_recvfrom(void *obj,void * restrict buf,
+ size_t maxlen, size_t *len, int flags, nr_transport_addr *from);
+static int nr_socket_multi_tcp_getaddr(void *obj, nr_transport_addr *addrp);
+static int nr_socket_multi_tcp_close(void *obj);
+static int nr_socket_multi_tcp_connect(void *sock, const nr_transport_addr *addr);
+static int nr_socket_multi_tcp_listen(void *obj, int backlog);
+
+static nr_socket_vtbl nr_socket_multi_tcp_vtbl={
+ 2,
+ nr_socket_multi_tcp_destroy,
+ nr_socket_multi_tcp_sendto,
+ nr_socket_multi_tcp_recvfrom,
+ 0,
+ nr_socket_multi_tcp_getaddr,
+ nr_socket_multi_tcp_connect,
+ 0,
+ 0,
+ nr_socket_multi_tcp_close,
+ nr_socket_multi_tcp_listen,
+ 0
+};
+
+static int nr_socket_multi_tcp_create_stun_server_socket(
+ nr_socket_multi_tcp *sock, nr_ice_stun_server * stun_server,
+ nr_transport_addr *addr, int max_pending)
+ {
+ int r, _status;
+ nr_tcp_socket_ctx *tcp_socket_ctx=0;
+ nr_socket * nrsock;
+
+ if (stun_server->addr.protocol != IPPROTO_TCP) {
+ r_log(LOG_ICE, LOG_INFO,
+ "%s:%d function %s skipping UDP STUN server(addr:%s)", __FILE__,
+ __LINE__, __FUNCTION__, stun_server->addr.as_string);
+ ABORT(R_BAD_ARGS);
+ }
+
+ if (nr_transport_addr_cmp(&stun_server->addr, addr,
+ NR_TRANSPORT_ADDR_CMP_MODE_VERSION)) {
+ r_log(LOG_ICE, LOG_INFO,
+ "%s:%d function %s skipping STUN with different IP version (%u) "
+ "than local socket (%u),",
+ __FILE__, __LINE__, __FUNCTION__, stun_server->addr.ip_version,
+ addr->ip_version);
+ ABORT(R_BAD_ARGS);
+ }
+
+ if ((r=nr_socket_factory_create_socket(sock->ctx->socket_factory,addr, &nrsock)))
+ ABORT(r);
+
+ /* This takes ownership of nrsock whether it fails or not. */
+ if ((r=nr_tcp_socket_ctx_create(nrsock, 0, max_pending, &tcp_socket_ctx)))
+ ABORT(r);
+
+ nr_transport_addr stun_server_addr;
+
+ nr_transport_addr_copy(&stun_server_addr, &stun_server->addr);
+ r = nr_socket_connect(tcp_socket_ctx->inner, &stun_server_addr);
+ if (r && r != R_WOULDBLOCK) {
+ r_log(LOG_ICE, LOG_WARNING,
+ "%s:%d function %s connect to STUN server(addr:%s) failed with "
+ "error %d",
+ __FILE__, __LINE__, __FUNCTION__, stun_server_addr.as_string, r);
+ ABORT(r);
+ }
+
+ if ((r = nr_tcp_socket_ctx_initialize(tcp_socket_ctx, &stun_server_addr,
+ sock)))
+ ABORT(r);
+
+ TAILQ_INSERT_TAIL(&sock->sockets, tcp_socket_ctx, entry);
+
+ _status=0;
+ abort:
+ if (_status) {
+ nr_tcp_socket_ctx_destroy(&tcp_socket_ctx);
+ r_log(LOG_ICE,LOG_DEBUG,"%s:%d function %s(addr:%s) failed with error %d",__FILE__,__LINE__,__FUNCTION__,addr->as_string,_status);
+ }
+ return(_status);
+ }
+
+int nr_socket_multi_tcp_create(struct nr_ice_ctx_ *ctx,
+ struct nr_ice_component_ *component,
+ nr_transport_addr *addr, nr_socket_tcp_type tcp_type,
+ int precreated_so_count, int max_pending, nr_socket **sockp)
+ {
+ int i=0;
+ int r, _status;
+ nr_socket_multi_tcp *sock=0;
+ nr_tcp_socket_ctx *tcp_socket_ctx;
+ nr_socket * nrsock;
+
+ if (!(sock = RCALLOC(sizeof(nr_socket_multi_tcp))))
+ ABORT(R_NO_MEMORY);
+
+ TAILQ_INIT(&sock->sockets);
+
+ sock->ctx=ctx;
+ sock->max_pending=max_pending;
+ sock->tcp_type=tcp_type;
+ nr_transport_addr_copy(&sock->addr, addr);
+
+ if((tcp_type==TCP_TYPE_PASSIVE) &&
+ ((r=nr_socket_factory_create_socket(sock->ctx->socket_factory, addr, &sock->listen_socket))))
+ ABORT(r);
+
+ if (tcp_type!=TCP_TYPE_ACTIVE) {
+ nr_ice_stun_server *stun_servers;
+ nr_ice_turn_server *turn_servers;
+ int stun_server_ct, turn_server_ct;
+ if (component) {
+ stun_servers = component->stream->stun_servers;
+ turn_servers = component->stream->turn_servers;
+ stun_server_ct = component->stream->stun_server_ct;
+ turn_server_ct = component->stream->turn_server_ct;
+ } else {
+ /* Mainly for unit-testing */
+ stun_servers = ctx->stun_servers_cfg;
+ turn_servers = ctx->turn_servers_cfg;
+ stun_server_ct = ctx->stun_server_ct_cfg;
+ turn_server_ct = ctx->turn_server_ct_cfg;
+ }
+ if (stun_servers) {
+ for (i=0; i<stun_server_ct; ++i) {
+ if ((r=nr_socket_multi_tcp_create_stun_server_socket(sock,
+ stun_servers+i, addr, max_pending))) {
+ if (r!=R_BAD_ARGS) {
+ r_log(LOG_ICE,LOG_WARNING,"%s:%d function %s failed to connect STUN server from addr:%s with error %d",__FILE__,__LINE__,__FUNCTION__,addr->as_string,r);
+ }
+ }
+ }
+ }
+ if (turn_servers) {
+ for (i=0; i<turn_server_ct; ++i) {
+ if ((r=nr_socket_multi_tcp_create_stun_server_socket(sock,
+ &(turn_servers[i]).turn_server, addr, max_pending))) {
+ if (r!=R_BAD_ARGS) {
+ r_log(LOG_ICE,LOG_WARNING,"%s:%d function %s failed to connect TURN server from addr:%s with error %d",__FILE__,__LINE__,__FUNCTION__,addr->as_string,r);
+ }
+ }
+ }
+ }
+ }
+
+ if ((tcp_type==TCP_TYPE_SO)) {
+ for (i=0; i<precreated_so_count; ++i) {
+
+ if ((r=nr_socket_factory_create_socket(sock->ctx->socket_factory, addr, &nrsock)))
+ ABORT(r);
+
+ /* This takes ownership of nrsock whether it fails or not. */
+ if ((r=nr_tcp_socket_ctx_create(nrsock, 1, max_pending, &tcp_socket_ctx))){
+ ABORT(r);
+ }
+ TAILQ_INSERT_TAIL(&sock->sockets, tcp_socket_ctx, entry);
+ }
+ }
+
+ if((r=nr_socket_create_int(sock, &nr_socket_multi_tcp_vtbl, sockp)))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ if (_status) {
+ r_log(LOG_ICE,LOG_DEBUG,"%s:%d function %s(addr:%s) failed with error %d",__FILE__,__LINE__,__FUNCTION__,addr->as_string,_status);
+ nr_socket_multi_tcp_destroy((void**)&sock);
+ }
+ return(_status);
+ }
+
+int nr_socket_multi_tcp_set_readable_cb(nr_socket *sock,
+ NR_async_cb readable_cb, void *readable_cb_arg)
+ {
+ nr_socket_multi_tcp *mtcp_sock = (nr_socket_multi_tcp *)sock->obj;
+
+ mtcp_sock->readable_cb=readable_cb;
+ mtcp_sock->readable_cb_arg=readable_cb_arg;
+
+ return 0;
+ }
+
+#define PREALLOC_CONNECT_FRAMED 0
+#define PREALLOC_CONNECT_NON_FRAMED 1
+#define PREALLOC_DONT_CONNECT_UNLESS_SO 2
+
+static int nr_socket_multi_tcp_get_sock_connected_to(nr_socket_multi_tcp *sock,
+ const nr_transport_addr *to, int preallocated_connect_mode, nr_socket **ret_sock)
+ {
+ int r, _status;
+ nr_tcp_socket_ctx *tcp_sock_ctx;
+ nr_socket * nrsock;
+
+ TAILQ_FOREACH(tcp_sock_ctx, &sock->sockets, entry) {
+ if (!nr_transport_addr_is_wildcard(&tcp_sock_ctx->remote_addr)) {
+ if (!nr_transport_addr_cmp(to, &tcp_sock_ctx->remote_addr, NR_TRANSPORT_ADDR_CMP_MODE_ALL)) {
+ *ret_sock=tcp_sock_ctx->inner;
+ return(0);
+ }
+ }
+ }
+
+ tcp_sock_ctx=NULL;
+ /* not connected yet */
+ if (sock->tcp_type != TCP_TYPE_ACTIVE) {
+ if (preallocated_connect_mode == PREALLOC_DONT_CONNECT_UNLESS_SO && sock->tcp_type != TCP_TYPE_SO)
+ ABORT(R_FAILED);
+
+ /* find free preallocated socket and connect */
+ TAILQ_FOREACH(tcp_sock_ctx, &sock->sockets, entry) {
+ if (nr_transport_addr_is_wildcard(&tcp_sock_ctx->remote_addr)) {
+ if (preallocated_connect_mode == PREALLOC_CONNECT_NON_FRAMED && tcp_sock_ctx->is_framed)
+ continue;
+ if (preallocated_connect_mode != PREALLOC_CONNECT_NON_FRAMED && !tcp_sock_ctx->is_framed)
+ continue;
+
+ if ((r=nr_socket_connect(tcp_sock_ctx->inner, to))){
+ if (r!=R_WOULDBLOCK)
+ ABORT(r);
+ }
+
+ if ((r=nr_tcp_socket_ctx_initialize(tcp_sock_ctx, to, sock)))
+ ABORT(r);
+
+ *ret_sock=tcp_sock_ctx->inner;
+
+ return(0);
+ }
+ }
+ tcp_sock_ctx=NULL;
+ ABORT(R_FAILED);
+ }
+
+ /* if active type - create new socket for each new remote addr */
+ assert(sock->tcp_type == TCP_TYPE_ACTIVE);
+
+ if ((r=nr_socket_factory_create_socket(sock->ctx->socket_factory, &sock->addr, &nrsock)))
+ ABORT(r);
+
+ /* This takes ownership of nrsock whether it fails or not. */
+ if ((r=nr_tcp_socket_ctx_create(nrsock, 1, sock->max_pending, &tcp_sock_ctx))){
+ ABORT(r);
+ }
+
+ TAILQ_INSERT_TAIL(&sock->sockets, tcp_sock_ctx, entry);
+
+ if ((r=nr_socket_connect(tcp_sock_ctx->inner, to))){
+ if (r!=R_WOULDBLOCK)
+ ABORT(r);
+ }
+
+ if ((r=nr_tcp_socket_ctx_initialize(tcp_sock_ctx, to, sock)))
+ ABORT(r);
+
+ *ret_sock=tcp_sock_ctx->inner;
+ tcp_sock_ctx=NULL;
+
+ _status=0;
+ abort:
+ if (_status) {
+ if (tcp_sock_ctx) {
+ r_log(LOG_ICE,LOG_DEBUG,"%s:%d function %s failed with error %d, tcp_sock_ctx remote_addr: %s",__FILE__,__LINE__,__FUNCTION__,_status, tcp_sock_ctx->remote_addr.as_string);
+ TAILQ_REMOVE(&sock->sockets, tcp_sock_ctx, entry);
+ nr_tcp_socket_ctx_destroy(&tcp_sock_ctx);
+ } else {
+ r_log(LOG_ICE,LOG_DEBUG,"%s:%d function %s failed with error %d, tcp_sock_ctx=NULL",__FILE__,__LINE__,__FUNCTION__,_status);
+ }
+ }
+
+ return(_status);
+ }
+
+int nr_socket_multi_tcp_stun_server_connect(nr_socket *sock,
+ const nr_transport_addr *addr)
+ {
+ int r, _status;
+ nr_socket_multi_tcp *mtcp_sock = (nr_socket_multi_tcp *)sock->obj;
+ nr_socket *nrsock;
+
+ assert(mtcp_sock->tcp_type != TCP_TYPE_ACTIVE);
+ if (mtcp_sock->tcp_type == TCP_TYPE_ACTIVE)
+ ABORT(R_INTERNAL);
+
+ if ((r=nr_socket_multi_tcp_get_sock_connected_to(mtcp_sock,addr,PREALLOC_CONNECT_NON_FRAMED,&nrsock)))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ if (_status)
+ r_log(LOG_ICE,LOG_DEBUG,"%s:%d function %s(addr:%s) failed with error %d",__FILE__,__LINE__,__FUNCTION__,addr->as_string,_status);
+ return(_status);
+ }
+
+static int nr_socket_multi_tcp_destroy(void **objp)
+ {
+ nr_socket_multi_tcp *sock;
+ nr_tcp_socket_ctx *tcpsock;
+ NR_SOCKET fd;
+
+ if (!objp || !*objp)
+ return 0;
+
+ sock=(nr_socket_multi_tcp *)*objp;
+ *objp=0;
+
+ /* Cancel waiting on the socket */
+ if (sock->listen_socket && !nr_socket_getfd(sock->listen_socket, &fd)) {
+ NR_ASYNC_CANCEL(fd, NR_ASYNC_WAIT_READ);
+ }
+
+ nr_socket_destroy(&sock->listen_socket);
+
+ while (!TAILQ_EMPTY(&sock->sockets)) {
+
+ tcpsock = TAILQ_FIRST(&sock->sockets);
+ TAILQ_REMOVE(&sock->sockets, tcpsock, entry);
+
+ if (!nr_socket_getfd(tcpsock->inner, &fd)) {
+ NR_ASYNC_CANCEL(fd, NR_ASYNC_WAIT_READ);
+ }
+
+ nr_tcp_socket_ctx_destroy(&tcpsock);
+ }
+
+ RFREE(sock);
+
+ return 0;
+ }
+
+static int nr_socket_multi_tcp_sendto(void *obj, const void *msg, size_t len,
+ int flags, const nr_transport_addr *to)
+ {
+ int r, _status;
+ nr_socket_multi_tcp *sock=(nr_socket_multi_tcp *)obj;
+ nr_socket *nrsock;
+
+ if ((r=nr_socket_multi_tcp_get_sock_connected_to(sock, to,
+ PREALLOC_DONT_CONNECT_UNLESS_SO, &nrsock)))
+ ABORT(r);
+
+ if((r=nr_socket_sendto(nrsock, msg, len, flags, to)))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ if (_status)
+ r_log(LOG_ICE,LOG_DEBUG,"%s:%d function %s(to:%s) failed with error %d",__FILE__,__LINE__,__FUNCTION__,to->as_string,_status);
+
+ return(_status);
+}
+
+static int nr_socket_multi_tcp_recvfrom(void *obj,void * restrict buf,
+ size_t maxlen, size_t *len, int flags, nr_transport_addr *from)
+ {
+ int r, _status = 0;
+ nr_socket_multi_tcp *sock=(nr_socket_multi_tcp *)obj;
+ nr_tcp_socket_ctx *tcpsock;
+
+ if (TAILQ_EMPTY(&sock->sockets))
+ ABORT(R_FAILED);
+
+ TAILQ_FOREACH(tcpsock, &sock->sockets, entry) {
+ if (nr_transport_addr_is_wildcard(&tcpsock->remote_addr))
+ continue;
+ r=nr_socket_recvfrom(tcpsock->inner, buf, maxlen, len, flags, from);
+ if (!r)
+ return 0;
+
+ if (r!=R_WOULDBLOCK) {
+ NR_SOCKET fd;
+ r_log(LOG_ICE,LOG_DEBUG,
+ "%s:%d function %s(to:%s) failed with error %d",__FILE__,
+ __LINE__,__FUNCTION__,tcpsock->remote_addr.as_string,r);
+ if (!nr_socket_getfd(tcpsock->inner, &fd)) {
+ NR_ASYNC_CANCEL(fd, NR_ASYNC_WAIT_READ);
+ NR_ASYNC_CANCEL(fd, NR_ASYNC_WAIT_WRITE);
+ }
+
+ TAILQ_REMOVE(&sock->sockets, tcpsock, entry);
+ nr_tcp_socket_ctx_destroy(&tcpsock);
+ ABORT(r);
+ }
+ }
+
+ /* this also gets returned if all tcpsocks have wildcard remote_addr */
+ _status=R_WOULDBLOCK;
+ abort:
+
+ return(_status);
+ }
+
+static int nr_socket_multi_tcp_getaddr(void *obj, nr_transport_addr *addrp)
+ {
+ nr_socket_multi_tcp *sock=(nr_socket_multi_tcp *)obj;
+
+ return nr_transport_addr_copy(addrp,&sock->addr);
+ }
+
+static int nr_socket_multi_tcp_close(void *obj)
+ {
+ nr_socket_multi_tcp *sock=(nr_socket_multi_tcp *)obj;
+ nr_tcp_socket_ctx *tcpsock;
+
+ if(sock->listen_socket)
+ nr_socket_close(sock->listen_socket);
+
+ TAILQ_FOREACH(tcpsock, &sock->sockets, entry) {
+ nr_socket_close(tcpsock->inner); //ignore errors
+ }
+
+ return 0;
+ }
+
+static void nr_tcp_socket_readable_cb(NR_SOCKET s, int how, void *arg)
+ {
+ nr_socket_multi_tcp *sock=(nr_socket_multi_tcp *)arg;
+
+ // rearm
+ NR_ASYNC_WAIT(s, NR_ASYNC_WAIT_READ, nr_tcp_socket_readable_cb, arg);
+
+ if (sock->readable_cb)
+ sock->readable_cb(s, how, sock->readable_cb_arg);
+ }
+
+static int nr_socket_multi_tcp_connect(void *obj, const nr_transport_addr *addr)
+ {
+ int r, _status;
+ nr_socket_multi_tcp *sock=(nr_socket_multi_tcp *)obj;
+ nr_socket *nrsock;
+
+ if ((r=nr_socket_multi_tcp_get_sock_connected_to(sock,addr,PREALLOC_CONNECT_FRAMED,&nrsock)))
+ ABORT(r);
+
+ _status=0;
+abort:
+ if (_status)
+ r_log(LOG_ICE,LOG_DEBUG,"%s:%d function %s(addr:%s) failed with error %d",__FILE__,__LINE__,__FUNCTION__,addr->as_string,_status);
+
+ return(_status);
+ }
+
+static void nr_tcp_multi_lsocket_readable_cb(NR_SOCKET s, int how, void *arg)
+ {
+ nr_socket_multi_tcp *sock=(nr_socket_multi_tcp *)arg;
+ nr_socket *newsock;
+ nr_transport_addr remote_addr;
+ nr_tcp_socket_ctx *tcp_sock_ctx;
+ int r, _status;
+
+ // rearm
+ NR_ASYNC_WAIT(s, NR_ASYNC_WAIT_READ, nr_tcp_multi_lsocket_readable_cb, arg);
+
+ /* accept */
+ if ((r=nr_socket_accept(sock->listen_socket, &remote_addr, &newsock)))
+ ABORT(r);
+
+ /* This takes ownership of newsock whether it fails or not. */
+ if ((r=nr_tcp_socket_ctx_create(newsock, 1, sock->max_pending, &tcp_sock_ctx)))
+ ABORT(r);
+
+ nr_socket_buffered_set_connected_to(tcp_sock_ctx->inner, &remote_addr);
+
+ if ((r=nr_tcp_socket_ctx_initialize(tcp_sock_ctx, &remote_addr, sock))) {
+ nr_tcp_socket_ctx_destroy(&tcp_sock_ctx);
+ ABORT(r);
+ }
+
+ TAILQ_INSERT_HEAD(&sock->sockets, tcp_sock_ctx, entry);
+
+ _status=0;
+abort:
+ if (_status) {
+ r_log(LOG_ICE,LOG_WARNING,"%s:%d %s failed to accept new TCP connection: %d",__FILE__,__LINE__,__FUNCTION__,_status);
+ } else {
+ r_log(LOG_ICE,LOG_INFO,"%s:%d %s accepted new TCP connection from %s",__FILE__,__LINE__,__FUNCTION__,remote_addr.as_string);
+ }
+ }
+
+static int nr_socket_multi_tcp_listen(void *obj, int backlog)
+ {
+ int r, _status;
+ nr_socket_multi_tcp *sock=(nr_socket_multi_tcp *)obj;
+ NR_SOCKET fd;
+
+ if(!sock->listen_socket)
+ ABORT(R_FAILED);
+
+ if ((r=nr_socket_listen(sock->listen_socket, backlog)))
+ ABORT(r);
+
+ if ((r=nr_socket_getfd(sock->listen_socket, &fd)))
+ ABORT(r);
+
+ NR_ASYNC_WAIT(fd, NR_ASYNC_WAIT_READ, nr_tcp_multi_lsocket_readable_cb, sock);
+
+ _status=0;
+ abort:
+ if (_status)
+ r_log(LOG_ICE,LOG_WARNING,"%s:%d function %s failed with error %d",__FILE__,__LINE__,__FUNCTION__,_status);
+
+ return(_status);
+ }
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/net/nr_socket_multi_tcp.h b/dom/media/webrtc/transport/third_party/nICEr/src/net/nr_socket_multi_tcp.h
new file mode 100644
index 0000000000..8413e67293
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/net/nr_socket_multi_tcp.h
@@ -0,0 +1,53 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+Copyright (c) 2014, Mozilla
+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 Adobe Systems, Network Resonance 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
+OWNER 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 _nr_socket_multi_tcp_h
+#define _nr_socket_multi_tcp_h
+
+#include "nr_socket.h"
+
+/* Argument use_framing is 0 only in call from test code (STUN TCP server
+ listening socket). For other purposes it should be always set to true */
+
+int nr_socket_multi_tcp_create(struct nr_ice_ctx_ *ctx,
+ struct nr_ice_component_ *component,
+ nr_transport_addr *addr, nr_socket_tcp_type tcp_type,
+ int precreated_so_count, int max_pending, nr_socket **sockp);
+
+int nr_socket_multi_tcp_set_readable_cb(nr_socket *sock,
+ NR_async_cb readable_cb,void *readable_cb_arg);
+
+int nr_socket_multi_tcp_stun_server_connect(nr_socket *sock,
+ const nr_transport_addr *addr);
+
+#endif
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/net/nr_socket_wrapper.c b/dom/media/webrtc/transport/third_party/nICEr/src/net/nr_socket_wrapper.c
new file mode 100644
index 0000000000..4ad59527c1
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/net/nr_socket_wrapper.c
@@ -0,0 +1,84 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+Copyright (c) 2013, Mozilla
+
+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 Adobe Systems, Network Resonance, Mozilla 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
+OWNER 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.
+*/
+
+#include <nr_api.h>
+#include "nr_socket_wrapper.h"
+
+#include <assert.h>
+
+int nr_socket_wrapper_factory_create_int(void *obj, nr_socket_wrapper_factory_vtbl *vtbl,
+ nr_socket_wrapper_factory **wrapperp)
+{
+ int _status;
+ nr_socket_wrapper_factory *wrapper=0;
+
+ if (!(wrapper=RCALLOC(sizeof(nr_socket_wrapper_factory))))
+ ABORT(R_NO_MEMORY);
+
+ wrapper->obj=obj;
+ wrapper->vtbl=vtbl;
+
+ *wrapperp=wrapper;
+ _status=0;
+abort:
+ return(_status);
+}
+
+int nr_socket_wrapper_factory_wrap(nr_socket_wrapper_factory *wrapper,
+ nr_socket *inner,
+ nr_socket **socketp)
+{
+ return wrapper->vtbl->wrap(wrapper->obj, inner, socketp);
+}
+
+int nr_socket_wrapper_factory_destroy(nr_socket_wrapper_factory **wrapperp)
+{
+ nr_socket_wrapper_factory *wrapper;
+
+ if (!wrapperp || !*wrapperp)
+ return 0;
+
+ wrapper = *wrapperp;
+ *wrapperp = 0;
+
+ assert(wrapper->vtbl);
+ if (wrapper->vtbl)
+ wrapper->vtbl->destroy(&wrapper->obj);
+
+ RFREE(wrapper);
+
+ return 0;
+}
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/net/nr_socket_wrapper.h b/dom/media/webrtc/transport/third_party/nICEr/src/net/nr_socket_wrapper.h
new file mode 100644
index 0000000000..717518e23e
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/net/nr_socket_wrapper.h
@@ -0,0 +1,63 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+Copyright (c) 2013, Mozilla
+
+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 Adobe Systems, Network Resonance, Mozilla 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
+OWNER 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 _nr_socket_wrapper_h
+#define _nr_socket_wrapper_h
+
+#include "nr_socket.h"
+
+typedef struct nr_socket_wrapper_factory_vtbl_ {
+ int (*wrap)(void *obj,
+ nr_socket *socket,
+ nr_socket **socketp);
+ int (*destroy)(void **obj);
+} nr_socket_wrapper_factory_vtbl;
+
+typedef struct nr_socket_wrapper_factory_ {
+ void *obj;
+ nr_socket_wrapper_factory_vtbl *vtbl;
+} nr_socket_wrapper_factory;
+
+
+int nr_socket_wrapper_factory_create_int(void *obj, nr_socket_wrapper_factory_vtbl *vtbl,
+ nr_socket_wrapper_factory **wrapperp);
+
+
+int nr_socket_wrapper_factory_wrap(nr_socket_wrapper_factory *wrapper, nr_socket *inner,
+ nr_socket **socketp);
+
+int nr_socket_wrapper_factory_destroy(nr_socket_wrapper_factory **wrapperp);
+
+#endif
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/net/transport_addr.c b/dom/media/webrtc/transport/third_party/nICEr/src/net/transport_addr.c
new file mode 100644
index 0000000000..efedb3782a
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/net/transport_addr.c
@@ -0,0 +1,559 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+*/
+
+#include <csi_platform.h>
+#include <stdio.h>
+#include <memory.h>
+#include <sys/types.h>
+#include <errno.h>
+#ifdef WIN32
+#include <winsock2.h>
+#else
+#include <unistd.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#endif
+#include <assert.h>
+#include "nr_api.h"
+#include "util.h"
+#include "transport_addr.h"
+
+int nr_transport_addr_fmt_addr_string(nr_transport_addr *addr)
+ {
+ int _status;
+ /* Max length for normalized IPv6 address string representation is 39 */
+ char buffer[40];
+ const char *protocol;
+
+ switch(addr->protocol){
+ case IPPROTO_TCP:
+ if (addr->tls) {
+ protocol = "TLS";
+ } else {
+ protocol = "TCP";
+ }
+ break;
+ case IPPROTO_UDP:
+ protocol = "UDP";
+ break;
+ default:
+ ABORT(R_INTERNAL);
+ }
+
+ switch(addr->ip_version){
+ case NR_IPV4:
+ if (!inet_ntop(AF_INET, &addr->u.addr4.sin_addr,buffer,sizeof(buffer)))
+ strcpy(buffer, "[error]");
+ snprintf(addr->as_string,sizeof(addr->as_string),"IP4:%s:%d/%s",buffer,(int)ntohs(addr->u.addr4.sin_port),protocol);
+ break;
+ case NR_IPV6:
+ if (!inet_ntop(AF_INET6, &addr->u.addr6.sin6_addr,buffer,sizeof(buffer)))
+ strcpy(buffer, "[error]");
+ snprintf(addr->as_string,sizeof(addr->as_string),"IP6:[%s]:%d/%s",buffer,(int)ntohs(addr->u.addr6.sin6_port),protocol);
+ break;
+ default:
+ ABORT(R_INTERNAL);
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_transport_addr_fmt_ifname_addr_string(const nr_transport_addr *addr, char *buf, int len)
+ {
+ int _status;
+ /* leave room for a fully-expanded IPV4-mapped IPV6 address */
+ char buffer[46];
+
+ switch(addr->ip_version){
+ case NR_IPV4:
+ if (!inet_ntop(AF_INET, &addr->u.addr4.sin_addr,buffer,sizeof(buffer))) {
+ strncpy(buffer, "[error]", sizeof(buffer));
+ }
+ break;
+ case NR_IPV6:
+ if (!inet_ntop(AF_INET6, &addr->u.addr6.sin6_addr,buffer,sizeof(buffer))) {
+ strncpy(buffer, "[error]", sizeof(buffer));
+ }
+ break;
+ default:
+ ABORT(R_INTERNAL);
+ }
+ buffer[sizeof(buffer) - 1] = '\0';
+
+ snprintf(buf,len,"%s:%s",addr->ifname,buffer);
+ buf[len - 1] = '\0';
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_sockaddr_to_transport_addr(struct sockaddr *saddr, int protocol, int keep, nr_transport_addr *addr)
+ {
+ int r,_status;
+
+ if(!keep) memset(addr,0,sizeof(nr_transport_addr));
+
+ switch(protocol){
+ case IPPROTO_TCP:
+ case IPPROTO_UDP:
+ break;
+ default:
+ ABORT(R_BAD_ARGS);
+ }
+
+ addr->protocol=protocol;
+
+ if(saddr->sa_family==AF_INET){
+ addr->ip_version=NR_IPV4;
+
+ memcpy(&addr->u.addr4,saddr,sizeof(struct sockaddr_in));
+ }
+ else if(saddr->sa_family==AF_INET6){
+ addr->ip_version=NR_IPV6;
+
+ memcpy(&addr->u.addr6, saddr, sizeof(struct sockaddr_in6));
+ }
+ else
+ ABORT(R_BAD_ARGS);
+
+ if(r=nr_transport_addr_fmt_addr_string(addr))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+
+int nr_transport_addr_copy(nr_transport_addr *to, const nr_transport_addr *from)
+ {
+ memcpy(to,from,sizeof(nr_transport_addr));
+ return 0;
+ }
+
+int nr_transport_addr_copy_keep_ifname(nr_transport_addr *to, const nr_transport_addr *from)
+ {
+ int r,_status;
+ char save_ifname[MAXIFNAME];
+
+ strncpy(save_ifname, to->ifname, MAXIFNAME);
+ save_ifname[MAXIFNAME-1]=0; /* Ensure null termination */
+
+ if (r=nr_transport_addr_copy(to, from))
+ ABORT(r);
+
+ strncpy(to->ifname, save_ifname, MAXIFNAME);
+
+ if (r=nr_transport_addr_fmt_addr_string(to))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ return _status;
+ }
+
+int nr_transport_addr_copy_addrport(nr_transport_addr *to, const nr_transport_addr *from)
+ {
+ int r,_status;
+
+ switch (from->ip_version) {
+ case NR_IPV4:
+ memcpy(&to->u.addr4, &from->u.addr4, sizeof(to->u.addr4));
+ break;
+ case NR_IPV6:
+ memcpy(&to->u.addr6, &from->u.addr6, sizeof(to->u.addr6));
+ break;
+ default:
+ ABORT(R_BAD_ARGS);
+ }
+
+ to->ip_version = from->ip_version;
+
+ if (r=nr_transport_addr_fmt_addr_string(to)) {
+ ABORT(r);
+ }
+
+ _status=0;
+ abort:
+ return _status;
+ }
+
+/* Convenience fxn. Is this the right API?*/
+int nr_ip4_port_to_transport_addr(UINT4 ip4, UINT2 port, int protocol, nr_transport_addr *addr)
+ {
+ int r,_status;
+
+ memset(addr, 0, sizeof(nr_transport_addr));
+
+ addr->ip_version=NR_IPV4;
+ addr->protocol=protocol;
+#ifdef HAVE_SIN_LEN
+ addr->u.addr4.sin_len=sizeof(struct sockaddr_in);
+#endif
+ addr->u.addr4.sin_family=PF_INET;
+ addr->u.addr4.sin_port=htons(port);
+ addr->u.addr4.sin_addr.s_addr=htonl(ip4);
+
+ if(r=nr_transport_addr_fmt_addr_string(addr))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_str_port_to_transport_addr(const char *ip, UINT2 port, int protocol, nr_transport_addr *addr_out)
+ {
+ int r,_status;
+ struct in_addr addr;
+ struct in6_addr addr6;
+
+ if (inet_pton(AF_INET, ip, &addr) == 1) {
+ if(r=nr_ip4_port_to_transport_addr(ntohl(addr.s_addr),port,protocol,addr_out))
+ ABORT(r);
+ } else if (inet_pton(AF_INET6, ip, &addr6) == 1) {
+ if(r=nr_ip6_port_to_transport_addr(&addr6,port,protocol,addr_out))
+ ABORT(r);
+ } else {
+ ABORT(R_BAD_DATA);
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_ip6_port_to_transport_addr(struct in6_addr* addr6, UINT2 port, int protocol, nr_transport_addr *addr)
+ {
+ int r,_status;
+
+ memset(addr, 0, sizeof(nr_transport_addr));
+
+ addr->ip_version=NR_IPV6;
+ addr->protocol=protocol;
+ addr->u.addr6.sin6_family=PF_INET6;
+ addr->u.addr6.sin6_port=htons(port);
+ memcpy(addr->u.addr6.sin6_addr.s6_addr, addr6->s6_addr, sizeof(addr6->s6_addr));
+
+ if(r=nr_transport_addr_fmt_addr_string(addr))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_transport_addr_get_addrstring(const nr_transport_addr *addr, char *str, int maxlen)
+ {
+ int _status;
+
+ if (addr->fqdn[0]) {
+ strncpy(str, addr->fqdn, maxlen);
+ } else {
+ const char* res;
+ switch (addr->ip_version) {
+ case NR_IPV4:
+ res = inet_ntop(AF_INET, &addr->u.addr4.sin_addr, str, maxlen);
+ break;
+ case NR_IPV6:
+ res = inet_ntop(AF_INET6, &addr->u.addr6.sin6_addr, str, maxlen);
+ break;
+ default:
+ ABORT(R_INTERNAL);
+ }
+
+ if (!res) {
+ if (errno == ENOSPC) {
+ ABORT(R_BAD_ARGS);
+ }
+ ABORT(R_INTERNAL);
+ }
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_transport_addr_get_port(const nr_transport_addr *addr, int *port)
+ {
+ int _status;
+
+ switch(addr->ip_version){
+ case NR_IPV4:
+ *port=ntohs(addr->u.addr4.sin_port);
+ break;
+ case NR_IPV6:
+ *port=ntohs(addr->u.addr6.sin6_port);
+ break;
+ default:
+ ABORT(R_INTERNAL);
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_transport_addr_set_port(nr_transport_addr *addr, int port)
+ {
+ int _status;
+
+ switch(addr->ip_version){
+ case NR_IPV4:
+ addr->u.addr4.sin_port=htons(port);
+ break;
+ case NR_IPV6:
+ addr->u.addr6.sin6_port=htons(port);
+ break;
+ default:
+ ABORT(R_INTERNAL);
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+/* memcmp() may not work if, for instance, the string or interface
+ haven't been made. Hmmm.. */
+int nr_transport_addr_cmp(const nr_transport_addr *addr1,const nr_transport_addr *addr2,int mode)
+ {
+ assert(mode);
+
+ if(addr1->ip_version != addr2->ip_version)
+ return(1);
+
+ if(mode < NR_TRANSPORT_ADDR_CMP_MODE_PROTOCOL)
+ return(0);
+
+ if(addr1->protocol != addr2->protocol)
+ return(1);
+
+ if(mode < NR_TRANSPORT_ADDR_CMP_MODE_ADDR)
+ return(0);
+
+ switch(addr1->ip_version){
+ case NR_IPV4:
+ if(addr1->u.addr4.sin_addr.s_addr != addr2->u.addr4.sin_addr.s_addr)
+ return(1);
+ if(mode < NR_TRANSPORT_ADDR_CMP_MODE_ALL)
+ return(0);
+ if(addr1->u.addr4.sin_port != addr2->u.addr4.sin_port)
+ return(1);
+ break;
+ case NR_IPV6:
+ if(memcmp(addr1->u.addr6.sin6_addr.s6_addr,addr2->u.addr6.sin6_addr.s6_addr,sizeof(struct in6_addr)))
+ return(1);
+ if(mode < NR_TRANSPORT_ADDR_CMP_MODE_ALL)
+ return(0);
+ if(addr1->u.addr6.sin6_port != addr2->u.addr6.sin6_port)
+ return(1);
+ break;
+ default:
+ abort();
+ }
+
+ return(0);
+ }
+
+int nr_transport_addr_is_loopback(const nr_transport_addr *addr)
+ {
+ switch(addr->ip_version){
+ case NR_IPV4:
+ switch(addr->u.addr4.sin_family){
+ case AF_INET:
+ if (((ntohl(addr->u.addr4.sin_addr.s_addr)>>24)&0xff)==0x7f)
+ return 1;
+ break;
+ default:
+ NR_UNIMPLEMENTED;
+ break;
+ }
+ break;
+
+ case NR_IPV6:
+ if(!memcmp(addr->u.addr6.sin6_addr.s6_addr,in6addr_loopback.s6_addr,sizeof(struct in6_addr)))
+ return(1);
+ break;
+ default:
+ NR_UNIMPLEMENTED;
+ }
+
+ return(0);
+ }
+
+int nr_transport_addr_is_link_local(const nr_transport_addr *addr)
+ {
+ switch(addr->ip_version){
+ case NR_IPV4:
+ /* RFC3927: 169.254/16 */
+ if ((ntohl(addr->u.addr4.sin_addr.s_addr) & 0xFFFF0000) == 0xA9FE0000)
+ return(1);
+ break;
+ case NR_IPV6:
+ {
+ UINT4* addrTop = (UINT4*)(addr->u.addr6.sin6_addr.s6_addr);
+ if ((*addrTop & htonl(0xFFC00000)) == htonl(0xFE800000))
+ return(2);
+ }
+ break;
+ default:
+ NR_UNIMPLEMENTED;
+ }
+
+ return(0);
+ }
+
+int nr_transport_addr_is_mac_based(const nr_transport_addr *addr)
+ {
+ switch(addr->ip_version){
+ case NR_IPV4:
+ // IPv4 has no MAC based self assigned IP addresses
+ return(0);
+ case NR_IPV6:
+ {
+ // RFC 2373, Appendix A: lower 64bit 0x020000FFFE000000
+ // indicates a MAC based IPv6 address
+ UINT4* macCom = (UINT4*)(addr->u.addr6.sin6_addr.s6_addr + 8);
+ UINT4* macExt = (UINT4*)(addr->u.addr6.sin6_addr.s6_addr + 12);
+ if ((*macCom & htonl(0x020000FF)) == htonl(0x020000FF) &&
+ (*macExt & htonl(0xFF000000)) == htonl(0xFE000000)) {
+ return(1);
+ }
+ }
+ break;
+ default:
+ NR_UNIMPLEMENTED;
+ }
+ return(0);
+ }
+
+int nr_transport_addr_is_teredo(const nr_transport_addr *addr)
+ {
+ switch(addr->ip_version){
+ case NR_IPV4:
+ return(0);
+ case NR_IPV6:
+ {
+ UINT4* addrTop = (UINT4*)(addr->u.addr6.sin6_addr.s6_addr);
+ if ((*addrTop & htonl(0xFFFFFFFF)) == htonl(0x20010000))
+ return(1);
+ }
+ break;
+ default:
+ NR_UNIMPLEMENTED;
+ }
+
+ return(0);
+ }
+
+int nr_transport_addr_check_compatibility(const nr_transport_addr *addr1, const nr_transport_addr *addr2)
+ {
+ // first make sure we're comparing the same ip versions and protocols
+ if ((addr1->ip_version != addr2->ip_version) ||
+ (addr1->protocol != addr2->protocol)) {
+ return(1);
+ }
+
+ if (!addr1->fqdn[0] && !addr2->fqdn[0]) {
+ // now make sure the link local status matches
+ if (nr_transport_addr_is_link_local(addr1) !=
+ nr_transport_addr_is_link_local(addr2)) {
+ return(1);
+ }
+ }
+ return(0);
+ }
+
+int nr_transport_addr_is_wildcard(const nr_transport_addr *addr)
+ {
+ switch(addr->ip_version){
+ case NR_IPV4:
+ if(addr->u.addr4.sin_addr.s_addr==INADDR_ANY)
+ return(1);
+ if(addr->u.addr4.sin_port==0)
+ return(1);
+ break;
+ case NR_IPV6:
+ if(!memcmp(addr->u.addr6.sin6_addr.s6_addr,in6addr_any.s6_addr,sizeof(struct in6_addr)))
+ return(1);
+ if(addr->u.addr6.sin6_port==0)
+ return(1);
+ break;
+ default:
+ NR_UNIMPLEMENTED;
+ }
+
+ return(0);
+ }
+
+nr_transport_addr_mask nr_private_ipv4_addrs[] = {
+ /* RFC1918: 10/8 */
+ {0x0A000000, 0xFF000000},
+ /* RFC1918: 172.16/12 */
+ {0xAC100000, 0xFFF00000},
+ /* RFC1918: 192.168/16 */
+ {0xC0A80000, 0xFFFF0000},
+ /* RFC6598: 100.64/10 */
+ {0x64400000, 0xFFC00000}
+};
+
+int nr_transport_addr_get_private_addr_range(const nr_transport_addr *addr)
+ {
+ switch(addr->ip_version){
+ case NR_IPV4:
+ {
+ UINT4 ip = ntohl(addr->u.addr4.sin_addr.s_addr);
+ for (size_t i=0; i<(sizeof(nr_private_ipv4_addrs)/sizeof(nr_transport_addr_mask)); i++) {
+ if ((ip & nr_private_ipv4_addrs[i].mask) == nr_private_ipv4_addrs[i].addr)
+ return i + 1;
+ }
+ }
+ break;
+ case NR_IPV6:
+ return(0);
+ default:
+ NR_UNIMPLEMENTED;
+ }
+
+ return(0);
+ }
+
+int nr_transport_addr_is_reliable_transport(const nr_transport_addr *addr)
+ {
+ return addr->protocol == IPPROTO_TCP;
+ }
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/net/transport_addr.h b/dom/media/webrtc/transport/third_party/nICEr/src/net/transport_addr.h
new file mode 100644
index 0000000000..e8679a7e5a
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/net/transport_addr.h
@@ -0,0 +1,128 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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 _transport_addr_h
+#define _transport_addr_h
+
+#include <stdbool.h>
+#include <sys/types.h>
+#ifdef WIN32
+
+// FIXME: This is dangerous, but exactly the pattern used in
+// nrappkit/src/port/win32/include/csi_platform.h
+// Not good because INT8 are typedefed to different values in
+// <winsock2.h> and <r_types.h>.
+// {
+typedef unsigned char UBLAH_IGNORE_ME_PLEASE;
+typedef signed char BLAH_IGNORE_ME_PLEASE;
+#define UINT8 UBLAH_IGNORE_ME_PLEASE
+#define INT8 BLAH_IGNORE_ME_PLEASE
+#include <winsock2.h>
+#undef UINT8
+#undef INT8
+#include <r_types.h>
+
+// }
+
+#include <ws2tcpip.h>
+#else
+#include <sys/socket.h>
+#include <netinet/in.h>
+#endif
+
+#include "r_types.h"
+
+/* Length of a string hex representation of a MD5 hash */
+#define MAXIFNAME 33
+
+/* Generic transport address
+
+ This spans both sockaddr_in and sockaddr_in6
+ */
+typedef struct nr_transport_addr_ {
+ UCHAR ip_version; /* 4 or 6 */
+#define NR_IPV4 4
+#define NR_IPV6 6
+ UCHAR protocol; /* IPPROTO_TCP, IPPROTO_UDP */
+ union {
+ struct sockaddr_in addr4;
+ struct sockaddr_in6 addr6;
+ } u;
+ char ifname[MAXIFNAME];
+ /* A string version.
+ 56 = 5 ("IP6:[") + 39 (ipv6 address) + 2 ("]:") + 5 (port) + 4 (/UDP) + 1 (null) */
+ char as_string[56];
+ char fqdn[256];
+ bool is_proxied;
+ bool tls;
+} nr_transport_addr;
+
+typedef struct nr_transport_addr_mask_ {
+ UINT4 addr;
+ UINT4 mask;
+} nr_transport_addr_mask;
+
+int nr_sockaddr_to_transport_addr(struct sockaddr *saddr, int protocol, int keep, nr_transport_addr *addr);
+
+// addresses, ports in local byte order
+int nr_ip4_port_to_transport_addr(UINT4 ip4, UINT2 port, int protocol, nr_transport_addr *addr);
+int nr_str_port_to_transport_addr(const char *str, UINT2 port, int protocol, nr_transport_addr *addr);
+int nr_ip6_port_to_transport_addr(struct in6_addr* addr6, UINT2 port, int protocol, nr_transport_addr *addr);
+
+int nr_transport_addr_get_addrstring(const nr_transport_addr *addr, char *str, int maxlen);
+int nr_transport_addr_get_port(const nr_transport_addr *addr, int *port);
+int nr_transport_addr_cmp(const nr_transport_addr *addr1,const nr_transport_addr *addr2,int mode);
+#define NR_TRANSPORT_ADDR_CMP_MODE_VERSION 1
+#define NR_TRANSPORT_ADDR_CMP_MODE_PROTOCOL 2
+#define NR_TRANSPORT_ADDR_CMP_MODE_ADDR 3
+#define NR_TRANSPORT_ADDR_CMP_MODE_ALL 4
+
+int nr_transport_addr_is_wildcard(const nr_transport_addr *addr);
+int nr_transport_addr_is_loopback(const nr_transport_addr *addr);
+int nr_transport_addr_get_private_addr_range(const nr_transport_addr *addr);
+int nr_transport_addr_is_link_local(const nr_transport_addr *addr);
+int nr_transport_addr_is_mac_based(const nr_transport_addr *addr);
+int nr_transport_addr_is_teredo(const nr_transport_addr *addr);
+int nr_transport_addr_check_compatibility(const nr_transport_addr *addr1, const nr_transport_addr *addr2);
+int nr_transport_addr_copy(nr_transport_addr *to, const nr_transport_addr *from);
+int nr_transport_addr_copy_keep_ifname(nr_transport_addr *to, const nr_transport_addr *from);
+/* Copies _just_ the address and port (also handles IP version) */
+int nr_transport_addr_copy_addrport(nr_transport_addr *to, const nr_transport_addr *from);
+int nr_transport_addr_fmt_addr_string(nr_transport_addr *addr);
+int nr_transport_addr_fmt_ifname_addr_string(const nr_transport_addr *addr, char *buf, int len);
+int nr_transport_addr_set_port(nr_transport_addr *addr, int port);
+int nr_transport_addr_is_reliable_transport(const nr_transport_addr *addr);
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/net/transport_addr_reg.c b/dom/media/webrtc/transport/third_party/nICEr/src/net/transport_addr_reg.c
new file mode 100644
index 0000000000..10f93f1947
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/net/transport_addr_reg.c
@@ -0,0 +1,230 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+*/
+
+#include <csi_platform.h>
+#include <stdio.h>
+#include <string.h>
+#include <memory.h>
+#include <sys/types.h>
+#ifdef WIN32
+#include <winsock2.h>
+#else
+#include <strings.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#endif
+#include <assert.h>
+#include "nr_api.h"
+#include "util.h"
+#include "transport_addr.h"
+#include "transport_addr_reg.h"
+
+#ifndef INET6_ADDRSTRLEN
+#define INET6_ADDRSTRLEN 46 /* Value used by linux/BSD */
+#endif
+
+int
+nr_reg_get_transport_addr(NR_registry prefix, int keep, nr_transport_addr *addr)
+{
+ int r,_status;
+ unsigned int count;
+ char *address = 0;
+ UINT2 port = 0;
+ char *ifname = 0;
+ char *protocol = 0;
+ int p;
+
+ if ((r=NR_reg_get_child_count(prefix, &count)))
+ ABORT(r);
+
+ if (count == 0)
+ ABORT(R_NOT_FOUND);
+
+ if ((r=NR_reg_alloc2_string(prefix, "address", &address))) {
+ if (r != R_NOT_FOUND)
+ ABORT(r);
+ address = 0;
+ }
+
+ if ((r=NR_reg_alloc2_string(prefix, "ifname", &ifname))) {
+ if (r != R_NOT_FOUND)
+ ABORT(r);
+ ifname = 0;
+ }
+
+ if ((r=NR_reg_get2_uint2(prefix, "port", &port))) {
+ if (r != R_NOT_FOUND)
+ ABORT(r);
+ port = 0;
+ }
+
+ if ((r=NR_reg_alloc2_string(prefix, "protocol", &protocol))) {
+ if (r != R_NOT_FOUND)
+ ABORT(r);
+ p = IPPROTO_UDP;
+
+ protocol = 0;
+ }
+ else {
+ if (!strcasecmp("tcp", protocol))
+ p = IPPROTO_TCP;
+ else if (!strcasecmp("udp", protocol))
+ p = IPPROTO_UDP;
+ else
+ ABORT(R_BAD_DATA);
+ }
+
+ if (!keep) memset(addr, 0, sizeof(*addr));
+
+ if ((r=nr_str_port_to_transport_addr(address?address:"0.0.0.0", port, p, addr)))
+ ABORT(r);
+
+ if (ifname) {
+ (void)strlcpy(addr->ifname, ifname, sizeof(addr->ifname));
+ }
+
+ _status=0;
+ abort:
+ RFREE(protocol);
+ RFREE(ifname);
+ RFREE(address);
+ return(_status);
+}
+
+int
+nr_reg_set_transport_addr(NR_registry prefix, int keep, nr_transport_addr *addr)
+{
+ int r,_status;
+
+ if (! keep) {
+ if ((r=NR_reg_del(prefix)))
+ ABORT(r);
+ }
+
+ switch (addr->ip_version) {
+ case NR_IPV4:
+ if (!nr_transport_addr_is_wildcard(addr)) {
+ if ((r=NR_reg_set2_string(prefix, "address", inet_ntoa(addr->u.addr4.sin_addr))))
+ ABORT(r);
+ }
+
+ if (addr->u.addr4.sin_port != 0) {
+ if ((r=NR_reg_set2_uint2(prefix, "port", ntohs(addr->u.addr4.sin_port))))
+ ABORT(r);
+ }
+ break;
+
+ case NR_IPV6:
+ if (!nr_transport_addr_is_wildcard(addr)) {
+ char address[INET6_ADDRSTRLEN];
+ if(!inet_ntop(AF_INET6, &addr->u.addr6.sin6_addr,address,sizeof(address))) {
+ ABORT(R_BAD_DATA);
+ }
+
+ if ((r=NR_reg_set2_string(prefix, "address", address))) {
+ ABORT(r);
+ }
+ }
+
+ if (addr->u.addr6.sin6_port != 0) {
+ if ((r=NR_reg_set2_uint2(prefix, "port", ntohs(addr->u.addr6.sin6_port))))
+ ABORT(r);
+ }
+ break;
+ default:
+ ABORT(R_INTERNAL);
+ break;
+ }
+
+ /* We abort if neither NR_IPV4 or NR_IPV6 above */
+ switch (addr->protocol) {
+ case IPPROTO_TCP:
+ if ((r=NR_reg_set2_string(prefix, "protocol", "tcp")))
+ ABORT(r);
+ break;
+ case IPPROTO_UDP:
+ if ((r=NR_reg_set2_string(prefix, "protocol", "udp")))
+ ABORT(r);
+ break;
+ default:
+ NR_UNIMPLEMENTED;
+ break;
+ }
+
+ if (strlen(addr->ifname) > 0) {
+ if ((r=NR_reg_set2_string(prefix, "ifname", addr->ifname)))
+ ABORT(r);
+ }
+
+ _status=0;
+ abort:
+ if (_status)
+ NR_reg_del(prefix);
+ return _status;
+}
+
+int
+nr_reg_get_transport_addr2(NR_registry prefix, char *name, int keep, nr_transport_addr *addr)
+{
+ int r, _status;
+ NR_registry registry;
+
+ if ((r=NR_reg_make_registry(prefix, name, registry)))
+ ABORT(r);
+
+ if ((r=nr_reg_get_transport_addr(registry, keep, addr)))
+ ABORT(r);
+
+ _status = 0;
+abort:
+ return _status;
+}
+
+int
+nr_reg_set_transport_addr2(NR_registry prefix, char *name, int keep, nr_transport_addr *addr)
+{
+ int r, _status;
+ NR_registry registry;
+
+ if ((r=NR_reg_make_registry(prefix, name, registry)))
+ ABORT(r);
+
+ if ((r=nr_reg_set_transport_addr(registry, keep, addr)))
+ ABORT(r);
+
+ _status = 0;
+abort:
+ return _status;
+}
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/net/transport_addr_reg.h b/dom/media/webrtc/transport/third_party/nICEr/src/net/transport_addr_reg.h
new file mode 100644
index 0000000000..761953a9ce
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/net/transport_addr_reg.h
@@ -0,0 +1,46 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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 _transport_addr_reg_h
+#define _transport_addr_reg_h
+
+#include "registry.h"
+
+int nr_reg_get_transport_addr(NR_registry prefix, int keep, nr_transport_addr *addr);
+int nr_reg_set_transport_addr(NR_registry prefix, int keep, nr_transport_addr *addr);
+int nr_reg_get_transport_addr2(NR_registry prefix, char *name, int keep, nr_transport_addr *addr);
+int nr_reg_set_transport_addr2(NR_registry prefix, char *name, int keep, nr_transport_addr *addr);
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs-bsd.c b/dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs-bsd.c
new file mode 100644
index 0000000000..ec2d084445
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs-bsd.c
@@ -0,0 +1,110 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#if defined(BSD) || defined(DARWIN)
+#include "addrs-bsd.h"
+#include <csi_platform.h>
+#include <assert.h>
+#include <string.h>
+#include "util.h"
+#include "stun_util.h"
+#include "util.h"
+#include <r_macros.h>
+
+#include <sys/types.h> /* getifaddrs */
+#include <ifaddrs.h> /* getifaddrs */
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <errno.h>
+#include <netinet/in.h>
+#include <netinet6/in6_var.h>
+
+static int
+stun_ifaddr_get_v6_flags(struct ifaddrs *ifaddr)
+{
+ if (ifaddr->ifa_addr->sa_family != AF_INET6) {
+ return 0;
+ }
+
+ int flags = 0;
+ int s = socket(AF_INET6, SOCK_DGRAM, 0);
+ if (!s) {
+ r_log(NR_LOG_STUN, LOG_ERR, "socket(AF_INET6, SOCK_DGRAM, 0) failed, errno=%d", errno);
+ assert(0);
+ return 0;
+ }
+ struct in6_ifreq ifr6;
+ memset(&ifr6, 0, sizeof(ifr6));
+ strncpy(ifr6.ifr_name, ifaddr->ifa_name, sizeof(ifr6.ifr_name));
+ /* ifr_addr is a sockaddr_in6, ifa_addr is a sockaddr* */
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)ifaddr->ifa_addr;
+ ifr6.ifr_addr = *sin6;
+ if (ioctl(s, SIOCGIFAFLAG_IN6, &ifr6) != -1) {
+ flags = ifr6.ifr_ifru.ifru_flags6;
+ } else {
+ r_log(NR_LOG_STUN, LOG_ERR, "ioctl(SIOCGIFAFLAG_IN6) failed, errno=%d", errno);
+ assert(0);
+ }
+ close(s);
+ return flags;
+}
+
+static int
+stun_ifaddr_is_disallowed_v6(int flags) {
+ return flags & (IN6_IFF_ANYCAST | IN6_IFF_TENTATIVE | IN6_IFF_DUPLICATED | IN6_IFF_DETACHED | IN6_IFF_DEPRECATED);
+}
+
+int stun_getaddrs_filtered(nr_local_addr addrs[], int maxaddrs, int *count)
+{
+ int r,_status,flags;
+ struct ifaddrs* if_addrs_head=NULL;
+ struct ifaddrs* if_addr;
+
+ *count = 0;
+
+ if (maxaddrs <= 0)
+ ABORT(R_BAD_ARGS);
+
+ if (getifaddrs(&if_addrs_head) == -1) {
+ r_log(NR_LOG_STUN, LOG_ERR, "getifaddrs error e = %d", errno);
+ ABORT(R_INTERNAL);
+ }
+
+ if_addr = if_addrs_head;
+
+ while (if_addr && *count < maxaddrs) {
+ /* This can be null */
+ if (if_addr->ifa_addr) {
+ switch (if_addr->ifa_addr->sa_family) {
+ case AF_INET:
+ case AF_INET6:
+ flags = stun_ifaddr_get_v6_flags(if_addr);
+ if (!stun_ifaddr_is_disallowed_v6(flags)) {
+ if (r=nr_sockaddr_to_transport_addr(if_addr->ifa_addr, IPPROTO_UDP, 0, &(addrs[*count].addr))) {
+ r_log(NR_LOG_STUN, LOG_ERR, "nr_sockaddr_to_transport_addr error r = %d", r);
+ } else {
+ if (flags & IN6_IFF_TEMPORARY) {
+ addrs[*count].flags |= NR_ADDR_FLAG_TEMPORARY;
+ }
+ (void)strlcpy(addrs[*count].addr.ifname, if_addr->ifa_name, sizeof(addrs[*count].addr.ifname));
+ ++(*count);
+ }
+ }
+ break;
+ default:
+ ;
+ }
+ }
+
+ if_addr = if_addr->ifa_next;
+ }
+
+ _status=0;
+abort:
+ if (if_addrs_head) {
+ freeifaddrs(if_addrs_head);
+ }
+ return(_status);
+}
+#endif
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs-bsd.h b/dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs-bsd.h
new file mode 100644
index 0000000000..b575586f48
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs-bsd.h
@@ -0,0 +1,13 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef STUN_IFADDRS_BSD_H_
+#define STUN_IFADDRS_BSD_H_
+
+#include "local_addr.h"
+
+int stun_getaddrs_filtered(nr_local_addr addrs[], int maxaddrs, int *count);
+
+#endif /* STUN_IFADDRS_BSD_H_ */
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs-netlink.c b/dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs-netlink.c
new file mode 100644
index 0000000000..8d22e5979e
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs-netlink.c
@@ -0,0 +1,285 @@
+/*
+Copyright (c) 2011, The WebRTC project authors. 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 Google 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
+HOLDER 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.
+*/
+
+#if defined(LINUX)
+#include <net/if.h>
+#include "addrs-netlink.h"
+#include <csi_platform.h>
+#include <assert.h>
+#include <string.h>
+#include "util.h"
+#include "stun_util.h"
+#include "util.h"
+#include <r_macros.h>
+
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/utsname.h>
+#include <sys/ioctl.h>
+#include <netinet/in.h>
+#include <unistd.h>
+#include <errno.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+#ifdef ANDROID
+/* Work around an Android NDK < r8c bug */
+#undef __unused
+#else
+#include <linux/if.h> /* struct ifreq, IFF_POINTTOPOINT */
+#include <linux/wireless.h> /* struct iwreq */
+#include <linux/ethtool.h> /* struct ethtool_cmd */
+#include <linux/sockios.h> /* SIOCETHTOOL */
+#endif /* ANDROID */
+
+
+struct netlinkrequest {
+ struct nlmsghdr header;
+ struct ifaddrmsg msg;
+};
+
+static const int kMaxReadSize = 4096;
+
+static void set_ifname(nr_local_addr *addr, struct ifaddrmsg* msg) {
+ assert(sizeof(addr->addr.ifname) > IF_NAMESIZE);
+ if_indextoname(msg->ifa_index, addr->addr.ifname);
+}
+
+static int get_siocgifflags(nr_local_addr *addr) {
+ int fd = socket(AF_INET, SOCK_DGRAM, 0);
+ if (fd == -1) {
+ assert(0);
+ return 0;
+ }
+ struct ifreq ifr;
+ memset(&ifr, 0, sizeof(ifr));
+ strncpy(ifr.ifr_name, addr->addr.ifname, IFNAMSIZ - 1);
+ int rc = ioctl(fd, SIOCGIFFLAGS, &ifr);
+ close(fd);
+ if (rc == -1) {
+ assert(0);
+ return 0;
+ }
+ return ifr.ifr_flags;
+}
+
+static int set_sockaddr(nr_local_addr *addr, struct ifaddrmsg* msg, struct rtattr* rta) {
+ assert(rta->rta_type == IFA_ADDRESS || rta->rta_type == IFA_LOCAL);
+ void *data = RTA_DATA(rta);
+ size_t len = RTA_PAYLOAD(rta);
+ if (msg->ifa_family == AF_INET) {
+ struct sockaddr_in sa;
+ memset(&sa, 0, sizeof(struct sockaddr_in));
+ sa.sin_family = AF_INET;
+ memcpy(&sa.sin_addr, data, len);
+ return nr_sockaddr_to_transport_addr((struct sockaddr*)&sa, IPPROTO_UDP, 0, &(addr->addr));
+ } else if (msg->ifa_family == AF_INET6) {
+ struct sockaddr_in6 sa;
+ memset(&sa, 0, sizeof(struct sockaddr_in6));
+ sa.sin6_family = AF_INET6;
+ /* We do not set sin6_scope_id to ifa_index, because that is only valid for
+ * link local addresses, and we don't use those anyway */
+ memcpy(&sa.sin6_addr, data, len);
+ return nr_sockaddr_to_transport_addr((struct sockaddr*)&sa, IPPROTO_UDP, 0, &(addr->addr));
+ }
+
+ return R_BAD_ARGS;
+}
+
+static int
+stun_ifaddr_is_disallowed_v6(int flags) {
+ return flags & (IFA_F_TENTATIVE | IFA_F_OPTIMISTIC | IFA_F_DADFAILED | IFA_F_DEPRECATED);
+}
+
+static int
+stun_convert_netlink(nr_local_addr *addr, struct ifaddrmsg *address_msg, struct rtattr* rta)
+{
+ int r = set_sockaddr(addr, address_msg, rta);
+ if (r) {
+ r_log(NR_LOG_STUN, LOG_ERR, "set_sockaddr error r = %d", r);
+ return r;
+ }
+
+ set_ifname(addr, address_msg);
+
+ if (address_msg->ifa_flags & IFA_F_TEMPORARY) {
+ addr->flags |= NR_ADDR_FLAG_TEMPORARY;
+ }
+
+ int flags = get_siocgifflags(addr);
+ if (flags & IFF_POINTOPOINT)
+ {
+ addr->interface.type = NR_INTERFACE_TYPE_UNKNOWN | NR_INTERFACE_TYPE_VPN;
+ /* TODO (Bug 896913): find backend network type of this VPN */
+ }
+
+#if defined(LINUX) && !defined(ANDROID)
+ struct ethtool_cmd ecmd;
+ struct ifreq ifr;
+ struct iwreq wrq;
+ int e;
+ int s = socket(AF_INET, SOCK_DGRAM, 0);
+
+ strncpy(ifr.ifr_name, addr->addr.ifname, sizeof(ifr.ifr_name));
+ /* TODO (Bug 896851): interface property for Android */
+ /* Getting ethtool for ethernet information. */
+ ecmd.cmd = ETHTOOL_GSET;
+ /* In/out param */
+ ifr.ifr_data = (void*)&ecmd;
+
+ e = ioctl(s, SIOCETHTOOL, &ifr);
+ if (e == 0)
+ {
+ /* For wireless network, we won't get ethtool, it's a wired
+ * connection */
+ addr->interface.type = NR_INTERFACE_TYPE_WIRED;
+#ifdef DONT_HAVE_ETHTOOL_SPEED_HI
+ addr->interface.estimated_speed = ecmd.speed;
+#else
+ addr->interface.estimated_speed = ((ecmd.speed_hi << 16) | ecmd.speed) * 1000;
+#endif
+ }
+
+ strncpy(wrq.ifr_name, addr->addr.ifname, sizeof(wrq.ifr_name));
+ e = ioctl(s, SIOCGIWRATE, &wrq);
+ if (e == 0)
+ {
+ addr->interface.type = NR_INTERFACE_TYPE_WIFI;
+ addr->interface.estimated_speed = wrq.u.bitrate.value / 1000;
+ }
+
+ close(s);
+
+#else
+ addr->interface.type = NR_INTERFACE_TYPE_UNKNOWN;
+ addr->interface.estimated_speed = 0;
+#endif
+ return 0;
+}
+
+int
+stun_getaddrs_filtered(nr_local_addr addrs[], int maxaddrs, int *count)
+{
+ int _status;
+ int fd = 0;
+
+ /* Scope everything else since we're using ABORT. */
+ {
+ *count = 0;
+
+ if (maxaddrs <= 0)
+ ABORT(R_BAD_ARGS);
+
+ fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+ if (fd < 0) {
+ ABORT(R_INTERNAL);
+ }
+
+ struct netlinkrequest ifaddr_request;
+ memset(&ifaddr_request, 0, sizeof(ifaddr_request));
+ ifaddr_request.header.nlmsg_flags = NLM_F_ROOT | NLM_F_REQUEST;
+ ifaddr_request.header.nlmsg_type = RTM_GETADDR;
+ ifaddr_request.header.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg));
+
+ ssize_t bytes = send(fd, &ifaddr_request, ifaddr_request.header.nlmsg_len, 0);
+ if ((size_t)bytes != ifaddr_request.header.nlmsg_len) {
+ ABORT(R_INTERNAL);
+ }
+
+ char buf[kMaxReadSize];
+ ssize_t amount_read = recv(fd, &buf, kMaxReadSize, 0);
+ while ((amount_read > 0) && (*count != maxaddrs)) {
+ struct nlmsghdr* header = (struct nlmsghdr*)&buf[0];
+ size_t header_size = (size_t)amount_read;
+ for ( ; NLMSG_OK(header, header_size) && (*count != maxaddrs);
+ header = NLMSG_NEXT(header, header_size)) {
+ switch (header->nlmsg_type) {
+ case NLMSG_DONE:
+ /* Success. Return. */
+ close(fd);
+ return 0;
+ case NLMSG_ERROR:
+ ABORT(R_INTERNAL);
+ case RTM_NEWADDR: {
+ struct ifaddrmsg* address_msg =
+ (struct ifaddrmsg*)NLMSG_DATA(header);
+ struct rtattr* rta = IFA_RTA(address_msg);
+ ssize_t payload_len = IFA_PAYLOAD(header);
+ bool found = false;
+ while (RTA_OK(rta, payload_len)) {
+ // This is a bit convoluted. IFA_ADDRESS and IFA_LOCAL are the
+ // same thing except when using a POINTTOPOINT interface, in
+ // which case IFA_LOCAL is the local address, and IFA_ADDRESS is
+ // the remote address. In a reasonable world, that would mean we
+ // could just use IFA_LOCAL all the time. Sadly, IFA_LOCAL is not
+ // always set (IPv6 in particular). So, we have to be on the
+ // lookout for both, and prefer IFA_LOCAL when present.
+ if (rta->rta_type == IFA_ADDRESS || rta->rta_type == IFA_LOCAL) {
+ int family = address_msg->ifa_family;
+ if ((family == AF_INET || family == AF_INET6) &&
+ !stun_ifaddr_is_disallowed_v6(address_msg->ifa_flags) &&
+ !stun_convert_netlink(&addrs[*count], address_msg, rta)) {
+ found = true;
+ if (rta->rta_type == IFA_LOCAL) {
+ // IFA_LOCAL is what we really want; if we find it we're
+ // done. If this is IFA_ADDRESS instead, we do not proceed
+ // yet, and allow a subsequent IFA_LOCAL to overwrite what
+ // we just put in |addrs|.
+ break;
+ }
+ }
+ }
+ /* TODO: Use IFA_LABEL instead of if_indextoname? We would need
+ * to remember how many nr_local_addr we've converted for this
+ * ifaddrmsg, and set the label on all of them. */
+ rta = RTA_NEXT(rta, payload_len);
+ }
+
+ if (found) {
+ ++(*count);
+ }
+ break;
+ }
+ }
+ }
+ amount_read = recv(fd, &buf, kMaxReadSize, 0);
+ }
+ }
+
+ _status=0;
+abort:
+ close(fd);
+ return(_status);
+}
+
+#endif /* defined(LINUX) */
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs-netlink.h b/dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs-netlink.h
new file mode 100644
index 0000000000..b2b07bddc9
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs-netlink.h
@@ -0,0 +1,45 @@
+/*
+Copyright (c) 2011, The WebRTC project authors. 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 Google 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
+HOLDER 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 WEBRTC_BASE_IFADDRS_NETLINK_H_
+#define WEBRTC_BASE_IFADDRS_NETLINK_H_
+
+#include <stdio.h>
+#include <sys/socket.h>
+#include "local_addr.h"
+
+/* for platforms with netlink (android and linux) */
+/* Filters out things like deprecated addresses, and stuff that doesn't pass
+ * IPv6 duplicate address detection */
+int stun_getaddrs_filtered(nr_local_addr addrs[], int maxaddrs, int *count);
+#endif /* WEBRTC_BASE_IFADDRS_NETLINK_H_ */
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs-win32.c b/dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs-win32.c
new file mode 100644
index 0000000000..4fdf5e5d44
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs-win32.c
@@ -0,0 +1,210 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifdef WIN32
+
+#include "addrs-win32.h"
+#include <csi_platform.h>
+#include <assert.h>
+#include <string.h>
+#include "util.h"
+#include "stun_util.h"
+#include "util.h"
+#include <r_macros.h>
+#include "nr_crypto.h"
+
+#include <winsock2.h>
+#include <iphlpapi.h>
+#include <tchar.h>
+
+#define WIN32_MAX_NUM_INTERFACES 20
+
+#define NR_MD5_HASH_LENGTH 16
+
+#define _NR_MAX_KEY_LENGTH 256
+#define _NR_MAX_NAME_LENGTH 512
+
+#define _ADAPTERS_BASE_REG "SYSTEM\\CurrentControlSet\\Control\\Network\\{4D36E972-E325-11CE-BFC1-08002BE10318}"
+
+static int nr_win32_get_adapter_friendly_name(char *adapter_GUID, char **friendly_name)
+{
+ int r,_status;
+ HKEY adapter_reg;
+ TCHAR adapter_key[_NR_MAX_KEY_LENGTH];
+ TCHAR keyval_buf[_NR_MAX_KEY_LENGTH];
+ TCHAR adapter_GUID_tchar[_NR_MAX_NAME_LENGTH];
+ DWORD keyval_len, key_type;
+ size_t converted_chars, newlen;
+ char *my_fn = 0;
+
+#ifdef _UNICODE
+ mbstowcs_s(&converted_chars, adapter_GUID_tchar, strlen(adapter_GUID)+1,
+ adapter_GUID, _TRUNCATE);
+#else
+ strlcpy(adapter_GUID_tchar, adapter_GUID, _NR_MAX_NAME_LENGTH);
+#endif
+
+ _tcscpy_s(adapter_key, _NR_MAX_KEY_LENGTH, TEXT(_ADAPTERS_BASE_REG));
+ _tcscat_s(adapter_key, _NR_MAX_KEY_LENGTH, TEXT("\\"));
+ _tcscat_s(adapter_key, _NR_MAX_KEY_LENGTH, adapter_GUID_tchar);
+ _tcscat_s(adapter_key, _NR_MAX_KEY_LENGTH, TEXT("\\Connection"));
+
+ r = RegOpenKeyEx(HKEY_LOCAL_MACHINE, adapter_key, 0, KEY_READ, &adapter_reg);
+
+ if (r != ERROR_SUCCESS) {
+ r_log(NR_LOG_STUN, LOG_ERR, "Got error %d opening adapter reg key\n", r);
+ ABORT(R_INTERNAL);
+ }
+
+ keyval_len = sizeof(keyval_buf);
+ r = RegQueryValueEx(adapter_reg, TEXT("Name"), NULL, &key_type,
+ (BYTE *)keyval_buf, &keyval_len);
+
+ RegCloseKey(adapter_reg);
+
+#ifdef UNICODE
+ newlen = wcslen(keyval_buf)+1;
+ my_fn = (char *) RCALLOC(newlen);
+ if (!my_fn) {
+ ABORT(R_NO_MEMORY);
+ }
+ wcstombs_s(&converted_chars, my_fn, newlen, keyval_buf, _TRUNCATE);
+#else
+ my_fn = r_strdup(keyval_buf);
+#endif
+
+ *friendly_name = my_fn;
+ _status=0;
+
+abort:
+ if (_status) {
+ if (my_fn) free(my_fn);
+ }
+ return(_status);
+}
+
+static int stun_win32_address_disallowed(IP_ADAPTER_UNICAST_ADDRESS *addr)
+{
+ return (addr->DadState != NldsPreferred) &&
+ (addr->DadState != IpDadStatePreferred);
+}
+
+static int stun_win32_address_temp_v6(IP_ADAPTER_UNICAST_ADDRESS *addr)
+{
+ return (addr->Address.lpSockaddr->sa_family == AF_INET6) &&
+ (addr->SuffixOrigin == IpSuffixOriginRandom);
+}
+
+int
+stun_getaddrs_filtered(nr_local_addr addrs[], int maxaddrs, int *count)
+{
+ int r, _status;
+ PIP_ADAPTER_ADDRESSES AdapterAddresses = NULL, tmpAddress = NULL;
+ // recomended per https://msdn.microsoft.com/en-us/library/windows/desktop/aa365915(v=vs.85).aspx
+ static const ULONG initialBufLen = 15000;
+ ULONG buflen = initialBufLen;
+ char bin_hashed_ifname[NR_MD5_HASH_LENGTH];
+ char hex_hashed_ifname[MAXIFNAME];
+ int n = 0;
+
+ *count = 0;
+
+ if (maxaddrs <= 0)
+ ABORT(R_BAD_ARGS);
+
+ /* According to MSDN (see above) we have try GetAdapterAddresses() multiple times */
+ for (n = 0; n < 5; n++) {
+ AdapterAddresses = (PIP_ADAPTER_ADDRESSES) RMALLOC(buflen);
+ if (AdapterAddresses == NULL) {
+ r_log(NR_LOG_STUN, LOG_ERR, "Error allocating buf for GetAdaptersAddresses()");
+ ABORT(R_NO_MEMORY);
+ }
+
+ r = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER, NULL, AdapterAddresses, &buflen);
+ if (r == NO_ERROR) {
+ break;
+ }
+ r_log(NR_LOG_STUN, LOG_ERR, "GetAdaptersAddresses() returned error (%d)", r);
+ RFREE(AdapterAddresses);
+ AdapterAddresses = NULL;
+ }
+
+ if (n >= 5) {
+ r_log(NR_LOG_STUN, LOG_ERR, "5 failures calling GetAdaptersAddresses()");
+ ABORT(R_INTERNAL);
+ }
+
+ n = 0;
+
+ /* Loop through the adapters */
+
+ for (tmpAddress = AdapterAddresses; tmpAddress != NULL; tmpAddress = tmpAddress->Next) {
+
+ if (tmpAddress->OperStatus != IfOperStatusUp)
+ continue;
+
+ if ((tmpAddress->IfIndex != 0) || (tmpAddress->Ipv6IfIndex != 0)) {
+ IP_ADAPTER_UNICAST_ADDRESS *u = 0;
+
+ if(r=nr_crypto_md5((UCHAR *)tmpAddress->FriendlyName,
+ wcslen(tmpAddress->FriendlyName) * sizeof(wchar_t),
+ bin_hashed_ifname))
+ ABORT(r);
+ if(r=nr_bin2hex(bin_hashed_ifname, sizeof(bin_hashed_ifname),
+ hex_hashed_ifname))
+ ABORT(r);
+
+ for (u = tmpAddress->FirstUnicastAddress; u != 0; u = u->Next) {
+ SOCKET_ADDRESS *sa_addr = &u->Address;
+
+ if ((sa_addr->lpSockaddr->sa_family != AF_INET) &&
+ (sa_addr->lpSockaddr->sa_family != AF_INET6)) {
+ r_log(NR_LOG_STUN, LOG_DEBUG, "Unrecognized sa_family for address on adapter %lu", tmpAddress->IfIndex);
+ continue;
+ }
+
+ if (stun_win32_address_disallowed(u)) {
+ continue;
+ }
+
+ if ((r=nr_sockaddr_to_transport_addr((struct sockaddr*)sa_addr->lpSockaddr, IPPROTO_UDP, 0, &(addrs[n].addr)))) {
+ ABORT(r);
+ }
+
+ strlcpy(addrs[n].addr.ifname, hex_hashed_ifname, sizeof(addrs[n].addr.ifname));
+ if (tmpAddress->IfType == IF_TYPE_ETHERNET_CSMACD) {
+ addrs[n].interface.type = NR_INTERFACE_TYPE_WIRED;
+ } else if (tmpAddress->IfType == IF_TYPE_IEEE80211) {
+ /* Note: this only works for >= Win Vista */
+ addrs[n].interface.type = NR_INTERFACE_TYPE_WIFI;
+ } else {
+ addrs[n].interface.type = NR_INTERFACE_TYPE_UNKNOWN;
+ }
+#if (_WIN32_WINNT >= 0x0600)
+ /* Note: only >= Vista provide link speed information */
+ addrs[n].interface.estimated_speed = tmpAddress->TransmitLinkSpeed / 1000;
+#else
+ addrs[n].interface.estimated_speed = 0;
+#endif
+ if (stun_win32_address_temp_v6(u)) {
+ addrs[n].flags |= NR_ADDR_FLAG_TEMPORARY;
+ }
+
+ if (++n >= maxaddrs)
+ goto done;
+ }
+ }
+ }
+
+ done:
+ *count = n;
+ _status = 0;
+
+ abort:
+ RFREE(AdapterAddresses);
+ return _status;
+}
+
+#endif //WIN32
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs-win32.h b/dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs-win32.h
new file mode 100644
index 0000000000..a00802192a
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs-win32.h
@@ -0,0 +1,13 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef STUN_IFADDRS_WIN32_H_
+#define STUN_IFADDRS_WIN32_H_
+
+#include "local_addr.h"
+
+int stun_getaddrs_filtered(nr_local_addr addrs[], int maxaddrs, int *count);
+
+#endif /* STUN_IFADDRS_WIN32_H_ */
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs.c b/dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs.c
new file mode 100644
index 0000000000..362b7d828e
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs.c
@@ -0,0 +1,176 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+*/
+
+#include <csi_platform.h>
+#include <assert.h>
+#include <string.h>
+
+#ifdef WIN32
+#include "addrs-win32.h"
+#elif defined(BSD) || defined(DARWIN)
+#include "addrs-bsd.h"
+#else
+#include "addrs-netlink.h"
+#endif
+
+#include "stun.h"
+#include "addrs.h"
+#include "util.h"
+
+static int
+nr_stun_is_duplicate_addr(nr_local_addr addrs[], int count, nr_local_addr *addr)
+{
+ int i;
+ int different;
+
+ for (i = 0; i < count; ++i) {
+ different = nr_transport_addr_cmp(&addrs[i].addr, &(addr->addr),
+ NR_TRANSPORT_ADDR_CMP_MODE_ALL);
+ if (!different)
+ return 1; /* duplicate */
+ }
+
+ return 0;
+}
+
+int
+nr_stun_filter_addrs(nr_local_addr addrs[], int remove_loopback, int remove_link_local, int *count)
+{
+ int r, _status;
+ nr_local_addr *tmp = 0;
+ int i;
+ int n;
+ /* We prefer temp ipv6 for their privacy properties. If we cannot get
+ * that, we prefer ipv6 that are not based on mac address. */
+ int filter_mac_ipv6 = 0;
+ int filter_teredo_ipv6 = 0;
+ int filter_non_temp_ipv6 = 0;
+
+ tmp = RMALLOC(*count * sizeof(*tmp));
+ if (!tmp)
+ ABORT(R_NO_MEMORY);
+
+ for (i = 0; i < *count; ++i) {
+ if (addrs[i].addr.ip_version == NR_IPV6) {
+ if (nr_transport_addr_is_teredo(&addrs[i].addr)) {
+ addrs[i].interface.type |= NR_INTERFACE_TYPE_TEREDO;
+ /* Prefer teredo over mac-based address. Probably will never see
+ * both. */
+ filter_mac_ipv6 = 1;
+ } else {
+ filter_teredo_ipv6 = 1;
+ }
+
+ if (!nr_transport_addr_is_mac_based(&addrs[i].addr)) {
+ filter_mac_ipv6 = 1;
+ }
+
+ if (addrs[i].flags & NR_ADDR_FLAG_TEMPORARY) {
+ filter_non_temp_ipv6 = 1;
+ }
+ }
+ }
+
+ n = 0;
+ for (i = 0; i < *count; ++i) {
+ if (nr_stun_is_duplicate_addr(tmp, n, &addrs[i])) {
+ /* skip addrs[i], it's a duplicate */
+ }
+ else if (remove_loopback && nr_transport_addr_is_loopback(&addrs[i].addr)) {
+ /* skip addrs[i], it's a loopback */
+ }
+ else if (remove_link_local &&
+ nr_transport_addr_is_link_local(&addrs[i].addr)) {
+ /* skip addrs[i], it's a link-local address */
+ }
+ else if (filter_mac_ipv6 &&
+ nr_transport_addr_is_mac_based(&addrs[i].addr)) {
+ /* skip addrs[i], it's MAC based */
+ }
+ else if (filter_teredo_ipv6 &&
+ nr_transport_addr_is_teredo(&addrs[i].addr)) {
+ /* skip addrs[i], it's a Teredo address */
+ }
+ else if (filter_non_temp_ipv6 &&
+ (addrs[i].addr.ip_version == NR_IPV6) &&
+ !(addrs[i].flags & NR_ADDR_FLAG_TEMPORARY)) {
+ /* skip addrs[i], it's a non-temporary ipv6, and we have a temporary */
+ }
+ else {
+ /* otherwise, copy it to the temporary array */
+ if ((r=nr_local_addr_copy(&tmp[n], &addrs[i])))
+ ABORT(r);
+ ++n;
+ }
+ }
+
+ *count = n;
+
+ memset(addrs, 0, *count * sizeof(*addrs));
+ /* copy temporary array into passed in/out array */
+ for (i = 0; i < *count; ++i) {
+ if ((r=nr_local_addr_copy(&addrs[i], &tmp[i])))
+ ABORT(r);
+ }
+
+ _status = 0;
+ abort:
+ RFREE(tmp);
+ return _status;
+}
+
+#ifndef USE_PLATFORM_NR_STUN_GET_ADDRS
+
+int
+nr_stun_get_addrs(nr_local_addr addrs[], int maxaddrs, int *count)
+{
+ int _status=0;
+ int i;
+ char typestr[100];
+
+ // Ensure output records are always fully defined. See bug 1589990.
+ if (maxaddrs > 0) {
+ memset(addrs, 0, maxaddrs * sizeof(nr_local_addr));
+ }
+
+ _status = stun_getaddrs_filtered(addrs, maxaddrs, count);
+
+ for (i = 0; i < *count; ++i) {
+ nr_local_addr_fmt_info_string(addrs+i,typestr,sizeof(typestr));
+ r_log(NR_LOG_STUN, LOG_DEBUG, "Address %d: %s on %s, type: %s\n",
+ i,addrs[i].addr.as_string,addrs[i].addr.ifname,typestr);
+ }
+
+ return _status;
+}
+
+#endif
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs.h b/dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs.h
new file mode 100644
index 0000000000..522bd92fd5
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs.h
@@ -0,0 +1,43 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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 _addrs_h_
+#define _addrs_h_
+
+#include "transport_addr.h"
+#include "local_addr.h"
+
+int nr_stun_get_addrs(nr_local_addr addrs[], int maxaddrs, int *count);
+int nr_stun_filter_addrs(nr_local_addr addrs[], int remove_loopback, int remove_link_local, int *count);
+
+#endif
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/stun/nr_socket_buffered_stun.c b/dom/media/webrtc/transport/third_party/nICEr/src/stun/nr_socket_buffered_stun.c
new file mode 100644
index 0000000000..4b5b92fb75
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/stun/nr_socket_buffered_stun.c
@@ -0,0 +1,656 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+Copyright (c) 2013, Mozilla
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+*/
+
+#include <nr_api.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <assert.h>
+#include <inttypes.h>
+
+#include "p_buf.h"
+#include "nr_socket.h"
+#include "stun.h"
+#include "nr_socket_buffered_stun.h"
+
+#define NR_MAX_FRAME_SIZE 0xFFFF
+
+typedef struct nr_frame_header_ {
+ UINT2 frame_length;
+ char data[0];
+} nr_frame_header;
+
+typedef struct nr_socket_buffered_stun_ {
+ nr_socket *inner;
+ nr_transport_addr remote_addr;
+ int connected;
+
+ /* Read state */
+ int read_state;
+#define NR_ICE_SOCKET_READ_NONE 0
+#define NR_ICE_SOCKET_READ_HDR 1
+#define NR_ICE_SOCKET_READ_FAILED 2
+ UCHAR *buffer;
+ size_t buffer_size;
+ size_t bytes_needed;
+ size_t bytes_read;
+ NR_async_cb readable_cb;
+ void *readable_cb_arg;
+
+ /* Write state */
+ nr_p_buf_ctx *p_bufs;
+ nr_p_buf_head pending_writes;
+ size_t pending;
+ size_t max_pending;
+ nr_framing_type framing_type;
+} nr_socket_buffered_stun;
+
+static int nr_socket_buffered_stun_destroy(void **objp);
+static int nr_socket_buffered_stun_sendto(void *obj,const void *msg, size_t len,
+ int flags, const nr_transport_addr *to);
+static int nr_socket_buffered_stun_recvfrom(void *obj,void * restrict buf,
+ size_t maxlen, size_t *len, int flags, nr_transport_addr *from);
+static int nr_socket_buffered_stun_getfd(void *obj, NR_SOCKET *fd);
+static int nr_socket_buffered_stun_getaddr(void *obj, nr_transport_addr *addrp);
+static int nr_socket_buffered_stun_close(void *obj);
+static int nr_socket_buffered_stun_connect(void *sock, const nr_transport_addr *addr);
+static int nr_socket_buffered_stun_write(void *obj,const void *msg, size_t len, size_t *written);
+static void nr_socket_buffered_stun_writable_cb(NR_SOCKET s, int how, void *arg);
+static int nr_socket_buffered_stun_listen(void *obj, int backlog);
+static int nr_socket_buffered_stun_accept(void *obj, nr_transport_addr *addrp, nr_socket **sockp);
+
+static nr_socket_vtbl nr_socket_buffered_stun_vtbl={
+ 2,
+ nr_socket_buffered_stun_destroy,
+ nr_socket_buffered_stun_sendto,
+ nr_socket_buffered_stun_recvfrom,
+ nr_socket_buffered_stun_getfd,
+ nr_socket_buffered_stun_getaddr,
+ nr_socket_buffered_stun_connect,
+ 0,
+ 0,
+ nr_socket_buffered_stun_close,
+ nr_socket_buffered_stun_listen,
+ nr_socket_buffered_stun_accept
+};
+
+void nr_socket_buffered_stun_set_readable_cb(nr_socket *sock,
+ NR_async_cb readable_cb, void *readable_cb_arg)
+{
+ nr_socket_buffered_stun *buf_sock = (nr_socket_buffered_stun *)sock->obj;
+
+ buf_sock->readable_cb = readable_cb;
+ buf_sock->readable_cb_arg = readable_cb_arg;
+}
+
+int nr_socket_buffered_set_connected_to(nr_socket *sock, nr_transport_addr *remote_addr)
+{
+ nr_socket_buffered_stun *buf_sock = (nr_socket_buffered_stun *)sock->obj;
+ int r, _status;
+
+ if ((r=nr_transport_addr_copy(&buf_sock->remote_addr, remote_addr)))
+ ABORT(r);
+
+ buf_sock->connected = 1;
+
+ _status=0;
+abort:
+ return(_status);
+}
+
+int nr_socket_buffered_stun_create(nr_socket *inner, int max_pending,
+ nr_framing_type framing_type, nr_socket **sockp)
+{
+ int r, _status;
+ nr_socket_buffered_stun *sock = 0;
+ size_t frame_size;
+
+ if (!(sock = RCALLOC(sizeof(nr_socket_buffered_stun))))
+ ABORT(R_NO_MEMORY);
+
+ sock->inner = inner;
+ sock->framing_type = framing_type;
+
+ if ((r=nr_ip4_port_to_transport_addr(INADDR_ANY, 0, IPPROTO_TCP, &sock->remote_addr)))
+ ABORT(r);
+
+ switch (framing_type) {
+ case ICE_TCP_FRAMING:
+ frame_size = sizeof(nr_frame_header);
+ sock->buffer_size = sizeof(nr_frame_header) + NR_MAX_FRAME_SIZE;
+ sock->bytes_needed = sizeof(nr_frame_header);
+ break;
+ case TURN_TCP_FRAMING:
+ frame_size = 0;
+ sock->buffer_size = NR_STUN_MAX_MESSAGE_SIZE;
+ sock->bytes_needed = sizeof(nr_stun_message_header);
+ break;
+ default:
+ assert(0);
+ ABORT(R_BAD_ARGS);
+ }
+
+ /* TODO(ekr@rtfm.com): Check this */
+ if (!(sock->buffer = RMALLOC(sock->buffer_size)))
+ ABORT(R_NO_MEMORY);
+
+ sock->read_state = NR_ICE_SOCKET_READ_NONE;
+ sock->connected = 0;
+
+ STAILQ_INIT(&sock->pending_writes);
+ if ((r=nr_p_buf_ctx_create(sock->buffer_size, &sock->p_bufs)))
+ ABORT(r);
+ sock->max_pending = max_pending + frame_size;
+
+ if ((r=nr_socket_create_int(sock, &nr_socket_buffered_stun_vtbl, sockp)))
+ ABORT(r);
+
+ _status=0;
+abort:
+ if (_status && sock) {
+ void *sock_v = sock;
+ sock->inner = 0; /* Give up ownership so we don't destroy */
+ nr_socket_buffered_stun_destroy(&sock_v);
+ }
+ return(_status);
+}
+
+/* Note: This destroys the inner socket */
+int nr_socket_buffered_stun_destroy(void **objp)
+{
+ nr_socket_buffered_stun *sock;
+ NR_SOCKET fd;
+
+ if (!objp || !*objp)
+ return 0;
+
+ sock = (nr_socket_buffered_stun *)*objp;
+ *objp = 0;
+
+ /* Free the buffer if needed */
+ RFREE(sock->buffer);
+
+ /* Cancel waiting on the socket */
+ if (sock->inner && !nr_socket_getfd(sock->inner, &fd)) {
+ NR_ASYNC_CANCEL(fd, NR_ASYNC_WAIT_WRITE);
+ NR_ASYNC_CANCEL(fd, NR_ASYNC_WAIT_READ);
+ }
+
+ nr_p_buf_free_chain(sock->p_bufs, &sock->pending_writes);
+ nr_p_buf_ctx_destroy(&sock->p_bufs);
+ nr_socket_destroy(&sock->inner);
+ RFREE(sock);
+
+ return 0;
+}
+
+static int nr_socket_buffered_stun_sendto(void *obj,const void *msg, size_t len,
+ int flags, const nr_transport_addr *to)
+{
+ nr_socket_buffered_stun *sock = (nr_socket_buffered_stun *)obj;
+ int r, _status;
+ size_t written;
+ nr_frame_header *frame = NULL;
+
+ /* Check that we are writing to the connected address if
+ connected */
+ if (!nr_transport_addr_is_wildcard(&sock->remote_addr)) {
+ if (nr_transport_addr_cmp(&sock->remote_addr, to, NR_TRANSPORT_ADDR_CMP_MODE_ALL)) {
+ r_log(LOG_GENERIC, LOG_ERR, "Sendto on connected socket doesn't match");
+ ABORT(R_BAD_DATA);
+ }
+ }
+
+ if (sock->framing_type == ICE_TCP_FRAMING) {
+
+ assert(len <= NR_MAX_FRAME_SIZE);
+ if (len > NR_MAX_FRAME_SIZE)
+ ABORT(R_FAILED);
+
+ if (!(frame = RMALLOC(len + sizeof(nr_frame_header))))
+ ABORT(R_NO_MEMORY);
+
+ frame->frame_length = htons(len);
+ memcpy(frame->data, msg, len);
+ len += sizeof(nr_frame_header);
+ msg = frame;
+ }
+
+ if ((r=nr_socket_buffered_stun_write(obj, msg, len, &written)))
+ ABORT(r);
+
+ if (len != written)
+ ABORT(R_IO_ERROR);
+
+ _status=0;
+abort:
+ RFREE(frame);
+ return _status;
+}
+
+static void nr_socket_buffered_stun_failed(nr_socket_buffered_stun *sock)
+ {
+ NR_SOCKET fd;
+
+ sock->read_state = NR_ICE_SOCKET_READ_FAILED;
+
+ /* Cancel waiting on the socket */
+ if (sock->inner && !nr_socket_getfd(sock->inner, &fd)) {
+ NR_ASYNC_CANCEL(fd, NR_ASYNC_WAIT_WRITE);
+ NR_ASYNC_CANCEL(fd, NR_ASYNC_WAIT_READ);
+ }
+ }
+
+static int nr_socket_buffered_stun_recvfrom(void *obj,void * restrict buf,
+ size_t maxlen, size_t *len, int flags, nr_transport_addr *from)
+{
+ int r, _status;
+ size_t bytes_read;
+ nr_socket_buffered_stun *sock = (nr_socket_buffered_stun *)obj;
+ nr_frame_header *frame = (nr_frame_header *)sock->buffer;
+ size_t skip_hdr_size = (sock->framing_type == ICE_TCP_FRAMING) ? sizeof(nr_frame_header) : 0;
+
+ if (sock->read_state == NR_ICE_SOCKET_READ_FAILED) {
+ ABORT(R_FAILED);
+ }
+
+ while (sock->bytes_needed) {
+ /* Read all the expected bytes */
+ assert(sock->bytes_needed <= sock->buffer_size - sock->bytes_read);
+
+ if(r=nr_socket_read(sock->inner,
+ sock->buffer + sock->bytes_read,
+ sock->bytes_needed, &bytes_read, 0))
+ ABORT(r);
+
+ assert(bytes_read <= sock->bytes_needed);
+ sock->bytes_needed -= bytes_read;
+ sock->bytes_read += bytes_read;
+
+ /* Unfinished */
+ if (sock->bytes_needed)
+ ABORT(R_WOULDBLOCK);
+
+ /* No more bytes expected */
+ if (sock->read_state == NR_ICE_SOCKET_READ_NONE) {
+ size_t remaining_length;
+ if (sock->framing_type == ICE_TCP_FRAMING) {
+ if (sock->bytes_read < sizeof(nr_frame_header))
+ ABORT(R_BAD_DATA);
+ remaining_length = ntohs(frame->frame_length);
+ } else {
+ int tmp_length;
+
+ /* Parse the header */
+ if (r = nr_stun_message_length(sock->buffer, sock->bytes_read, &tmp_length))
+ ABORT(r);
+ assert(tmp_length >= 0);
+ if (tmp_length < 0)
+ ABORT(R_BAD_DATA);
+ remaining_length = tmp_length;
+
+ }
+ /* Check to see if we have enough room */
+ if ((sock->buffer_size - sock->bytes_read) < remaining_length)
+ ABORT(R_BAD_DATA);
+
+ sock->read_state = NR_ICE_SOCKET_READ_HDR;
+ /* Set ourselves up to read the rest of the data */
+ sock->bytes_needed = remaining_length;
+ }
+ }
+
+ assert(skip_hdr_size <= sock->bytes_read);
+ if (skip_hdr_size > sock->bytes_read)
+ ABORT(R_BAD_DATA);
+ sock->bytes_read -= skip_hdr_size;
+
+ if (maxlen < sock->bytes_read)
+ ABORT(R_BAD_ARGS);
+
+ *len = sock->bytes_read;
+ memcpy(buf, sock->buffer + skip_hdr_size, sock->bytes_read);
+
+ sock->bytes_read = 0;
+ sock->read_state = NR_ICE_SOCKET_READ_NONE;
+ sock->bytes_needed = (sock->framing_type == ICE_TCP_FRAMING) ? sizeof(nr_frame_header) : sizeof(nr_stun_message_header);
+
+ if ((r = nr_transport_addr_copy(from, &sock->remote_addr))) ABORT(r);
+
+ _status=0;
+abort:
+ if (_status && (_status != R_WOULDBLOCK)) {
+ nr_socket_buffered_stun_failed(sock);
+ }
+
+ return(_status);
+}
+
+static int nr_socket_buffered_stun_getfd(void *obj, NR_SOCKET *fd)
+{
+ nr_socket_buffered_stun *sock = (nr_socket_buffered_stun *)obj;
+
+ return nr_socket_getfd(sock->inner, fd);
+}
+
+static int nr_socket_buffered_stun_getaddr(void *obj, nr_transport_addr *addrp)
+{
+ nr_socket_buffered_stun *sock = (nr_socket_buffered_stun *)obj;
+
+ return nr_socket_getaddr(sock->inner, addrp);
+}
+
+static int nr_socket_buffered_stun_close(void *obj)
+{
+ nr_socket_buffered_stun *sock = (nr_socket_buffered_stun *)obj;
+ NR_SOCKET fd;
+
+ /* Cancel waiting on the socket */
+ if (sock->inner && !nr_socket_getfd(sock->inner, &fd)) {
+ NR_ASYNC_CANCEL(fd, NR_ASYNC_WAIT_WRITE);
+ }
+
+ return nr_socket_close(sock->inner);
+}
+
+static int nr_socket_buffered_stun_listen(void *obj, int backlog)
+{
+ int r, _status;
+ nr_socket_buffered_stun *sock = (nr_socket_buffered_stun *)obj;
+
+ if (!sock->inner)
+ ABORT(R_FAILED);
+
+ if ((r=nr_socket_listen(sock->inner, backlog)))
+ ABORT(r);
+
+ _status=0;
+abort:
+ return(_status);
+}
+
+
+static int nr_socket_buffered_stun_accept(void *obj, nr_transport_addr *addrp, nr_socket **sockp)
+{
+ nr_socket_buffered_stun *bsock = (nr_socket_buffered_stun *)obj;
+
+ return nr_socket_accept(bsock->inner, addrp, sockp);
+}
+
+static void nr_socket_buffered_stun_connected_cb(NR_SOCKET s, int how, void *arg)
+{
+ nr_socket_buffered_stun *sock = (nr_socket_buffered_stun *)arg;
+ int r, _status;
+ NR_SOCKET fd;
+
+ assert(!sock->connected);
+
+ sock->connected = 1;
+
+ if ((r=nr_socket_getfd(sock->inner, &fd)))
+ ABORT(r);
+ NR_ASYNC_CANCEL(fd, NR_ASYNC_WAIT_WRITE);
+
+ // once connected arm for read
+ if (sock->readable_cb) {
+ NR_ASYNC_WAIT(fd, NR_ASYNC_WAIT_READ, sock->readable_cb, sock->readable_cb_arg);
+ }
+
+ if (sock->pending) {
+ r_log(LOG_GENERIC, LOG_INFO, "Invoking writable_cb on connected (%u)", (uint32_t) sock->pending);
+ nr_socket_buffered_stun_writable_cb(s, how, arg);
+ }
+
+ _status=0;
+abort:
+ if (_status) {
+ r_log(LOG_GENERIC, LOG_ERR, "Failure in nr_socket_buffered_stun_connected_cb: %d", _status);
+
+ }
+}
+
+static int nr_socket_buffered_stun_connect(void *obj, const nr_transport_addr *addr)
+{
+ nr_socket_buffered_stun *sock = (nr_socket_buffered_stun *)obj;
+ int r, _status;
+
+ if ((r=nr_transport_addr_copy(&sock->remote_addr, addr)))
+ ABORT(r);
+
+ if ((r=nr_socket_connect(sock->inner, addr))) {
+ if (r == R_WOULDBLOCK) {
+ NR_SOCKET fd;
+
+ if ((r=nr_socket_getfd(sock->inner, &fd)))
+ ABORT(r);
+
+ NR_ASYNC_WAIT(fd, NR_ASYNC_WAIT_WRITE, nr_socket_buffered_stun_connected_cb, sock);
+ ABORT(R_WOULDBLOCK);
+ }
+ ABORT(r);
+ } else {
+ r_log(LOG_GENERIC, LOG_INFO, "Connected without blocking");
+ sock->connected = 1;
+ }
+
+ _status=0;
+abort:
+ return(_status);
+}
+
+static int nr_socket_buffered_stun_arm_writable_cb(nr_socket_buffered_stun *sock)
+{
+ int r, _status;
+ NR_SOCKET fd;
+
+ if ((r=nr_socket_getfd(sock->inner, &fd)))
+ ABORT(r);
+
+ NR_ASYNC_WAIT(fd, NR_ASYNC_WAIT_WRITE, nr_socket_buffered_stun_writable_cb, sock);
+
+ _status=0;
+abort:
+ return(_status);
+}
+
+static int nr_socket_buffered_stun_write(void *obj,const void *msg, size_t len, size_t *written)
+{
+ nr_socket_buffered_stun *sock = (nr_socket_buffered_stun *)obj;
+ int already_armed = 0;
+ int r,_status;
+ size_t written2 = 0;
+ size_t original_len = len;
+
+ /* Buffers are close to full, report error. Do this now so we never
+ get partial writes */
+ if ((sock->pending + len) > sock->max_pending) {
+ r_log(LOG_GENERIC, LOG_INFO, "Write buffer for %s full (%u + %u > %u) - re-arming @%p",
+ sock->remote_addr.as_string, (uint32_t)sock->pending, (uint32_t)len, (uint32_t)sock->max_pending,
+ &(sock->pending));
+ ABORT(R_WOULDBLOCK);
+ }
+
+
+ if (sock->connected && !sock->pending) {
+ r = nr_socket_write(sock->inner, msg, len, &written2, 0);
+ if (r) {
+ if (r != R_WOULDBLOCK) {
+ r_log(LOG_GENERIC, LOG_ERR, "Write error for %s - %d",
+ sock->remote_addr.as_string, r);
+ ABORT(r);
+ }
+ r_log(LOG_GENERIC, LOG_INFO, "Write of %" PRIu64 " blocked for %s",
+ (uint64_t) len, sock->remote_addr.as_string);
+
+ written2=0;
+ }
+ } else {
+ already_armed = 1;
+ }
+
+ /* Buffer what's left */
+ len -= written2;
+
+ if (len) {
+ if ((r=nr_p_buf_write_to_chain(sock->p_bufs, &sock->pending_writes,
+ ((UCHAR *)msg) + written2, len))) {
+ r_log(LOG_GENERIC, LOG_ERR, "Write_to_chain error for %s - %d",
+ sock->remote_addr.as_string, r);
+
+ ABORT(r);
+ }
+
+ sock->pending += len;
+ }
+
+ if (sock->pending) {
+ if (!already_armed) {
+ if ((r=nr_socket_buffered_stun_arm_writable_cb(sock)))
+ ABORT(r);
+ }
+ r_log(LOG_GENERIC, LOG_INFO, "Write buffer not empty for %s %u - %s armed (@%p),%s connected",
+ sock->remote_addr.as_string, (uint32_t)sock->pending,
+ already_armed ? "already" : "", &sock->pending,
+ sock->connected ? "" : " not");
+ }
+
+ *written = original_len;
+
+ _status=0;
+abort:
+ return _status;
+}
+
+static void nr_socket_buffered_stun_writable_cb(NR_SOCKET s, int how, void *arg)
+{
+ nr_socket_buffered_stun *sock = (nr_socket_buffered_stun *)arg;
+ int r,_status;
+ nr_p_buf *n1, *n2;
+
+ if (sock->read_state == NR_ICE_SOCKET_READ_FAILED) {
+ ABORT(R_FAILED);
+ }
+
+ /* Try to flush */
+ STAILQ_FOREACH_SAFE(n1, &sock->pending_writes, entry, n2) {
+ size_t written = 0;
+
+ if ((r=nr_socket_write(sock->inner, n1->data + n1->r_offset,
+ n1->length - n1->r_offset,
+ &written, 0))) {
+
+ r_log(LOG_GENERIC, LOG_ERR, "Write error for %s - %d",
+ sock->remote_addr.as_string, r);
+ ABORT(r);
+ }
+
+ n1->r_offset += written;
+ assert(sock->pending >= written);
+ sock->pending -= written;
+
+ if (n1->r_offset < n1->length) {
+ /* We wrote something, but not everything */
+ r_log(LOG_GENERIC, LOG_INFO, "Write in callback didn't write all (remaining %u of %u) for %s",
+ n1->length - n1->r_offset, n1->length,
+ sock->remote_addr.as_string);
+ ABORT(R_WOULDBLOCK);
+ }
+
+ /* We are done with this p_buf */
+ STAILQ_REMOVE_HEAD(&sock->pending_writes, entry);
+ nr_p_buf_free(sock->p_bufs, n1);
+ }
+
+ assert(!sock->pending);
+ _status=0;
+abort:
+ r_log(LOG_GENERIC, LOG_INFO, "Writable_cb %s (%u (%p) pending)",
+ sock->remote_addr.as_string, (uint32_t)sock->pending, &(sock->pending));
+ if (_status && _status != R_WOULDBLOCK) {
+ r_log(LOG_GENERIC, LOG_ERR, "Failure in writable_cb: %d", _status);
+ nr_socket_buffered_stun_failed(sock);
+ /* Report this failure up; the only way to do this is a readable callback.
+ * Once the user tries to read (using nr_socket_buffered_stun_recvfrom), it
+ * will notice that there has been a failure. */
+ if (sock->readable_cb) {
+ sock->readable_cb(s, NR_ASYNC_WAIT_READ, sock->readable_cb_arg);
+ }
+ } else if (sock->pending) {
+ nr_socket_buffered_stun_arm_writable_cb(sock);
+ }
+}
+
+int nr_socket_buffered_stun_reset(nr_socket* sock_arg, nr_socket* new_inner) {
+ int r, _status;
+ NR_SOCKET fd;
+
+ nr_socket_buffered_stun* sock = (nr_socket_buffered_stun*)sock_arg->obj;
+
+ if (sock->inner && !nr_socket_getfd(sock->inner, &fd)) {
+ r_log(LOG_GENERIC, LOG_DEBUG, "In %s, canceling wait on old socket", __FUNCTION__);
+ NR_ASYNC_CANCEL(fd, NR_ASYNC_WAIT_WRITE);
+ NR_ASYNC_CANCEL(fd, NR_ASYNC_WAIT_READ);
+ }
+
+ nr_socket_destroy(&sock->inner);
+ sock->inner = new_inner;
+
+ sock->read_state = NR_ICE_SOCKET_READ_NONE;
+ sock->connected = 0;
+
+ sock->bytes_read = 0;
+ sock->bytes_needed = (sock->framing_type == ICE_TCP_FRAMING)
+ ? sizeof(nr_frame_header)
+ : sizeof(nr_stun_message_header);
+ sock->pending = 0;
+
+ nr_p_buf_free_chain(sock->p_bufs, &sock->pending_writes);
+ nr_p_buf_ctx_destroy(&sock->p_bufs);
+
+ STAILQ_INIT(&sock->pending_writes);
+
+ if ((r = nr_p_buf_ctx_create(sock->buffer_size, &sock->p_bufs))) {
+ ABORT(r);
+ }
+
+ if ((r = nr_ip4_port_to_transport_addr(INADDR_ANY, 0, IPPROTO_TCP,
+ &sock->remote_addr))) {
+ ABORT(r);
+ }
+
+ _status = 0;
+abort:
+ return (_status);
+}
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/stun/nr_socket_buffered_stun.h b/dom/media/webrtc/transport/third_party/nICEr/src/stun/nr_socket_buffered_stun.h
new file mode 100644
index 0000000000..c635ee393d
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/stun/nr_socket_buffered_stun.h
@@ -0,0 +1,66 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+Copyright (c) 2013, Mozilla
+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 Adobe Systems, Network Resonance 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
+OWNER 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 _nr_socket_buffered_stun_h
+#define _nr_socket_buffered_stun_h
+
+#include "nr_socket.h"
+
+/* Wrapper socket which provides buffered STUN-oriented I/O
+
+ 1. Writes don't block and are automatically flushed when needed.
+ 2. All reads are in units of STUN messages
+
+ This socket takes ownership of the inner socket |sock|.
+ */
+
+typedef enum {
+ TURN_TCP_FRAMING=0,
+ ICE_TCP_FRAMING
+} nr_framing_type;
+
+void nr_socket_buffered_stun_set_readable_cb(nr_socket *sock,
+ NR_async_cb readable_cb, void *readable_cb_arg);
+
+int nr_socket_buffered_stun_create(nr_socket *inner, int max_pending,
+ nr_framing_type framing_type, nr_socket **sockp);
+
+int nr_socket_buffered_set_connected_to(nr_socket *sock,
+ nr_transport_addr *remote_addr);
+
+int nr_socket_buffered_stun_reset(nr_socket *sock, nr_socket *new_inner);
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/stun/nr_socket_turn.c b/dom/media/webrtc/transport/third_party/nICEr/src/stun/nr_socket_turn.c
new file mode 100644
index 0000000000..1a0162b13e
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/stun/nr_socket_turn.c
@@ -0,0 +1,195 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+*/
+
+#ifdef USE_TURN
+
+#include <csi_platform.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <assert.h>
+
+#include "stun.h"
+#include "turn_client_ctx.h"
+#include "nr_socket_turn.h"
+
+
+static char *nr_socket_turn_magic_cookie = "nr_socket_turn";
+
+typedef struct nr_socket_turn_ {
+ char *magic_cookie;
+ nr_turn_client_ctx *turn;
+} nr_socket_turn;
+
+
+static int nr_socket_turn_destroy(void **objp);
+static int nr_socket_turn_sendto(void *obj,const void *msg, size_t len,
+ int flags, const nr_transport_addr *to);
+static int nr_socket_turn_recvfrom(void *obj,void * restrict buf,
+ size_t maxlen, size_t *len, int flags, nr_transport_addr *from);
+static int nr_socket_turn_getfd(void *obj, NR_SOCKET *fd);
+static int nr_socket_turn_getaddr(void *obj, nr_transport_addr *addrp);
+static int nr_socket_turn_close(void *obj);
+
+static nr_socket_vtbl nr_socket_turn_vtbl={
+ 2,
+ nr_socket_turn_destroy,
+ nr_socket_turn_sendto,
+ nr_socket_turn_recvfrom,
+ nr_socket_turn_getfd,
+ nr_socket_turn_getaddr,
+ 0,
+ 0,
+ 0,
+ nr_socket_turn_close,
+ 0,
+ 0
+};
+
+int nr_socket_turn_create(nr_socket **sockp)
+ {
+ int r,_status;
+ nr_socket_turn *sturn=0;
+
+ if(!(sturn=RCALLOC(sizeof(nr_socket_turn))))
+ ABORT(R_NO_MEMORY);
+
+ sturn->magic_cookie = nr_socket_turn_magic_cookie;
+
+ if(r=nr_socket_create_int(sturn, &nr_socket_turn_vtbl, sockp))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ if(_status){
+ nr_socket_turn_destroy((void **)&sturn);
+ }
+ return(_status);
+ }
+
+static int nr_socket_turn_destroy(void **objp)
+ {
+ int _status;
+ nr_socket_turn *sturn;
+
+ if(!objp || !*objp)
+ return(0);
+
+ sturn=*objp;
+ *objp=0;
+
+ assert(sturn->magic_cookie == nr_socket_turn_magic_cookie);
+
+ /* we don't own the socket, so don't destroy it */
+
+ RFREE(sturn);
+
+ _status=0;
+ return(_status);
+ }
+
+static int nr_socket_turn_sendto(void *obj,const void *msg, size_t len,
+ int flags, const nr_transport_addr *addr)
+ {
+ int r,_status;
+ nr_socket_turn *sturn=obj;
+
+ assert(sturn->magic_cookie == nr_socket_turn_magic_cookie);
+ assert(sturn->turn);
+
+ if ((r = nr_turn_client_send_indication(sturn->turn, msg, len, flags,
+ addr)))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+static int nr_socket_turn_recvfrom(void *obj,void * restrict buf,
+ size_t maxlen, size_t *len, int flags, nr_transport_addr *addr)
+ {
+ /* Reading from TURN sockets is done by the indication
+ processing code in turn_client_ctx. */
+ assert(0);
+
+ return(R_INTERNAL);
+ }
+
+static int nr_socket_turn_getfd(void *obj, NR_SOCKET *fd)
+ {
+ /* You should never directly be touching this fd. */
+ assert(0);
+
+ return(R_INTERNAL);
+ }
+
+static int nr_socket_turn_getaddr(void *obj, nr_transport_addr *addrp)
+ {
+ nr_socket_turn *sturn=obj;
+ int r, _status;
+
+ assert(sturn->magic_cookie == nr_socket_turn_magic_cookie);
+ assert(sturn->turn);
+
+ /* This returns the relayed address */
+ if ((r=nr_turn_client_get_relayed_address(sturn->turn, addrp)))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+static int nr_socket_turn_close(void *obj)
+ {
+ /* No-op */
+#ifndef NDEBUG
+ nr_socket_turn *sturn=obj;
+ assert(sturn->magic_cookie == nr_socket_turn_magic_cookie);
+#endif
+
+ return 0;
+ }
+
+int nr_socket_turn_set_ctx(nr_socket *sock, nr_turn_client_ctx *ctx)
+{
+ nr_socket_turn *sturn=(nr_socket_turn*)sock->obj;
+ assert(sturn->magic_cookie == nr_socket_turn_magic_cookie);
+ assert(!sturn->turn);
+
+ sturn->turn = ctx;
+
+ return 0;
+}
+
+#endif /* USE_TURN */
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/stun/nr_socket_turn.h b/dom/media/webrtc/transport/third_party/nICEr/src/stun/nr_socket_turn.h
new file mode 100644
index 0000000000..13506045a8
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/stun/nr_socket_turn.h
@@ -0,0 +1,48 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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 _nr_socket_turn_h
+#define _nr_socket_turn_h
+
+#include "nr_socket.h"
+
+/* This is a partial implementation of an nr_socket wrapped
+ around TURN. It implements only the nr_socket features
+ actually used by the ICE stack. You can't, for instance,
+ read off the socket */
+int nr_socket_turn_create(nr_socket **sockp);
+int nr_socket_turn_set_ctx(nr_socket *sock, nr_turn_client_ctx *ctx);
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun.h b/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun.h
new file mode 100644
index 0000000000..a32751d795
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun.h
@@ -0,0 +1,218 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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 _STUN_H
+#define _STUN_H
+
+#ifdef WIN32
+#include <winsock2.h>
+#else
+#include <sys/param.h>
+#include <sys/socket.h>
+#ifndef LINUX
+#include <net/if.h>
+#ifdef DARWIN
+#include <net/if_var.h>
+#endif
+#include <net/if_dl.h>
+#include <net/if_types.h>
+#else
+#include <linux/if.h>
+#endif
+#ifndef BSD
+#include <net/route.h>
+#endif
+#include <netinet/in.h>
+#ifndef LINUX
+#include <netinet/in_var.h>
+#endif
+#include <arpa/inet.h>
+#include <netdb.h>
+#endif
+#include <time.h>
+
+#include "nr_api.h"
+#include "stun_msg.h"
+#include "stun_build.h"
+#include "stun_codec.h"
+#include "stun_hint.h"
+#include "stun_util.h"
+#include "nr_socket.h"
+#include "stun_client_ctx.h"
+#include "stun_server_ctx.h"
+#include "stun_proc.h"
+
+#define NR_STUN_VERSION "rfc3489bis-11"
+#define NR_STUN_PORT 3478
+
+/* STUN attributes */
+#define NR_STUN_ATTR_MAPPED_ADDRESS 0x0001
+#define NR_STUN_ATTR_USERNAME 0x0006
+#define NR_STUN_ATTR_MESSAGE_INTEGRITY 0x0008
+#define NR_STUN_ATTR_ERROR_CODE 0x0009
+#define NR_STUN_ATTR_UNKNOWN_ATTRIBUTES 0x000A
+#define NR_STUN_ATTR_REALM 0x0014
+#define NR_STUN_ATTR_NONCE 0x0015
+#define NR_STUN_ATTR_XOR_MAPPED_ADDRESS 0x0020
+#define NR_STUN_ATTR_SERVER 0x8022
+#define NR_STUN_ATTR_ALTERNATE_SERVER 0x8023
+#define NR_STUN_ATTR_FINGERPRINT 0x8028
+
+/* for backwards compatibility with obsolete versions of the STUN spec */
+#define NR_STUN_ATTR_OLD_XOR_MAPPED_ADDRESS 0x8020
+
+#ifdef USE_STUND_0_96
+#define NR_STUN_ATTR_OLD_CHANGE_REQUEST 0x0003
+#endif /* USE_STUND_0_96 */
+
+#ifdef USE_RFC_3489_BACKWARDS_COMPATIBLE
+/* for backwards compatibility with obsolete versions of the STUN spec */
+#define NR_STUN_ATTR_OLD_PASSWORD 0x0007
+#define NR_STUN_ATTR_OLD_RESPONSE_ADDRESS 0x0002
+#define NR_STUN_ATTR_OLD_SOURCE_ADDRESS 0x0004
+#define NR_STUN_ATTR_OLD_CHANGED_ADDRESS 0x0005
+#endif /* USE_RFC_3489_BACKWARDS_COMPATIBLE */
+
+#ifdef USE_ICE
+/* ICE attributes */
+#define NR_STUN_ATTR_PRIORITY 0x0024
+#define NR_STUN_ATTR_USE_CANDIDATE 0x0025
+#define NR_STUN_ATTR_ICE_CONTROLLED 0x8029
+#define NR_STUN_ATTR_ICE_CONTROLLING 0x802A
+#endif /* USE_ICE */
+
+#ifdef USE_TURN
+/* TURN attributes */
+#define NR_STUN_ATTR_LIFETIME 0x000d
+/* from an expired draft defined as optional, but in the required range */
+#define NR_STUN_ATTR_BANDWIDTH 0x0010
+#define NR_STUN_ATTR_XOR_PEER_ADDRESS 0x0012
+#define NR_STUN_ATTR_DATA 0x0013
+#define NR_STUN_ATTR_XOR_RELAY_ADDRESS 0x0016
+#define NR_STUN_ATTR_REQUESTED_TRANSPORT 0x0019
+
+#define NR_STUN_ATTR_REQUESTED_TRANSPORT_UDP 17
+#endif /* USE_TURN */
+
+/*
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |M|M|M|M|M|C|M|M|M|C|M|M|M|M|
+ * |1|1|9|8|7|1|6|5|4|0|3|2|1|0|
+ * |1|0| | | | | | | | | | | | |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * Figure 3: Format of STUN Message Type Field
+ */
+#define NR_STUN_METHOD_TYPE_BITS(m) \
+ ((((m) & 0xf80) << 2) | (((m) & 0x070) << 1) | ((m) & 0x00f))
+
+#define NR_STUN_CLASS_TYPE_BITS(c) \
+ ((((c) & 0x002) << 7) | (((c) & 0x001) << 4))
+
+#define NR_STUN_GET_TYPE_METHOD(t) \
+ ((((t) >> 2) & 0xf80) | (((t) >> 1) & 0x070) | ((t) & 0x00f))
+
+#define NR_STUN_GET_TYPE_CLASS(t) \
+ ((((t) >> 7) & 0x002) | (((t) >> 4) & 0x001))
+
+#define NR_STUN_TYPE(m,c) (NR_STUN_METHOD_TYPE_BITS((m)) | NR_STUN_CLASS_TYPE_BITS((c)))
+
+/* building blocks for message types */
+#define NR_METHOD_BINDING 0x001
+#define NR_CLASS_REQUEST 0x0
+#define NR_CLASS_INDICATION 0x1
+#define NR_CLASS_RESPONSE 0x2
+#define NR_CLASS_ERROR_RESPONSE 0x3
+
+/* define types for STUN messages */
+#define NR_STUN_MSG_BINDING_REQUEST NR_STUN_TYPE(NR_METHOD_BINDING, \
+ NR_CLASS_REQUEST)
+#define NR_STUN_MSG_BINDING_INDICATION NR_STUN_TYPE(NR_METHOD_BINDING, \
+ NR_CLASS_INDICATION)
+#define NR_STUN_MSG_BINDING_RESPONSE NR_STUN_TYPE(NR_METHOD_BINDING, \
+ NR_CLASS_RESPONSE)
+#define NR_STUN_MSG_BINDING_ERROR_RESPONSE NR_STUN_TYPE(NR_METHOD_BINDING, \
+ NR_CLASS_ERROR_RESPONSE)
+
+#ifdef USE_TURN
+/* building blocks for TURN message types */
+#define NR_METHOD_ALLOCATE 0x003
+#define NR_METHOD_REFRESH 0x004
+
+#define NR_METHOD_SEND 0x006
+#define NR_METHOD_DATA 0x007
+#define NR_METHOD_CREATE_PERMISSION 0x008
+#define NR_METHOD_CHANNEL_BIND 0x009
+
+/* define types for a TURN message */
+#define NR_STUN_MSG_ALLOCATE_REQUEST NR_STUN_TYPE(NR_METHOD_ALLOCATE, \
+ NR_CLASS_REQUEST)
+#define NR_STUN_MSG_ALLOCATE_RESPONSE NR_STUN_TYPE(NR_METHOD_ALLOCATE, \
+ NR_CLASS_RESPONSE)
+#define NR_STUN_MSG_ALLOCATE_ERROR_RESPONSE NR_STUN_TYPE(NR_METHOD_ALLOCATE, \
+ NR_CLASS_ERROR_RESPONSE)
+#define NR_STUN_MSG_REFRESH_REQUEST NR_STUN_TYPE(NR_METHOD_REFRESH, \
+ NR_CLASS_REQUEST)
+#define NR_STUN_MSG_REFRESH_RESPONSE NR_STUN_TYPE(NR_METHOD_REFRESH, \
+ NR_CLASS_RESPONSE)
+#define NR_STUN_MSG_REFRESH_ERROR_RESPONSE NR_STUN_TYPE(NR_METHOD_REFRESH, \
+ NR_CLASS_ERROR_RESPONSE)
+
+#define NR_STUN_MSG_SEND_INDICATION NR_STUN_TYPE(NR_METHOD_SEND, \
+ NR_CLASS_INDICATION)
+#define NR_STUN_MSG_DATA_INDICATION NR_STUN_TYPE(NR_METHOD_DATA, \
+ NR_CLASS_INDICATION)
+
+#define NR_STUN_MSG_PERMISSION_REQUEST NR_STUN_TYPE(NR_METHOD_CREATE_PERMISSION, \
+ NR_CLASS_REQUEST)
+#define NR_STUN_MSG_PERMISSION_RESPONSE NR_STUN_TYPE(NR_METHOD_CREATE_PERMISSION, \
+ NR_CLASS_RESPONSE)
+#define NR_STUN_MSG_PERMISSION_ERROR_RESPONSE NR_STUN_TYPE(NR_METHOD_CREATE_PERMISSION, \
+ NR_CLASS_ERROR_RESPONSE)
+
+#define NR_STUN_MSG_CHANNEL_BIND_REQUEST NR_STUN_TYPE(NR_METHOD_CHANNEL_BIND, \
+ NR_CLASS_REQUEST)
+#define NR_STUN_MSG_CHANNEL_BIND_RESPONSE NR_STUN_TYPE(NR_METHOD_CHANNEL_BIND, \
+ NR_CLASS_RESPONSE)
+#define NR_STUN_MSG_CHANNEL_BIND_ERROR_RESPONSE NR_STUN_TYPE(NR_METHOD_CHANNEL_BIND, \
+ NR_CLASS_ERROR_RESPONSE)
+
+
+#endif /* USE_TURN */
+
+
+#define NR_STUN_AUTH_RULE_OPTIONAL (1<<0)
+#define NR_STUN_AUTH_RULE_SHORT_TERM (1<<8)
+#define NR_STUN_AUTH_RULE_LONG_TERM (1<<9)
+
+#endif
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_build.c b/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_build.c
new file mode 100644
index 0000000000..001b38e7c4
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_build.c
@@ -0,0 +1,611 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+*/
+
+#include <csi_platform.h>
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "nr_api.h"
+#include "stun.h"
+#include "registry.h"
+#include "stun_reg.h"
+#include "nr_crypto.h"
+
+
+/* draft-ietf-behave-rfc3489bis-10.txt S 7.1 */
+/* draft-ietf-behave-rfc3489bis-10.txt S 10.1.1 */
+/* note that S 10.1.1 states the message MUST include MESSAGE-INTEGRITY
+ * and USERNAME, but that's not correct -- for instance ICE keepalive
+ * messages don't include these (See draft-ietf-mmusic-ice-18.txt S 10:
+ * "If STUN is being used for keepalives, a STUN Binding Indication is
+ * used. The Indication MUST NOT utilize any authentication mechanism")
+ */
+int
+nr_stun_form_request_or_indication(int mode, int msg_type, nr_stun_message **msg)
+{
+ int r,_status;
+ nr_stun_message *req = 0;
+
+ assert(NR_STUN_GET_TYPE_CLASS(msg_type) == NR_CLASS_REQUEST
+ || NR_STUN_GET_TYPE_CLASS(msg_type) == NR_CLASS_INDICATION);
+
+ *msg = 0;
+
+ if ((r=nr_stun_message_create(&req)))
+ ABORT(r);
+
+ req->header.type = msg_type;
+
+ nr_crypto_random_bytes((UCHAR*)&req->header.id,sizeof(req->header.id));
+
+ switch (mode) {
+ default:
+ if ((r=nr_stun_message_add_fingerprint_attribute(req)))
+ ABORT(r);
+ /* fall through */
+ case NR_STUN_MODE_STUN_NO_AUTH:
+ req->header.magic_cookie = NR_STUN_MAGIC_COOKIE;
+ break;
+
+#ifdef USE_STUND_0_96
+ case NR_STUN_MODE_STUND_0_96:
+ req->header.magic_cookie = NR_STUN_MAGIC_COOKIE2;
+
+ /* actually, stund 0.96 just ignores the fingerprint
+ * attribute, but don't bother to send it */
+
+ break;
+#endif /* USE_STUND_0_96 */
+
+ }
+
+ *msg = req;
+
+ _status=0;
+ abort:
+ if (_status) RFREE(req);
+ return _status;
+}
+
+int
+nr_stun_build_req_lt_auth(nr_stun_client_stun_binding_request_params *params, nr_stun_message **msg)
+{
+ int r,_status;
+ nr_stun_message *req = 0;
+
+ if ((r=nr_stun_form_request_or_indication(NR_STUN_MODE_STUN, NR_STUN_MSG_BINDING_REQUEST, &req)))
+ ABORT(r);
+
+ if ((r=nr_stun_message_add_username_attribute(req, params->username)))
+ ABORT(r);
+
+ if (params->realm && params->nonce) {
+ if ((r=nr_stun_message_add_realm_attribute(req, params->realm)))
+ ABORT(r);
+
+ if ((r=nr_stun_message_add_nonce_attribute(req, params->nonce)))
+ ABORT(r);
+
+ if ((r=nr_stun_message_add_message_integrity_attribute(req, params->password)))
+ ABORT(r);
+ }
+
+ *msg = req;
+
+ _status=0;
+ abort:
+ if (_status) nr_stun_message_destroy(&req);
+ return _status;
+}
+
+int
+nr_stun_build_req_st_auth(nr_stun_client_stun_binding_request_params *params, nr_stun_message **msg)
+{
+ int r,_status;
+ nr_stun_message *req = 0;
+
+ if ((r=nr_stun_form_request_or_indication(NR_STUN_MODE_STUN, NR_STUN_MSG_BINDING_REQUEST, &req)))
+ ABORT(r);
+
+ if ((r=nr_stun_message_add_username_attribute(req, params->username)))
+ ABORT(r);
+
+ if (params->password) {
+ if ((r=nr_stun_message_add_message_integrity_attribute(req, params->password)))
+ ABORT(r);
+ }
+
+ *msg = req;
+
+ _status=0;
+ abort:
+ if (_status) nr_stun_message_destroy(&req);
+ return _status;
+}
+
+int
+nr_stun_build_req_no_auth(nr_stun_client_stun_binding_request_params *params, nr_stun_message **msg)
+{
+ int r,_status;
+ nr_stun_message *req = 0;
+
+ if ((r=nr_stun_form_request_or_indication(NR_STUN_MODE_STUN_NO_AUTH, NR_STUN_MSG_BINDING_REQUEST, &req)))
+ ABORT(r);
+
+ *msg = req;
+
+ _status=0;
+ abort:
+ if (_status) nr_stun_message_destroy(&req);
+ return _status;
+}
+
+int
+nr_stun_build_keepalive(nr_stun_client_stun_keepalive_params *params, nr_stun_message **msg)
+{
+ int r,_status;
+ nr_stun_message *ind = 0;
+
+ if ((r=nr_stun_form_request_or_indication(NR_STUN_MODE_STUN, NR_STUN_MSG_BINDING_INDICATION, &ind)))
+ ABORT(r);
+
+ *msg = ind;
+
+ _status=0;
+ abort:
+ if (_status) nr_stun_message_destroy(&ind);
+ return _status;
+}
+
+#ifdef USE_STUND_0_96
+int
+nr_stun_build_req_stund_0_96(nr_stun_client_stun_binding_request_stund_0_96_params *params, nr_stun_message **msg)
+{
+ int r,_status;
+ nr_stun_message *req = 0;
+
+ if ((r=nr_stun_form_request_or_indication(NR_STUN_MODE_STUND_0_96, NR_STUN_MSG_BINDING_REQUEST, &req)))
+ ABORT(r);
+
+ if ((r=nr_stun_message_add_change_request_attribute(req, 0)))
+ ABORT(r);
+
+ assert(! nr_stun_message_has_attribute(req, NR_STUN_ATTR_USERNAME, 0));
+ assert(! nr_stun_message_has_attribute(req, NR_STUN_ATTR_MESSAGE_INTEGRITY, 0));
+
+ *msg = req;
+
+ _status=0;
+ abort:
+ if (_status) nr_stun_message_destroy(&req);
+ return _status;
+}
+#endif /* USE_STUND_0_96 */
+
+#ifdef USE_ICE
+int
+nr_stun_build_use_candidate(nr_stun_client_ice_binding_request_params *params, nr_stun_message **msg)
+{
+ int r,_status;
+ nr_stun_message *req = 0;
+
+ if ((r=nr_stun_form_request_or_indication(NR_STUN_MODE_STUN, NR_STUN_MSG_BINDING_REQUEST, &req)))
+ ABORT(r);
+
+ if ((r=nr_stun_message_add_username_attribute(req, params->username)))
+ ABORT(r);
+
+ if ((r=nr_stun_message_add_message_integrity_attribute(req, &params->password)))
+ ABORT(r);
+
+ if ((r=nr_stun_message_add_use_candidate_attribute(req)))
+ ABORT(r);
+
+ if ((r=nr_stun_message_add_priority_attribute(req, params->priority)))
+ ABORT(r);
+
+ if ((r=nr_stun_message_add_ice_controlling_attribute(req, params->tiebreaker)))
+ ABORT(r);
+
+ *msg = req;
+
+ _status=0;
+ abort:
+ if (_status) nr_stun_message_destroy(&req);
+ return _status;
+}
+
+int
+nr_stun_build_req_ice(nr_stun_client_ice_binding_request_params *params, nr_stun_message **msg)
+{
+ int r,_status;
+ nr_stun_message *req = 0;
+
+ if ((r=nr_stun_form_request_or_indication(NR_STUN_MODE_STUN, NR_STUN_MSG_BINDING_REQUEST, &req)))
+ ABORT(r);
+
+ if ((r=nr_stun_message_add_username_attribute(req, params->username)))
+ ABORT(r);
+
+ if ((r=nr_stun_message_add_message_integrity_attribute(req, &params->password)))
+ ABORT(r);
+
+ if ((r=nr_stun_message_add_priority_attribute(req, params->priority)))
+ ABORT(r);
+
+ switch (params->control) {
+ case NR_ICE_CONTROLLING:
+ if ((r=nr_stun_message_add_ice_controlling_attribute(req, params->tiebreaker)))
+ ABORT(r);
+ break;
+ case NR_ICE_CONTROLLED:
+ if ((r=nr_stun_message_add_ice_controlled_attribute(req, params->tiebreaker)))
+ ABORT(r);
+ break;
+ default:
+ assert(0);
+ ABORT(R_INTERNAL);
+ }
+
+ *msg = req;
+
+ _status=0;
+ abort:
+ if (_status) nr_stun_message_destroy(&req);
+ return _status;
+}
+#endif /* USE_ICE */
+
+#ifdef USE_TURN
+
+#ifndef __isascii
+#define __isascii(c) (((c) & ~0x7F) == 0)
+#endif
+
+/* Long-term passwords are computed over the key:
+
+ key = MD5(username ":" realm ":" SASLprep(password))
+
+ Per RFC 5389 S 15.4
+*/
+int
+nr_stun_compute_lt_message_integrity_password(const char *username, const char *realm,
+ Data *password, Data *hmac_key)
+{
+ char digest_input[1000];
+ size_t i;
+ int r, _status;
+ size_t len;
+
+ /* First check that the password is ASCII. We are supposed to
+ SASLprep but we don't support this yet
+ TODO(ekr@rtfm.com): Add SASLprep for password.
+ */
+ for (i=0; i<password->len; i++) {
+ if (!__isascii(password->data[i]))
+ ABORT(R_BAD_DATA);
+ }
+
+ if (hmac_key->len < 16)
+ ABORT(R_BAD_ARGS);
+
+ snprintf(digest_input, sizeof(digest_input), "%s:%s:", username, realm);
+ if ((sizeof(digest_input) - strlen(digest_input)) < password->len)
+ ABORT(R_BAD_DATA);
+
+ len = strlen(digest_input);
+ memcpy(digest_input + len, password->data, password->len);
+
+
+ if (r=nr_crypto_md5((UCHAR *)digest_input, len + password->len, hmac_key->data))
+ ABORT(r);
+ hmac_key->len=16;
+
+ _status=0;
+abort:
+ return(_status);
+}
+
+static int
+nr_stun_build_auth_params(nr_stun_client_auth_params *auth, nr_stun_message *req)
+{
+ int r, _status;
+ UCHAR hmac_key_d[16];
+ Data hmac_key;
+
+ ATTACH_DATA(hmac_key, hmac_key_d);
+
+ if (!auth->authenticate)
+ goto done;
+
+ assert(auth->username);
+ assert(auth->password.len);
+ assert(auth->realm);
+ assert(auth->nonce);
+
+ if (r=nr_stun_compute_lt_message_integrity_password(auth->username,
+ auth->realm,
+ &auth->password,
+ &hmac_key))
+ ABORT(r);
+
+ if (!auth->username) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "STUN authentication requested but no username provided");
+ ABORT(R_INTERNAL);
+ }
+
+ if (!auth->password.len) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "STUN authentication requested but no password provided");
+ ABORT(R_INTERNAL);
+ }
+
+ if (!auth->realm) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "STUN authentication requested but no realm provided");
+ ABORT(R_INTERNAL);
+ }
+
+ if (!auth->nonce) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "STUN authentication requested but no nonce provided");
+ ABORT(R_INTERNAL);
+ }
+
+ if ((r=nr_stun_message_add_username_attribute(req, auth->username)))
+ ABORT(r);
+
+ if ((r=nr_stun_message_add_realm_attribute(req, auth->realm)))
+ ABORT(r);
+
+ if ((r=nr_stun_message_add_nonce_attribute(req, auth->nonce)))
+ ABORT(r);
+
+ if ((r=nr_stun_message_add_message_integrity_attribute(req, &hmac_key)))
+ ABORT(r);
+
+done:
+ _status=0;
+abort:
+ return(_status);
+}
+
+int
+nr_stun_build_allocate_request(nr_stun_client_auth_params *auth, nr_stun_client_allocate_request_params *params, nr_stun_message **msg)
+{
+ int r,_status;
+ nr_stun_message *req = 0;
+
+ if ((r=nr_stun_form_request_or_indication(NR_STUN_MODE_STUN, NR_STUN_MSG_ALLOCATE_REQUEST, &req)))
+ ABORT(r);
+
+ if ((r=nr_stun_message_add_requested_transport_attribute(req, NR_STUN_ATTR_REQUESTED_TRANSPORT_UDP)))
+ ABORT(r);
+
+ if ((r=nr_stun_message_add_lifetime_attribute(req, params->lifetime_secs)))
+ ABORT(r);
+
+ /* TODO(ekr@rtfm.com): Add the SOFTWARE attribute (Firefox bug 857666) */
+
+ if ((r=nr_stun_build_auth_params(auth, req)))
+ ABORT(r);
+
+ *msg = req;
+
+ _status=0;
+ abort:
+ if (_status) nr_stun_message_destroy(&req);
+ return _status;
+}
+
+
+int nr_stun_build_refresh_request(nr_stun_client_auth_params *auth, nr_stun_client_refresh_request_params *params, nr_stun_message **msg)
+{
+ int r,_status;
+ nr_stun_message *req = 0;
+
+ if ((r=nr_stun_form_request_or_indication(NR_STUN_MODE_STUN, NR_STUN_MSG_REFRESH_REQUEST, &req)))
+ ABORT(r);
+
+ if ((r=nr_stun_message_add_lifetime_attribute(req, params->lifetime_secs)))
+ ABORT(r);
+
+
+ /* TODO(ekr@rtfm.com): Add the SOFTWARE attribute (Firefox bug 857666) */
+
+ if ((r=nr_stun_build_auth_params(auth, req)))
+ ABORT(r);
+
+ *msg = req;
+
+ _status=0;
+ abort:
+ if (_status) nr_stun_message_destroy(&req);
+ return _status;
+}
+
+
+int nr_stun_build_permission_request(nr_stun_client_auth_params *auth, nr_stun_client_permission_request_params *params, nr_stun_message **msg)
+{
+ int r,_status;
+ nr_stun_message *req = 0;
+
+ if ((r=nr_stun_form_request_or_indication(NR_STUN_MODE_STUN, NR_STUN_MSG_PERMISSION_REQUEST, &req)))
+ ABORT(r);
+
+ if ((r=nr_stun_message_add_xor_peer_address_attribute(req, &params->remote_addr)))
+ ABORT(r);
+
+ if ((r=nr_stun_build_auth_params(auth, req)))
+ ABORT(r);
+
+ *msg = req;
+
+ _status=0;
+ abort:
+ if (_status) nr_stun_message_destroy(&req);
+ return _status;
+}
+
+int
+nr_stun_build_send_indication(nr_stun_client_send_indication_params *params, nr_stun_message **msg)
+{
+ int r,_status;
+ nr_stun_message *ind = 0;
+
+ if ((r=nr_stun_form_request_or_indication(NR_STUN_MODE_STUN, NR_STUN_MSG_SEND_INDICATION, &ind)))
+ ABORT(r);
+
+ if ((r=nr_stun_message_add_xor_peer_address_attribute(ind, &params->remote_addr)))
+ ABORT(r);
+
+ if ((r=nr_stun_message_add_data_attribute(ind, params->data.data, params->data.len)))
+ ABORT(r);
+
+ *msg = ind;
+
+ _status=0;
+ abort:
+ if (_status) nr_stun_message_destroy(&ind);
+ return _status;
+}
+
+int
+nr_stun_build_data_indication(nr_stun_client_data_indication_params *params, nr_stun_message **msg)
+{
+ int r,_status;
+ nr_stun_message *ind = 0;
+
+ if ((r=nr_stun_form_request_or_indication(NR_STUN_MODE_STUN, NR_STUN_MSG_DATA_INDICATION, &ind)))
+ ABORT(r);
+
+ if ((r=nr_stun_message_add_xor_peer_address_attribute(ind, &params->remote_addr)))
+ ABORT(r);
+
+ if ((r=nr_stun_message_add_data_attribute(ind, params->data.data, params->data.len)))
+ ABORT(r);
+
+ *msg = ind;
+
+ _status=0;
+ abort:
+ if (_status) nr_stun_message_destroy(&ind);
+ return _status;
+}
+
+#endif /* USE_TURN */
+
+/* draft-ietf-behave-rfc3489bis-10.txt S 7.3.1.1 */
+int
+nr_stun_form_success_response(nr_stun_message *req, nr_transport_addr *from, Data *password, nr_stun_message *res)
+{
+ int r,_status;
+ int request_method;
+ char server_name[NR_STUN_MAX_SERVER_BYTES+1]; /* +1 for \0 */
+
+ /* set up information for default response */
+
+ request_method = NR_STUN_GET_TYPE_METHOD(req->header.type);
+ res->header.type = NR_STUN_TYPE(request_method, NR_CLASS_RESPONSE);
+ res->header.magic_cookie = req->header.magic_cookie;
+ memcpy(&res->header.id, &req->header.id, sizeof(res->header.id));
+
+ r_log(NR_LOG_STUN, LOG_DEBUG, "Mapped Address = %s", from->as_string);
+
+ if ((r=nr_stun_message_add_xor_mapped_address_attribute(res, from)))
+ ABORT(r);
+
+ if (!NR_reg_get_string(NR_STUN_REG_PREF_SERVER_NAME, server_name, sizeof(server_name))) {
+ if ((r=nr_stun_message_add_server_attribute(res, server_name)))
+ ABORT(r);
+ }
+
+ if (res->header.magic_cookie == NR_STUN_MAGIC_COOKIE) {
+ if (password != 0) {
+ if ((r=nr_stun_message_add_message_integrity_attribute(res, password)))
+ ABORT(r);
+ }
+
+ if ((r=nr_stun_message_add_fingerprint_attribute(res)))
+ ABORT(r);
+ }
+
+ _status=0;
+ abort:
+ return _status;
+}
+
+/* draft-ietf-behave-rfc3489bis-10.txt S 7.3.1.1 */
+void
+nr_stun_form_error_response(nr_stun_message *req, nr_stun_message* res, int number, char* msg)
+{
+ char *str;
+ int request_method;
+ char server_name[NR_STUN_MAX_SERVER_BYTES+1]; /* +1 for \0 */
+
+ if (number < 300 || number > 699)
+ number = 500;
+
+ r_log(NR_LOG_STUN, LOG_INFO, "Responding with error %d: %s", number, msg);
+
+ request_method = NR_STUN_GET_TYPE_METHOD(req->header.type);
+ res->header.type = NR_STUN_TYPE(request_method, NR_CLASS_ERROR_RESPONSE);
+ res->header.magic_cookie = req->header.magic_cookie;
+ memcpy(&res->header.id, &req->header.id, sizeof(res->header.id));
+
+ /* during development we should never see 500s (hopefully not in deployment either) */
+
+ str = 0;
+ switch (number) {
+ case 300: str = "Try Alternate"; break;
+ case 400: str = "Bad Request"; break;
+ case 401: str = "Unauthorized"; break;
+ case 420: str = "Unknown Attribute"; break;
+ case 438: str = "Stale Nonce"; break;
+#ifdef USE_ICE
+ case 487: str = "Role Conflict"; break;
+#endif
+ case 500: str = "Server Error"; break;
+ }
+ if (str == 0) {
+ str = "Unknown";
+ }
+
+ if (nr_stun_message_add_error_code_attribute(res, number, str)) {
+ assert(0); /* should never happen */
+ }
+
+ if (!NR_reg_get_string(NR_STUN_REG_PREF_SERVER_NAME, server_name, sizeof(server_name))) {
+ nr_stun_message_add_server_attribute(res, server_name);
+ }
+}
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_build.h b/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_build.h
new file mode 100644
index 0000000000..c3f91a87b0
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_build.h
@@ -0,0 +1,147 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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 _stun_build_h
+#define _stun_build_h
+
+#include "stun.h"
+
+#define NR_STUN_MODE_STUN 1
+#ifdef USE_STUND_0_96
+#define NR_STUN_MODE_STUND_0_96 2 /* backwards compatibility mode */
+#endif /* USE_STUND_0_96 */
+#define NR_STUN_MODE_STUN_NO_AUTH 3
+int nr_stun_form_request_or_indication(int mode, int msg_type, nr_stun_message **msg);
+
+typedef struct nr_stun_client_stun_binding_request_params_ {
+ char *username;
+ Data *password;
+ char *nonce;
+ char *realm;
+} nr_stun_client_stun_binding_request_params;
+
+int nr_stun_build_req_lt_auth(nr_stun_client_stun_binding_request_params *params, nr_stun_message **msg);
+int nr_stun_build_req_st_auth(nr_stun_client_stun_binding_request_params *params, nr_stun_message **msg);
+int nr_stun_build_req_no_auth(nr_stun_client_stun_binding_request_params *params, nr_stun_message **msg);
+
+
+typedef struct nr_stun_client_stun_keepalive_params_ {
+#if defined(WIN32) || defined(__clang__)
+ // VC++ and clang give error and warning respectively if no members
+ int dummy;
+#endif
+} nr_stun_client_stun_keepalive_params;
+
+int nr_stun_build_keepalive(nr_stun_client_stun_keepalive_params *params, nr_stun_message **msg);
+
+
+#ifdef USE_STUND_0_96
+typedef struct nr_stun_client_stun_binding_request_stund_0_96_params_ {
+#ifdef WIN32 // silly VC++ gives error if no members
+ int dummy;
+#endif
+} nr_stun_client_stun_binding_request_stund_0_96_params;
+
+int nr_stun_build_req_stund_0_96(nr_stun_client_stun_binding_request_stund_0_96_params *params, nr_stun_message **msg);
+#endif /* USE_STUND_0_96 */
+
+
+#ifdef USE_ICE
+typedef struct nr_stun_client_ice_binding_request_params_ {
+ char *username;
+ Data password;
+ UINT4 priority;
+ int control;
+#define NR_ICE_CONTROLLING 1
+#define NR_ICE_CONTROLLED 2
+ UINT8 tiebreaker;
+} nr_stun_client_ice_binding_request_params;
+
+int nr_stun_build_use_candidate(nr_stun_client_ice_binding_request_params *params, nr_stun_message **msg);
+
+int nr_stun_build_req_ice(nr_stun_client_ice_binding_request_params *params, nr_stun_message **msg);
+#endif /* USE_ICE */
+
+
+typedef struct nr_stun_client_auth_params_ {
+ char authenticate;
+ char *username;
+ char *realm;
+ char *nonce;
+ Data password;
+} nr_stun_client_auth_params;
+
+#ifdef USE_TURN
+typedef struct nr_stun_client_allocate_request_params_ {
+ UINT4 lifetime_secs;
+} nr_stun_client_allocate_request_params;
+
+int nr_stun_build_allocate_request(nr_stun_client_auth_params *auth, nr_stun_client_allocate_request_params *params, nr_stun_message **msg);
+
+
+typedef struct nr_stun_client_refresh_request_params_ {
+ UINT4 lifetime_secs;
+} nr_stun_client_refresh_request_params;
+
+int nr_stun_build_refresh_request(nr_stun_client_auth_params *auth, nr_stun_client_refresh_request_params *params, nr_stun_message **msg);
+
+
+
+typedef struct nr_stun_client_permission_request_params_ {
+ nr_transport_addr remote_addr;
+} nr_stun_client_permission_request_params;
+
+int nr_stun_build_permission_request(nr_stun_client_auth_params *auth, nr_stun_client_permission_request_params *params, nr_stun_message **msg);
+
+
+typedef struct nr_stun_client_send_indication_params_ {
+ nr_transport_addr remote_addr;
+ Data data;
+} nr_stun_client_send_indication_params;
+
+int nr_stun_build_send_indication(nr_stun_client_send_indication_params *params, nr_stun_message **msg);
+
+typedef struct nr_stun_client_data_indication_params_ {
+ nr_transport_addr remote_addr;
+ Data data;
+} nr_stun_client_data_indication_params;
+
+int nr_stun_build_data_indication(nr_stun_client_data_indication_params *params, nr_stun_message **msg);
+#endif /* USE_TURN */
+
+int nr_stun_form_success_response(nr_stun_message *req, nr_transport_addr *from, Data *password, nr_stun_message *res);
+void nr_stun_form_error_response(nr_stun_message *request, nr_stun_message* response, int number, char* msg);
+int nr_stun_compute_lt_message_integrity_password(const char *username, const char *realm,
+ Data *password, Data *hmac_key);
+
+#endif
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_client_ctx.c b/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_client_ctx.c
new file mode 100644
index 0000000000..50b9f74a5b
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_client_ctx.c
@@ -0,0 +1,888 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+*/
+
+#include <assert.h>
+#include <string.h>
+#include <math.h>
+
+#include <nr_api.h>
+#include "stun.h"
+#include "async_timer.h"
+#include "registry.h"
+#include "stun_reg.h"
+#include "nr_crypto.h"
+#include "r_time.h"
+
+static int nr_stun_client_send_request(nr_stun_client_ctx *ctx);
+static void nr_stun_client_timer_expired_cb(NR_SOCKET s, int b, void *cb_arg);
+static int nr_stun_client_get_password(void *arg, nr_stun_message *msg, Data **password);
+
+#define NR_STUN_TRANSPORT_ADDR_CHECK_WILDCARD 1
+#define NR_STUN_TRANSPORT_ADDR_CHECK_LOOPBACK 2
+
+int nr_stun_client_ctx_create(char *label, nr_socket *sock, nr_transport_addr *peer, UINT4 RTO, nr_stun_client_ctx **ctxp)
+ {
+ nr_stun_client_ctx *ctx=0;
+ char allow_loopback;
+ int r,_status;
+
+ if ((r=nr_stun_startup()))
+ ABORT(r);
+
+ if(!(ctx=RCALLOC(sizeof(nr_stun_client_ctx))))
+ ABORT(R_NO_MEMORY);
+
+ ctx->state=NR_STUN_CLIENT_STATE_INITTED;
+
+ if(!(ctx->label=r_strdup(label)))
+ ABORT(R_NO_MEMORY);
+
+ ctx->sock=sock;
+
+ nr_socket_getaddr(sock,&ctx->my_addr);
+ nr_transport_addr_copy(&ctx->peer_addr,peer);
+ assert(ctx->my_addr.protocol==ctx->peer_addr.protocol);
+
+ if (RTO != 0) {
+ ctx->rto_ms = RTO;
+ } else if (NR_reg_get_uint4(NR_STUN_REG_PREF_CLNT_RETRANSMIT_TIMEOUT, &ctx->rto_ms)) {
+ ctx->rto_ms = 100;
+ }
+
+ if (NR_reg_get_double(NR_STUN_REG_PREF_CLNT_RETRANSMIT_BACKOFF, &ctx->retransmission_backoff_factor))
+ ctx->retransmission_backoff_factor = 2.0;
+
+ if (NR_reg_get_uint4(NR_STUN_REG_PREF_CLNT_MAXIMUM_TRANSMITS, &ctx->maximum_transmits))
+ ctx->maximum_transmits = 7;
+
+ if (NR_reg_get_uint4(NR_STUN_REG_PREF_CLNT_FINAL_RETRANSMIT_BACKOFF, &ctx->maximum_transmits_timeout_ms))
+ ctx->maximum_transmits_timeout_ms = 16 * ctx->rto_ms;
+
+ ctx->mapped_addr_check_mask = NR_STUN_TRANSPORT_ADDR_CHECK_WILDCARD;
+ if (NR_reg_get_char(NR_STUN_REG_PREF_ALLOW_LOOPBACK_ADDRS, &allow_loopback) ||
+ !allow_loopback) {
+ ctx->mapped_addr_check_mask |= NR_STUN_TRANSPORT_ADDR_CHECK_LOOPBACK;
+ }
+
+ if (ctx->my_addr.protocol == IPPROTO_TCP) {
+ /* Because TCP is reliable there is only one final timeout value.
+ * We store the timeout value for TCP in here, because timeout_ms gets
+ * reset to 0 in client_reset() which gets called from client_start() */
+ ctx->maximum_transmits_timeout_ms = ctx->rto_ms *
+ pow(ctx->retransmission_backoff_factor,
+ ctx->maximum_transmits);
+ ctx->maximum_transmits = 1;
+ }
+
+ *ctxp=ctx;
+
+ _status=0;
+ abort:
+ if(_status){
+ nr_stun_client_ctx_destroy(&ctx);
+ }
+ return(_status);
+ }
+
+static void nr_stun_client_fire_finished_cb(nr_stun_client_ctx *ctx)
+ {
+ if (ctx->finished_cb) {
+ NR_async_cb finished_cb = ctx->finished_cb;
+ ctx->finished_cb = 0; /* prevent 2nd call */
+ /* finished_cb call must be absolutely last thing in function
+ * because as a side effect this ctx may be operated on in the
+ * callback */
+ finished_cb(0,0,ctx->cb_arg);
+ }
+ }
+
+int nr_stun_client_start(nr_stun_client_ctx *ctx, int mode, NR_async_cb finished_cb, void *cb_arg)
+ {
+ int r,_status;
+
+ if (ctx->state != NR_STUN_CLIENT_STATE_INITTED)
+ ABORT(R_NOT_PERMITTED);
+
+ ctx->mode=mode;
+
+ ctx->state=NR_STUN_CLIENT_STATE_RUNNING;
+ ctx->finished_cb=finished_cb;
+ ctx->cb_arg=cb_arg;
+
+ if(mode!=NR_STUN_CLIENT_MODE_KEEPALIVE){
+ if(r=nr_stun_client_send_request(ctx))
+ ABORT(r);
+ }
+
+ _status=0;
+ abort:
+ if (ctx->state != NR_STUN_CLIENT_STATE_RUNNING) {
+ nr_stun_client_fire_finished_cb(ctx);
+ }
+
+ return(_status);
+ }
+
+ int nr_stun_client_restart(nr_stun_client_ctx* ctx,
+ const nr_transport_addr* peer_addr) {
+ int r,_status;
+ int mode;
+ NR_async_cb finished_cb;
+ void *cb_arg;
+ if (ctx->state != NR_STUN_CLIENT_STATE_RUNNING)
+ ABORT(R_NOT_PERMITTED);
+
+ mode = ctx->mode;
+ finished_cb = ctx->finished_cb;
+ cb_arg = ctx->cb_arg;
+
+ nr_stun_client_reset(ctx);
+ nr_transport_addr_copy(&ctx->peer_addr, peer_addr);
+
+ if (r=nr_stun_client_start(ctx, mode, finished_cb, cb_arg))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int
+nr_stun_client_reset(nr_stun_client_ctx *ctx)
+{
+ /* Cancel the timer firing */
+ if (ctx->timer_handle){
+ NR_async_timer_cancel(ctx->timer_handle);
+ ctx->timer_handle=0;
+ }
+
+ nr_stun_message_destroy(&ctx->request);
+ ctx->request = 0;
+
+ nr_stun_message_destroy(&ctx->response);
+ ctx->response = 0;
+
+ memset(&ctx->results, 0, sizeof(ctx->results));
+
+ ctx->mode = 0;
+ ctx->finished_cb = 0;
+ ctx->cb_arg = 0;
+ ctx->request_ct = 0;
+ ctx->timeout_ms = 0;
+
+ ctx->state = NR_STUN_CLIENT_STATE_INITTED;
+
+ return 0;
+}
+
+static void nr_stun_client_timer_expired_cb(NR_SOCKET s, int b, void *cb_arg)
+ {
+ int _status;
+ nr_stun_client_ctx *ctx=cb_arg;
+ struct timeval now;
+ INT8 ms_waited;
+
+ /* Prevent this timer from being cancelled later */
+ ctx->timer_handle=0;
+
+ /* Shouldn't happen */
+ if(ctx->state==NR_STUN_CLIENT_STATE_CANCELLED)
+ ABORT(R_REJECTED);
+
+ gettimeofday(&now, 0);
+ if (r_timeval_diff_ms(&now, &ctx->timer_set, &ms_waited)) {
+ r_log(NR_LOG_STUN,LOG_DEBUG,"STUN-CLIENT(%s): Timer expired",ctx->label);
+ }
+ else {
+ r_log(NR_LOG_STUN,LOG_DEBUG,"STUN-CLIENT(%s): Timer expired (after %llu ms)",ctx->label, ms_waited);
+ }
+
+ if (ctx->request_ct >= ctx->maximum_transmits) {
+ r_log(NR_LOG_STUN,LOG_INFO,"STUN-CLIENT(%s): Timed out",ctx->label);
+ ctx->state=NR_STUN_CLIENT_STATE_TIMED_OUT;
+ ABORT(R_FAILED);
+ }
+
+ if (ctx->state != NR_STUN_CLIENT_STATE_RUNNING)
+ ABORT(R_NOT_PERMITTED);
+
+ // track retransmits for ice telemetry
+ nr_accumulate_count(&(ctx->retransmit_ct), 1);
+
+ /* as a side effect will reset the timer */
+ nr_stun_client_send_request(ctx);
+
+ _status = 0;
+ abort:
+ if (ctx->state != NR_STUN_CLIENT_STATE_RUNNING) {
+ /* Cancel the timer firing */
+ if (ctx->timer_handle){
+ NR_async_timer_cancel(ctx->timer_handle);
+ ctx->timer_handle=0;
+ }
+
+ nr_stun_client_fire_finished_cb(ctx);
+ }
+ if (_status) {
+ // cb doesn't return anything, but we should probably log that we aborted
+ // This also quiets the unused variable warnings.
+ r_log(NR_LOG_STUN,LOG_DEBUG,"STUN-CLIENT(%s): Timer expired cb abort with status: %d",
+ ctx->label, _status);
+ }
+ return;
+ }
+
+int nr_stun_client_force_retransmit(nr_stun_client_ctx *ctx)
+ {
+ int r,_status;
+
+ if (ctx->state != NR_STUN_CLIENT_STATE_RUNNING)
+ ABORT(R_NOT_PERMITTED);
+
+ if (ctx->request_ct > ctx->maximum_transmits) {
+ r_log(NR_LOG_STUN,LOG_INFO,"STUN-CLIENT(%s): Too many retransmit attempts",ctx->label);
+ ABORT(R_FAILED);
+ }
+
+ /* if there is a scheduled retransimt, get rid of the scheduled retransmit
+ * and retransmit immediately */
+ if (ctx->timer_handle) {
+ NR_async_timer_cancel(ctx->timer_handle);
+ ctx->timer_handle=0;
+
+ if (r=nr_stun_client_send_request(ctx))
+ ABORT(r);
+ }
+
+ _status=0;
+ abort:
+
+ return(_status);
+ }
+
+static int nr_stun_client_send_request(nr_stun_client_ctx *ctx)
+ {
+ int r,_status;
+ char string[256];
+
+ if (ctx->state != NR_STUN_CLIENT_STATE_RUNNING)
+ ABORT(R_NOT_PERMITTED);
+
+ r_log(NR_LOG_STUN,LOG_DEBUG,"STUN-CLIENT(%s): Sending check request (my_addr=%s,peer_addr=%s)",ctx->label,ctx->my_addr.as_string,ctx->peer_addr.as_string);
+
+ if (ctx->request == 0) {
+ switch (ctx->mode) {
+ case NR_STUN_CLIENT_MODE_BINDING_REQUEST_LONG_TERM_AUTH:
+ ctx->params.stun_binding_request.nonce = ctx->nonce;
+ ctx->params.stun_binding_request.realm = ctx->realm;
+ assert(0);
+ ABORT(R_INTERNAL);
+ /* TODO(ekr@rtfm.com): Need to implement long-term auth for binding
+ requests */
+ if ((r=nr_stun_build_req_lt_auth(&ctx->params.stun_binding_request, &ctx->request)))
+ ABORT(r);
+ break;
+ case NR_STUN_CLIENT_MODE_BINDING_REQUEST_SHORT_TERM_AUTH:
+ if ((r=nr_stun_build_req_st_auth(&ctx->params.stun_binding_request, &ctx->request)))
+ ABORT(r);
+ break;
+ case NR_STUN_CLIENT_MODE_BINDING_REQUEST_NO_AUTH:
+ if ((r=nr_stun_build_req_no_auth(&ctx->params.stun_binding_request, &ctx->request)))
+ ABORT(r);
+ break;
+ case NR_STUN_CLIENT_MODE_KEEPALIVE:
+ if ((r=nr_stun_build_keepalive(&ctx->params.stun_keepalive, &ctx->request)))
+ ABORT(r);
+ break;
+#ifdef USE_STUND_0_96
+ case NR_STUN_CLIENT_MODE_BINDING_REQUEST_STUND_0_96:
+ if ((r=nr_stun_build_req_stund_0_96(&ctx->params.stun_binding_request_stund_0_96, &ctx->request)))
+ ABORT(r);
+ break;
+#endif /* USE_STUND_0_96 */
+
+#ifdef USE_ICE
+ case NR_ICE_CLIENT_MODE_USE_CANDIDATE:
+ if ((r=nr_stun_build_use_candidate(&ctx->params.ice_binding_request, &ctx->request)))
+ ABORT(r);
+ break;
+ case NR_ICE_CLIENT_MODE_BINDING_REQUEST:
+ if ((r=nr_stun_build_req_ice(&ctx->params.ice_binding_request, &ctx->request)))
+ ABORT(r);
+ break;
+#endif /* USE_ICE */
+
+#ifdef USE_TURN
+ case NR_TURN_CLIENT_MODE_ALLOCATE_REQUEST:
+ if ((r=nr_stun_build_allocate_request(&ctx->auth_params, &ctx->params.allocate_request, &ctx->request)))
+ ABORT(r);
+ break;
+ case NR_TURN_CLIENT_MODE_REFRESH_REQUEST:
+ if ((r=nr_stun_build_refresh_request(&ctx->auth_params, &ctx->params.refresh_request, &ctx->request)))
+ ABORT(r);
+ break;
+ case NR_TURN_CLIENT_MODE_PERMISSION_REQUEST:
+ if ((r=nr_stun_build_permission_request(&ctx->auth_params, &ctx->params.permission_request, &ctx->request)))
+ ABORT(r);
+ break;
+ case NR_TURN_CLIENT_MODE_SEND_INDICATION:
+ if ((r=nr_stun_build_send_indication(&ctx->params.send_indication, &ctx->request)))
+ ABORT(r);
+ break;
+#endif /* USE_TURN */
+
+ default:
+ assert(0);
+ ABORT(R_FAILED);
+ break;
+ }
+ }
+
+ if (ctx->request->length == 0) {
+ if ((r=nr_stun_encode_message(ctx->request)))
+ ABORT(r);
+ }
+
+ snprintf(string, sizeof(string)-1, "STUN-CLIENT(%s): Sending to %s ", ctx->label, ctx->peer_addr.as_string);
+ r_dump(NR_LOG_STUN, LOG_DEBUG, string, (char*)ctx->request->buffer, ctx->request->length);
+
+ assert(ctx->my_addr.protocol==ctx->peer_addr.protocol);
+
+ if(r=nr_socket_sendto(ctx->sock, ctx->request->buffer, ctx->request->length, 0, &ctx->peer_addr)) {
+ if (r != R_WOULDBLOCK) {
+ ABORT(r);
+ }
+ r_log(NR_LOG_STUN,LOG_WARNING,"STUN-CLIENT(%s): nr_socket_sendto blocked, treating as dropped packet",ctx->label);
+ }
+
+ ctx->request_ct++;
+
+ if (NR_STUN_GET_TYPE_CLASS(ctx->request->header.type) == NR_CLASS_INDICATION) {
+ /* no need to set the timer because indications don't receive a
+ * response */
+ }
+ else {
+ if (ctx->request_ct >= ctx->maximum_transmits) {
+ /* Reliable transport only get here once. Unreliable get here for
+ * their final timeout. */
+ ctx->timeout_ms += ctx->maximum_transmits_timeout_ms;
+ }
+ else if (ctx->timeout_ms) {
+ /* exponential backoff */
+ ctx->timeout_ms *= ctx->retransmission_backoff_factor;
+ }
+ else {
+ /* initial timeout unreliable transports */
+ ctx->timeout_ms = ctx->rto_ms;
+ }
+
+ r_log(NR_LOG_STUN,LOG_DEBUG,"STUN-CLIENT(%s): Next timer will fire in %u ms",ctx->label, ctx->timeout_ms);
+
+ gettimeofday(&ctx->timer_set, 0);
+
+ assert(ctx->timeout_ms);
+ NR_ASYNC_TIMER_SET(ctx->timeout_ms, nr_stun_client_timer_expired_cb, ctx, &ctx->timer_handle);
+ }
+
+ _status=0;
+ abort:
+ if (_status) {
+ nr_stun_client_failed(ctx);
+ }
+ return(_status);
+ }
+
+static int nr_stun_client_get_password(void *arg, nr_stun_message *msg, Data **password)
+{
+ *password = (Data*)arg;
+ if (!arg)
+ return(R_NOT_FOUND);
+ return(0);
+}
+
+int nr_stun_transport_addr_check(nr_transport_addr* addr, UINT4 check)
+ {
+ if((check & NR_STUN_TRANSPORT_ADDR_CHECK_WILDCARD) && nr_transport_addr_is_wildcard(addr))
+ return(R_BAD_DATA);
+
+ if ((check & NR_STUN_TRANSPORT_ADDR_CHECK_LOOPBACK) && nr_transport_addr_is_loopback(addr))
+ return(R_BAD_DATA);
+
+ return(0);
+ }
+
+int nr_stun_client_process_response(nr_stun_client_ctx *ctx, UCHAR *msg, int len, nr_transport_addr *peer_addr)
+ {
+ int r,_status;
+ char string[256];
+ char *username = 0;
+ Data *password = 0;
+ int allow_unauthed_redirect = 0;
+ nr_stun_message_attribute *attr;
+ nr_transport_addr *mapped_addr = 0;
+ int fail_on_error = 0;
+ UCHAR hmac_key_d[16];
+ Data hmac_key;
+ int compute_lt_key=0;
+ /* TODO(bcampen@mozilla.com): Bug 1023619, refactor this. */
+ int response_matched=0;
+
+ ATTACH_DATA(hmac_key, hmac_key_d);
+
+ if ((ctx->state != NR_STUN_CLIENT_STATE_RUNNING) &&
+ (ctx->state != NR_STUN_CLIENT_STATE_WAITING))
+ ABORT(R_REJECTED);
+
+ r_log(NR_LOG_STUN,LOG_DEBUG,"STUN-CLIENT(%s): Inspecting STUN response (my_addr=%s, peer_addr=%s)",ctx->label,ctx->my_addr.as_string,peer_addr->as_string);
+
+ snprintf(string, sizeof(string)-1, "STUN-CLIENT(%s): Received ", ctx->label);
+ r_dump(NR_LOG_STUN, LOG_DEBUG, string, (char*)msg, len);
+
+ /* determine password */
+ switch (ctx->mode) {
+ case NR_STUN_CLIENT_MODE_BINDING_REQUEST_LONG_TERM_AUTH:
+ /* If the STUN server responds with an error, give up, since we don't
+ * want to delay the completion of gathering. */
+ fail_on_error = 1;
+ compute_lt_key = 1;
+ /* Fall through */
+ case NR_STUN_CLIENT_MODE_BINDING_REQUEST_SHORT_TERM_AUTH:
+ password = ctx->params.stun_binding_request.password;
+ break;
+
+ case NR_STUN_CLIENT_MODE_BINDING_REQUEST_NO_AUTH:
+ /* If the STUN server responds with an error, give up, since we don't
+ * want to delay the completion of gathering. */
+ fail_on_error = 1;
+ break;
+
+ case NR_STUN_CLIENT_MODE_BINDING_REQUEST_STUND_0_96:
+ /* If the STUN server responds with an error, give up, since we don't
+ * want to delay the completion of gathering. */
+ fail_on_error = 1;
+ break;
+
+#ifdef USE_ICE
+ case NR_ICE_CLIENT_MODE_BINDING_REQUEST:
+ /* We do not set fail_on_error here. The error might be transient, and
+ * retrying isn't going to cause a slowdown. */
+ password = &ctx->params.ice_binding_request.password;
+ break;
+ case NR_ICE_CLIENT_MODE_USE_CANDIDATE:
+ /* We do not set fail_on_error here. The error might be transient, and
+ * retrying isn't going to cause a slowdown. */
+ password = &ctx->params.ice_binding_request.password;
+ break;
+#endif /* USE_ICE */
+
+#ifdef USE_TURN
+ case NR_TURN_CLIENT_MODE_ALLOCATE_REQUEST:
+ fail_on_error = 1;
+ compute_lt_key = 1;
+ /* Do not require mutual auth on redirect responses to Allocate requests. */
+ allow_unauthed_redirect = 1;
+ username = ctx->auth_params.username;
+ password = &ctx->auth_params.password;
+ /* do nothing */
+ break;
+ case NR_TURN_CLIENT_MODE_REFRESH_REQUEST:
+ fail_on_error = 1;
+ compute_lt_key = 1;
+ username = ctx->auth_params.username;
+ password = &ctx->auth_params.password;
+ /* do nothing */
+ break;
+ case NR_TURN_CLIENT_MODE_PERMISSION_REQUEST:
+ fail_on_error = 1;
+ compute_lt_key = 1;
+ username = ctx->auth_params.username;
+ password = &ctx->auth_params.password;
+ /* do nothing */
+ break;
+ case NR_TURN_CLIENT_MODE_SEND_INDICATION:
+ /* do nothing -- we just got our DATA-INDICATION */
+ break;
+#endif /* USE_TURN */
+
+ default:
+ assert(0);
+ ABORT(R_FAILED);
+ break;
+ }
+
+ if (compute_lt_key) {
+ if (!ctx->realm || !username) {
+ r_log(NR_LOG_STUN, LOG_DEBUG, "Long-term auth required but no realm/username specified. Randomizing key");
+ /* Fill the key with random bytes to guarantee non-match */
+ if (r=nr_crypto_random_bytes(hmac_key_d, sizeof(hmac_key_d)))
+ ABORT(r);
+ }
+ else {
+ if (r=nr_stun_compute_lt_message_integrity_password(username, ctx->realm,
+ password, &hmac_key))
+ ABORT(r);
+ }
+ password = &hmac_key;
+ }
+
+ if (ctx->response) {
+ nr_stun_message_destroy(&ctx->response);
+ }
+
+ /* TODO(bcampen@mozilla.com): Bug 1023619, refactor this. */
+ if ((r=nr_stun_message_create2(&ctx->response, msg, len)))
+ ABORT(r);
+
+ if ((r=nr_stun_decode_message(ctx->response, nr_stun_client_get_password, password))) {
+ r_log(NR_LOG_STUN,LOG_WARNING,"STUN-CLIENT(%s): error decoding response",ctx->label);
+ ABORT(r);
+ }
+
+ /* This will return an error if request and response don't match,
+ which is how we reject responses that match other contexts. */
+ if ((r=nr_stun_receive_message(ctx->request, ctx->response))) {
+ r_log(NR_LOG_STUN,LOG_DEBUG,"STUN-CLIENT(%s): Response is not for us",ctx->label);
+ ABORT(r);
+ }
+
+ r_log(NR_LOG_STUN,LOG_INFO,
+ "STUN-CLIENT(%s): Received response; processing",ctx->label);
+ response_matched=1;
+
+ if (allow_unauthed_redirect &&
+ nr_stun_message_has_attribute(ctx->response, NR_STUN_ATTR_ERROR_CODE,
+ &attr) &&
+ (attr->u.error_code.number / 100 == 3)) {
+ password = 0;
+ }
+
+/* TODO: !nn! currently using password!=0 to mean that auth is required,
+ * TODO: !nn! but we should probably pass that in explicitly via the
+ * TODO: !nn! usage (ctx->mode?) */
+ if (password) {
+ if (nr_stun_message_has_attribute(ctx->response, NR_STUN_ATTR_NONCE, 0)) {
+ if ((r=nr_stun_receive_response_long_term_auth(ctx->response, ctx))) {
+ r_log(NR_LOG_STUN,LOG_WARNING,"STUN-CLIENT(%s): long term auth failed",ctx->label);
+ ABORT(r);
+ }
+ }
+ else {
+ if ((r=nr_stun_receive_response_short_term_auth(ctx->response))) {
+ r_log(NR_LOG_STUN,LOG_WARNING,"STUN-CLIENT(%s): short term auth failed",ctx->label);
+ ABORT(r);
+ }
+ }
+ }
+
+ if (NR_STUN_GET_TYPE_CLASS(ctx->response->header.type) == NR_CLASS_RESPONSE) {
+ if ((r=nr_stun_process_success_response(ctx->response))) {
+ r_log(NR_LOG_STUN,LOG_WARNING,"STUN-CLIENT(%s): nr_stun_process_success_response failed",ctx->label);
+ ABORT(r);
+ }
+ }
+ else {
+ if (fail_on_error) {
+ ctx->state = NR_STUN_CLIENT_STATE_FAILED;
+ }
+ /* Note: most times we call process_error_response, we get r != 0.
+
+ However, if the error is to be discarded, we get r == 0, smash
+ the error code, and just keep going.
+ */
+ if ((r=nr_stun_process_error_response(ctx->response, &ctx->error_code))) {
+ r_log(NR_LOG_STUN,LOG_WARNING,"STUN-CLIENT(%s): nr_stun_process_error_response failed",ctx->label);
+ ABORT(r);
+ }
+ else {
+ ctx->error_code = 0xffff;
+ /* drop the error on the floor */
+ r_log(NR_LOG_STUN,LOG_WARNING,"STUN-CLIENT(%s): processed error response",ctx->label);
+ ABORT(R_FAILED);
+ }
+ }
+
+ r_log(NR_LOG_STUN,LOG_DEBUG,"STUN-CLIENT(%s): Successfully parsed mode=%d",ctx->label,ctx->mode);
+
+/* TODO: !nn! this should be moved to individual message receive/processing sections */
+ switch (ctx->mode) {
+ case NR_STUN_CLIENT_MODE_BINDING_REQUEST_LONG_TERM_AUTH:
+ case NR_STUN_CLIENT_MODE_BINDING_REQUEST_SHORT_TERM_AUTH:
+ if (! nr_stun_message_has_attribute(ctx->response, NR_STUN_ATTR_XOR_MAPPED_ADDRESS, 0)) {
+ r_log(NR_LOG_STUN,LOG_WARNING,"STUN-CLIENT(%s): No XOR-MAPPED-ADDRESS",ctx->label);
+ ABORT(R_BAD_DATA);
+ }
+ if (! nr_stun_message_has_attribute(ctx->response, NR_STUN_ATTR_MESSAGE_INTEGRITY, 0)) {
+ r_log(NR_LOG_STUN,LOG_WARNING,"STUN-CLIENT(%s): No MESSAGE-INTEGRITY",ctx->label);
+ ABORT(R_BAD_DATA);
+ }
+
+ mapped_addr = &ctx->results.stun_binding_response.mapped_addr;
+ break;
+
+ case NR_STUN_CLIENT_MODE_BINDING_REQUEST_NO_AUTH:
+ if (! nr_stun_message_has_attribute(ctx->response, NR_STUN_ATTR_XOR_MAPPED_ADDRESS, 0)) {
+ if (nr_stun_message_has_attribute(ctx->response, NR_STUN_ATTR_MAPPED_ADDRESS, 0)) {
+ /* Compensate for a bug in Google's STUN servers where they always respond with MAPPED-ADDRESS */
+ r_log(NR_LOG_STUN,LOG_INFO,"STUN-CLIENT(%s): No XOR-MAPPED-ADDRESS but MAPPED-ADDRESS. Falling back (though server is wrong).", ctx->label);
+ }
+ else {
+ r_log(NR_LOG_STUN,LOG_WARNING,"STUN-CLIENT(%s): No XOR-MAPPED-ADDRESS or MAPPED-ADDRESS",ctx->label);
+ ABORT(R_BAD_DATA);
+ }
+ }
+
+ mapped_addr = &ctx->results.stun_binding_response.mapped_addr;
+ break;
+
+ case NR_STUN_CLIENT_MODE_BINDING_REQUEST_STUND_0_96:
+ if (! nr_stun_message_has_attribute(ctx->response, NR_STUN_ATTR_MAPPED_ADDRESS, 0) && ! nr_stun_message_has_attribute(ctx->response, NR_STUN_ATTR_XOR_MAPPED_ADDRESS, 0)) {
+ r_log(NR_LOG_STUN,LOG_WARNING,"STUN-CLIENT(%s): No MAPPED-ADDRESS",ctx->label);
+ ABORT(R_BAD_DATA);
+ }
+
+ mapped_addr = &ctx->results.stun_binding_response_stund_0_96.mapped_addr;
+ break;
+
+#ifdef USE_ICE
+ case NR_ICE_CLIENT_MODE_BINDING_REQUEST:
+ if (! nr_stun_message_has_attribute(ctx->response, NR_STUN_ATTR_XOR_MAPPED_ADDRESS, 0)) {
+ r_log(NR_LOG_STUN,LOG_WARNING,"STUN-CLIENT(%s): No XOR-MAPPED-ADDRESS",ctx->label);
+ ABORT(R_BAD_DATA);
+ }
+ if (! nr_stun_message_has_attribute(ctx->response, NR_STUN_ATTR_MESSAGE_INTEGRITY, 0)) {
+ r_log(NR_LOG_STUN,LOG_WARNING,"STUN-CLIENT(%s): No MESSAGE-INTEGRITY",ctx->label);
+ ABORT(R_BAD_DATA);
+ }
+
+ mapped_addr = &ctx->results.stun_binding_response.mapped_addr;
+ break;
+ case NR_ICE_CLIENT_MODE_USE_CANDIDATE:
+ if (! nr_stun_message_has_attribute(ctx->response, NR_STUN_ATTR_XOR_MAPPED_ADDRESS, 0)) {
+ r_log(NR_LOG_STUN,LOG_WARNING,"STUN-CLIENT(%s): No XOR-MAPPED-ADDRESS",ctx->label);
+ ABORT(R_BAD_DATA);
+ }
+ if (! nr_stun_message_has_attribute(ctx->response, NR_STUN_ATTR_MESSAGE_INTEGRITY, 0)) {
+ r_log(NR_LOG_STUN,LOG_WARNING,"STUN-CLIENT(%s): No MESSAGE-INTEGRITY",ctx->label);
+ ABORT(R_BAD_DATA);
+ }
+
+ mapped_addr = &ctx->results.stun_binding_response.mapped_addr;
+ break;
+#endif /* USE_ICE */
+
+#ifdef USE_TURN
+ case NR_TURN_CLIENT_MODE_ALLOCATE_REQUEST:
+ if (! nr_stun_message_has_attribute(ctx->response, NR_STUN_ATTR_XOR_MAPPED_ADDRESS, 0)) {
+ r_log(NR_LOG_STUN,LOG_WARNING,"STUN-CLIENT(%s): No XOR-MAPPED-ADDRESS",ctx->label);
+ ABORT(R_BAD_DATA);
+ }
+ if (! nr_stun_message_has_attribute(ctx->response, NR_STUN_ATTR_MESSAGE_INTEGRITY, 0)) {
+ r_log(NR_LOG_STUN,LOG_WARNING,"STUN-CLIENT(%s): No MESSAGE-INTEGRITY",ctx->label);
+ ABORT(R_BAD_DATA);
+ }
+
+ if (!nr_stun_message_has_attribute(ctx->response, NR_STUN_ATTR_XOR_RELAY_ADDRESS, &attr)) {
+ r_log(NR_LOG_STUN,LOG_WARNING,"STUN-CLIENT(%s): No XOR-RELAYED-ADDRESS",ctx->label);
+ ABORT(R_BAD_DATA);
+ }
+
+ if ((r=nr_stun_transport_addr_check(&attr->u.relay_address.unmasked,
+ ctx->mapped_addr_check_mask))) {
+ r_log(NR_LOG_STUN,LOG_WARNING,"STUN-CLIENT(%s): nr_stun_transport_addr_check failed",ctx->label);
+ ABORT(r);
+ }
+
+ if ((r=nr_transport_addr_copy(
+ &ctx->results.allocate_response.relay_addr,
+ &attr->u.relay_address.unmasked))) {
+ r_log(NR_LOG_STUN,LOG_WARNING,"STUN-CLIENT(%s): nr_transport_addr_copy failed",ctx->label);
+ ABORT(r);
+ }
+
+ if (!nr_stun_message_has_attribute(ctx->response, NR_STUN_ATTR_LIFETIME, &attr)) {
+ r_log(NR_LOG_STUN,LOG_WARNING,"STUN-CLIENT(%s): No LIFETIME",ctx->label);
+ ABORT(R_BAD_DATA);
+ }
+ ctx->results.allocate_response.lifetime_secs=attr->u.lifetime_secs;
+
+ r_log(NR_LOG_STUN,LOG_DEBUG,"STUN-CLIENT(%s): Received relay address: %s", ctx->label, ctx->results.allocate_response.relay_addr.as_string);
+
+ mapped_addr = &ctx->results.allocate_response.mapped_addr;
+
+ break;
+ case NR_TURN_CLIENT_MODE_REFRESH_REQUEST:
+ if (! nr_stun_message_has_attribute(ctx->response, NR_STUN_ATTR_MESSAGE_INTEGRITY, 0)) {
+ r_log(NR_LOG_STUN,LOG_WARNING,"STUN-CLIENT(%s): No MESSAGE-INTEGRITY",ctx->label);
+ ABORT(R_BAD_DATA);
+ }
+ if (!nr_stun_message_has_attribute(ctx->response, NR_STUN_ATTR_LIFETIME, &attr)) {
+ r_log(NR_LOG_STUN,LOG_WARNING,"STUN-CLIENT(%s): No LIFETIME",ctx->label);
+ ABORT(R_BAD_DATA);
+ }
+ ctx->results.refresh_response.lifetime_secs=attr->u.lifetime_secs;
+ break;
+ case NR_TURN_CLIENT_MODE_PERMISSION_REQUEST:
+ if (! nr_stun_message_has_attribute(ctx->response, NR_STUN_ATTR_MESSAGE_INTEGRITY, 0)) {
+ r_log(NR_LOG_STUN,LOG_WARNING,"STUN-CLIENT(%s): No MESSAGE-INTEGRITY",ctx->label);
+ ABORT(R_BAD_DATA);
+ }
+ break;
+#endif /* USE_TURN */
+
+ default:
+ assert(0);
+ ABORT(R_FAILED);
+ break;
+ }
+
+ /* make sure we have the most up-to-date address from this peer */
+ if (nr_transport_addr_cmp(&ctx->peer_addr, peer_addr, NR_TRANSPORT_ADDR_CMP_MODE_ALL)) {
+ r_log(NR_LOG_STUN,LOG_INFO,"STUN-CLIENT(%s): Peer moved from %s to %s", ctx->label, ctx->peer_addr.as_string, peer_addr->as_string);
+ nr_transport_addr_copy(&ctx->peer_addr, peer_addr);
+ }
+
+ if (mapped_addr) {
+ if (nr_stun_message_has_attribute(ctx->response, NR_STUN_ATTR_XOR_MAPPED_ADDRESS, &attr)) {
+ if ((r=nr_stun_transport_addr_check(&attr->u.xor_mapped_address.unmasked,
+ ctx->mapped_addr_check_mask))) {
+ r_log(NR_LOG_STUN,LOG_WARNING,"STUN-CLIENT(%s): XOR-MAPPED-ADDRESS is bogus",ctx->label);
+ ABORT(r);
+ }
+
+ if ((r=nr_transport_addr_copy(mapped_addr, &attr->u.xor_mapped_address.unmasked))) {
+ r_log(NR_LOG_STUN,LOG_WARNING,"STUN-CLIENT(%s): nr_transport_addr_copy failed",ctx->label);
+ ABORT(r);
+ }
+ }
+ else if (nr_stun_message_has_attribute(ctx->response, NR_STUN_ATTR_MAPPED_ADDRESS, &attr)) {
+ if ((r=nr_stun_transport_addr_check(&attr->u.mapped_address,
+ ctx->mapped_addr_check_mask))) {
+ r_log(NR_LOG_STUN,LOG_WARNING,"STUN-CLIENT(%s): MAPPED-ADDRESS is bogus",ctx->label);
+ ABORT(r);
+ }
+
+ if ((r=nr_transport_addr_copy(mapped_addr, &attr->u.mapped_address))) {
+ r_log(NR_LOG_STUN,LOG_WARNING,"STUN-CLIENT(%s): nr_transport_addr_copy failed",ctx->label);
+ ABORT(r);
+ }
+ }
+ else {
+ r_log(NR_LOG_STUN,LOG_WARNING,"STUN-CLIENT(%s): No mapped address!",ctx->label);
+ ABORT(R_BAD_DATA);
+ }
+
+ // STUN doesn't distinguish protocol in mapped address, therefore
+ // assign used protocol from peer_addr
+ if (mapped_addr->protocol!=peer_addr->protocol){
+ mapped_addr->protocol=peer_addr->protocol;
+ nr_transport_addr_fmt_addr_string(mapped_addr);
+ }
+
+ r_log(NR_LOG_STUN,LOG_DEBUG,"STUN-CLIENT(%s): Received mapped address: %s", ctx->label, mapped_addr->as_string);
+ }
+
+ ctx->state=NR_STUN_CLIENT_STATE_DONE;
+
+ _status=0;
+ abort:
+ if(_status && response_matched){
+ r_log(NR_LOG_STUN,LOG_WARNING,"STUN-CLIENT(%s): Error processing response: %s, stun error code %d.", ctx->label, nr_strerror(_status), (int)ctx->error_code);
+ }
+
+ if ((ctx->state != NR_STUN_CLIENT_STATE_RUNNING) &&
+ (ctx->state != NR_STUN_CLIENT_STATE_WAITING)) {
+ /* Cancel the timer firing */
+ if (ctx->timer_handle) {
+ NR_async_timer_cancel(ctx->timer_handle);
+ ctx->timer_handle = 0;
+ }
+
+ nr_stun_client_fire_finished_cb(ctx);
+ }
+
+ return(_status);
+ }
+
+int nr_stun_client_ctx_destroy(nr_stun_client_ctx **ctxp)
+ {
+ nr_stun_client_ctx *ctx;
+
+ if(!ctxp || !*ctxp)
+ return(0);
+
+ ctx=*ctxp;
+ *ctxp=0;
+
+ nr_stun_client_reset(ctx);
+
+ RFREE(ctx->nonce);
+ RFREE(ctx->realm);
+
+ RFREE(ctx->label);
+ RFREE(ctx);
+
+ return(0);
+ }
+
+
+int nr_stun_client_cancel(nr_stun_client_ctx *ctx)
+ {
+ /* Cancel the timer firing */
+ if (ctx->timer_handle){
+ NR_async_timer_cancel(ctx->timer_handle);
+ ctx->timer_handle=0;
+ }
+
+ /* Mark cancelled so we ignore any returned messsages */
+ ctx->state=NR_STUN_CLIENT_STATE_CANCELLED;
+ return(0);
+}
+
+int nr_stun_client_wait(nr_stun_client_ctx *ctx)
+ {
+ nr_stun_client_cancel(ctx);
+ ctx->state=NR_STUN_CLIENT_STATE_WAITING;
+
+ ctx->request_ct = ctx->maximum_transmits;
+ ctx->timeout_ms = ctx->maximum_transmits_timeout_ms;
+ NR_ASYNC_TIMER_SET(ctx->timeout_ms, nr_stun_client_timer_expired_cb, ctx, &ctx->timer_handle);
+
+ return(0);
+ }
+
+int nr_stun_client_failed(nr_stun_client_ctx *ctx)
+ {
+ nr_stun_client_cancel(ctx);
+ ctx->state=NR_STUN_CLIENT_STATE_FAILED;
+ nr_stun_client_fire_finished_cb(ctx);
+ return(0);
+ }
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_client_ctx.h b/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_client_ctx.h
new file mode 100644
index 0000000000..0cc9045e2a
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_client_ctx.h
@@ -0,0 +1,200 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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 _stun_client_ctx_h
+#define _stun_client_ctx_h
+
+/* forward declaration */
+typedef struct nr_stun_client_ctx_ nr_stun_client_ctx;
+
+#include "stun.h"
+
+/* Checklist for adding new STUN transaction types
+
+ 1. Add new method type in stun.h (NR_METHOD_*)
+ 2. Add new MSGs in stun.h (NR_STUN_MSG_*)
+ 3. Add new messages to stun_util.c:nr_stun_msg_type
+ 4. Add new request type to stun_build.h
+ 4. Add new message builder to stun_build.c
+ 5. Add new response type to stun_client_ctx.h
+ 6. Add new arm to stun_client_ctx.c:nr_stun_client_send_request
+ 7. Add new arms to nr_stun_client_process_response
+ 8. Add new arms to stun_hint.c:nr_is_stun_message
+*/
+
+
+
+
+typedef union nr_stun_client_params_ {
+
+ nr_stun_client_stun_binding_request_params stun_binding_request;
+ nr_stun_client_stun_keepalive_params stun_keepalive;
+#ifdef USE_STUND_0_96
+ nr_stun_client_stun_binding_request_stund_0_96_params stun_binding_request_stund_0_96;
+#endif /* USE_STUND_0_96 */
+
+#ifdef USE_ICE
+ nr_stun_client_ice_binding_request_params ice_binding_request;
+#endif /* USE_ICE */
+
+#ifdef USE_TURN
+ nr_stun_client_allocate_request_params allocate_request;
+ nr_stun_client_refresh_request_params refresh_request;
+ nr_stun_client_permission_request_params permission_request;
+ nr_stun_client_send_indication_params send_indication;
+#endif /* USE_TURN */
+
+} nr_stun_client_params;
+
+typedef struct nr_stun_client_stun_binding_response_results_ {
+ nr_transport_addr mapped_addr;
+} nr_stun_client_stun_binding_response_results;
+
+typedef struct nr_stun_client_stun_binding_response_stund_0_96_results_ {
+ nr_transport_addr mapped_addr;
+} nr_stun_client_stun_binding_response_stund_0_96_results;
+
+#ifdef USE_ICE
+typedef struct nr_stun_client_ice_use_candidate_results_ {
+#ifdef WIN32 // silly VC++ gives error if no members
+ int dummy;
+#endif
+} nr_stun_client_ice_use_candidate_results;
+
+typedef struct nr_stun_client_ice_binding_response_results_ {
+ nr_transport_addr mapped_addr;
+} nr_stun_client_ice_binding_response_results;
+#endif /* USE_ICE */
+
+#ifdef USE_TURN
+typedef struct nr_stun_client_allocate_response_results_ {
+ nr_transport_addr relay_addr;
+ nr_transport_addr mapped_addr;
+ UINT4 lifetime_secs;
+} nr_stun_client_allocate_response_results;
+
+typedef struct nr_stun_client_refresh_response_results_ {
+ UINT4 lifetime_secs;
+} nr_stun_client_refresh_response_results;
+
+typedef struct nr_stun_client_permission_response_results_ {
+ UINT4 lifetime_secs;
+} nr_stun_client_permission_response_results;
+
+#endif /* USE_TURN */
+
+typedef union nr_stun_client_results_ {
+ nr_stun_client_stun_binding_response_results stun_binding_response;
+ nr_stun_client_stun_binding_response_stund_0_96_results stun_binding_response_stund_0_96;
+
+#ifdef USE_ICE
+ nr_stun_client_ice_use_candidate_results ice_use_candidate;
+ nr_stun_client_ice_binding_response_results ice_binding_response;
+#endif /* USE_ICE */
+
+#ifdef USE_TURN
+ nr_stun_client_allocate_response_results allocate_response;
+ nr_stun_client_refresh_response_results refresh_response;
+#endif /* USE_TURN */
+} nr_stun_client_results;
+
+struct nr_stun_client_ctx_ {
+ int state;
+#define NR_STUN_CLIENT_STATE_INITTED 0
+#define NR_STUN_CLIENT_STATE_RUNNING 1
+#define NR_STUN_CLIENT_STATE_DONE 2
+#define NR_STUN_CLIENT_STATE_FAILED 3
+#define NR_STUN_CLIENT_STATE_TIMED_OUT 4
+#define NR_STUN_CLIENT_STATE_CANCELLED 5
+#define NR_STUN_CLIENT_STATE_WAITING 6
+
+ int mode;
+#define NR_STUN_CLIENT_MODE_BINDING_REQUEST_SHORT_TERM_AUTH 1
+#define NR_STUN_CLIENT_MODE_BINDING_REQUEST_LONG_TERM_AUTH 2
+#define NR_STUN_CLIENT_MODE_BINDING_REQUEST_NO_AUTH 3
+#define NR_STUN_CLIENT_MODE_KEEPALIVE 4
+#define NR_STUN_CLIENT_MODE_BINDING_REQUEST_STUND_0_96 5
+#ifdef USE_ICE
+#define NR_ICE_CLIENT_MODE_USE_CANDIDATE 10
+#define NR_ICE_CLIENT_MODE_BINDING_REQUEST 11
+#endif /* USE_ICE */
+#ifdef USE_TURN
+#define NR_TURN_CLIENT_MODE_ALLOCATE_REQUEST 20
+#define NR_TURN_CLIENT_MODE_REFRESH_REQUEST 21
+#define NR_TURN_CLIENT_MODE_SEND_INDICATION 22
+#define NR_TURN_CLIENT_MODE_DATA_INDICATION 24
+#define NR_TURN_CLIENT_MODE_PERMISSION_REQUEST 25
+#endif /* USE_TURN */
+
+ char *label;
+ nr_transport_addr my_addr;
+ nr_transport_addr peer_addr;
+ nr_socket *sock;
+ nr_stun_client_auth_params auth_params;
+ nr_stun_client_params params;
+ nr_stun_client_results results;
+ char *nonce;
+ char *realm;
+ void *timer_handle;
+ UINT2 request_ct;
+ UINT2 retransmit_ct;
+ UINT4 rto_ms; /* retransmission time out */
+ double retransmission_backoff_factor;
+ UINT4 maximum_transmits;
+ UINT4 maximum_transmits_timeout_ms;
+ UINT4 mapped_addr_check_mask; /* What checks to run on mapped addresses */
+ int timeout_ms;
+ struct timeval timer_set;
+ NR_async_cb finished_cb;
+ void *cb_arg;
+ nr_stun_message *request;
+ nr_stun_message *response;
+ UINT2 error_code;
+};
+
+int nr_stun_client_ctx_create(char *label, nr_socket *sock, nr_transport_addr *peer, UINT4 RTO, nr_stun_client_ctx **ctxp);
+int nr_stun_client_start(nr_stun_client_ctx *ctx, int mode, NR_async_cb finished_cb, void *cb_arg);
+int nr_stun_client_restart(nr_stun_client_ctx* ctx,
+ const nr_transport_addr* peer_addr);
+int nr_stun_client_force_retransmit(nr_stun_client_ctx *ctx);
+int nr_stun_client_reset(nr_stun_client_ctx *ctx);
+int nr_stun_client_ctx_destroy(nr_stun_client_ctx **ctxp);
+int nr_stun_transport_addr_check(nr_transport_addr* addr, UINT4 mask);
+int nr_stun_client_process_response(nr_stun_client_ctx *ctx, UCHAR *msg, int len, nr_transport_addr *peer_addr);
+int nr_stun_client_cancel(nr_stun_client_ctx *ctx);
+int nr_stun_client_wait(nr_stun_client_ctx *ctx);
+int nr_stun_client_failed(nr_stun_client_ctx *ctx);
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_codec.c b/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_codec.c
new file mode 100644
index 0000000000..ae748a667b
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_codec.c
@@ -0,0 +1,1550 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+*/
+
+#include <errno.h>
+#include <csi_platform.h>
+
+#ifdef WIN32
+#include <winsock2.h>
+#include <stdlib.h>
+#include <io.h>
+#include <time.h>
+#else /* UNIX */
+#include <string.h>
+#endif /* end UNIX */
+#include <assert.h>
+#include <stddef.h>
+
+#include "nr_api.h"
+#include "stun.h"
+#include "byteorder.h"
+#include "r_crc32.h"
+#include "nr_crypto.h"
+
+#define NR_STUN_IPV4_FAMILY 0x01
+#define NR_STUN_IPV6_FAMILY 0x02
+
+#define SKIP_ATTRIBUTE_DECODE -1
+
+static int nr_stun_find_attr_info(UINT2 type, nr_stun_attr_info **info);
+
+static int nr_stun_fix_attribute_ordering(nr_stun_message *msg);
+
+static int nr_stun_encode_htons(UINT2 data, size_t buflen, UCHAR *buf, size_t *offset);
+static int nr_stun_encode_htonl(UINT4 data, size_t buflen, UCHAR *buf, size_t *offset);
+static int nr_stun_encode_htonll(UINT8 data, size_t buflen, UCHAR *buf, size_t *offset);
+static int nr_stun_encode(UCHAR *data, size_t length, size_t buflen, UCHAR *buf, size_t *offset);
+
+static int nr_stun_decode_htons(UCHAR *buf, size_t buflen, size_t *offset, UINT2 *data);
+static int nr_stun_decode_htonl(UCHAR *buf, size_t buflen, size_t *offset, UINT4 *data);
+static int nr_stun_decode_htonll(UCHAR *buf, size_t buflen, size_t *offset, UINT8 *data);
+static int nr_stun_decode(size_t length, UCHAR *buf, size_t buflen, size_t *offset, UCHAR *data);
+
+static int nr_stun_attr_string_illegal(nr_stun_attr_info *attr_info, size_t len, void *data, size_t max_bytes, size_t max_chars);
+
+static int nr_stun_attr_error_code_illegal(nr_stun_attr_info *attr_info, size_t attrlen, void *data);
+static int nr_stun_attr_nonce_illegal(nr_stun_attr_info *attr_info, size_t attrlen, void *data);
+static int nr_stun_attr_realm_illegal(nr_stun_attr_info *attr_info, size_t attrlen, void *data);
+static int nr_stun_attr_server_illegal(nr_stun_attr_info *attr_info, size_t attrlen, void *data);
+static int nr_stun_attr_username_illegal(nr_stun_attr_info *attr_info, size_t attrlen, void *data);
+static int
+nr_stun_attr_codec_fingerprint_decode(nr_stun_attr_info *attr_info, size_t attrlen, UCHAR *buf, size_t offset, size_t buflen, void *data);
+
+
+int
+nr_stun_encode_htons(UINT2 data, size_t buflen, UCHAR *buf, size_t *offset)
+{
+ UINT2 d = htons(data);
+
+ if (*offset + sizeof(d) >= buflen) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Attempted buffer overrun: %d + %zd >= %d", *offset, sizeof(d), buflen);
+ return R_BAD_DATA;
+ }
+
+ memcpy(&buf[*offset], &d, sizeof(d));
+ *offset += sizeof(d);
+
+ return 0;
+}
+
+int
+nr_stun_encode_htonl(UINT4 data, size_t buflen, UCHAR *buf, size_t *offset)
+{
+ UINT4 d = htonl(data);
+
+ if (*offset + sizeof(d) > buflen) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Attempted buffer overrun: %d + %zd > %d", *offset, sizeof(d), buflen);
+ return R_BAD_DATA;
+ }
+
+ memcpy(&buf[*offset], &d, sizeof(d));
+ *offset += sizeof(d);
+
+ return 0;
+}
+
+int
+nr_stun_encode_htonll(UINT8 data, size_t buflen, UCHAR *buf, size_t *offset)
+{
+ UINT8 d = nr_htonll(data);
+
+ if (*offset + sizeof(d) > buflen) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Attempted buffer overrun: %d + %zd > %d", *offset, sizeof(d), buflen);
+ return R_BAD_DATA;
+ }
+
+ memcpy(&buf[*offset], &d, sizeof(d));
+ *offset += sizeof(d);
+
+ return 0;
+}
+
+int
+nr_stun_encode(UCHAR *data, size_t length, size_t buflen, UCHAR *buf, size_t *offset)
+{
+ if (*offset + length > buflen) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Attempted buffer overrun: %d + %d > %d", *offset, length, buflen);
+ return R_BAD_DATA;
+ }
+
+ memcpy(&buf[*offset], data, length);
+ *offset += length;
+
+ return 0;
+}
+
+
+int
+nr_stun_decode_htons(UCHAR *buf, size_t buflen, size_t *offset, UINT2 *data)
+{
+ UINT2 d;
+
+ if (*offset + sizeof(d) > buflen) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Attempted buffer overrun: %d + %zd > %d", *offset, sizeof(d), buflen);
+ return R_BAD_DATA;
+ }
+
+ memcpy(&d, &buf[*offset], sizeof(d));
+ *offset += sizeof(d);
+ *data = htons(d);
+
+ return 0;
+}
+
+int
+nr_stun_decode_htonl(UCHAR *buf, size_t buflen, size_t *offset, UINT4 *data)
+{
+ UINT4 d;
+
+ if (*offset + sizeof(d) > buflen) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Attempted buffer overrun: %d + %zd > %d", *offset, sizeof(d), buflen);
+ return R_BAD_DATA;
+ }
+
+ memcpy(&d, &buf[*offset], sizeof(d));
+ *offset += sizeof(d);
+ *data = htonl(d);
+
+ return 0;
+}
+
+int
+nr_stun_decode_htonll(UCHAR *buf, size_t buflen, size_t *offset, UINT8 *data)
+{
+ UINT8 d;
+
+ if (*offset + sizeof(d) > buflen) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Attempted buffer overrun: %d + %zd > %d", *offset, sizeof(d), buflen);
+ return R_BAD_DATA;
+ }
+
+ memcpy(&d, &buf[*offset], sizeof(d));
+ *offset += sizeof(d);
+ *data = nr_htonll(d);
+
+ return 0;
+}
+
+int
+nr_stun_decode(size_t length, UCHAR *buf, size_t buflen, size_t *offset, UCHAR *data)
+{
+ if (*offset + length > buflen) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Attempted buffer overrun: %d + %d > %d", *offset, length, buflen);
+ return R_BAD_DATA;
+ }
+
+ memcpy(data, &buf[*offset], length);
+ *offset += length;
+
+ return 0;
+}
+
+/**
+ * The argument must be a non-null pointer to a zero-terminated string.
+ *
+ * If the argument is valid UTF-8, returns the number of code points in the
+ * string excluding the zero-terminator.
+ *
+ * If the argument is invalid UTF-8, returns a lower bound for the number of
+ * code points in the string. (If UTF-8 error handling was performed on the
+ * string, new REPLACEMENT CHARACTER code points could be introduced in
+ * a way that would increase the total number of code points compared to
+ * what this function counts.)
+ */
+size_t
+nr_count_utf8_code_points_without_validation(const char *s) {
+ size_t nchars = 0;
+ char c;
+ while ((c = *s)) {
+ if ((c & 0xC0) != 0x80) {
+ ++nchars;
+ }
+ ++s;
+ }
+ return nchars;
+}
+
+int
+nr_stun_attr_string_illegal(nr_stun_attr_info *attr_info, size_t len, void *data, size_t max_bytes, size_t max_chars)
+{
+ int _status;
+ char *s = data;
+ size_t nchars;
+
+ if (len > max_bytes) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "%s is too large: %d bytes", attr_info->name, len);
+ ABORT(R_FAILED);
+ }
+
+ nchars = nr_count_utf8_code_points_without_validation(s);
+ if (nchars > max_chars) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "%s is too large: %zd characters", attr_info->name, nchars);
+ ABORT(R_FAILED);
+ }
+
+ _status = 0;
+ abort:
+ return _status;
+}
+
+int
+nr_stun_attr_error_code_illegal(nr_stun_attr_info *attr_info, size_t attrlen, void *data)
+{
+ int r,_status;
+ nr_stun_attr_error_code *ec = data;
+
+ if (ec->number < 300 || ec->number > 699)
+ ABORT(R_FAILED);
+
+ if ((r=nr_stun_attr_string_illegal(attr_info, strlen(ec->reason), ec->reason, NR_STUN_MAX_ERROR_CODE_REASON_BYTES, NR_STUN_MAX_ERROR_CODE_REASON_CHARS)))
+ ABORT(r);
+
+ _status = 0;
+ abort:
+ return _status;
+}
+
+int
+nr_stun_attr_nonce_illegal(nr_stun_attr_info *attr_info, size_t attrlen, void *data)
+{
+ return nr_stun_attr_string_illegal(attr_info, attrlen, data, NR_STUN_MAX_NONCE_BYTES, NR_STUN_MAX_NONCE_CHARS);
+}
+
+int
+nr_stun_attr_realm_illegal(nr_stun_attr_info *attr_info, size_t attrlen, void *data)
+{
+ return nr_stun_attr_string_illegal(attr_info, attrlen, data, NR_STUN_MAX_REALM_BYTES, NR_STUN_MAX_REALM_CHARS);
+}
+
+int
+nr_stun_attr_server_illegal(nr_stun_attr_info *attr_info, size_t attrlen, void *data)
+{
+ return nr_stun_attr_string_illegal(attr_info, attrlen, data, NR_STUN_MAX_SERVER_BYTES, NR_STUN_MAX_SERVER_CHARS);
+}
+
+int
+nr_stun_attr_username_illegal(nr_stun_attr_info *attr_info, size_t attrlen, void *data)
+{
+ return nr_stun_attr_string_illegal(attr_info, attrlen, data, NR_STUN_MAX_USERNAME_BYTES, -1);
+}
+
+static int
+nr_stun_attr_codec_UCHAR_print(nr_stun_attr_info *attr_info, char *msg, void *data)
+{
+ r_log(NR_LOG_STUN, LOG_DEBUG, "%s %s: %u", msg, attr_info->name, *(UCHAR*)data);
+ return 0;
+}
+
+static int
+nr_stun_attr_codec_UCHAR_encode(nr_stun_attr_info *attr_info, void *data, size_t offset, size_t buflen, UCHAR *buf, size_t *attrlen)
+{
+ int start = offset;
+ UINT4 tmp = *((UCHAR *)data);
+ tmp <<= 24;
+
+ if (nr_stun_encode_htons(attr_info->type , buflen, buf, &offset)
+ || nr_stun_encode_htons(sizeof(UINT4) , buflen, buf, &offset)
+ || nr_stun_encode_htonl(tmp , buflen, buf, &offset))
+ return R_FAILED;
+
+ *attrlen = offset - start;
+
+ return 0;
+}
+
+static int
+nr_stun_attr_codec_UCHAR_decode(nr_stun_attr_info *attr_info, size_t attrlen, UCHAR *buf, size_t offset, size_t buflen, void *data)
+{
+ UINT4 tmp;
+
+ if (attrlen != sizeof(UINT4)) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Integer is illegal size: %d", attrlen);
+ return R_FAILED;
+ }
+
+ if (nr_stun_decode_htonl(buf, buflen, &offset, &tmp))
+ return R_FAILED;
+
+ *((UCHAR *)data) = (tmp >> 24) & 0xff;
+
+ return 0;
+}
+
+nr_stun_attr_codec nr_stun_attr_codec_UCHAR = {
+ "UCHAR",
+ nr_stun_attr_codec_UCHAR_print,
+ nr_stun_attr_codec_UCHAR_encode,
+ nr_stun_attr_codec_UCHAR_decode
+};
+
+static int
+nr_stun_attr_codec_UINT4_print(nr_stun_attr_info *attr_info, char *msg, void *data)
+{
+ r_log(NR_LOG_STUN, LOG_DEBUG, "%s %s: %u", msg, attr_info->name, *(UINT4*)data);
+ return 0;
+}
+
+static int
+nr_stun_attr_codec_UINT4_encode(nr_stun_attr_info *attr_info, void *data, size_t offset, size_t buflen, UCHAR *buf, size_t *attrlen)
+{
+ int start = offset;
+
+ if (nr_stun_encode_htons(attr_info->type , buflen, buf, &offset)
+ || nr_stun_encode_htons(sizeof(UINT4) , buflen, buf, &offset)
+ || nr_stun_encode_htonl(*(UINT4*)data , buflen, buf, &offset))
+ return R_FAILED;
+
+ *attrlen = offset - start;
+
+ return 0;
+}
+
+static int
+nr_stun_attr_codec_UINT4_decode(nr_stun_attr_info *attr_info, size_t attrlen, UCHAR *buf, size_t offset, size_t buflen, void *data)
+{
+ if (attrlen != sizeof(UINT4)) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Integer is illegal size: %d", attrlen);
+ return R_FAILED;
+ }
+
+ if (nr_stun_decode_htonl(buf, buflen, &offset, (UINT4*)data))
+ return R_FAILED;
+
+ return 0;
+}
+
+nr_stun_attr_codec nr_stun_attr_codec_UINT4 = {
+ "UINT4",
+ nr_stun_attr_codec_UINT4_print,
+ nr_stun_attr_codec_UINT4_encode,
+ nr_stun_attr_codec_UINT4_decode
+};
+
+static int
+nr_stun_attr_codec_UINT8_print(nr_stun_attr_info *attr_info, char *msg, void *data)
+{
+ r_log(NR_LOG_STUN, LOG_DEBUG, "%s %s: %llu", msg, attr_info->name, *(UINT8*)data);
+ return 0;
+}
+
+static int
+nr_stun_attr_codec_UINT8_encode(nr_stun_attr_info *attr_info, void *data, size_t offset, size_t buflen, UCHAR *buf, size_t *attrlen)
+{
+ int start = offset;
+
+ if (nr_stun_encode_htons(attr_info->type , buflen, buf, &offset)
+ || nr_stun_encode_htons(sizeof(UINT8) , buflen, buf, &offset)
+ || nr_stun_encode_htonll(*(UINT8*)data , buflen, buf, &offset))
+ return R_FAILED;
+
+ *attrlen = offset - start;
+
+ return 0;
+}
+
+static int
+nr_stun_attr_codec_UINT8_decode(nr_stun_attr_info *attr_info, size_t attrlen, UCHAR *buf, size_t offset, size_t buflen, void *data)
+{
+ if (attrlen != sizeof(UINT8)) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Integer is illegal size: %d", attrlen);
+ return R_FAILED;
+ }
+
+ if (nr_stun_decode_htonll(buf, buflen, &offset, (UINT8*)data))
+ return R_FAILED;
+
+ return 0;
+}
+
+nr_stun_attr_codec nr_stun_attr_codec_UINT8 = {
+ "UINT8",
+ nr_stun_attr_codec_UINT8_print,
+ nr_stun_attr_codec_UINT8_encode,
+ nr_stun_attr_codec_UINT8_decode
+};
+
+static int
+nr_stun_attr_codec_addr_print(nr_stun_attr_info *attr_info, char *msg, void *data)
+{
+ r_log(NR_LOG_STUN, LOG_DEBUG, "%s %s: %s", msg, attr_info->name, ((nr_transport_addr*)data)->as_string);
+ return 0;
+}
+
+static int
+nr_stun_attr_codec_addr_encode(nr_stun_attr_info *attr_info, void *data, size_t offset, size_t buflen, UCHAR *buf, size_t *attrlen)
+{
+ int r,_status;
+ int start = offset;
+ nr_transport_addr *addr = data;
+ UCHAR pad = '\0';
+ UCHAR family;
+
+ if ((r=nr_stun_encode_htons(attr_info->type, buflen, buf, &offset)))
+ ABORT(r);
+
+ switch (addr->ip_version) {
+ case NR_IPV4:
+ family = NR_STUN_IPV4_FAMILY;
+ if (nr_stun_encode_htons(8 , buflen, buf, &offset)
+ || nr_stun_encode(&pad, 1 , buflen, buf, &offset)
+ || nr_stun_encode(&family, 1 , buflen, buf, &offset)
+ || nr_stun_encode_htons(ntohs(addr->u.addr4.sin_port), buflen, buf, &offset)
+ || nr_stun_encode_htonl(ntohl(addr->u.addr4.sin_addr.s_addr), buflen, buf, &offset))
+ ABORT(R_FAILED);
+ break;
+
+ case NR_IPV6:
+ family = NR_STUN_IPV6_FAMILY;
+ if (nr_stun_encode_htons(20 , buflen, buf, &offset)
+ || nr_stun_encode(&pad, 1 , buflen, buf, &offset)
+ || nr_stun_encode(&family, 1 , buflen, buf, &offset)
+ || nr_stun_encode_htons(ntohs(addr->u.addr6.sin6_port), buflen, buf, &offset)
+ || nr_stun_encode(addr->u.addr6.sin6_addr.s6_addr, 16, buflen, buf, &offset))
+ ABORT(R_FAILED);
+ break;
+
+ default:
+ assert(0);
+ ABORT(R_INTERNAL);
+ break;
+ }
+
+ *attrlen = offset - start;
+
+ _status = 0;
+ abort:
+ return _status;
+}
+
+static int
+nr_stun_attr_codec_addr_decode(nr_stun_attr_info *attr_info, size_t attrlen, UCHAR *buf, size_t offset, size_t buflen, void *data)
+{
+ int _status;
+ UCHAR pad;
+ UCHAR family;
+ UINT2 port;
+ UINT4 addr4;
+ struct in6_addr addr6;
+ nr_transport_addr *result = data;
+
+ if (nr_stun_decode(1, buf, buflen, &offset, &pad)
+ || nr_stun_decode(1, buf, buflen, &offset, &family))
+ ABORT(R_FAILED);
+
+ switch (family) {
+ case NR_STUN_IPV4_FAMILY:
+ if (attrlen != 8) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Illegal attribute length: %d", attrlen);
+ ABORT(R_FAILED);
+ }
+
+ if (nr_stun_decode_htons(buf, buflen, &offset, &port)
+ || nr_stun_decode_htonl(buf, buflen, &offset, &addr4))
+ ABORT(R_FAILED);
+
+ if (nr_ip4_port_to_transport_addr(addr4, port, IPPROTO_UDP, result))
+ ABORT(R_FAILED);
+ break;
+
+ case NR_STUN_IPV6_FAMILY:
+ if (attrlen != 20) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Illegal attribute length: %d", attrlen);
+ ABORT(R_FAILED);
+ }
+
+ if (nr_stun_decode_htons(buf, buflen, &offset, &port)
+ || nr_stun_decode(16, buf, buflen, &offset, addr6.s6_addr))
+ ABORT(R_FAILED);
+
+ if (nr_ip6_port_to_transport_addr(&addr6, port, IPPROTO_UDP, result))
+ ABORT(R_FAILED);
+ break;
+
+ default:
+ r_log(NR_LOG_STUN, LOG_WARNING, "Illegal address family: %d", family);
+ ABORT(R_FAILED);
+ break;
+ }
+
+ _status = 0;
+ abort:
+ return _status;
+}
+
+nr_stun_attr_codec nr_stun_attr_codec_addr = {
+ "addr",
+ nr_stun_attr_codec_addr_print,
+ nr_stun_attr_codec_addr_encode,
+ nr_stun_attr_codec_addr_decode
+};
+
+static int
+nr_stun_attr_codec_data_print(nr_stun_attr_info *attr_info, char *msg, void *data)
+{
+ nr_stun_attr_data *d = data;
+ r_dump(NR_LOG_STUN, LOG_DEBUG, attr_info->name, (char*)d->data, d->length);
+ return 0;
+}
+
+static int
+nr_stun_attr_codec_data_encode(nr_stun_attr_info *attr_info, void *data, size_t offset, size_t buflen, UCHAR *buf, size_t *attrlen)
+{
+ nr_stun_attr_data *d = data;
+ int start = offset;
+
+ if (nr_stun_encode_htons(attr_info->type , buflen, buf, &offset)
+ || nr_stun_encode_htons(d->length , buflen, buf, &offset)
+ || nr_stun_encode(d->data, d->length , buflen, buf, &offset))
+ return R_FAILED;
+
+ *attrlen = offset - start;
+
+ return 0;
+}
+
+static int
+nr_stun_attr_codec_data_decode(nr_stun_attr_info *attr_info, size_t attrlen, UCHAR *buf, size_t offset, size_t buflen, void *data)
+{
+ int _status;
+ nr_stun_attr_data *result = data;
+
+ /* -1 because it is going to be null terminated just to be safe */
+ if (attrlen >= (sizeof(result->data) - 1)) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Too much data: %d bytes", attrlen);
+ ABORT(R_FAILED);
+ }
+
+ if (nr_stun_decode(attrlen, buf, buflen, &offset, result->data))
+ ABORT(R_FAILED);
+
+ result->length = attrlen;
+ result->data[attrlen] = '\0'; /* just to be nice */
+
+ _status=0;
+ abort:
+ return _status;
+}
+
+nr_stun_attr_codec nr_stun_attr_codec_data = {
+ "data",
+ nr_stun_attr_codec_data_print,
+ nr_stun_attr_codec_data_encode,
+ nr_stun_attr_codec_data_decode
+};
+
+static int
+nr_stun_attr_codec_error_code_print(nr_stun_attr_info *attr_info, char *msg, void *data)
+{
+ nr_stun_attr_error_code *error_code = data;
+ r_log(NR_LOG_STUN, LOG_DEBUG, "%s %s: %d %s",
+ msg, attr_info->name, error_code->number,
+ error_code->reason);
+ return 0;
+}
+
+static int
+nr_stun_attr_codec_error_code_encode(nr_stun_attr_info *attr_info, void *data, size_t offset, size_t buflen, UCHAR *buf, size_t *attrlen)
+{
+ nr_stun_attr_error_code *error_code = data;
+ int start = offset;
+ int length = strlen(error_code->reason);
+ UCHAR pad[2] = { 0 };
+ UCHAR class = error_code->number / 100;
+ UCHAR number = error_code->number % 100;
+
+ if (nr_stun_encode_htons(attr_info->type , buflen, buf, &offset)
+ || nr_stun_encode_htons(4 + length , buflen, buf, &offset)
+ || nr_stun_encode(pad, 2 , buflen, buf, &offset)
+ || nr_stun_encode(&class, 1 , buflen, buf, &offset)
+ || nr_stun_encode(&number, 1 , buflen, buf, &offset)
+ || nr_stun_encode((UCHAR*)error_code->reason, length, buflen, buf, &offset))
+ return R_FAILED;
+
+ *attrlen = offset - start;
+
+ return 0;
+}
+
+static int
+nr_stun_attr_codec_error_code_decode(nr_stun_attr_info *attr_info, size_t attrlen, UCHAR *buf, size_t offset, size_t buflen, void *data)
+{
+ int _status;
+ nr_stun_attr_error_code *result = data;
+ UCHAR pad[2];
+ UCHAR class;
+ UCHAR number;
+ size_t size_reason;
+
+ if (nr_stun_decode(2, buf, buflen, &offset, pad)
+ || nr_stun_decode(1, buf, buflen, &offset, &class)
+ || nr_stun_decode(1, buf, buflen, &offset, &number))
+ ABORT(R_FAILED);
+
+ result->number = (class * 100) + number;
+
+ size_reason = attrlen - 4;
+
+ /* -1 because the string will be null terminated */
+ if (size_reason > (sizeof(result->reason) - 1)) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Reason is too large, truncating");
+ /* don't fail, but instead truncate the reason */
+ size_reason = sizeof(result->reason) - 1;
+ }
+
+ if (nr_stun_decode(size_reason, buf, buflen, &offset, (UCHAR*)result->reason))
+ ABORT(R_FAILED);
+ result->reason[size_reason] = '\0';
+
+ _status=0;
+ abort:
+ return _status;
+}
+
+nr_stun_attr_codec nr_stun_attr_codec_error_code = {
+ "error_code",
+ nr_stun_attr_codec_error_code_print,
+ nr_stun_attr_codec_error_code_encode,
+ nr_stun_attr_codec_error_code_decode
+};
+
+static int
+nr_stun_attr_codec_fingerprint_print(nr_stun_attr_info *attr_info, char *msg, void *data)
+{
+ nr_stun_attr_fingerprint *fingerprint = data;
+ r_log(NR_LOG_STUN, LOG_DEBUG, "%s %s: %08x", msg, attr_info->name, fingerprint->checksum);
+ return 0;
+}
+
+static int
+nr_stun_attr_codec_fingerprint_encode(nr_stun_attr_info *attr_info, void *data, size_t offset, size_t buflen, UCHAR *buf, size_t *attrlen)
+{
+ UINT4 checksum;
+ nr_stun_attr_fingerprint *fingerprint = data;
+ nr_stun_message_header *header = (nr_stun_message_header*)buf;
+
+ /* the length must include the FINGERPRINT attribute when computing
+ * the fingerprint */
+ header->length = ntohs(header->length);
+ header->length += 8; /* Fingerprint */
+ header->length = htons(header->length);
+
+ if (r_crc32((char*)buf, offset, &checksum)) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Unable to compute fingerprint");
+ return R_FAILED;
+ }
+
+ fingerprint->checksum = checksum ^ 0x5354554e;
+
+ r_log(NR_LOG_STUN, LOG_DEBUG, "Computed FINGERPRINT %08x", fingerprint->checksum);
+
+ fingerprint->valid = 1;
+ return nr_stun_attr_codec_UINT4.encode(attr_info, &fingerprint->checksum, offset, buflen, buf, attrlen);
+}
+
+static int
+nr_stun_attr_codec_fingerprint_decode(nr_stun_attr_info *attr_info, size_t attrlen, UCHAR *buf, size_t offset, size_t buflen, void *data)
+{
+ int r,_status;
+ nr_stun_attr_fingerprint *fingerprint = data;
+ nr_stun_message_header *header = (nr_stun_message_header*)buf;
+ size_t length;
+ UINT4 checksum;
+
+ if ((r=nr_stun_attr_codec_UINT4.decode(attr_info, attrlen, buf, offset, buflen, &fingerprint->checksum)))
+ ABORT(r);
+
+ offset -= 4; /* rewind to before the length and type fields */
+
+ /* the length must include the FINGERPRINT attribute when computing
+ * the fingerprint */
+ length = offset; /* right before FINGERPRINT */
+ length -= sizeof(*header); /* remove header length */
+ length += 8; /* add length of Fingerprint */
+ header->length = htons(length);
+
+ /* make sure FINGERPRINT is final attribute in message */
+ if (length + sizeof(*header) != buflen) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Fingerprint is not final attribute in message");
+ ABORT(R_FAILED);
+ }
+
+ if (r_crc32((char*)buf, offset, &checksum)) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Unable to compute fingerprint");
+ ABORT(R_FAILED);
+ }
+
+ fingerprint->valid = (fingerprint->checksum == (checksum ^ 0x5354554e));
+
+ r_log(NR_LOG_STUN, LOG_DEBUG, "Computed FINGERPRINT %08x", (checksum ^ 0x5354554e));
+ if (! fingerprint->valid)
+ r_log(NR_LOG_STUN, LOG_WARNING, "Invalid FINGERPRINT %08x", fingerprint->checksum);
+
+ _status=0;
+ abort:
+ return _status;
+}
+
+nr_stun_attr_codec nr_stun_attr_codec_fingerprint = {
+ "fingerprint",
+ nr_stun_attr_codec_fingerprint_print,
+ nr_stun_attr_codec_fingerprint_encode,
+ nr_stun_attr_codec_fingerprint_decode
+};
+
+static int
+nr_stun_attr_codec_flag_print(nr_stun_attr_info *attr_info, char *msg, void *data)
+{
+ r_log(NR_LOG_STUN, LOG_DEBUG, "%s %s: on", msg, attr_info->name);
+ return 0;
+}
+
+static int
+nr_stun_attr_codec_flag_encode(nr_stun_attr_info *attr_info, void *data, size_t offset, size_t buflen, UCHAR *buf, size_t *attrlen)
+{
+ int start = offset;
+
+ if (nr_stun_encode_htons(attr_info->type , buflen, buf, &offset)
+ || nr_stun_encode_htons(0 , buflen, buf, &offset))
+ return R_FAILED;
+
+ *attrlen = offset - start;
+
+ return 0;
+}
+
+static int
+nr_stun_attr_codec_flag_decode(nr_stun_attr_info *attr_info, size_t attrlen, UCHAR *buf, size_t offset, size_t buflen, void *data)
+{
+ if (attrlen != 0) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Illegal flag length: %d", attrlen);
+ return R_FAILED;
+ }
+
+ return 0;
+}
+
+nr_stun_attr_codec nr_stun_attr_codec_flag = {
+ "flag",
+ nr_stun_attr_codec_flag_print,
+ nr_stun_attr_codec_flag_encode,
+ nr_stun_attr_codec_flag_decode
+};
+
+static int
+nr_stun_attr_codec_message_integrity_print(nr_stun_attr_info *attr_info, char *msg, void *data)
+{
+ nr_stun_attr_message_integrity *integrity = data;
+ r_dump(NR_LOG_STUN, LOG_DEBUG, attr_info->name, (char*)integrity->hash, sizeof(integrity->hash));
+ return 0;
+}
+
+static int
+nr_stun_compute_message_integrity(UCHAR *buf, int offset, UCHAR *password, int passwordlen, UCHAR *computedHMAC)
+{
+ int r,_status;
+ UINT2 hold;
+ UINT2 length;
+ nr_stun_message_header *header;
+
+ r_log(NR_LOG_STUN, LOG_DEBUG, "Computing MESSAGE-INTEGRITY");
+
+ header = (nr_stun_message_header*)buf;
+ hold = header->length;
+
+ /* adjust the length of the message */
+ length = offset;
+ length -= sizeof(*header);
+ length += 24; /* for MESSAGE-INTEGRITY attribute */
+ header->length = htons(length);
+
+ if ((r=nr_crypto_hmac_sha1((UCHAR*)password, passwordlen,
+ buf, offset, computedHMAC)))
+ ABORT(r);
+
+ r_dump(NR_LOG_STUN, LOG_DEBUG, "Computed MESSAGE-INTEGRITY ", (char*)computedHMAC, 20);
+
+ _status=0;
+ abort:
+ header->length = hold;
+ return _status;
+}
+
+static int
+nr_stun_attr_codec_message_integrity_encode(nr_stun_attr_info *attr_info, void *data, size_t offset, size_t buflen, UCHAR *buf, size_t *attrlen)
+{
+ int start = offset;
+ nr_stun_attr_message_integrity *integrity = data;
+
+ if (nr_stun_compute_message_integrity(buf, offset, integrity->password, integrity->passwordlen, integrity->hash))
+ return R_FAILED;
+
+ if (nr_stun_encode_htons(attr_info->type , buflen, buf, &offset)
+ || nr_stun_encode_htons(sizeof(integrity->hash) , buflen, buf, &offset)
+ || nr_stun_encode(integrity->hash, sizeof(integrity->hash) , buflen, buf, &offset))
+ return R_FAILED;
+
+ *attrlen = offset - start;
+
+ return 0;
+}
+
+static int
+nr_stun_attr_codec_message_integrity_decode(nr_stun_attr_info *attr_info, size_t attrlen, UCHAR *buf, size_t offset, size_t buflen, void *data)
+{
+ int _status;
+ int start;
+ nr_stun_attr_message_integrity *result = data;
+ UCHAR computedHMAC[20];
+
+ result->valid = 0;
+
+ if (attrlen != 20) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "%s must be 20 bytes, not %d", attr_info->name, attrlen);
+ ABORT(R_FAILED);
+ }
+
+ start = offset - 4; /* rewind to before the length and type fields */
+ if (start < 0)
+ ABORT(R_INTERNAL);
+
+ if (nr_stun_decode(attrlen, buf, buflen, &offset, result->hash))
+ ABORT(R_FAILED);
+
+ if (result->unknown_user) {
+ result->valid = 0;
+ }
+ else {
+ if (nr_stun_compute_message_integrity(buf, start, result->password, result->passwordlen, computedHMAC))
+ ABORT(R_FAILED);
+
+ assert(sizeof(computedHMAC) == sizeof(result->hash));
+
+ result->valid = (memcmp(computedHMAC, result->hash, 20) == 0);
+ }
+
+ _status=0;
+ abort:
+ return _status;
+}
+
+nr_stun_attr_codec nr_stun_attr_codec_message_integrity = {
+ "message_integrity",
+ nr_stun_attr_codec_message_integrity_print,
+ nr_stun_attr_codec_message_integrity_encode,
+ nr_stun_attr_codec_message_integrity_decode
+};
+
+static int
+nr_stun_attr_codec_noop_decode(nr_stun_attr_info *attr_info, size_t attrlen, UCHAR *buf, size_t offset, size_t buflen, void *data)
+{
+ return SKIP_ATTRIBUTE_DECODE;
+}
+
+nr_stun_attr_codec nr_stun_attr_codec_noop = {
+ "NOOP",
+ 0, /* ignore, never print these attributes */
+ 0, /* ignore, never encode these attributes */
+ nr_stun_attr_codec_noop_decode
+};
+
+static int
+nr_stun_attr_codec_quoted_string_print(nr_stun_attr_info *attr_info, char *msg, void *data)
+{
+ r_log(NR_LOG_STUN, LOG_DEBUG, "%s %s: %s",
+ msg, attr_info->name, (char*)data);
+ return 0;
+}
+
+static int
+nr_stun_attr_codec_quoted_string_encode(nr_stun_attr_info *attr_info, void *data, size_t offset, size_t buflen, UCHAR *buf, size_t *attrlen)
+{
+//TODO: !nn! syntax check, conversion if not quoted already?
+//We'll just restrict this in the API -- EKR
+ return nr_stun_attr_codec_string.encode(attr_info, data, offset, buflen, buf, attrlen);
+}
+
+static int
+nr_stun_attr_codec_quoted_string_decode(nr_stun_attr_info *attr_info, size_t attrlen, UCHAR *buf, size_t offset, size_t buflen, void *data)
+{
+//TODO: !nn! I don't see any need to unquote this but we may
+//find one later -- EKR
+ return nr_stun_attr_codec_string.decode(attr_info, attrlen, buf, offset, buflen, data);
+}
+
+nr_stun_attr_codec nr_stun_attr_codec_quoted_string = {
+ "quoted_string",
+ nr_stun_attr_codec_quoted_string_print,
+ nr_stun_attr_codec_quoted_string_encode,
+ nr_stun_attr_codec_quoted_string_decode
+};
+
+static int
+nr_stun_attr_codec_string_print(nr_stun_attr_info *attr_info, char *msg, void *data)
+{
+ r_log(NR_LOG_STUN, LOG_DEBUG, "%s %s: %s",
+ msg, attr_info->name, (char*)data);
+ return 0;
+}
+
+static int
+nr_stun_attr_codec_string_encode(nr_stun_attr_info *attr_info, void *data, size_t offset, size_t buflen, UCHAR *buf, size_t *attrlen)
+{
+ int start = offset;
+ char *str = data;
+ int length = strlen(str);
+
+ if (nr_stun_encode_htons(attr_info->type , buflen, buf, &offset)
+ || nr_stun_encode_htons(length , buflen, buf, &offset)
+ || nr_stun_encode((UCHAR*)str, length , buflen, buf, &offset))
+ return R_FAILED;
+
+ *attrlen = offset - start;
+
+ return 0;
+}
+
+static int
+nr_stun_attr_codec_string_decode(nr_stun_attr_info *attr_info, size_t attrlen, UCHAR *buf, size_t offset, size_t buflen, void *data)
+{
+ int _status;
+ char *result = data;
+
+ /* actual enforcement of the specific string size happens elsewhere */
+ if (attrlen >= NR_STUN_MAX_STRING_SIZE) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "String is too large: %d bytes", attrlen);
+ ABORT(R_FAILED);
+ }
+
+ if (nr_stun_decode(attrlen, buf, buflen, &offset, (UCHAR*)result))
+ ABORT(R_FAILED);
+ result[attrlen] = '\0'; /* just to be nice */
+
+ if (strlen(result) != attrlen) {
+ /* stund 0.96 sends a final null in the Server attribute, so
+ * only error if the null appears anywhere else in a string */
+ if (strlen(result) != attrlen-1) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Error in string: %zd/%d", strlen(result), attrlen);
+ ABORT(R_FAILED);
+ }
+ }
+
+ _status = 0;
+ abort:
+ return _status;
+}
+
+nr_stun_attr_codec nr_stun_attr_codec_string = {
+ "string",
+ nr_stun_attr_codec_string_print,
+ nr_stun_attr_codec_string_encode,
+ nr_stun_attr_codec_string_decode
+};
+
+static int
+nr_stun_attr_codec_unknown_attributes_print(nr_stun_attr_info *attr_info, char *msg, void *data)
+{
+ nr_stun_attr_unknown_attributes *unknown_attributes = data;
+ char type[9];
+ char str[64 + (NR_STUN_MAX_UNKNOWN_ATTRIBUTES * sizeof(type))];
+ int i;
+
+ snprintf(str, sizeof(str), "%s %s:", msg, attr_info->name);
+ for (i = 0; i < unknown_attributes->num_attributes; ++i) {
+ snprintf(type, sizeof(type), "%s 0x%04x", ((i>0)?",":""), unknown_attributes->attribute[i]);
+ strlcat(str, type, sizeof(str));
+ }
+
+ r_log(NR_LOG_STUN, LOG_DEBUG, "%s", str);
+ return 0;
+}
+
+static int
+nr_stun_attr_codec_unknown_attributes_encode(nr_stun_attr_info *attr_info, void *data, size_t offset, size_t buflen, UCHAR *buf, size_t *attrlen)
+{
+ int _status;
+ int start = offset;
+ nr_stun_attr_unknown_attributes *unknown_attributes = data;
+ int length = (2 * unknown_attributes->num_attributes);
+ int i;
+
+ if (unknown_attributes->num_attributes > NR_STUN_MAX_UNKNOWN_ATTRIBUTES) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Too many UNKNOWN-ATTRIBUTES: %d", unknown_attributes->num_attributes);
+ ABORT(R_FAILED);
+ }
+
+ if (nr_stun_encode_htons(attr_info->type , buflen, buf, &offset)
+ || nr_stun_encode_htons(length , buflen, buf, &offset))
+ ABORT(R_FAILED);
+
+ for (i = 0; i < unknown_attributes->num_attributes; ++i) {
+ if (nr_stun_encode_htons(unknown_attributes->attribute[i], buflen, buf, &offset))
+ ABORT(R_FAILED);
+ }
+
+ *attrlen = offset - start;
+
+ _status = 0;
+ abort:
+ return _status;
+}
+
+static int
+nr_stun_attr_codec_unknown_attributes_decode(nr_stun_attr_info *attr_info, size_t attrlen, UCHAR *buf, size_t offset, size_t buflen, void *data)
+{
+ int _status;
+ nr_stun_attr_unknown_attributes *unknown_attributes = data;
+ int i;
+ UINT2 *a;
+
+ if ((attrlen % 4) != 0) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Attribute is illegal size: %d", attrlen);
+ ABORT(R_REJECTED);
+ }
+
+ unknown_attributes->num_attributes = attrlen / 2;
+
+ if (unknown_attributes->num_attributes > NR_STUN_MAX_UNKNOWN_ATTRIBUTES) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Too many UNKNOWN-ATTRIBUTES: %d", unknown_attributes->num_attributes);
+ ABORT(R_REJECTED);
+ }
+
+ for (i = 0; i < unknown_attributes->num_attributes; ++i) {
+ a = &(unknown_attributes->attribute[i]);
+ if (nr_stun_decode_htons(buf, buflen, &offset, a))
+ return R_FAILED;
+ }
+
+ _status = 0;
+ abort:
+ return _status;
+}
+
+nr_stun_attr_codec nr_stun_attr_codec_unknown_attributes = {
+ "unknown_attributes",
+ nr_stun_attr_codec_unknown_attributes_print,
+ nr_stun_attr_codec_unknown_attributes_encode,
+ nr_stun_attr_codec_unknown_attributes_decode
+};
+
+static int
+nr_stun_attr_codec_xor_mapped_address_print(nr_stun_attr_info *attr_info, char *msg, void *data)
+{
+ nr_stun_attr_xor_mapped_address *xor_mapped_address = data;
+ r_log(NR_LOG_STUN, LOG_DEBUG, "%s %s: %s (unmasked) %s (masked)",
+ msg, attr_info->name,
+ xor_mapped_address->unmasked.as_string,
+ xor_mapped_address->masked.as_string);
+ return 0;
+}
+
+static int
+nr_stun_attr_codec_xor_mapped_address_encode(nr_stun_attr_info *attr_info, void *data, size_t offset, size_t buflen, UCHAR *buf, size_t *attrlen)
+{
+ nr_stun_attr_xor_mapped_address *xor_mapped_address = data;
+ nr_stun_message_header *header = (nr_stun_message_header*)buf;
+ UINT4 magic_cookie;
+
+ r_log(NR_LOG_STUN, LOG_DEBUG, "Unmasked XOR-MAPPED-ADDRESS = %s", xor_mapped_address->unmasked.as_string);
+
+ /* this needs to be the magic cookie in the header and not
+ * the MAGIC_COOKIE constant because if we're talking to
+ * older servers (that don't have a magic cookie) they use
+ * message ID for this */
+ magic_cookie = ntohl(header->magic_cookie);
+
+ nr_stun_xor_mapped_address(magic_cookie, header->id, &xor_mapped_address->unmasked, &xor_mapped_address->masked);
+
+ r_log(NR_LOG_STUN, LOG_DEBUG, "Masked XOR-MAPPED-ADDRESS = %s", xor_mapped_address->masked.as_string);
+
+ if (nr_stun_attr_codec_addr.encode(attr_info, &xor_mapped_address->masked, offset, buflen, buf, attrlen))
+ return R_FAILED;
+
+ return 0;
+}
+
+static int
+nr_stun_attr_codec_xor_mapped_address_decode(nr_stun_attr_info *attr_info, size_t attrlen, UCHAR *buf, size_t offset, size_t buflen, void *data)
+{
+ int r,_status;
+ nr_stun_attr_xor_mapped_address *xor_mapped_address = data;
+ nr_stun_message_header *header = (nr_stun_message_header*)buf;
+ UINT4 magic_cookie;
+
+ if ((r=nr_stun_attr_codec_addr.decode(attr_info, attrlen, buf, offset, buflen, &xor_mapped_address->masked)))
+ ABORT(r);
+
+ r_log(NR_LOG_STUN, LOG_DEBUG, "Masked XOR-MAPPED-ADDRESS = %s", xor_mapped_address->masked.as_string);
+
+ /* this needs to be the magic cookie in the header and not
+ * the MAGIC_COOKIE constant because if we're talking to
+ * older servers (that don't have a magic cookie) they use
+ * message ID for this */
+ magic_cookie = ntohl(header->magic_cookie);
+
+ nr_stun_xor_mapped_address(magic_cookie, header->id, &xor_mapped_address->masked, &xor_mapped_address->unmasked);
+
+ r_log(NR_LOG_STUN, LOG_DEBUG, "Unmasked XOR-MAPPED-ADDRESS = %s", xor_mapped_address->unmasked.as_string);
+
+ _status = 0;
+ abort:
+ return _status;
+}
+
+nr_stun_attr_codec nr_stun_attr_codec_xor_mapped_address = {
+ "xor_mapped_address",
+ nr_stun_attr_codec_xor_mapped_address_print,
+ nr_stun_attr_codec_xor_mapped_address_encode,
+ nr_stun_attr_codec_xor_mapped_address_decode
+};
+
+nr_stun_attr_codec nr_stun_attr_codec_old_xor_mapped_address = {
+ "xor_mapped_address",
+ nr_stun_attr_codec_xor_mapped_address_print,
+ 0, /* never encode this type */
+ nr_stun_attr_codec_xor_mapped_address_decode
+};
+
+nr_stun_attr_codec nr_stun_attr_codec_xor_peer_address = {
+ "xor_peer_address",
+ nr_stun_attr_codec_xor_mapped_address_print,
+ nr_stun_attr_codec_xor_mapped_address_encode,
+ nr_stun_attr_codec_xor_mapped_address_decode
+};
+
+#define NR_ADD_STUN_ATTRIBUTE(type, name, codec, illegal) \
+ { (type), (name), &(codec), illegal },
+
+#define NR_ADD_STUN_ATTRIBUTE_IGNORE(type, name) \
+ { (type), (name), &nr_stun_attr_codec_noop, 0 },
+
+
+static nr_stun_attr_info attrs[] = {
+ NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_ALTERNATE_SERVER, "ALTERNATE-SERVER", nr_stun_attr_codec_addr, 0)
+#ifdef USE_STUND_0_96
+ NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_OLD_CHANGE_REQUEST, "CHANGE-REQUEST", nr_stun_attr_codec_UINT4, 0)
+#endif
+ NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_ERROR_CODE, "ERROR-CODE", nr_stun_attr_codec_error_code, nr_stun_attr_error_code_illegal)
+ NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_FINGERPRINT, "FINGERPRINT", nr_stun_attr_codec_fingerprint, 0)
+ NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_MAPPED_ADDRESS, "MAPPED-ADDRESS", nr_stun_attr_codec_addr, 0)
+ NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_MESSAGE_INTEGRITY, "MESSAGE-INTEGRITY", nr_stun_attr_codec_message_integrity, 0)
+ NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_NONCE, "NONCE", nr_stun_attr_codec_quoted_string, nr_stun_attr_nonce_illegal)
+ NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_REALM, "REALM", nr_stun_attr_codec_quoted_string, nr_stun_attr_realm_illegal)
+ NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_SERVER, "SERVER", nr_stun_attr_codec_string, nr_stun_attr_server_illegal)
+ NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_UNKNOWN_ATTRIBUTES, "UNKNOWN-ATTRIBUTES", nr_stun_attr_codec_unknown_attributes, 0)
+ NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_USERNAME, "USERNAME", nr_stun_attr_codec_string, nr_stun_attr_username_illegal)
+ NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_XOR_MAPPED_ADDRESS, "XOR-MAPPED-ADDRESS", nr_stun_attr_codec_xor_mapped_address, 0)
+
+#ifdef USE_ICE
+ NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_ICE_CONTROLLED, "ICE-CONTROLLED", nr_stun_attr_codec_UINT8, 0)
+ NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_ICE_CONTROLLING, "ICE-CONTROLLING", nr_stun_attr_codec_UINT8, 0)
+ NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_PRIORITY, "PRIORITY", nr_stun_attr_codec_UINT4, 0)
+ NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_USE_CANDIDATE, "USE-CANDIDATE", nr_stun_attr_codec_flag, 0)
+#endif
+
+#ifdef USE_TURN
+ NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_DATA, "DATA", nr_stun_attr_codec_data, 0)
+ NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_LIFETIME, "LIFETIME", nr_stun_attr_codec_UINT4, 0)
+ NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_XOR_RELAY_ADDRESS, "XOR-RELAY-ADDRESS", nr_stun_attr_codec_xor_mapped_address, 0)
+ NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_XOR_PEER_ADDRESS, "XOR-PEER-ADDRESS", nr_stun_attr_codec_xor_peer_address, 0)
+ NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_REQUESTED_TRANSPORT, "REQUESTED-TRANSPORT", nr_stun_attr_codec_UCHAR, 0)
+ NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_BANDWIDTH, "BANDWIDTH", nr_stun_attr_codec_UINT4, 0)
+#endif /* USE_TURN */
+
+ /* for backwards compatibilty */
+ NR_ADD_STUN_ATTRIBUTE(NR_STUN_ATTR_OLD_XOR_MAPPED_ADDRESS, "Old XOR-MAPPED-ADDRESS", nr_stun_attr_codec_old_xor_mapped_address, 0)
+#ifdef USE_RFC_3489_BACKWARDS_COMPATIBLE
+ NR_ADD_STUN_ATTRIBUTE_IGNORE(NR_STUN_ATTR_OLD_RESPONSE_ADDRESS, "RESPONSE-ADDRESS")
+ NR_ADD_STUN_ATTRIBUTE_IGNORE(NR_STUN_ATTR_OLD_SOURCE_ADDRESS, "SOURCE-ADDRESS")
+ NR_ADD_STUN_ATTRIBUTE_IGNORE(NR_STUN_ATTR_OLD_CHANGED_ADDRESS, "CHANGED-ADDRESS")
+ NR_ADD_STUN_ATTRIBUTE_IGNORE(NR_STUN_ATTR_OLD_PASSWORD, "PASSWORD")
+#endif /* USE_RFC_3489_BACKWARDS_COMPATIBLE */
+};
+
+
+int
+nr_stun_find_attr_info(UINT2 type, nr_stun_attr_info **info)
+{
+ int _status;
+ size_t i;
+
+ *info = 0;
+ for (i = 0; i < sizeof(attrs)/sizeof(*attrs); ++i) {
+ if (type == attrs[i].type) {
+ *info = &attrs[i];
+ break;
+ }
+ }
+
+ if (*info == 0)
+ ABORT(R_NOT_FOUND);
+
+ _status=0;
+ abort:
+ return(_status);
+}
+
+int
+nr_stun_fix_attribute_ordering(nr_stun_message *msg)
+{
+ nr_stun_message_attribute *message_integrity;
+ nr_stun_message_attribute *fingerprint;
+
+ /* 2nd to the last */
+ if (nr_stun_message_has_attribute(msg, NR_STUN_ATTR_MESSAGE_INTEGRITY, &message_integrity)) {
+ TAILQ_REMOVE(&msg->attributes, message_integrity, entry);
+ TAILQ_INSERT_TAIL(&msg->attributes, message_integrity, entry);
+ }
+
+ /* last */
+ if (nr_stun_message_has_attribute(msg, NR_STUN_ATTR_FINGERPRINT, &fingerprint)) {
+ TAILQ_REMOVE(&msg->attributes, fingerprint, entry);
+ TAILQ_INSERT_TAIL(&msg->attributes, fingerprint, entry);
+ }
+
+ return 0;
+}
+
+// Since this sanity check is only a collection of assert statements and those
+// assert statements are compiled out in non-debug builds, undef SANITY_CHECKS
+// so we can avoid the warning that padding_bytes is never used in opt builds.
+#ifdef NDEBUG
+#undef SANITY_CHECKS
+#endif
+
+#ifdef SANITY_CHECKS
+static void sanity_check_encoding_stuff(nr_stun_message *msg)
+{
+ nr_stun_message_attribute *attr = 0;
+ int padding_bytes;
+ int l;
+
+ r_log(NR_LOG_STUN, LOG_DEBUG, "Starting to sanity check encoding");
+
+ l = 0;
+ TAILQ_FOREACH(attr, &msg->attributes, entry) {
+ padding_bytes = 0;
+ if ((attr->length % 4) != 0) {
+ padding_bytes = 4 - (attr->length % 4);
+ }
+ assert(attr->length == (attr->encoding_length - (4 + padding_bytes)));
+ assert(((void*)attr->encoding) == (msg->buffer + 20 + l));
+ l += attr->encoding_length;
+ assert((l % 4) == 0);
+ }
+ assert(l == msg->header.length);
+}
+#endif /* SANITY_CHECKS */
+
+
+int
+nr_stun_encode_message(nr_stun_message *msg)
+{
+ int r,_status;
+ size_t length_offset;
+ size_t length_offset_hold;
+ nr_stun_attr_info *attr_info;
+ nr_stun_message_attribute *attr;
+ int padding_bytes;
+
+ r_log(NR_LOG_STUN, LOG_DEBUG, "Encoding STUN message");
+
+ nr_stun_fix_attribute_ordering(msg);
+
+ msg->name = nr_stun_msg_type(msg->header.type);
+ msg->length = 0;
+ msg->header.length = 0;
+
+ if ((r=nr_stun_encode_htons(msg->header.type, sizeof(msg->buffer), msg->buffer, &msg->length)))
+ ABORT(r);
+ if (msg->name)
+ r_log(NR_LOG_STUN, LOG_DEBUG, "Encoded MsgType: %s", msg->name);
+ else
+ r_log(NR_LOG_STUN, LOG_DEBUG, "Encoded MsgType: 0x%03x", msg->header.type);
+
+ /* grab the offset to be used later to re-write the header length field */
+ length_offset_hold = msg->length;
+
+ if ((r=nr_stun_encode_htons(msg->header.length, sizeof(msg->buffer), msg->buffer, &msg->length)))
+ ABORT(r);
+
+ if ((r=nr_stun_encode_htonl(msg->header.magic_cookie, sizeof(msg->buffer), msg->buffer, &msg->length)))
+ ABORT(r);
+ r_log(NR_LOG_STUN, LOG_DEBUG, "Encoded Cookie: %08x", msg->header.magic_cookie);
+
+ if ((r=nr_stun_encode((UCHAR*)(&msg->header.id), sizeof(msg->header.id), sizeof(msg->buffer), msg->buffer, &msg->length)))
+ ABORT(r);
+ r_dump(NR_LOG_STUN, LOG_DEBUG, "Encoded ID", (void*)&msg->header.id, sizeof(msg->header.id));
+
+ TAILQ_FOREACH(attr, &msg->attributes, entry) {
+ if ((r=nr_stun_find_attr_info(attr->type, &attr_info))) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Unrecognized attribute: 0x%04x", attr->type);
+ ABORT(R_INTERNAL);
+ }
+
+ attr->name = attr_info->name;
+ attr->type_name = attr_info->codec->name;
+ attr->encoding = (nr_stun_encoded_attribute*)&msg->buffer[msg->length];
+
+ if (attr_info->codec->encode != 0) {
+ if ((r=attr_info->codec->encode(attr_info, &attr->u, msg->length, sizeof(msg->buffer), msg->buffer, &attr->encoding_length))) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Unable to encode %s", attr_info->name);
+ ABORT(r);
+ }
+
+ msg->length += attr->encoding_length;
+ attr->length = attr->encoding_length - 4; /* -4 for type and length fields */
+
+ if (attr_info->illegal) {
+ if ((r=attr_info->illegal(attr_info, attr->length, &attr->u)))
+ ABORT(r);
+ }
+
+ attr_info->codec->print(attr_info, "Encoded", &attr->u);
+
+ if ((attr->length % 4) == 0) {
+ padding_bytes = 0;
+ }
+ else {
+ padding_bytes = 4 - (attr->length % 4);
+ nr_stun_encode((UCHAR*)"\0\0\0\0", padding_bytes, sizeof(msg->buffer), msg->buffer, &msg->length);
+ attr->encoding_length += padding_bytes;
+ }
+
+ msg->header.length += attr->encoding_length;
+ length_offset = length_offset_hold;
+ (void)nr_stun_encode_htons(msg->header.length, sizeof(msg->buffer), msg->buffer, &length_offset);
+ }
+ else {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Missing encode function for attribute: %s", attr_info->name);
+ }
+ }
+
+ r_log(NR_LOG_STUN, LOG_DEBUG, "Encoded Length: %d", msg->header.length);
+
+ assert(msg->length < NR_STUN_MAX_MESSAGE_SIZE);
+
+#ifdef SANITY_CHECKS
+ sanity_check_encoding_stuff(msg);
+#endif /* SANITY_CHECKS */
+
+ _status=0;
+abort:
+ return _status;
+}
+
+int
+nr_stun_decode_message(nr_stun_message *msg, int (*get_password)(void *arg, nr_stun_message *msg, Data **password), void *arg)
+{
+ int r,_status;
+ int offset;
+ int size;
+ int padding_bytes;
+ nr_stun_message_attribute *attr;
+ nr_stun_attr_info *attr_info;
+ Data *password;
+
+ r_log(NR_LOG_STUN, LOG_DEBUG, "Parsing STUN message of %d bytes", msg->length);
+
+ if (!TAILQ_EMPTY(&msg->attributes))
+ ABORT(R_BAD_ARGS);
+
+ if (sizeof(nr_stun_message_header) > msg->length) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Message too small");
+ ABORT(R_FAILED);
+ }
+
+ memcpy(&msg->header, msg->buffer, sizeof(msg->header));
+ msg->header.type = ntohs(msg->header.type);
+ msg->header.length = ntohs(msg->header.length);
+ msg->header.magic_cookie = ntohl(msg->header.magic_cookie);
+
+ msg->name = nr_stun_msg_type(msg->header.type);
+
+ if (msg->name)
+ r_log(NR_LOG_STUN, LOG_DEBUG, "Parsed MsgType: %s", msg->name);
+ else
+ r_log(NR_LOG_STUN, LOG_DEBUG, "Parsed MsgType: 0x%03x", msg->header.type);
+ r_log(NR_LOG_STUN, LOG_DEBUG, "Parsed Length: %d", msg->header.length);
+ r_log(NR_LOG_STUN, LOG_DEBUG, "Parsed Cookie: %08x", msg->header.magic_cookie);
+ r_dump(NR_LOG_STUN, LOG_DEBUG, "Parsed ID", (void*)&msg->header.id, sizeof(msg->header.id));
+
+ if (msg->header.length + sizeof(msg->header) != msg->length) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Inconsistent message header length: %d/%d",
+ msg->header.length, msg->length);
+ ABORT(R_FAILED);
+ }
+
+ size = msg->header.length;
+
+ if ((size % 4) != 0) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Illegal message size: %d", msg->header.length);
+ ABORT(R_FAILED);
+ }
+
+ offset = sizeof(msg->header);
+
+ while (size > 0) {
+ r_log(NR_LOG_STUN, LOG_DEBUG, "size = %d", size);
+
+ if (size < 4) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Illegal message length: %d", size);
+ ABORT(R_FAILED);
+ }
+
+ if ((r=nr_stun_message_attribute_create(msg, &attr)))
+ ABORT(R_NO_MEMORY);
+
+ attr->encoding = (nr_stun_encoded_attribute*)&msg->buffer[offset];
+ attr->type = ntohs(attr->encoding->type);
+ attr->length = ntohs(attr->encoding->length);
+ attr->encoding_length = attr->length + 4;
+
+ if ((attr->length % 4) != 0) {
+ padding_bytes = 4 - (attr->length % 4);
+ attr->encoding_length += padding_bytes;
+ }
+
+ if ((attr->encoding_length) > (size_t)size) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Attribute length larger than remaining message size: %d/%d", attr->encoding_length, size);
+ ABORT(R_FAILED);
+ }
+
+ if ((r=nr_stun_find_attr_info(attr->type, &attr_info))) {
+ if (attr->type <= 0x7FFF)
+ ++msg->comprehension_required_unknown_attributes;
+ else
+ ++msg->comprehension_optional_unknown_attributes;
+ r_log(NR_LOG_STUN, LOG_INFO, "Unrecognized attribute: 0x%04x", attr->type);
+ }
+ else {
+ attr_info->name = attr_info->name;
+ attr->type_name = attr_info->codec->name;
+
+ if (attr->type == NR_STUN_ATTR_MESSAGE_INTEGRITY) {
+ if (get_password && get_password(arg, msg, &password) == 0) {
+ if (password->len > sizeof(attr->u.message_integrity.password)) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Password too long: %d bytes", password->len);
+ ABORT(R_FAILED);
+ }
+
+ memcpy(attr->u.message_integrity.password, password->data, password->len);
+ attr->u.message_integrity.passwordlen = password->len;
+ }
+ else {
+ /* set to user "not found" */
+ attr->u.message_integrity.unknown_user = 1;
+ }
+ }
+ else if (attr->type == NR_STUN_ATTR_OLD_XOR_MAPPED_ADDRESS) {
+ attr->type = NR_STUN_ATTR_XOR_MAPPED_ADDRESS;
+ r_log(NR_LOG_STUN, LOG_INFO, "Translating obsolete XOR-MAPPED-ADDRESS type");
+ }
+
+ if ((r=attr_info->codec->decode(attr_info, attr->length, msg->buffer, offset+4, msg->length, &attr->u))) {
+ if (r == SKIP_ATTRIBUTE_DECODE) {
+ r_log(NR_LOG_STUN, LOG_INFO, "Skipping %s", attr_info->name);
+ }
+ else {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Unable to parse %s", attr_info->name);
+ }
+
+ attr->invalid = 1;
+ }
+ else {
+ attr_info->codec->print(attr_info, "Parsed", &attr->u);
+
+#ifdef USE_STUN_PEDANTIC
+ r_log(NR_LOG_STUN, LOG_DEBUG, "Before pedantic attr_info checks");
+ if (attr_info->illegal) {
+ if ((r=attr_info->illegal(attr_info, attr->length, &attr->u))) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Failed pedantic attr_info checks");
+ ABORT(r);
+ }
+ }
+ r_log(NR_LOG_STUN, LOG_DEBUG, "After pedantic attr_info checks");
+#endif /* USE_STUN_PEDANTIC */
+ }
+ }
+
+ offset += attr->encoding_length;
+ size -= attr->encoding_length;
+ }
+
+#ifdef SANITY_CHECKS
+ sanity_check_encoding_stuff(msg);
+#endif /* SANITY_CHECKS */
+
+ _status=0;
+ abort:
+ return _status;
+}
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_codec.h b/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_codec.h
new file mode 100644
index 0000000000..4e4ff60e0c
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_codec.h
@@ -0,0 +1,78 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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 _stun_codec_h
+#define _stun_codec_h
+
+#include "stun_msg.h"
+
+typedef struct nr_stun_attr_info_ nr_stun_attr_info;
+
+typedef struct nr_stun_attr_codec_ {
+ char *name;
+ int (*print)(nr_stun_attr_info *attr_info, char *msg, void *data);
+ int (*encode)(nr_stun_attr_info *attr_info, void *data, size_t offset, size_t buflen, UCHAR *buf, size_t *attrlen);
+ int (*decode)(nr_stun_attr_info *attr_info, size_t attrlen, UCHAR *buf, size_t offset, size_t buflen, void *data);
+} nr_stun_attr_codec;
+
+struct nr_stun_attr_info_ {
+ UINT2 type;
+ char *name;
+ nr_stun_attr_codec *codec;
+ int (*illegal)(nr_stun_attr_info *attr_info, size_t attrlen, void *data);
+};
+
+extern nr_stun_attr_codec nr_stun_attr_codec_UINT4;
+extern nr_stun_attr_codec nr_stun_attr_codec_UINT8;
+extern nr_stun_attr_codec nr_stun_attr_codec_addr;
+extern nr_stun_attr_codec nr_stun_attr_codec_bytes;
+extern nr_stun_attr_codec nr_stun_attr_codec_data;
+extern nr_stun_attr_codec nr_stun_attr_codec_error_code;
+extern nr_stun_attr_codec nr_stun_attr_codec_fingerprint;
+extern nr_stun_attr_codec nr_stun_attr_codec_flag;
+extern nr_stun_attr_codec nr_stun_attr_codec_message_integrity;
+extern nr_stun_attr_codec nr_stun_attr_codec_noop;
+extern nr_stun_attr_codec nr_stun_attr_codec_quoted_string;
+extern nr_stun_attr_codec nr_stun_attr_codec_string;
+extern nr_stun_attr_codec nr_stun_attr_codec_unknown_attributes;
+extern nr_stun_attr_codec nr_stun_attr_codec_xor_mapped_address;
+extern nr_stun_attr_codec nr_stun_attr_codec_xor_peer_address;
+extern nr_stun_attr_codec nr_stun_attr_codec_old_xor_mapped_address;
+
+size_t nr_count_utf8_code_points_without_validation(const char *s);
+int nr_stun_encode_message(nr_stun_message *msg);
+int nr_stun_decode_message(nr_stun_message *msg, int (*get_password)(void *arg, nr_stun_message *msg, Data **password), void *arg);
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_hint.c b/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_hint.c
new file mode 100644
index 0000000000..8f118e8942
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_hint.c
@@ -0,0 +1,245 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+*/
+
+#include <errno.h>
+#include <csi_platform.h>
+
+#ifdef WIN32
+#include <winsock2.h>
+#include <stdlib.h>
+#include <io.h>
+#include <time.h>
+#else /* UNIX */
+#include <string.h>
+#endif /* end UNIX */
+#include <assert.h>
+
+#include "stun.h"
+
+
+/* returns 0 if it's not a STUN message
+ * 1 if it's likely to be a STUN message
+ * 2 if it's super likely to be a STUN message
+ * 3 if it really is a STUN message */
+int
+nr_is_stun_message(UCHAR *buf, size_t len)
+{
+ const UINT4 cookie = htonl(NR_STUN_MAGIC_COOKIE);
+ const UINT4 cookie2 = htonl(NR_STUN_MAGIC_COOKIE2);
+#if 0
+ nr_stun_message msg;
+#endif
+ UINT2 type;
+ nr_stun_encoded_attribute* attr;
+ unsigned int attrLen;
+ int atrType;
+
+ if (sizeof(nr_stun_message_header) > len)
+ return 0;
+
+ if ((buf[0] & (0x80|0x40)) != 0)
+ return 0;
+
+ memcpy(&type, buf, 2);
+ type = ntohs(type);
+
+ switch (type) {
+ case NR_STUN_MSG_BINDING_REQUEST:
+ case NR_STUN_MSG_BINDING_INDICATION:
+ case NR_STUN_MSG_BINDING_RESPONSE:
+ case NR_STUN_MSG_BINDING_ERROR_RESPONSE:
+
+#ifdef USE_TURN
+ case NR_STUN_MSG_ALLOCATE_REQUEST:
+ case NR_STUN_MSG_ALLOCATE_RESPONSE:
+ case NR_STUN_MSG_ALLOCATE_ERROR_RESPONSE:
+ case NR_STUN_MSG_REFRESH_REQUEST:
+ case NR_STUN_MSG_REFRESH_RESPONSE:
+ case NR_STUN_MSG_REFRESH_ERROR_RESPONSE:
+ case NR_STUN_MSG_PERMISSION_REQUEST:
+ case NR_STUN_MSG_PERMISSION_RESPONSE:
+ case NR_STUN_MSG_PERMISSION_ERROR_RESPONSE:
+ case NR_STUN_MSG_CHANNEL_BIND_REQUEST:
+ case NR_STUN_MSG_CHANNEL_BIND_RESPONSE:
+ case NR_STUN_MSG_CHANNEL_BIND_ERROR_RESPONSE:
+ case NR_STUN_MSG_SEND_INDICATION:
+ case NR_STUN_MSG_DATA_INDICATION:
+#ifdef NR_STUN_MSG_CONNECT_REQUEST
+ case NR_STUN_MSG_CONNECT_REQUEST:
+#endif
+#ifdef NR_STUN_MSG_CONNECT_RESPONSE
+ case NR_STUN_MSG_CONNECT_RESPONSE:
+#endif
+#ifdef NR_STUN_MSG_CONNECT_ERROR_RESPONSE
+ case NR_STUN_MSG_CONNECT_ERROR_RESPONSE:
+#endif
+#ifdef NR_STUN_MSG_CONNECT_STATUS_INDICATION
+ case NR_STUN_MSG_CONNECT_STATUS_INDICATION:
+#endif
+#endif /* USE_TURN */
+
+ /* ok so far, continue */
+ break;
+ default:
+ return 0;
+ break;
+ }
+
+ if (!memcmp(&cookie2, &buf[4], sizeof(UINT4))) {
+ /* return here because if it's an old-style message then there will
+ * not be a fingerprint in the message */
+ return 1;
+ }
+
+ if (memcmp(&cookie, &buf[4], sizeof(UINT4)))
+ return 0;
+
+ /* the magic cookie was right, so it's pretty darn likely that what we've
+ * got here is a STUN message */
+
+ attr = (nr_stun_encoded_attribute*)(buf + (len - 8));
+ attrLen = ntohs(attr->length);
+ atrType = ntohs(attr->type);
+
+ if (atrType != NR_STUN_ATTR_FINGERPRINT || attrLen != 4)
+ return 1;
+
+ /* the fingerprint is in the right place and looks sane, so we can be quite
+ * sure we've got a STUN message */
+
+#if 0
+/* nevermind this check ... there's a reasonable chance that a NAT has modified
+ * the message (and thus the fingerprint check will fail), but it's still an
+ * otherwise-perfectly-good STUN message, so skip the check since we're going
+ * to return "true" whether the check succeeds or fails */
+
+ if (nr_stun_parse_attr_UINT4(buf + (len - 4), attrLen, &msg.fingerprint))
+ return 2;
+
+
+ if (nr_stun_compute_fingerprint(buf, len - 8, &computedFingerprint))
+ return 2;
+
+ if (msg.fingerprint.number != computedFingerprint)
+ return 2;
+
+ /* and the fingerprint is good, so it's gotta be a STUN message */
+#endif
+
+ return 3;
+}
+
+int
+nr_is_stun_request_message(UCHAR *buf, size_t len)
+{
+ UINT2 type;
+
+ if (sizeof(nr_stun_message_header) > len)
+ return 0;
+
+ if (!nr_is_stun_message(buf, len))
+ return 0;
+
+ memcpy(&type, buf, 2);
+ type = ntohs(type);
+
+ return NR_STUN_GET_TYPE_CLASS(type) == NR_CLASS_REQUEST;
+}
+
+int
+nr_is_stun_indication_message(UCHAR *buf, size_t len)
+{
+ UINT2 type;
+
+ if (sizeof(nr_stun_message_header) > len)
+ return 0;
+
+ if (!nr_is_stun_message(buf, len))
+ return 0;
+
+ memcpy(&type, buf, 2);
+ type = ntohs(type);
+
+ return NR_STUN_GET_TYPE_CLASS(type) == NR_CLASS_INDICATION;
+}
+
+int
+nr_is_stun_response_message(UCHAR *buf, size_t len)
+{
+ UINT2 type;
+
+ if (sizeof(nr_stun_message_header) > len)
+ return 0;
+
+ if (!nr_is_stun_message(buf, len))
+ return 0;
+
+ memcpy(&type, buf, 2);
+ type = ntohs(type);
+
+ return NR_STUN_GET_TYPE_CLASS(type) == NR_CLASS_RESPONSE
+ || NR_STUN_GET_TYPE_CLASS(type) == NR_CLASS_ERROR_RESPONSE;
+}
+
+int
+nr_has_stun_cookie(UCHAR *buf, size_t len)
+{
+ static UINT4 cookie;
+
+ cookie = htonl(NR_STUN_MAGIC_COOKIE);
+
+ if (sizeof(nr_stun_message_header) > len)
+ return 0;
+
+ if (memcmp(&cookie, &buf[4], sizeof(UINT4)))
+ return 0;
+
+ return 1;
+}
+
+int
+nr_stun_message_length(UCHAR *buf, int buf_len, int *msg_len)
+{
+ nr_stun_message_header *hdr;
+
+ if (!nr_is_stun_message(buf, buf_len))
+ return(R_BAD_DATA);
+
+ hdr = (nr_stun_message_header *)buf;
+
+ *msg_len = ntohs(hdr->length);
+
+ return(0);
+}
+
+
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_hint.h b/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_hint.h
new file mode 100644
index 0000000000..c2badc1d2b
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_hint.h
@@ -0,0 +1,44 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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 _stun_hint_h
+#define _stun_hint_h
+
+int nr_is_stun_message(UCHAR *buf, size_t len);
+int nr_is_stun_request_message(UCHAR *buf, size_t len);
+int nr_is_stun_response_message(UCHAR *buf, size_t len);
+int nr_is_stun_indication_message(UCHAR *buf, size_t len);
+int nr_has_stun_cookie(UCHAR *buf, size_t len);
+int nr_stun_message_length(UCHAR *buf, int len, int *length);
+
+#endif
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_msg.c b/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_msg.c
new file mode 100644
index 0000000000..7e01686109
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_msg.c
@@ -0,0 +1,364 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+*/
+
+#include <errno.h>
+#include <csi_platform.h>
+
+#ifdef WIN32
+#include <winsock2.h>
+#include <stdlib.h>
+#include <io.h>
+#include <time.h>
+#else /* UNIX */
+#include <string.h>
+#endif /* end UNIX */
+#include <assert.h>
+
+#include "stun.h"
+
+
+int
+nr_stun_message_create(nr_stun_message **msg)
+{
+ int _status;
+ nr_stun_message *m = 0;
+
+ m = RCALLOC(sizeof(*m));
+ if (!m)
+ ABORT(R_NO_MEMORY);
+
+ TAILQ_INIT(&m->attributes);
+
+ *msg = m;
+
+ _status=0;
+ abort:
+ return(_status);
+}
+
+int
+nr_stun_message_create2(nr_stun_message **msg, UCHAR *buffer, size_t length)
+{
+ int r,_status;
+ nr_stun_message *m = 0;
+
+ if (length > sizeof(m->buffer)) {
+ ABORT(R_BAD_DATA);
+ }
+
+ if ((r=nr_stun_message_create(&m)))
+ ABORT(r);
+
+ memcpy(m->buffer, buffer, length);
+ m->length = length;
+
+ *msg = m;
+
+ _status=0;
+ abort:
+ return(_status);
+}
+
+int
+nr_stun_message_destroy(nr_stun_message **msg)
+{
+ int _status;
+ nr_stun_message_attribute_head *attrs;
+ nr_stun_message_attribute *attr;
+
+ if (msg && *msg) {
+ attrs = &(*msg)->attributes;
+ while (!TAILQ_EMPTY(attrs)) {
+ attr = TAILQ_FIRST(attrs);
+ nr_stun_message_attribute_destroy(*msg, &attr);
+ }
+
+ RFREE(*msg);
+
+ *msg = 0;
+ }
+
+ _status=0;
+/* abort: */
+ return(_status);
+}
+
+int
+nr_stun_message_attribute_create(nr_stun_message *msg, nr_stun_message_attribute **attr)
+{
+ int _status;
+ nr_stun_message_attribute *a = 0;
+
+ a = RCALLOC(sizeof(*a));
+ if (!a)
+ ABORT(R_NO_MEMORY);
+
+ TAILQ_INSERT_TAIL(&msg->attributes, a, entry);
+
+ *attr = a;
+
+ _status=0;
+ abort:
+ return(_status);
+}
+
+int
+nr_stun_message_attribute_destroy(nr_stun_message *msg, nr_stun_message_attribute **attr)
+{
+ int _status;
+ nr_stun_message_attribute *a = 0;
+
+ if (attr && *attr) {
+ a = *attr;
+ TAILQ_REMOVE(&msg->attributes, a, entry);
+
+ RFREE(a);
+
+ *attr = 0;
+ }
+
+ _status=0;
+/* abort: */
+ return(_status);
+}
+
+int
+nr_stun_message_has_attribute(nr_stun_message *msg, UINT2 type, nr_stun_message_attribute **attribute)
+{
+ nr_stun_message_attribute *attr = 0;
+ nr_stun_message_get_attribute(msg, type, 0, &attr);
+
+ if (attribute)
+ *attribute = attr;
+
+ return attr ? 1 : 0;
+}
+
+int
+nr_stun_message_get_attribute(nr_stun_message *msg, UINT2 type, UINT2 index, nr_stun_message_attribute **attribute)
+{
+ nr_stun_message_attribute *attr;
+ TAILQ_FOREACH(attr, &msg->attributes, entry) {
+ if (attr->type == type && !attr->invalid) {
+ if (!index) {
+ *attribute = attr;
+ return 0;
+ }
+ --index;
+ }
+ }
+ *attribute = 0;
+ return R_NOT_FOUND;
+}
+
+#define NR_STUN_MESSAGE_ADD_ATTRIBUTE(__type, __code) \
+ { \
+ int r,_status; \
+ nr_stun_message_attribute *attr = 0; \
+ if ((r=nr_stun_message_attribute_create(msg, &attr))) \
+ ABORT(r); \
+ attr->type = (__type); \
+ { __code } \
+ _status=0; \
+ abort: \
+ if (_status){ \
+ nr_stun_message_attribute_destroy(msg, &attr); \
+ } \
+ return(_status); \
+ }
+
+
+int
+nr_stun_message_add_alternate_server_attribute(nr_stun_message *msg, nr_transport_addr *alternate_server)
+NR_STUN_MESSAGE_ADD_ATTRIBUTE(
+ NR_STUN_ATTR_ALTERNATE_SERVER,
+ {
+ if ((r=nr_transport_addr_copy(&attr->u.alternate_server, alternate_server)))
+ ABORT(r);
+ }
+)
+
+int
+nr_stun_message_add_error_code_attribute(nr_stun_message *msg, UINT2 number, char *reason)
+NR_STUN_MESSAGE_ADD_ATTRIBUTE(
+ NR_STUN_ATTR_ERROR_CODE,
+ {
+ attr->u.error_code.number = number;
+ (void)strlcpy(attr->u.error_code.reason, reason, sizeof(attr->u.error_code.reason));
+ }
+)
+
+int
+nr_stun_message_add_fingerprint_attribute(nr_stun_message *msg)
+NR_STUN_MESSAGE_ADD_ATTRIBUTE(
+ NR_STUN_ATTR_FINGERPRINT,
+ {}
+)
+
+int
+nr_stun_message_add_message_integrity_attribute(nr_stun_message *msg, Data *password)
+NR_STUN_MESSAGE_ADD_ATTRIBUTE(
+ NR_STUN_ATTR_MESSAGE_INTEGRITY,
+ {
+ if (sizeof(attr->u.message_integrity.password) < password->len)
+ ABORT(R_BAD_DATA);
+
+ memcpy(attr->u.message_integrity.password, password->data, password->len);
+ attr->u.message_integrity.passwordlen = password->len;
+ }
+)
+
+int
+nr_stun_message_add_nonce_attribute(nr_stun_message *msg, char *nonce)
+NR_STUN_MESSAGE_ADD_ATTRIBUTE(
+ NR_STUN_ATTR_NONCE,
+ { (void)strlcpy(attr->u.nonce, nonce, sizeof(attr->u.nonce)); }
+)
+
+int
+nr_stun_message_add_realm_attribute(nr_stun_message *msg, char *realm)
+NR_STUN_MESSAGE_ADD_ATTRIBUTE(
+ NR_STUN_ATTR_REALM,
+ { (void)strlcpy(attr->u.realm, realm, sizeof(attr->u.realm)); }
+)
+
+int
+nr_stun_message_add_server_attribute(nr_stun_message *msg, char *server_name)
+NR_STUN_MESSAGE_ADD_ATTRIBUTE(
+ NR_STUN_ATTR_SERVER,
+ { (void)strlcpy(attr->u.server_name, server_name, sizeof(attr->u.server_name)); }
+)
+
+int
+nr_stun_message_add_unknown_attributes_attribute(nr_stun_message *msg, nr_stun_attr_unknown_attributes *unknown_attributes)
+NR_STUN_MESSAGE_ADD_ATTRIBUTE(
+ NR_STUN_ATTR_UNKNOWN_ATTRIBUTES,
+ { memcpy(&attr->u.unknown_attributes, unknown_attributes, sizeof(attr->u.unknown_attributes)); }
+)
+
+int
+nr_stun_message_add_username_attribute(nr_stun_message *msg, char *username)
+NR_STUN_MESSAGE_ADD_ATTRIBUTE(
+ NR_STUN_ATTR_USERNAME,
+ { (void)strlcpy(attr->u.username, username, sizeof(attr->u.username)); }
+)
+
+int
+nr_stun_message_add_requested_transport_attribute(nr_stun_message *msg, UCHAR protocol)
+NR_STUN_MESSAGE_ADD_ATTRIBUTE(
+ NR_STUN_ATTR_REQUESTED_TRANSPORT,
+ { attr->u.requested_transport = protocol; }
+)
+
+int
+nr_stun_message_add_xor_mapped_address_attribute(nr_stun_message *msg, nr_transport_addr *mapped_address)
+NR_STUN_MESSAGE_ADD_ATTRIBUTE(
+ NR_STUN_ATTR_XOR_MAPPED_ADDRESS,
+ {
+ if ((r=nr_transport_addr_copy(&attr->u.xor_mapped_address.unmasked, mapped_address)))
+ ABORT(r);
+ }
+)
+
+int
+nr_stun_message_add_xor_peer_address_attribute(nr_stun_message *msg, nr_transport_addr *peer_address)
+NR_STUN_MESSAGE_ADD_ATTRIBUTE(
+ NR_STUN_ATTR_XOR_PEER_ADDRESS,
+ {
+ if ((r=nr_transport_addr_copy(&attr->u.xor_mapped_address.unmasked, peer_address)))
+ ABORT(r);
+ }
+)
+
+#ifdef USE_ICE
+int
+nr_stun_message_add_ice_controlled_attribute(nr_stun_message *msg, UINT8 ice_controlled)
+NR_STUN_MESSAGE_ADD_ATTRIBUTE(
+ NR_STUN_ATTR_ICE_CONTROLLED,
+ { attr->u.ice_controlled = ice_controlled; }
+)
+
+int
+nr_stun_message_add_ice_controlling_attribute(nr_stun_message *msg, UINT8 ice_controlling)
+NR_STUN_MESSAGE_ADD_ATTRIBUTE(
+ NR_STUN_ATTR_ICE_CONTROLLING,
+ { attr->u.ice_controlling = ice_controlling; }
+)
+
+int
+nr_stun_message_add_priority_attribute(nr_stun_message *msg, UINT4 priority)
+NR_STUN_MESSAGE_ADD_ATTRIBUTE(
+ NR_STUN_ATTR_PRIORITY,
+ { attr->u.priority = priority; }
+)
+
+int
+nr_stun_message_add_use_candidate_attribute(nr_stun_message *msg)
+NR_STUN_MESSAGE_ADD_ATTRIBUTE(
+ NR_STUN_ATTR_USE_CANDIDATE,
+ {}
+)
+#endif /* USE_ICE */
+
+#ifdef USE_TURN
+int
+nr_stun_message_add_data_attribute(nr_stun_message *msg, UCHAR *data, int length)
+
+NR_STUN_MESSAGE_ADD_ATTRIBUTE(
+ NR_STUN_ATTR_DATA,
+ {
+ if (length > NR_STUN_MAX_MESSAGE_SIZE)
+ ABORT(R_BAD_ARGS);
+
+ memcpy(attr->u.data.data, data, length);
+ attr->u.data.length=length;
+ }
+)
+
+int
+nr_stun_message_add_lifetime_attribute(nr_stun_message *msg, UINT4 lifetime_secs)
+NR_STUN_MESSAGE_ADD_ATTRIBUTE(
+ NR_STUN_ATTR_LIFETIME,
+ { attr->u.lifetime_secs = lifetime_secs; }
+)
+
+#endif /* USE_TURN */
+
+#ifdef USE_STUND_0_96
+int
+nr_stun_message_add_change_request_attribute(nr_stun_message *msg, UINT4 change_request)
+NR_STUN_MESSAGE_ADD_ATTRIBUTE(
+ NR_STUN_ATTR_OLD_CHANGE_REQUEST,
+ { attr->u.change_request = change_request; }
+)
+#endif /* USE_STUND_0_96 */
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_msg.h b/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_msg.h
new file mode 100644
index 0000000000..ffd68d3eee
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_msg.h
@@ -0,0 +1,208 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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 _stun_msg_h
+#define _stun_msg_h
+
+#include "csi_platform.h"
+#include "nr_api.h"
+#include "transport_addr.h"
+
+#define NR_STUN_MAX_USERNAME_BYTES 513
+#define NR_STUN_MAX_ERROR_CODE_REASON_BYTES 763
+#define NR_STUN_MAX_ERROR_CODE_REASON_CHARS 128
+#define NR_STUN_MAX_REALM_BYTES 763
+#define NR_STUN_MAX_REALM_CHARS 128
+#define NR_STUN_MAX_NONCE_BYTES 763
+#define NR_STUN_MAX_NONCE_CHARS 128
+#define NR_STUN_MAX_SERVER_BYTES 763
+#define NR_STUN_MAX_SERVER_CHARS 128
+#define NR_STUN_MAX_STRING_SIZE 763 /* any possible string */
+#define NR_STUN_MAX_UNKNOWN_ATTRIBUTES 16
+#define NR_STUN_MAX_MESSAGE_SIZE 2048
+
+#define NR_STUN_MAGIC_COOKIE 0x2112A442
+#define NR_STUN_MAGIC_COOKIE2 0xc5cb4e1d /* used recognize old stun messages */
+
+typedef struct { UCHAR octet[12]; } UINT12;
+
+typedef struct nr_stun_attr_error_code_ {
+ UINT2 number;
+ char reason[NR_STUN_MAX_ERROR_CODE_REASON_BYTES+1]; /* +1 for \0 */
+} nr_stun_attr_error_code;
+
+typedef struct nr_stun_attr_fingerprint_ {
+ UINT4 checksum;
+ int valid;
+} nr_stun_attr_fingerprint;
+
+typedef struct nr_stun_attr_message_integrity_ {
+ UCHAR hash[20];
+ int unknown_user;
+ UCHAR password[1024];
+ int passwordlen;
+ int valid;
+} nr_stun_attr_message_integrity;
+
+typedef struct nr_stun_attr_unknown_attributes_ {
+ UINT2 attribute[NR_STUN_MAX_UNKNOWN_ATTRIBUTES];
+ int num_attributes;
+} nr_stun_attr_unknown_attributes;
+
+typedef struct nr_stun_attr_xor_mapped_address_ {
+ nr_transport_addr masked;
+ nr_transport_addr unmasked;
+} nr_stun_attr_xor_mapped_address;
+
+typedef struct nr_stun_attr_data_ {
+ UCHAR data[NR_STUN_MAX_MESSAGE_SIZE];
+ size_t length;
+} nr_stun_attr_data;
+
+
+typedef struct nr_stun_encoded_attribute_ {
+ UINT2 type;
+ UINT2 length;
+ UCHAR value[NR_STUN_MAX_MESSAGE_SIZE];
+} nr_stun_encoded_attribute;
+
+typedef struct nr_stun_message_attribute_ {
+ UINT2 type;
+ UINT2 length;
+ union {
+ nr_transport_addr address;
+ nr_transport_addr alternate_server;
+ nr_stun_attr_error_code error_code;
+ nr_stun_attr_fingerprint fingerprint;
+ nr_transport_addr mapped_address;
+ nr_stun_attr_message_integrity message_integrity;
+ char nonce[NR_STUN_MAX_NONCE_BYTES+1]; /* +1 for \0 */
+ char realm[NR_STUN_MAX_REALM_BYTES+1]; /* +1 for \0 */
+ nr_stun_attr_xor_mapped_address relay_address;
+ char server_name[NR_STUN_MAX_SERVER_BYTES+1]; /* +1 for \0 */
+ nr_stun_attr_unknown_attributes unknown_attributes;
+ char username[NR_STUN_MAX_USERNAME_BYTES+1]; /* +1 for \0 */
+ nr_stun_attr_xor_mapped_address xor_mapped_address;
+
+#ifdef USE_ICE
+ UINT4 priority;
+ UINT8 ice_controlled;
+ UINT8 ice_controlling;
+#endif /* USE_ICE */
+
+#ifdef USE_TURN
+ UINT4 lifetime_secs;
+ nr_transport_addr remote_address;
+ UCHAR requested_transport;
+ nr_stun_attr_data data;
+#endif /* USE_TURN */
+
+#ifdef USE_STUND_0_96
+ UINT4 change_request;
+#endif /* USE_STUND_0_96 */
+
+ /* make sure there's enough room here to place any possible
+ * attribute */
+ UCHAR largest_possible_attribute[NR_STUN_MAX_MESSAGE_SIZE];
+ } u;
+ nr_stun_encoded_attribute *encoding;
+ size_t encoding_length;
+ char *name;
+ char *type_name;
+ int invalid;
+ TAILQ_ENTRY(nr_stun_message_attribute_) entry;
+} nr_stun_message_attribute;
+
+typedef TAILQ_HEAD(nr_stun_message_attribute_head_,nr_stun_message_attribute_) nr_stun_message_attribute_head;
+
+typedef struct nr_stun_message_header_ {
+ UINT2 type;
+ UINT2 length;
+ UINT4 magic_cookie;
+ UINT12 id;
+} nr_stun_message_header;
+
+typedef struct nr_stun_message_ {
+ char *name;
+ UCHAR buffer[NR_STUN_MAX_MESSAGE_SIZE];
+ size_t length;
+ nr_stun_message_header header;
+ int comprehension_required_unknown_attributes;
+ int comprehension_optional_unknown_attributes;
+ nr_stun_message_attribute_head attributes;
+} nr_stun_message;
+
+int nr_stun_message_create(nr_stun_message **msg);
+int nr_stun_message_create2(nr_stun_message **msg, UCHAR *buffer, size_t length);
+int nr_stun_message_destroy(nr_stun_message **msg);
+
+int nr_stun_message_attribute_create(nr_stun_message *msg, nr_stun_message_attribute **attr);
+int nr_stun_message_attribute_destroy(nr_stun_message *msg, nr_stun_message_attribute **attr);
+
+int nr_stun_message_has_attribute(nr_stun_message *msg, UINT2 type, nr_stun_message_attribute **attribute);
+
+int nr_stun_message_get_attribute(nr_stun_message *msg, UINT2 type, UINT2 index, nr_stun_message_attribute **attribute);
+
+int nr_stun_message_add_alternate_server_attribute(nr_stun_message *msg, nr_transport_addr *alternate_server);
+int nr_stun_message_add_error_code_attribute(nr_stun_message *msg, UINT2 number, char *reason);
+int nr_stun_message_add_fingerprint_attribute(nr_stun_message *msg);
+int nr_stun_message_add_message_integrity_attribute(nr_stun_message *msg, Data *password);
+int nr_stun_message_add_nonce_attribute(nr_stun_message *msg, char *nonce);
+int nr_stun_message_add_realm_attribute(nr_stun_message *msg, char *realm);
+int nr_stun_message_add_server_attribute(nr_stun_message *msg, char *server_name);
+int nr_stun_message_add_unknown_attributes_attribute(nr_stun_message *msg, nr_stun_attr_unknown_attributes *unknown_attributes);
+int nr_stun_message_add_username_attribute(nr_stun_message *msg, char *username);
+int nr_stun_message_add_xor_mapped_address_attribute(nr_stun_message *msg, nr_transport_addr *mapped_address);
+
+#ifdef USE_ICE
+int nr_stun_message_add_ice_controlled_attribute(nr_stun_message *msg, UINT8 ice_controlled);
+int nr_stun_message_add_ice_controlling_attribute(nr_stun_message *msg, UINT8 ice_controlling);
+int nr_stun_message_add_priority_attribute(nr_stun_message *msg, UINT4 priority);
+int nr_stun_message_add_use_candidate_attribute(nr_stun_message *msg);
+#endif /* USE_ICE */
+
+#ifdef USE_TURN
+int nr_stun_message_add_data_attribute(nr_stun_message *msg, UCHAR *data, int length);
+int nr_stun_message_add_lifetime_attribute(nr_stun_message *msg, UINT4 lifetime_secs);
+int nr_stun_message_add_requested_transport_attribute(nr_stun_message *msg, UCHAR transport);
+int
+nr_stun_message_add_xor_peer_address_attribute(nr_stun_message *msg, nr_transport_addr *peer_address);
+#endif /* USE_TURN */
+
+#ifdef USE_STUND_0_96
+int nr_stun_message_add_change_request_attribute(nr_stun_message *msg, UINT4 change_request);
+#endif /* USE_STUND_0_96 */
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_proc.c b/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_proc.c
new file mode 100644
index 0000000000..13366e265d
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_proc.c
@@ -0,0 +1,554 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+*/
+
+#include <errno.h>
+#include <csi_platform.h>
+
+#ifdef WIN32
+#include <winsock2.h>
+#include <stdlib.h>
+#include <io.h>
+#include <time.h>
+#else /* UNIX */
+#include <string.h>
+#endif /* end UNIX */
+#include <assert.h>
+
+#include "stun.h"
+#include "stun_reg.h"
+#include "registry.h"
+
+static int
+nr_stun_add_realm_and_nonce(int new_nonce, nr_stun_server_client *clnt, nr_stun_message *res);
+
+/* draft-ietf-behave-rfc3489bis-10.txt S 7.3 */
+int
+nr_stun_receive_message(nr_stun_message *req, nr_stun_message *msg)
+{
+ int _status;
+ nr_stun_message_attribute *attr;
+
+#ifdef USE_RFC_3489_BACKWARDS_COMPATIBLE
+ /* if this message was generated by an RFC 3489 impementation,
+ * the call to nr_is_stun_message will fail, so skip that
+ * check and puke elsewhere if the message can't be decoded */
+ if (msg->header.magic_cookie == NR_STUN_MAGIC_COOKIE
+ || msg->header.magic_cookie == NR_STUN_MAGIC_COOKIE2) {
+#endif /* USE_RFC_3489_BACKWARDS_COMPATIBLE */
+ if (!nr_is_stun_message(msg->buffer, msg->length)) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Not a STUN message");
+ ABORT(R_REJECTED);
+ }
+#ifdef USE_RFC_3489_BACKWARDS_COMPATIBLE
+ }
+#endif /* USE_RFC_3489_BACKWARDS_COMPATIBLE */
+
+ if (req == 0) {
+ if (NR_STUN_GET_TYPE_CLASS(msg->header.type) != NR_CLASS_REQUEST) {
+ r_log(NR_LOG_STUN,LOG_WARNING,"Illegal message type: %03x", msg->header.type);
+ ABORT(R_REJECTED);
+ }
+ }
+ else {
+ if (NR_STUN_GET_TYPE_CLASS(msg->header.type) != NR_CLASS_RESPONSE
+ && NR_STUN_GET_TYPE_CLASS(msg->header.type) != NR_CLASS_ERROR_RESPONSE) {
+ r_log(NR_LOG_STUN,LOG_WARNING,"Illegal message class: %03x", msg->header.type);
+ ABORT(R_REJECTED);
+ }
+
+ if (NR_STUN_GET_TYPE_METHOD(req->header.type) != NR_STUN_GET_TYPE_METHOD(msg->header.type)) {
+ r_log(NR_LOG_STUN,LOG_WARNING,"Inconsistent message method: %03x expected %03x", msg->header.type, req->header.type);
+ ABORT(R_REJECTED);
+ }
+
+ if (nr_stun_different_transaction(msg->buffer, msg->length, req)) {
+ r_log(NR_LOG_STUN, LOG_DEBUG, "Unrecognized STUN transaction");
+ ABORT(R_REJECTED);
+ }
+ }
+
+ switch (msg->header.magic_cookie) {
+ case NR_STUN_MAGIC_COOKIE:
+ /* basically draft-ietf-behave-rfc3489bis-10.txt S 6 rules */
+
+ if (nr_stun_message_has_attribute(msg, NR_STUN_ATTR_FINGERPRINT, &attr)
+ && !attr->u.fingerprint.valid) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Invalid fingerprint");
+ ABORT(R_REJECTED);
+ }
+
+ break;
+
+#ifdef USE_STUND_0_96
+ case NR_STUN_MAGIC_COOKIE2:
+ /* nothing to check in this case */
+ break;
+#endif /* USE_STUND_0_96 */
+
+ default:
+#ifdef USE_RFC_3489_BACKWARDS_COMPATIBLE
+ /* in RFC 3489 there is no magic cookie, it's part of the transaction ID */
+#else
+#ifdef NDEBUG
+ /* in deployment builds we should always see a recognized magic cookie */
+ r_log(NR_LOG_STUN, LOG_WARNING, "Missing Magic Cookie");
+ ABORT(R_REJECTED);
+#else
+ /* ignore this condition because sometimes we like to pretend we're
+ * a server talking to old clients and their messages don't contain
+ * a magic cookie at all but rather the magic cookie field is part
+ * of their ID and therefore random */
+#endif /* NDEBUG */
+#endif /* USE_RFC_3489_BACKWARDS_COMPATIBLE */
+ break;
+ }
+
+ _status=0;
+ abort:
+ return _status;
+}
+
+/* draft-ietf-behave-rfc3489bis-10.txt S 7.3.1 */
+int
+nr_stun_process_request(nr_stun_message *req, nr_stun_message *res)
+{
+ int _status;
+#ifdef USE_STUN_PEDANTIC
+ int r;
+ nr_stun_attr_unknown_attributes unknown_attributes = { { 0 } };
+ nr_stun_message_attribute *attr;
+
+ if (req->comprehension_required_unknown_attributes > 0) {
+ nr_stun_form_error_response(req, res, 420, "Unknown Attributes");
+ r_log(NR_LOG_STUN, LOG_WARNING, "Request contains comprehension required but unknown attributes");
+
+ TAILQ_FOREACH(attr, &req->attributes, entry) {
+ if (attr->name == 0) {
+ /* unrecognized attribute */
+
+ /* should never happen, but truncate if it ever were to occur */
+ if (unknown_attributes.num_attributes > NR_STUN_MAX_UNKNOWN_ATTRIBUTES)
+ break;
+
+ unknown_attributes.attribute[unknown_attributes.num_attributes++] = attr->type;
+ }
+ }
+
+ assert(req->comprehension_required_unknown_attributes + req->comprehension_optional_unknown_attributes == unknown_attributes.num_attributes);
+
+ if ((r=nr_stun_message_add_unknown_attributes_attribute(res, &unknown_attributes)))
+ ABORT(R_ALREADY);
+
+ ABORT(R_ALREADY);
+ }
+#endif /* USE_STUN_PEDANTIC */
+
+ _status=0;
+#ifdef USE_STUN_PEDANTIC
+ abort:
+#endif /* USE_STUN_PEDANTIC */
+ return _status;
+}
+
+/* draft-ietf-behave-rfc3489bis-10.txt S 7.3.2 */
+int
+nr_stun_process_indication(nr_stun_message *ind)
+{
+ int _status;
+#ifdef USE_STUN_PEDANTIC
+
+ if (ind->comprehension_required_unknown_attributes > 0) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Indication contains comprehension required but unknown attributes");
+ ABORT(R_REJECTED);
+ }
+#endif /* USE_STUN_PEDANTIC */
+
+ _status=0;
+#ifdef USE_STUN_PEDANTIC
+ abort:
+#endif /* USE_STUN_PEDANTIC */
+ return _status;
+}
+
+/* RFC5389 S 7.3.3, except that we *also* allow a MAPPED_ADDRESS
+ to compensate for a bug in Google's STUN server where it
+ always returns MAPPED_ADDRESS.
+
+ Mozilla bug: 888274.
+ */
+int
+nr_stun_process_success_response(nr_stun_message *res)
+{
+ int _status;
+
+#ifdef USE_STUN_PEDANTIC
+ if (res->comprehension_required_unknown_attributes > 0) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Response contains comprehension required but unknown attributes");
+ ABORT(R_REJECTED);
+ }
+#endif /* USE_STUN_PEDANTIC */
+
+ if (NR_STUN_GET_TYPE_METHOD(res->header.type) == NR_METHOD_BINDING) {
+ if (! nr_stun_message_has_attribute(res, NR_STUN_ATTR_XOR_MAPPED_ADDRESS, 0) &&
+ ! nr_stun_message_has_attribute(res, NR_STUN_ATTR_MAPPED_ADDRESS, 0)) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Missing XOR-MAPPED-ADDRESS and MAPPED_ADDRESS");
+ ABORT(R_REJECTED);
+ }
+ }
+
+ _status=0;
+ abort:
+ return _status;
+}
+
+/* draft-ietf-behave-rfc3489bis-10.txt S 7.3.4 */
+int
+nr_stun_process_error_response(nr_stun_message *res, UINT2 *error_code)
+{
+ int _status;
+ nr_stun_message_attribute *attr;
+
+ if (res->comprehension_required_unknown_attributes > 0) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Error response contains comprehension required but unknown attributes");
+ ABORT(R_REJECTED);
+ }
+
+ if (! nr_stun_message_has_attribute(res, NR_STUN_ATTR_ERROR_CODE, &attr)) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Missing ERROR-CODE");
+ ABORT(R_REJECTED);
+ }
+
+ *error_code = attr->u.error_code.number;
+
+ switch (attr->u.error_code.number / 100) {
+ case 3:
+ /* We do not treat STUN/300 as retryable. The TURN Allocate handling
+ * code will reset the ctx if appropriate. */
+ ABORT(R_REJECTED);
+ break;
+
+ case 4:
+ /* If the error code is 400 through 499, the client declares the
+ * transaction failed; in the case of 420 (Unknown Attribute), the
+ * response should contain a UNKNOWN-ATTRIBUTES attribute that gives
+ * additional information. */
+ if (attr->u.error_code.number == 420)
+ ABORT(R_REJECTED);
+
+ /* it may be possible to restart given the info that was received in
+ * this response, so retry */
+ ABORT(R_RETRY);
+ break;
+
+ case 5:
+ /* If the error code is 500 through 599, the client MAY resend the
+ * request; clients that do so MUST limit the number of times they do
+ * this. */
+ /* let the retransmit mechanism handle resending the request */
+ break;
+
+ default:
+ ABORT(R_REJECTED);
+ break;
+ }
+
+ /* the spec says: "The client then does any processing specified by the authentication
+ * mechanism (see Section 10). This may result in a new transaction
+ * attempt." -- but this is handled already elsewhere, so needn't be repeated
+ * in this function */
+
+ _status=0;
+ abort:
+ return _status;
+}
+
+/* draft-ietf-behave-rfc3489bis-10.txt S 10.1.2 */
+int
+nr_stun_receive_request_or_indication_short_term_auth(nr_stun_message *msg,
+ nr_stun_message *res)
+{
+ int _status;
+ nr_stun_message_attribute *attr;
+
+ switch (msg->header.magic_cookie) {
+ default:
+ /* in RFC 3489 there is no magic cookie, it's part of the transaction ID */
+ /* drop thru */
+ case NR_STUN_MAGIC_COOKIE:
+ if (!nr_stun_message_has_attribute(msg, NR_STUN_ATTR_MESSAGE_INTEGRITY, &attr)) {
+ nr_stun_form_error_response(msg, res, 400, "Missing MESSAGE-INTEGRITY");
+ ABORT(R_ALREADY);
+ }
+
+ if (!nr_stun_message_has_attribute(msg, NR_STUN_ATTR_USERNAME, 0)) {
+ nr_stun_form_error_response(msg, res, 400, "Missing USERNAME");
+ ABORT(R_ALREADY);
+ }
+
+ if (attr->u.message_integrity.unknown_user) {
+ nr_stun_form_error_response(msg, res, 401, "Unrecognized USERNAME");
+ ABORT(R_ALREADY);
+ }
+
+ if (!attr->u.message_integrity.valid) {
+ nr_stun_form_error_response(msg, res, 401, "Bad MESSAGE-INTEGRITY");
+ ABORT(R_ALREADY);
+ }
+
+ break;
+
+#ifdef USE_STUND_0_96
+ case NR_STUN_MAGIC_COOKIE2:
+ /* nothing to check in this case */
+ break;
+#endif /* USE_STUND_0_96 */
+ }
+
+ _status=0;
+ abort:
+ return _status;
+}
+
+/* draft-ietf-behave-rfc3489bis-10.txt S 10.1.3 */
+int
+nr_stun_receive_response_short_term_auth(nr_stun_message *res)
+{
+ int _status;
+ nr_stun_message_attribute *attr;
+
+ switch (res->header.magic_cookie) {
+ default:
+ /* in RFC 3489 there is no magic cookie, it's part of the transaction ID */
+ /* drop thru */
+ case NR_STUN_MAGIC_COOKIE:
+ if (!nr_stun_message_has_attribute(res, NR_STUN_ATTR_MESSAGE_INTEGRITY, &attr)) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Missing MESSAGE-INTEGRITY");
+ ABORT(R_REJECTED);
+ }
+
+ if (!attr->u.message_integrity.valid) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Bad MESSAGE-INTEGRITY");
+ ABORT(R_REJECTED);
+ }
+
+ break;
+
+#ifdef USE_STUND_0_96
+ case NR_STUN_MAGIC_COOKIE2:
+ /* nothing to check in this case */
+ break;
+#endif /* USE_STUND_0_96 */
+ }
+
+ _status=0;
+ abort:
+ return _status;
+}
+
+static int
+nr_stun_add_realm_and_nonce(int new_nonce, nr_stun_server_client *clnt, nr_stun_message *res)
+{
+ int r,_status;
+ char *realm = 0;
+ char *nonce;
+ UINT2 size;
+
+ if ((r=NR_reg_alloc_string(NR_STUN_REG_PREF_SERVER_REALM, &realm)))
+ ABORT(r);
+
+ if ((r=nr_stun_message_add_realm_attribute(res, realm)))
+ ABORT(r);
+
+ if (clnt) {
+ if (strlen(clnt->nonce) < 1)
+ new_nonce = 1;
+
+ if (new_nonce) {
+ if (NR_reg_get_uint2(NR_STUN_REG_PREF_SERVER_NONCE_SIZE, &size))
+ size = 48;
+
+ if (size > (sizeof(clnt->nonce) - 1))
+ size = sizeof(clnt->nonce) - 1;
+
+ nr_random_alphanum(clnt->nonce, size);
+ clnt->nonce[size] = '\0';
+ }
+
+ nonce = clnt->nonce;
+ }
+ else {
+ /* user is not known, so use a bogus nonce since there's no way to
+ * store a good nonce with the client-specific data -- this nonce
+ * will be recognized as stale if the client attempts another
+ * request */
+ nonce = "STALE";
+ }
+
+ if ((r=nr_stun_message_add_nonce_attribute(res, nonce)))
+ ABORT(r);
+
+ _status=0;
+ abort:
+#ifdef USE_TURN
+assert(_status == 0); /* TODO: !nn! cleanup after I reimplmement TURN */
+#endif
+ RFREE(realm);
+ return _status;
+}
+
+/* draft-ietf-behave-rfc3489bis-10.txt S 10.2.1 - 10.2.2 */
+int
+nr_stun_receive_request_long_term_auth(nr_stun_message *req, nr_stun_server_ctx *ctx, nr_stun_message *res)
+{
+ int r,_status;
+ nr_stun_message_attribute *mi;
+ nr_stun_message_attribute *n;
+ nr_stun_server_client *clnt = 0;
+
+ switch (req->header.magic_cookie) {
+ default:
+ /* in RFC 3489 there is no magic cookie, it's part of the transaction ID */
+ /* drop thru */
+ case NR_STUN_MAGIC_COOKIE:
+ if (!nr_stun_message_has_attribute(req, NR_STUN_ATTR_USERNAME, 0)) {
+ nr_stun_form_error_response(req, res, 400, "Missing USERNAME");
+ nr_stun_add_realm_and_nonce(0, 0, res);
+ ABORT(R_ALREADY);
+ }
+
+ if ((r=nr_stun_get_message_client(ctx, req, &clnt))) {
+ nr_stun_form_error_response(req, res, 401, "Unrecognized USERNAME");
+ nr_stun_add_realm_and_nonce(0, 0, res);
+ ABORT(R_ALREADY);
+ }
+
+ if (!nr_stun_message_has_attribute(req, NR_STUN_ATTR_MESSAGE_INTEGRITY, &mi)) {
+ nr_stun_form_error_response(req, res, 401, "Missing MESSAGE-INTEGRITY");
+ nr_stun_add_realm_and_nonce(0, clnt, res);
+ ABORT(R_ALREADY);
+ }
+
+ assert(!mi->u.message_integrity.unknown_user);
+
+ if (!nr_stun_message_has_attribute(req, NR_STUN_ATTR_REALM, 0)) {
+ nr_stun_form_error_response(req, res, 400, "Missing REALM");
+ ABORT(R_ALREADY);
+ }
+
+ if (!nr_stun_message_has_attribute(req, NR_STUN_ATTR_NONCE, &n)) {
+ nr_stun_form_error_response(req, res, 400, "Missing NONCE");
+ ABORT(R_ALREADY);
+ }
+
+ assert(sizeof(clnt->nonce) == sizeof(n->u.nonce));
+ if (strncmp(clnt->nonce, n->u.nonce, sizeof(n->u.nonce))) {
+ nr_stun_form_error_response(req, res, 438, "Stale NONCE");
+ nr_stun_add_realm_and_nonce(1, clnt, res);
+ ABORT(R_ALREADY);
+ }
+
+ if (!mi->u.message_integrity.valid) {
+ nr_stun_form_error_response(req, res, 401, "Bad MESSAGE-INTEGRITY");
+ nr_stun_add_realm_and_nonce(0, clnt, res);
+ ABORT(R_ALREADY);
+ }
+
+ break;
+
+#ifdef USE_STUND_0_96
+ case NR_STUN_MAGIC_COOKIE2:
+ /* nothing to do in this case */
+ break;
+#endif /* USE_STUND_0_96 */
+ }
+
+ _status=0;
+ abort:
+
+ return _status;
+}
+
+/* draft-ietf-behave-rfc3489bis-10.txt S 10.2.3 */
+int
+nr_stun_receive_response_long_term_auth(nr_stun_message *res, nr_stun_client_ctx *ctx)
+{
+ int _status;
+ nr_stun_message_attribute *attr;
+
+ switch (res->header.magic_cookie) {
+ default:
+ /* in RFC 3489 there is no magic cookie, it's part of the transaction ID */
+ /* drop thru */
+ case NR_STUN_MAGIC_COOKIE:
+ if (nr_stun_message_has_attribute(res, NR_STUN_ATTR_REALM, &attr)) {
+ RFREE(ctx->realm);
+ ctx->realm = r_strdup(attr->u.realm);
+ if (!ctx->realm)
+ ABORT(R_NO_MEMORY);
+ }
+ else {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Missing REALM");
+ ABORT(R_REJECTED);
+ }
+
+ if (nr_stun_message_has_attribute(res, NR_STUN_ATTR_NONCE, &attr)) {
+ RFREE(ctx->nonce);
+ ctx->nonce = r_strdup(attr->u.nonce);
+ if (!ctx->nonce)
+ ABORT(R_NO_MEMORY);
+ }
+ else {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Missing NONCE");
+ ABORT(R_REJECTED);
+ }
+
+ if (nr_stun_message_has_attribute(res, NR_STUN_ATTR_MESSAGE_INTEGRITY, &attr)) {
+ if (!attr->u.message_integrity.valid) {
+ r_log(NR_LOG_STUN, LOG_WARNING, "Bad MESSAGE-INTEGRITY");
+ ABORT(R_REJECTED);
+ }
+ }
+
+ break;
+
+#ifdef USE_STUND_0_96
+ case NR_STUN_MAGIC_COOKIE2:
+ /* nothing to check in this case */
+ break;
+#endif /* USE_STUND_0_96 */
+ }
+
+ _status=0;
+ abort:
+ return _status;
+}
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_proc.h b/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_proc.h
new file mode 100644
index 0000000000..5975670779
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_proc.h
@@ -0,0 +1,53 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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 _stun_proc_h
+#define _stun_proc_h
+
+#include "stun.h"
+
+int nr_stun_receive_message(nr_stun_message *req, nr_stun_message *msg);
+int nr_stun_process_request(nr_stun_message *req, nr_stun_message *res);
+int nr_stun_process_indication(nr_stun_message *ind);
+int nr_stun_process_success_response(nr_stun_message *res);
+int nr_stun_process_error_response(nr_stun_message *res, UINT2 *error_code);
+
+int nr_stun_receive_request_or_indication_short_term_auth(nr_stun_message *msg, nr_stun_message *res);
+int nr_stun_receive_response_short_term_auth(nr_stun_message *res);
+
+int nr_stun_receive_request_long_term_auth(nr_stun_message *req, nr_stun_server_ctx *ctx, nr_stun_message *res);
+int nr_stun_receive_response_long_term_auth(nr_stun_message *res, nr_stun_client_ctx *ctx);
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_reg.h b/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_reg.h
new file mode 100644
index 0000000000..2167fcd91b
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_reg.h
@@ -0,0 +1,58 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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 _stun_reg_h
+#define _stun_reg_h
+#ifdef __cplusplus
+using namespace std;
+extern "C" {
+#endif /* __cplusplus */
+
+#define NR_STUN_REG_PREF_CLNT_RETRANSMIT_TIMEOUT "stun.client.retransmission_timeout"
+#define NR_STUN_REG_PREF_CLNT_RETRANSMIT_BACKOFF "stun.client.retransmission_backoff_factor"
+#define NR_STUN_REG_PREF_CLNT_MAXIMUM_TRANSMITS "stun.client.maximum_transmits"
+#define NR_STUN_REG_PREF_CLNT_FINAL_RETRANSMIT_BACKOFF "stun.client.final_retransmit_backoff"
+
+#define NR_STUN_REG_PREF_ALLOW_LOOPBACK_ADDRS "stun.allow_loopback"
+#define NR_STUN_REG_PREF_ALLOW_LINK_LOCAL_ADDRS "stun.allow_link_local"
+#define NR_STUN_REG_PREF_ADDRESS_PRFX "stun.address"
+#define NR_STUN_REG_PREF_SERVER_NAME "stun.server.name"
+#define NR_STUN_REG_PREF_SERVER_NONCE_SIZE "stun.server.nonce_size"
+#define NR_STUN_REG_PREF_SERVER_REALM "stun.server.realm"
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_server_ctx.c b/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_server_ctx.c
new file mode 100644
index 0000000000..b92b6b5ab6
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_server_ctx.c
@@ -0,0 +1,468 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+*/
+
+#include <string.h>
+#include <assert.h>
+
+#include "nr_api.h"
+#include "stun.h"
+
+static int nr_stun_server_destroy_client(nr_stun_server_client *clnt);
+static int nr_stun_server_send_response(nr_stun_server_ctx *ctx, nr_socket *sock, nr_transport_addr *peer_addr, nr_stun_message *res, nr_stun_server_client *clnt);
+static int nr_stun_server_process_request_auth_checks(nr_stun_server_ctx *ctx, nr_stun_message *req, int auth_rule, nr_stun_message *res);
+
+
+int nr_stun_server_ctx_create(char *label, nr_stun_server_ctx **ctxp)
+ {
+ int r,_status;
+ nr_stun_server_ctx *ctx=0;
+
+ if ((r=nr_stun_startup()))
+ ABORT(r);
+
+ if(!(ctx=RCALLOC(sizeof(nr_stun_server_ctx))))
+ ABORT(R_NO_MEMORY);
+
+ if(!(ctx->label=r_strdup(label)))
+ ABORT(R_NO_MEMORY);
+
+ STAILQ_INIT(&ctx->clients);
+
+ *ctxp=ctx;
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_stun_server_ctx_destroy(nr_stun_server_ctx **ctxp)
+ {
+ nr_stun_server_ctx *ctx;
+ nr_stun_server_client *clnt1,*clnt2;
+
+ if(!ctxp || !*ctxp)
+ return(0);
+
+ ctx=*ctxp;
+
+ STAILQ_FOREACH_SAFE(clnt1, &ctx->clients, entry, clnt2) {
+ nr_stun_server_destroy_client(clnt1);
+ }
+
+ nr_stun_server_destroy_client(ctx->default_client);
+
+ RFREE(ctx->label);
+ RFREE(ctx);
+
+ return(0);
+ }
+
+static int nr_stun_server_client_create(nr_stun_server_ctx *ctx, char *client_label, char *user, Data *pass, nr_stun_server_cb cb, void *cb_arg, nr_stun_server_client **clntp)
+ {
+ nr_stun_server_client *clnt=0;
+ int r,_status;
+
+ if(!(clnt=RCALLOC(sizeof(nr_stun_server_client))))
+ ABORT(R_NO_MEMORY);
+
+ if(!(clnt->label=r_strdup(client_label)))
+ ABORT(R_NO_MEMORY);
+
+ if(!(clnt->username=r_strdup(user)))
+ ABORT(R_NO_MEMORY);
+
+ if(r=r_data_copy(&clnt->password,pass))
+ ABORT(r);
+
+ r_log(NR_LOG_STUN,LOG_DEBUG,"STUN-SERVER(%s)/CLIENT(%s): Adding client for %s",ctx->label, client_label, user);
+ clnt->stun_server_cb=cb;
+ clnt->cb_arg=cb_arg;
+
+ *clntp = clnt;
+ _status=0;
+ abort:
+ if(_status){
+ nr_stun_server_destroy_client(clnt);
+ }
+ return(_status);
+ }
+
+int nr_stun_server_add_client(nr_stun_server_ctx *ctx, char *client_label, char *user, Data *pass, nr_stun_server_cb cb, void *cb_arg)
+ {
+ int r,_status;
+ nr_stun_server_client *clnt;
+
+ if (r=nr_stun_server_client_create(ctx, client_label, user, pass, cb, cb_arg, &clnt))
+ ABORT(r);
+
+ STAILQ_INSERT_TAIL(&ctx->clients,clnt,entry);
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_stun_server_add_default_client(nr_stun_server_ctx *ctx, char *ufrag, Data *pass, nr_stun_server_cb cb, void *cb_arg)
+ {
+ int r,_status;
+ nr_stun_server_client *clnt;
+
+ assert(!ctx->default_client);
+ if (ctx->default_client)
+ ABORT(R_INTERNAL);
+
+ if (r=nr_stun_server_client_create(ctx, "default_client", ufrag, pass, cb, cb_arg, &clnt))
+ ABORT(r);
+
+ ctx->default_client = clnt;
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_stun_server_remove_client(nr_stun_server_ctx *ctx, void *cb_arg)
+ {
+ nr_stun_server_client *clnt1,*clnt2;
+ int found = 0;
+
+ STAILQ_FOREACH_SAFE(clnt1, &ctx->clients, entry, clnt2) {
+ if(clnt1->cb_arg == cb_arg) {
+ STAILQ_REMOVE(&ctx->clients, clnt1, nr_stun_server_client_, entry);
+ nr_stun_server_destroy_client(clnt1);
+ found++;
+ }
+ }
+
+ if (!found)
+ ERETURN(R_NOT_FOUND);
+
+ return 0;
+ }
+
+static int nr_stun_server_get_password(void *arg, nr_stun_message *msg, Data **password)
+ {
+ int _status;
+ nr_stun_server_ctx *ctx = (nr_stun_server_ctx*)arg;
+ nr_stun_server_client *clnt = 0;
+ nr_stun_message_attribute *username_attribute;
+
+ if ((nr_stun_get_message_client(ctx, msg, &clnt))) {
+ if (! nr_stun_message_has_attribute(msg, NR_STUN_ATTR_USERNAME, &username_attribute)) {
+ r_log(NR_LOG_STUN,LOG_WARNING,"STUN-SERVER(%s): Missing Username",ctx->label);
+ ABORT(R_NOT_FOUND);
+ }
+
+ /* Although this is an exceptional condition, we'll already have seen a
+ * NOTICE-level log message about the unknown user, so additional log
+ * messages at any level higher than DEBUG are unnecessary. */
+
+ r_log(NR_LOG_STUN,LOG_DEBUG,"STUN-SERVER(%s): Unable to find password for unknown user: %s",ctx->label,username_attribute->u.username);
+ ABORT(R_NOT_FOUND);
+ }
+
+ *password = &clnt->password;
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_stun_server_process_request_auth_checks(nr_stun_server_ctx *ctx, nr_stun_message *req, int auth_rule, nr_stun_message *res)
+ {
+ int r,_status;
+
+ if (nr_stun_message_has_attribute(req, NR_STUN_ATTR_MESSAGE_INTEGRITY, 0)
+ || !(auth_rule & NR_STUN_AUTH_RULE_OPTIONAL)) {
+ /* favor long term credentials over short term, if both are supported */
+
+ if (auth_rule & NR_STUN_AUTH_RULE_LONG_TERM) {
+ if ((r=nr_stun_receive_request_long_term_auth(req, ctx, res)))
+ ABORT(r);
+ }
+ else if (auth_rule & NR_STUN_AUTH_RULE_SHORT_TERM) {
+ if ((r=nr_stun_receive_request_or_indication_short_term_auth(req, res)))
+ ABORT(r);
+ }
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_stun_server_process_request(nr_stun_server_ctx *ctx, nr_socket *sock, char *msg, int len, nr_transport_addr *peer_addr, int auth_rule)
+ {
+ int r,_status;
+ char string[256];
+ nr_stun_message *req = 0;
+ nr_stun_message *res = 0;
+ nr_stun_server_client *clnt = 0;
+ nr_stun_server_request info;
+ int error;
+ int dont_free = 0;
+ nr_transport_addr my_addr;
+
+ if ((r=nr_socket_getaddr(sock, &my_addr))) {
+ ABORT(r);
+ }
+
+ r_log(NR_LOG_STUN,LOG_DEBUG,"STUN-SERVER(%s): Received(my_addr=%s,peer_addr=%s)",ctx->label,my_addr.as_string,peer_addr->as_string);
+
+ snprintf(string, sizeof(string)-1, "STUN-SERVER(%s): Received ", ctx->label);
+ r_dump(NR_LOG_STUN, LOG_DEBUG, string, (char*)msg, len);
+
+ memset(&info,0,sizeof(info));
+
+ if ((r=nr_stun_message_create2(&req, (UCHAR*)msg, len)))
+ ABORT(r);
+
+ if ((r=nr_stun_message_create(&res)))
+ ABORT(r);
+
+ if ((r=nr_stun_decode_message(req, nr_stun_server_get_password, ctx))) {
+ /* RFC5389 S 7.3 says "If any errors are detected, the message is
+ * silently discarded." */
+#ifndef USE_STUN_PEDANTIC
+ /* ... but that seems like a bad idea, at least return a 400 so
+ * that the server isn't a black hole to the client */
+ nr_stun_form_error_response(req, res, 400, "Bad Request - Failed to decode request");
+ ABORT(R_ALREADY);
+#endif /* USE_STUN_PEDANTIC */
+ ABORT(R_REJECTED);
+ }
+
+ if ((r=nr_stun_receive_message(0, req))) {
+ /* RFC5389 S 7.3 says "If any errors are detected, the message is
+ * silently discarded." */
+#ifndef USE_STUN_PEDANTIC
+ /* ... but that seems like a bad idea, at least return a 400 so
+ * that the server isn't a black hole to the client */
+ nr_stun_form_error_response(req, res, 400, "Bad Request - Section 7.3 check failed");
+ ABORT(R_ALREADY);
+#endif /* USE_STUN_PEDANTIC */
+ ABORT(R_REJECTED);
+ }
+
+ if (NR_STUN_GET_TYPE_CLASS(req->header.type) != NR_CLASS_REQUEST
+ && NR_STUN_GET_TYPE_CLASS(req->header.type) != NR_CLASS_INDICATION) {
+ r_log(NR_LOG_STUN,LOG_WARNING,"STUN-SERVER(%s): Illegal message type: %04x",ctx->label,req->header.type);
+ /* RFC5389 S 7.3 says "If any errors are detected, the message is
+ * silently discarded." */
+#ifndef USE_STUN_PEDANTIC
+ /* ... but that seems like a bad idea, at least return a 400 so
+ * that the server isn't a black hole to the client */
+ nr_stun_form_error_response(req, res, 400, "Bad Request - Unsupported message type");
+ ABORT(R_ALREADY);
+#endif /* USE_STUN_PEDANTIC */
+ ABORT(R_REJECTED);
+ }
+
+ /* "The STUN agent then does any checks that are required by a
+ * authentication mechanism that the usage has specified" */
+ if ((r=nr_stun_server_process_request_auth_checks(ctx, req, auth_rule, res)))
+ ABORT(r);
+
+ if (NR_STUN_GET_TYPE_CLASS(req->header.type) == NR_CLASS_INDICATION) {
+ if ((r=nr_stun_process_indication(req)))
+ ABORT(r);
+ }
+ else {
+ if ((r=nr_stun_process_request(req, res)))
+ ABORT(r);
+ }
+
+ assert(res->header.type == 0);
+
+ clnt = 0;
+ if (NR_STUN_GET_TYPE_CLASS(req->header.type) == NR_CLASS_REQUEST) {
+ if ((nr_stun_get_message_client(ctx, req, &clnt))) {
+ if ((r=nr_stun_form_success_response(req, peer_addr, 0, res)))
+ ABORT(r);
+ }
+ else {
+ if ((r=nr_stun_form_success_response(req, peer_addr, &clnt->password, res)))
+ ABORT(r);
+ }
+ }
+
+ if(clnt && clnt->stun_server_cb){
+ r_log(NR_LOG_STUN,LOG_DEBUG,"Entering STUN server callback");
+
+ /* Set up the info */
+ if(r=nr_transport_addr_copy(&info.src_addr,peer_addr))
+ ABORT(r);
+
+ info.request = req;
+ info.response = res;
+
+ error = 0;
+ dont_free = 0;
+ if (clnt->stun_server_cb(clnt->cb_arg,ctx,sock,&info,&dont_free,&error)) {
+ if (error == 0)
+ error = 500;
+
+ nr_stun_form_error_response(req, res, error, "ICE Failure");
+ ABORT(R_ALREADY);
+ }
+ }
+
+ _status=0;
+ abort:
+ if (!res)
+ goto skip_response;
+
+ if (NR_STUN_GET_TYPE_CLASS(req->header.type) == NR_CLASS_INDICATION)
+ goto skip_response;
+
+ /* Now respond */
+
+ if (_status != 0 && ! nr_stun_message_has_attribute(res, NR_STUN_ATTR_ERROR_CODE, 0))
+ nr_stun_form_error_response(req, res, 500, "Failed to specify error");
+
+ if ((r=nr_stun_server_send_response(ctx, sock, peer_addr, res, clnt))) {
+ r_log(NR_LOG_STUN,LOG_ERR,"STUN-SERVER(label=%s): Failed sending response (my_addr=%s,peer_addr=%s)",ctx->label,my_addr.as_string,peer_addr->as_string);
+ _status = R_FAILED;
+ }
+
+#if 0
+ /* EKR: suppressed these checks because if you have an error when
+ you are sending an error, things go wonky */
+#ifdef SANITY_CHECKS
+ if (_status == R_ALREADY) {
+ assert(NR_STUN_GET_TYPE_CLASS(res->header.type) == NR_CLASS_ERROR_RESPONSE);
+ assert(nr_stun_message_has_attribute(res, NR_STUN_ATTR_ERROR_CODE, 0));
+ }
+ else {
+ assert(NR_STUN_GET_TYPE_CLASS(res->header.type) == NR_CLASS_RESPONSE);
+ assert(!nr_stun_message_has_attribute(res, NR_STUN_ATTR_ERROR_CODE, 0));
+ }
+#endif /* SANITY_CHECKS */
+#endif
+
+ if (0) {
+ skip_response:
+ _status = 0;
+ }
+
+ if (!dont_free) {
+ nr_stun_message_destroy(&res);
+ nr_stun_message_destroy(&req);
+ }
+
+ return(_status);
+ }
+
+static int nr_stun_server_send_response(nr_stun_server_ctx *ctx, nr_socket *sock, nr_transport_addr *peer_addr, nr_stun_message *res, nr_stun_server_client *clnt)
+ {
+ int r,_status;
+ char string[256];
+ nr_transport_addr my_addr;
+
+ if ((r=nr_socket_getaddr(sock, &my_addr))) {
+ ABORT(r);
+ }
+
+ r_log(NR_LOG_STUN,LOG_DEBUG,"STUN-SERVER(label=%s): Sending(my_addr=%s,peer_addr=%s)",ctx->label,my_addr.as_string,peer_addr->as_string);
+
+ if ((r=nr_stun_encode_message(res))) {
+ /* should never happen */
+ r_log(NR_LOG_STUN,LOG_ERR,"STUN-SERVER(label=%s): Unable to encode message", ctx->label);
+ }
+ else {
+ snprintf(string, sizeof(string)-1, "STUN(%s): Sending to %s ", ctx->label, peer_addr->as_string);
+ r_dump(NR_LOG_STUN, LOG_DEBUG, string, (char*)res->buffer, res->length);
+
+ if(r=nr_socket_sendto(sock,res->buffer,res->length,0,peer_addr))
+ ABORT(r);
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+static int nr_stun_server_destroy_client(nr_stun_server_client *clnt)
+ {
+ if (!clnt)
+ return 0;
+
+ RFREE(clnt->label);
+ RFREE(clnt->username);
+ r_data_zfree(&clnt->password);
+
+ RFREE(clnt);
+ return(0);
+ }
+
+int nr_stun_get_message_client(nr_stun_server_ctx *ctx, nr_stun_message *req, nr_stun_server_client **out)
+ {
+ int _status;
+ nr_stun_message_attribute *attr;
+ nr_stun_server_client *clnt=0;
+
+ if (! nr_stun_message_has_attribute(req, NR_STUN_ATTR_USERNAME, &attr)) {
+ r_log(NR_LOG_STUN,LOG_WARNING,"STUN-SERVER(%s): Missing Username",ctx->label);
+ ABORT(R_NOT_FOUND);
+ }
+
+ STAILQ_FOREACH(clnt, &ctx->clients, entry) {
+ if (!strncmp(clnt->username, attr->u.username,
+ sizeof(attr->u.username)))
+ break;
+ }
+
+ if (!clnt && ctx->default_client) {
+ /* If we can't find a specific client see if this matches the default,
+ which means that the username starts with our ufrag.
+ */
+ char *colon = strchr(attr->u.username, ':');
+ if (colon && !strncmp(ctx->default_client->username,
+ attr->u.username,
+ colon - attr->u.username)) {
+ clnt = ctx->default_client;
+ r_log(NR_LOG_STUN,LOG_NOTICE,"STUN-SERVER(%s): Falling back to default client, username=: %s",ctx->label,attr->u.username);
+ }
+ }
+
+ if (!clnt) {
+ r_log(NR_LOG_STUN,LOG_WARNING,"STUN-SERVER(%s): Request from unknown user: %s",ctx->label,attr->u.username);
+ ABORT(R_NOT_FOUND);
+ }
+
+ *out = clnt;
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_server_ctx.h b/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_server_ctx.h
new file mode 100644
index 0000000000..328675ee22
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_server_ctx.h
@@ -0,0 +1,80 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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 _stun_server_ctx_h
+#define _stun_server_ctx_h
+
+typedef struct nr_stun_server_ctx_ nr_stun_server_ctx;
+typedef struct nr_stun_server_client_ nr_stun_server_client;
+
+#include "stun_util.h"
+
+
+typedef struct nr_stun_server_request_{
+ nr_transport_addr src_addr;
+ nr_stun_message *request;
+ nr_stun_message *response;
+} nr_stun_server_request;
+
+typedef int (*nr_stun_server_cb)(void *cb_arg, struct nr_stun_server_ctx_ *ctx,nr_socket *sock,nr_stun_server_request *req, int *dont_free, int *error);
+
+struct nr_stun_server_client_ {
+ char *label;
+ char *username;
+ Data password;
+ nr_stun_server_cb stun_server_cb;
+ void *cb_arg;
+ char nonce[NR_STUN_MAX_NONCE_BYTES+1]; /* +1 for \0 */
+ STAILQ_ENTRY(nr_stun_server_client_) entry;
+};
+
+typedef STAILQ_HEAD(nr_stun_server_client_head_, nr_stun_server_client_) nr_stun_server_client_head;
+
+struct nr_stun_server_ctx_ {
+ char *label;
+ nr_stun_server_client_head clients;
+ nr_stun_server_client *default_client;
+};
+
+
+int nr_stun_server_ctx_create(char *label, nr_stun_server_ctx **ctxp);
+int nr_stun_server_ctx_destroy(nr_stun_server_ctx **ctxp);
+int nr_stun_server_add_client(nr_stun_server_ctx *ctx, char *client_label, char *user, Data *pass, nr_stun_server_cb cb, void *cb_arg);
+int nr_stun_server_remove_client(nr_stun_server_ctx *ctx, void *cb_arg);
+int nr_stun_server_add_default_client(nr_stun_server_ctx *ctx, char *ufrag, Data *pass, nr_stun_server_cb cb, void *cb_arg);
+int nr_stun_server_process_request(nr_stun_server_ctx *ctx, nr_socket *sock, char *msg, int len, nr_transport_addr *peer_addr, int auth_rule);
+int nr_stun_get_message_client(nr_stun_server_ctx *ctx, nr_stun_message *req, nr_stun_server_client **clnt);
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_util.c b/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_util.c
new file mode 100644
index 0000000000..cbb0128924
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_util.c
@@ -0,0 +1,352 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+*/
+
+#include <errno.h>
+#include <csi_platform.h>
+
+#ifdef WIN32
+#include <winsock2.h>
+#include <stdlib.h>
+#include <io.h>
+#include <time.h>
+#else /* UNIX */
+#include <string.h>
+#endif /* end UNIX */
+#include <assert.h>
+
+#include "stun.h"
+#include "stun_reg.h"
+#include "registry.h"
+#include "addrs.h"
+#include "transport_addr_reg.h"
+#include "nr_crypto.h"
+#include "hex.h"
+
+
+int NR_LOG_STUN = 0;
+
+int
+nr_stun_startup(void)
+{
+ int r,_status;
+
+ if ((r=r_log_register("stun", &NR_LOG_STUN)))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ return _status;
+}
+
+int
+nr_stun_xor_mapped_address(UINT4 magicCookie, UINT12 transactionId, nr_transport_addr *from, nr_transport_addr *to)
+{
+ int _status;
+
+ switch (from->ip_version) {
+ case NR_IPV4:
+ nr_ip4_port_to_transport_addr(
+ (ntohl(from->u.addr4.sin_addr.s_addr) ^ magicCookie),
+ (ntohs(from->u.addr4.sin_port) ^ (magicCookie>>16)),
+ from->protocol, to);
+ break;
+ case NR_IPV6:
+ {
+ union {
+ unsigned char addr[16];
+ UINT4 addr32[4];
+ } maskedAddr;
+
+ maskedAddr.addr32[0] = htonl(magicCookie); /* Passed in host byte order */
+ memcpy(&maskedAddr.addr32[1], transactionId.octet, sizeof(transactionId));
+
+ /* We now have the mask in network byte order */
+ /* Xor the address in network byte order */
+ for (size_t i = 0; i < sizeof(maskedAddr); ++i) {
+ maskedAddr.addr[i] ^= from->u.addr6.sin6_addr.s6_addr[i];
+ }
+
+ nr_ip6_port_to_transport_addr(
+ (struct in6_addr*)&maskedAddr,
+ (ntohs(from->u.addr6.sin6_port) ^ (magicCookie>>16)),
+ from->protocol, to);
+ }
+ break;
+ default:
+ assert(0);
+ ABORT(R_INTERNAL);
+ break;
+ }
+
+ _status = 0;
+ abort:
+ return _status;
+}
+
+int
+nr_stun_filter_local_addresses(nr_local_addr addrs[], int *count)
+{
+ int r,_status;
+ char allow_loopback = 0;
+ char allow_link_local = 0;
+
+ if ((r=NR_reg_get_char(NR_STUN_REG_PREF_ALLOW_LOOPBACK_ADDRS,
+ &allow_loopback))) {
+ if (r != R_NOT_FOUND) {
+ ABORT(r);
+ }
+ }
+
+ if ((r=NR_reg_get_char(NR_STUN_REG_PREF_ALLOW_LINK_LOCAL_ADDRS,
+ &allow_link_local))) {
+ if (r != R_NOT_FOUND) {
+ ABORT(r);
+ }
+ }
+
+ if ((r=nr_stun_filter_addrs(addrs,
+ !allow_loopback,
+ !allow_link_local,
+ count))) {
+ ABORT(r);
+ }
+
+ _status=0;
+ abort:
+ return _status;
+}
+
+int
+nr_stun_find_local_addresses(nr_local_addr addrs[], int maxaddrs, int *count)
+{
+ int r,_status;
+ //NR_registry *children = 0;
+
+ *count = 0;
+
+#if 0
+ // this really goes with the code commented out below. (mjf)
+ if ((r=NR_reg_get_child_count(NR_STUN_REG_PREF_ADDRESS_PRFX, (unsigned int*)count)))
+ if (r != R_NOT_FOUND)
+ ABORT(r);
+#endif
+
+ if (*count == 0) {
+ if ((r=nr_stun_get_addrs(addrs, maxaddrs, count)))
+ ABORT(r);
+
+ goto done;
+ }
+
+ if (*count >= maxaddrs) {
+ r_log(NR_LOG_STUN, LOG_INFO, "Address list truncated from %d to %d", *count, maxaddrs);
+ *count = maxaddrs;
+ }
+
+#if 0
+ if (*count > 0) {
+ /* TODO(ekr@rtfm.com): Commented out 2012-07-26.
+
+ This code is currently not used in Firefox and needs to be
+ ported to 64-bit */
+ children = RCALLOC((*count + 10) * sizeof(*children));
+ if (!children)
+ ABORT(R_NO_MEMORY);
+
+ assert(sizeof(size_t) == sizeof(*count));
+
+ if ((r=NR_reg_get_children(NR_STUN_REG_PREF_ADDRESS_PRFX, children, (size_t)(*count + 10), (size_t*)count)))
+ ABORT(r);
+
+ for (i = 0; i < *count; ++i) {
+ if ((r=nr_reg_get_transport_addr(children[i], 0, &addrs[i].addr)))
+ ABORT(r);
+ }
+ }
+#endif
+
+ done:
+
+ _status=0;
+ abort:
+ //RFREE(children);
+ return _status;
+}
+
+int
+nr_stun_different_transaction(UCHAR *msg, size_t len, nr_stun_message *req)
+{
+ int _status;
+ nr_stun_message_header header;
+ char reqid[44];
+ char msgid[44];
+ size_t unused;
+
+ if (sizeof(header) > len)
+ ABORT(R_FAILED);
+
+ assert(sizeof(header.id) == sizeof(UINT12));
+
+ memcpy(&header, msg, sizeof(header));
+
+ if (memcmp(&req->header.id, &header.id, sizeof(header.id))) {
+ nr_nbin2hex((UCHAR*)&req->header.id, sizeof(req->header.id), reqid, sizeof(reqid), &unused);
+ nr_nbin2hex((UCHAR*)&header.id, sizeof(header.id), msgid, sizeof(msgid), &unused);
+ r_log(NR_LOG_STUN, LOG_DEBUG, "Mismatched message IDs %s/%s", reqid, msgid);
+ ABORT(R_NOT_FOUND);
+ }
+
+ _status=0;
+ abort:
+ return _status;
+}
+
+char*
+nr_stun_msg_type(int type)
+{
+ char *ret = 0;
+
+ switch (type) {
+ case NR_STUN_MSG_BINDING_REQUEST:
+ ret = "BINDING-REQUEST";
+ break;
+ case NR_STUN_MSG_BINDING_INDICATION:
+ ret = "BINDING-INDICATION";
+ break;
+ case NR_STUN_MSG_BINDING_RESPONSE:
+ ret = "BINDING-RESPONSE";
+ break;
+ case NR_STUN_MSG_BINDING_ERROR_RESPONSE:
+ ret = "BINDING-ERROR-RESPONSE";
+ break;
+
+#ifdef USE_TURN
+ case NR_STUN_MSG_ALLOCATE_REQUEST:
+ ret = "ALLOCATE-REQUEST";
+ break;
+ case NR_STUN_MSG_ALLOCATE_RESPONSE:
+ ret = "ALLOCATE-RESPONSE";
+ break;
+ case NR_STUN_MSG_ALLOCATE_ERROR_RESPONSE:
+ ret = "ALLOCATE-ERROR-RESPONSE";
+ break;
+ case NR_STUN_MSG_REFRESH_REQUEST:
+ ret = "REFRESH-REQUEST";
+ break;
+ case NR_STUN_MSG_REFRESH_RESPONSE:
+ ret = "REFRESH-RESPONSE";
+ break;
+ case NR_STUN_MSG_REFRESH_ERROR_RESPONSE:
+ ret = "REFRESH-ERROR-RESPONSE";
+ break;
+ case NR_STUN_MSG_SEND_INDICATION:
+ ret = "SEND-INDICATION";
+ break;
+ case NR_STUN_MSG_DATA_INDICATION:
+ ret = "DATA-INDICATION";
+ break;
+ case NR_STUN_MSG_PERMISSION_REQUEST:
+ ret = "PERMISSION-REQUEST";
+ break;
+ case NR_STUN_MSG_PERMISSION_RESPONSE:
+ ret = "PERMISSION-RESPONSE";
+ break;
+ case NR_STUN_MSG_PERMISSION_ERROR_RESPONSE:
+ ret = "PERMISSION-ERROR-RESPONSE";
+ break;
+#endif /* USE_TURN */
+
+ default:
+ /* ret remains 0 */
+ break;
+ }
+
+ return ret;
+}
+
+int
+nr_random_alphanum(char *alphanum, int size)
+{
+ static char alphanums[256] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
+ 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
+ 'U', 'V', 'W', 'X', 'Y', 'Z',
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
+ 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
+ 'u', 'v', 'w', 'x', 'y', 'z',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
+ 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
+ 'U', 'V', 'W', 'X', 'Y', 'Z',
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
+ 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
+ 'u', 'v', 'w', 'x', 'y', 'z',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
+ 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
+ 'U', 'V', 'W', 'X', 'Y', 'Z',
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
+ 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
+ 'u', 'v', 'w', 'x', 'y', 'z',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
+ 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
+ 'U', 'V', 'W', 'X', 'Y', 'Z',
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
+ 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
+ 'u', 'v', 'w', 'x', 'y', 'z',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H' };
+ int i;
+
+ nr_crypto_random_bytes((UCHAR*)alphanum, size);
+
+ /* now convert from binary to alphanumeric */
+ for (i = 0; i < size; ++i)
+ alphanum[i] = alphanums[(UCHAR)alphanum[i]];
+
+ return 0;
+}
+
+#ifndef UINT2_MAX
+#define UINT2_MAX ((UINT2)(65535U))
+#endif
+
+void nr_accumulate_count(UINT2* orig_count, UINT2 add_count)
+ {
+ if (UINT2_MAX - add_count < *orig_count) {
+ // don't rollover, just stop accumulating at MAX value
+ *orig_count = UINT2_MAX;
+ } else {
+ *orig_count += add_count;
+ }
+ }
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_util.h b/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_util.h
new file mode 100644
index 0000000000..379b4fdd3d
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/stun/stun_util.h
@@ -0,0 +1,62 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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 _stun_util_h
+#define _stun_util_h
+
+#include "stun.h"
+#include "local_addr.h"
+
+extern int NR_LOG_STUN;
+
+int nr_stun_startup(void);
+
+int nr_stun_xor_mapped_address(UINT4 magicCookie, UINT12 transactionId, nr_transport_addr *from, nr_transport_addr *to);
+
+// removes duplicates, and based on prefs, loopback and link_local addresses
+int nr_stun_filter_local_addresses(nr_local_addr addrs[], int *count);
+
+int nr_stun_find_local_addresses(nr_local_addr addrs[], int maxaddrs, int *count);
+
+int nr_stun_different_transaction(UCHAR *msg, size_t len, nr_stun_message *req);
+
+char* nr_stun_msg_type(int type);
+
+int nr_random_alphanum(char *alphanum, int size);
+
+// accumulate a count without worrying about rollover
+void nr_accumulate_count(UINT2* orig_count, UINT2 add_count);
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/stun/turn_client_ctx.c b/dom/media/webrtc/transport/third_party/nICEr/src/stun/turn_client_ctx.c
new file mode 100644
index 0000000000..707d43a4a9
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/stun/turn_client_ctx.c
@@ -0,0 +1,1277 @@
+/*
+ Copyright (c) 2007, Adobe Systems, Incorporated
+ Copyright (c) 2013, Mozilla
+
+ 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 Adobe Systems, Network Resonance, Mozilla 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
+ OWNER 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.
+*/
+
+#ifdef USE_TURN
+
+#include <assert.h>
+#include <string.h>
+
+#include "nr_api.h"
+#include "r_time.h"
+#include "async_timer.h"
+#include "nr_socket_buffered_stun.h"
+#include "stun.h"
+#include "turn_client_ctx.h"
+#include "ice_ctx.h"
+
+int NR_LOG_TURN = 0;
+
+#define TURN_MAX_PENDING_BYTES 32000
+
+#define TURN_RTO 100 /* Hardcoded RTO estimate */
+#define TURN_LIFETIME_REQUEST_SECONDS 3600 /* One hour */
+#define TURN_USECS_PER_S 1000000
+#define TURN_REFRESH_SLACK_SECONDS 10 /* How long before expiry to refresh
+ allocations/permissions. The RFC 5766
+ Section 7 recommendation if 60 seconds,
+ but this is silly since the transaction
+ times out after about 5. */
+#define TURN_PERMISSION_LIFETIME_SECONDS 300 /* 5 minutes. From RFC 5766 2.3 */
+
+// Set to enable a temporary fix that will run the TURN reservation keep-alive
+// logic when data is received via a TURN relayed path: a DATA_INDICATION packet is received.
+// TODO(pkerr@mozilla.com) This should be replace/removed when bug 935806 is implemented.
+#define REFRESH_RESERVATION_ON_RECV 1
+
+static int nr_turn_stun_ctx_create(nr_turn_client_ctx *tctx, int type,
+ NR_async_cb success_cb,
+ NR_async_cb failure_cb,
+ nr_turn_stun_ctx **ctxp);
+static int nr_turn_stun_ctx_destroy(nr_turn_stun_ctx **ctxp);
+static void nr_turn_stun_ctx_cb(NR_SOCKET s, int how, void *arg);
+static int nr_turn_stun_set_auth_params(nr_turn_stun_ctx *ctx,
+ char *realm, char *nonce);
+static void nr_turn_client_refresh_timer_cb(NR_SOCKET s, int how, void *arg);
+static int nr_turn_client_refresh_setup(nr_turn_client_ctx *ctx,
+ nr_turn_stun_ctx **sctx);
+static int nr_turn_client_start_refresh_timer(nr_turn_client_ctx *ctx,
+ nr_turn_stun_ctx *sctx,
+ UINT4 lifetime);
+static int nr_turn_permission_create(nr_turn_client_ctx *ctx,
+ const nr_transport_addr *addr,
+ nr_turn_permission **permp);
+static int nr_turn_permission_find(nr_turn_client_ctx *ctx,
+ const nr_transport_addr *addr,
+ nr_turn_permission **permp);
+static int nr_turn_permission_destroy(nr_turn_permission **permp);
+static void nr_turn_client_refresh_cb(NR_SOCKET s, int how, void *arg);
+static void nr_turn_client_permissions_cb(NR_SOCKET s, int how, void *cb);
+static int nr_turn_client_send_stun_request(nr_turn_client_ctx *ctx,
+ nr_stun_message *req,
+ int flags);
+
+int nr_transport_addr_listnode_create(const nr_transport_addr *addr, nr_transport_addr_listnode **listnodep)
+{
+ nr_transport_addr_listnode *listnode = 0;
+ int r,_status;
+
+ if (!(listnode=RCALLOC(sizeof(nr_transport_addr_listnode)))) {
+ ABORT(R_NO_MEMORY);
+ }
+
+ if ((r = nr_transport_addr_copy(&listnode->value, addr))) {
+ ABORT(r);
+ }
+
+ *listnodep = listnode;
+ listnode = 0;
+ _status = 0;
+
+abort:
+ nr_transport_addr_listnode_destroy(&listnode);
+ return(_status);
+}
+
+void nr_transport_addr_listnode_destroy(nr_transport_addr_listnode **listnode)
+{
+ RFREE(*listnode);
+ *listnode = 0;
+}
+
+/* nr_turn_stun_ctx functions */
+static int nr_turn_stun_ctx_create(nr_turn_client_ctx *tctx, int mode,
+ NR_async_cb success_cb,
+ NR_async_cb error_cb,
+ nr_turn_stun_ctx **ctxp)
+{
+ nr_turn_stun_ctx *sctx = 0;
+ int r,_status;
+ char label[256];
+
+ if (!(sctx=RCALLOC(sizeof(nr_turn_stun_ctx))))
+ ABORT(R_NO_MEMORY);
+
+ /* TODO(ekr@rtfm.com): label by phase */
+ snprintf(label, sizeof(label), "%s:%s", tctx->label, ":TURN");
+
+ if ((r=nr_stun_client_ctx_create(label, tctx->sock, &tctx->turn_server_addr,
+ TURN_RTO, &sctx->stun))) {
+ ABORT(r);
+ }
+
+ /* Set the STUN auth parameters, but don't set authentication on.
+ For that we need the nonce, set in nr_turn_stun_set_auth_params.
+ */
+ sctx->stun->auth_params.username=tctx->username;
+ INIT_DATA(sctx->stun->auth_params.password,
+ tctx->password->data, tctx->password->len);
+
+ sctx->tctx=tctx;
+ sctx->success_cb=success_cb;
+ sctx->error_cb=error_cb;
+ sctx->mode=mode;
+ sctx->last_error_code=0;
+ STAILQ_INIT(&sctx->addresses_tried);
+
+ /* Add ourselves to the tctx's list */
+ STAILQ_INSERT_TAIL(&tctx->stun_ctxs, sctx, entry);
+ *ctxp=sctx;
+
+ _status=0;
+abort:
+ if (_status) {
+ nr_turn_stun_ctx_destroy(&sctx);
+ }
+ return(_status);
+}
+
+/* Note: this function does not pull us off the tctx's list. */
+static int nr_turn_stun_ctx_destroy(nr_turn_stun_ctx **ctxp)
+{
+ nr_turn_stun_ctx *ctx;
+
+ if (!ctxp || !*ctxp)
+ return 0;
+
+ ctx = *ctxp;
+ *ctxp = 0;
+
+ nr_stun_client_ctx_destroy(&ctx->stun);
+ RFREE(ctx->realm);
+ RFREE(ctx->nonce);
+
+ while (!STAILQ_EMPTY(&ctx->addresses_tried)) {
+ nr_transport_addr_listnode *listnode = STAILQ_FIRST(&ctx->addresses_tried);
+ STAILQ_REMOVE_HEAD(&ctx->addresses_tried, entry);
+ nr_transport_addr_listnode_destroy(&listnode);
+ }
+
+ RFREE(ctx);
+
+ return 0;
+}
+
+static int nr_turn_stun_set_auth_params(nr_turn_stun_ctx *ctx,
+ char *realm, char *nonce)
+{
+ int _status;
+
+ RFREE(ctx->realm);
+ RFREE(ctx->nonce);
+
+ assert(realm);
+ if (!realm)
+ ABORT(R_BAD_ARGS);
+ ctx->realm=r_strdup(realm);
+ if (!ctx->realm)
+ ABORT(R_NO_MEMORY);
+
+ assert(nonce);
+ if (!nonce)
+ ABORT(R_BAD_ARGS);
+ ctx->nonce=r_strdup(nonce);
+ if (!ctx->nonce)
+ ABORT(R_NO_MEMORY);
+
+ RFREE(ctx->stun->realm);
+ ctx->stun->realm = r_strdup(ctx->realm);
+ if (!ctx->stun->realm)
+ ABORT(R_NO_MEMORY);
+
+ ctx->stun->auth_params.realm = ctx->realm;
+ ctx->stun->auth_params.nonce = ctx->nonce;
+ ctx->stun->auth_params.authenticate = 1; /* May already be 1 */
+
+ _status=0;
+abort:
+ return(_status);
+}
+
+static int nr_turn_stun_ctx_start(nr_turn_stun_ctx *ctx)
+{
+ int r, _status;
+ nr_turn_client_ctx *tctx = ctx->tctx;
+ nr_transport_addr_listnode *address_tried = 0;
+
+ if ((r=nr_stun_client_reset(ctx->stun))) {
+ r_log(NR_LOG_TURN, LOG_ERR, "TURN(%s): Couldn't reset STUN",
+ tctx->label);
+ ABORT(r);
+ }
+
+ if ((r=nr_stun_client_start(ctx->stun, ctx->mode, nr_turn_stun_ctx_cb, ctx))) {
+ r_log(NR_LOG_TURN, LOG_ERR, "TURN(%s): Couldn't start STUN",
+ tctx->label);
+ ABORT(r);
+ }
+
+ if ((r=nr_transport_addr_listnode_create(&ctx->stun->peer_addr, &address_tried))) {
+ ABORT(r);
+ }
+
+ STAILQ_INSERT_TAIL(&ctx->addresses_tried, address_tried, entry);
+
+ _status=0;
+abort:
+ return _status;
+}
+
+static int nr_turn_stun_ctx_handle_redirect(nr_turn_stun_ctx *ctx)
+{
+ int r, _status;
+ nr_turn_client_ctx *tctx = ctx->tctx;
+ nr_stun_message_attribute *ec;
+ nr_stun_message_attribute *attr;
+ nr_transport_addr *alternate_addr = 0;
+ int index = 0;
+
+ if (!tctx->ctx) {
+ /* If we were to require TCP nr_sockets to allow multiple connect calls by
+ * disconnecting and re-connecting, we could avoid this requirement. */
+ r_log(NR_LOG_TURN, LOG_ERR,
+ "TURN(%s): nr_turn_stun_ctx_handle_redirect is not supported when "
+ "there is no ICE ctx, and by extension no socket factory, since we "
+ "need that to create new sockets in the TCP case",
+ tctx->label);
+ ABORT(R_BAD_ARGS);
+ }
+
+ if (ctx->stun->response->header.type != NR_STUN_MSG_ALLOCATE_ERROR_RESPONSE) {
+ r_log(NR_LOG_TURN, LOG_ERR,
+ "TURN(%s): nr_turn_stun_ctx_handle_redirect called for something "
+ "other than an Allocate error response (type=%d)",
+ tctx->label, ctx->stun->response->header.type);
+ ABORT(R_BAD_ARGS);
+ }
+
+ if (!nr_stun_message_has_attribute(ctx->stun->response,
+ NR_STUN_ATTR_ERROR_CODE, &ec) ||
+ (ec->u.error_code.number != 300)) {
+ r_log(NR_LOG_TURN, LOG_ERR,
+ "TURN(%s): nr_turn_stun_ctx_handle_redirect called without a "
+ "300 response",
+ tctx->label);
+ ABORT(R_BAD_ARGS);
+ }
+
+ while (!alternate_addr && !nr_stun_message_get_attribute(
+ ctx->stun->response, NR_STUN_ATTR_ALTERNATE_SERVER, index++, &attr)) {
+ alternate_addr = &attr->u.alternate_server;
+
+ // TODO: Someday we may need to handle IP version switching, but it is
+ // unclear how that is supposed to work with ICE when the IP version of
+ // the candidate's base address is fixed...
+ if (alternate_addr->ip_version != tctx->turn_server_addr.ip_version) {
+ r_log(NR_LOG_TURN, LOG_INFO,
+ "TURN(%s): nr_turn_stun_ctx_handle_redirect not trying %s, since it is a different IP version",
+ tctx->label, alternate_addr->as_string);
+ alternate_addr = 0;
+ continue;
+ }
+
+ /* Check if we've already tried this, and ignore if we have */
+ nr_transport_addr_listnode *address_tried = 0;
+ STAILQ_FOREACH(address_tried, &ctx->addresses_tried, entry) {
+ /* Ignore protocol */
+ alternate_addr->protocol = address_tried->value.protocol;
+ if (!nr_transport_addr_cmp(alternate_addr, &address_tried->value, NR_TRANSPORT_ADDR_CMP_MODE_ALL)) {
+ r_log(NR_LOG_TURN, LOG_INFO,
+ "TURN(%s): nr_turn_stun_ctx_handle_redirect already tried %s, ignoring",
+ tctx->label, alternate_addr->as_string);
+ alternate_addr = 0;
+ break;
+ }
+ }
+ }
+
+ if (!alternate_addr) {
+ /* Should we use a different error code depending on why we didn't find
+ * one? (eg; no ALTERNATE-SERVERS at all, none that we have not tried
+ * already, none that are of a compatible IP version?) */
+ r_log(NR_LOG_TURN, LOG_ERR,
+ "TURN(%s): nr_turn_stun_ctx_handle_redirect did not find a viable "
+ "ALTERNATE-SERVER",
+ tctx->label);
+ ABORT(R_FAILED);
+ }
+
+ r_log(NR_LOG_TURN, LOG_INFO,
+ "TURN(%s): nr_turn_stun_ctx_handle_redirect trying %s",
+ tctx->label, alternate_addr->as_string);
+
+ /* This also handles the call to nr_transport_addr_fmt_addr_string */
+ if ((r = nr_transport_addr_copy_addrport(&tctx->turn_server_addr,
+ alternate_addr))) {
+ r_log(NR_LOG_TURN, LOG_ERR,
+ "TURN(%s): nr_turn_stun_ctx_handle_redirect copying ALTERNATE-SERVER "
+ "failed(%d)!",
+ tctx->label, r);
+ assert(0);
+ ABORT(r);
+ }
+
+ /* TURN server address is now updated. Restart the STUN Allocate ctx. Note
+ * that we do not attempt to update the local address; if the TURN server
+ * redirects to something that is not best reached from the already-selected
+ * local address, oh well. */
+
+ if (tctx->turn_server_addr.protocol == IPPROTO_TCP) {
+ /* For TCP, we need to replace the underlying nr_socket, since we cannot
+ * un-connect it from the old server. */
+ /* If we were to require TCP nr_sockets to allow multiple connect calls by
+ * disconnecting and re-connecting, we could avoid this stuff, and just
+ * call nr_socket_connect. */
+ nr_transport_addr old_local_addr;
+ nr_socket* new_socket;
+ if ((r = nr_socket_getaddr(tctx->sock, &old_local_addr))) {
+ r_log(NR_LOG_TURN, LOG_ERR,
+ "TURN(%s): nr_turn_stun_ctx_handle_redirect "
+ "failed to get old local address (%d)!",
+ tctx->label, r);
+ assert(0);
+ ABORT(r);
+ }
+
+ if ((r = nr_socket_factory_create_socket(tctx->ctx->socket_factory,
+ &old_local_addr, &new_socket))) {
+ r_log(NR_LOG_TURN, LOG_ERR,
+ "TURN(%s): nr_turn_stun_ctx_handle_redirect "
+ "failed to create new raw TCP socket for redirect (%d)!",
+ tctx->label, r);
+ assert(0);
+ ABORT(r);
+ }
+
+ if ((r = nr_socket_buffered_stun_reset(tctx->sock, new_socket))) {
+ /* nr_socket_buffered_stun_reset always takes ownership of |new_socket| */
+ r_log(NR_LOG_TURN, LOG_ERR,
+ "TURN(%s): nr_turn_stun_ctx_handle_redirect "
+ "failed to update raw TCP socket (%d)!",
+ tctx->label, r);
+ assert(0);
+ ABORT(r);
+ }
+
+ if ((r = nr_socket_connect(tctx->sock, &tctx->turn_server_addr))) {
+ if (r != R_WOULDBLOCK) {
+ r_log(NR_LOG_TURN, LOG_ERR,
+ "TURN(%s): nr_turn_stun_ctx_handle_redirect nr_socket_connect "
+ "failed(%d)!",
+ tctx->label, r);
+ assert(0);
+ ABORT(r);
+ }
+ }
+ }
+
+ nr_transport_addr_copy(&ctx->stun->peer_addr, &tctx->turn_server_addr);
+
+ if ((r = nr_turn_stun_ctx_start(ctx))) {
+ r_log(NR_LOG_TURN, LOG_ERR,
+ "TURN(%s): nr_turn_stun_ctx_handle_redirect nr_turn_stun_ctx_start "
+ "failed(%d)!",
+ tctx->label, r);
+ assert(0);
+ ABORT(r);
+ }
+
+ _status = 0;
+abort:
+ return _status;
+}
+
+static void nr_turn_stun_ctx_cb(NR_SOCKET s, int how, void *arg)
+{
+ int r, _status;
+ nr_turn_stun_ctx *ctx = (nr_turn_stun_ctx *)arg;
+
+ ctx->last_error_code = ctx->stun->error_code;
+
+ switch (ctx->stun->state) {
+ case NR_STUN_CLIENT_STATE_DONE:
+ /* Save the realm and nonce */
+ if (ctx->stun->realm && (!ctx->tctx->realm || strcmp(ctx->stun->realm,
+ ctx->tctx->realm))) {
+ RFREE(ctx->tctx->realm);
+ ctx->tctx->realm = r_strdup(ctx->stun->realm);
+ if (!ctx->tctx->realm)
+ ABORT(R_NO_MEMORY);
+ }
+ if (ctx->stun->nonce && (!ctx->tctx->nonce || strcmp(ctx->stun->nonce,
+ ctx->tctx->nonce))) {
+ RFREE(ctx->tctx->nonce);
+ ctx->tctx->nonce = r_strdup(ctx->stun->nonce);
+ if (!ctx->tctx->nonce)
+ ABORT(R_NO_MEMORY);
+ }
+
+ ctx->retry_ct=0;
+ ctx->success_cb(0, 0, ctx);
+ break;
+
+ case NR_STUN_CLIENT_STATE_FAILED:
+ /* Special case: if this is an authentication error,
+ we retry once. This allows the 401/438 nonce retry
+ paradigm. After that, we fail */
+ /* TODO(ekr@rtfm.com): 401 needs a #define */
+ /* TODO(ekr@rtfm.com): Add alternate-server (Mozilla bug 857688) */
+ if (ctx->stun->error_code == 438) {
+ // track 438s for ice telemetry
+ nr_accumulate_count(&(ctx->tctx->cnt_438s), 1);
+ }
+ if (ctx->stun->error_code == 401 || ctx->stun->error_code == 438) {
+ if (ctx->retry_ct > 0) {
+ if (ctx->stun->error_code == 401) {
+ // track 401s for ice telemetry
+ nr_accumulate_count(&(ctx->tctx->cnt_401s), 1);
+ }
+ r_log(NR_LOG_TURN, LOG_WARNING, "TURN(%s): Exceeded the number of retries", ctx->tctx->label);
+ ABORT(R_FAILED);
+ }
+
+ if (!ctx->stun->nonce) {
+ r_log(NR_LOG_TURN, LOG_WARNING, "TURN(%s): 401 but no nonce", ctx->tctx->label);
+ ABORT(R_FAILED);
+ }
+ if (!ctx->stun->realm) {
+ r_log(NR_LOG_TURN, LOG_WARNING, "TURN(%s): 401 but no realm", ctx->tctx->label);
+ ABORT(R_FAILED);
+ }
+
+ /* Try to retry */
+ if ((r=nr_turn_stun_set_auth_params(ctx, ctx->stun->realm,
+ ctx->stun->nonce)))
+ ABORT(r);
+
+ ctx->stun->error_code = 0; /* Reset to avoid inf-looping */
+
+ if ((r=nr_turn_stun_ctx_start(ctx))) {
+ r_log(NR_LOG_TURN, LOG_ERR, "TURN(%s): Couldn't start STUN", ctx->tctx->label);
+ ABORT(r);
+ }
+
+ ctx->retry_ct++;
+ } else if (ctx->stun->error_code == 300) {
+ r_log(NR_LOG_TURN, LOG_INFO,
+ "TURN(%s): Redirect received, restarting TURN", ctx->tctx->label);
+ /* We don't limit this with a retry counter, we limit redirects by
+ * checking whether we've tried the ALTERNATE-SERVER yet. */
+ ctx->retry_ct = 0;
+ if ((r = nr_turn_stun_ctx_handle_redirect(ctx))) {
+ ABORT(r);
+ }
+ } else {
+ ABORT(R_FAILED);
+ }
+ break;
+
+ case NR_STUN_CLIENT_STATE_TIMED_OUT:
+ ABORT(R_FAILED);
+ break;
+
+ case NR_STUN_CLIENT_STATE_CANCELLED:
+ assert(0); /* Shouldn't happen */
+ return;
+ break;
+
+ default:
+ assert(0); /* Shouldn't happen */
+ return;
+ }
+
+ _status=0;
+abort:
+ if (_status) {
+ ctx->error_cb(0, 0, ctx);
+ }
+}
+
+/* nr_turn_client_ctx functions */
+int nr_turn_client_ctx_create(const char* label, nr_socket* sock,
+ const char* username, Data* password,
+ nr_transport_addr* addr, nr_ice_ctx* ice_ctx,
+ nr_turn_client_ctx** ctxp) {
+ nr_turn_client_ctx *ctx=0;
+ int r,_status;
+
+ if ((r=r_log_register("turn", &NR_LOG_TURN)))
+ ABORT(r);
+
+ if(!(ctx=RCALLOC(sizeof(nr_turn_client_ctx))))
+ ABORT(R_NO_MEMORY);
+
+ STAILQ_INIT(&ctx->stun_ctxs);
+ STAILQ_INIT(&ctx->permissions);
+
+ if(!(ctx->label=r_strdup(label)))
+ ABORT(R_NO_MEMORY);
+
+ ctx->sock=sock;
+ ctx->username = r_strdup(username);
+ if (!ctx->username)
+ ABORT(R_NO_MEMORY);
+
+ if ((r=r_data_create(&ctx->password, password->data, password->len)))
+ ABORT(r);
+ if ((r=nr_transport_addr_copy(&ctx->turn_server_addr, addr)))
+ ABORT(r);
+
+ ctx->state = NR_TURN_CLIENT_STATE_INITTED;
+ if (addr->protocol == IPPROTO_TCP) {
+ if ((r=nr_socket_connect(ctx->sock, &ctx->turn_server_addr))) {
+ if (r != R_WOULDBLOCK)
+ ABORT(r);
+ }
+ }
+
+ ctx->ctx = ice_ctx;
+
+ *ctxp=ctx;
+
+ _status=0;
+abort:
+ if(_status){
+ nr_turn_client_ctx_destroy(&ctx);
+ }
+ return(_status);
+}
+
+int
+nr_turn_client_ctx_destroy(nr_turn_client_ctx **ctxp)
+{
+ nr_turn_client_ctx *ctx;
+
+ if(!ctxp || !*ctxp)
+ return(0);
+
+ ctx=*ctxp;
+ *ctxp = 0;
+
+ if (ctx->label)
+ r_log(NR_LOG_TURN, LOG_DEBUG, "TURN(%s): destroy", ctx->label);
+
+ nr_turn_client_deallocate(ctx);
+
+ /* Cancel frees the rest of our data */
+ RFREE(ctx->label);
+ ctx->label = 0;
+
+ nr_turn_client_cancel(ctx);
+
+ RFREE(ctx->username);
+ ctx->username = 0;
+ r_data_destroy(&ctx->password);
+ RFREE(ctx->nonce);
+ ctx->nonce = 0;
+ RFREE(ctx->realm);
+ ctx->realm = 0;
+
+ /* Destroy the STUN client ctxs */
+ while (!STAILQ_EMPTY(&ctx->stun_ctxs)) {
+ nr_turn_stun_ctx *stun = STAILQ_FIRST(&ctx->stun_ctxs);
+ STAILQ_REMOVE_HEAD(&ctx->stun_ctxs, entry);
+ nr_turn_stun_ctx_destroy(&stun);
+ }
+
+ /* Destroy the permissions */
+ while (!STAILQ_EMPTY(&ctx->permissions)) {
+ nr_turn_permission *perm = STAILQ_FIRST(&ctx->permissions);
+ STAILQ_REMOVE_HEAD(&ctx->permissions, entry);
+ nr_turn_permission_destroy(&perm);
+ }
+
+ RFREE(ctx);
+
+ return(0);
+}
+
+int nr_turn_client_cancel(nr_turn_client_ctx *ctx)
+{
+ nr_turn_stun_ctx *stun = 0;
+
+ if (ctx->state == NR_TURN_CLIENT_STATE_CANCELLED ||
+ ctx->state == NR_TURN_CLIENT_STATE_FAILED)
+ return(0);
+
+ if (ctx->label)
+ r_log(NR_LOG_TURN, LOG_INFO, "TURN(%s): cancelling", ctx->label);
+
+ /* Cancel the STUN client ctxs */
+ stun = STAILQ_FIRST(&ctx->stun_ctxs);
+ while (stun) {
+ nr_stun_client_cancel(stun->stun);
+ stun = STAILQ_NEXT(stun, entry);
+ }
+
+ /* Cancel the timers, if not already cancelled */
+ NR_async_timer_cancel(ctx->connected_timer_handle);
+ NR_async_timer_cancel(ctx->refresh_timer_handle);
+
+ ctx->state = NR_TURN_CLIENT_STATE_CANCELLED;
+
+ return(0);
+}
+
+int nr_turn_client_send_stun_request(nr_turn_client_ctx *ctx,
+ nr_stun_message *req,
+ int flags)
+{
+ int r,_status;
+
+ if ((r=nr_stun_encode_message(req)))
+ ABORT(r);
+
+ if ((r=nr_socket_sendto(ctx->sock,
+ req->buffer, req->length, flags,
+ &ctx->turn_server_addr))) {
+ r_log(NR_LOG_TURN, LOG_WARNING, "TURN(%s): Failed sending request",
+ ctx->label);
+ ABORT(r);
+ }
+
+ _status=0;
+abort:
+ return(_status);
+}
+
+int nr_turn_client_deallocate(nr_turn_client_ctx *ctx)
+{
+ int r,_status;
+ nr_stun_message *aloc = 0;
+ nr_stun_client_auth_params auth;
+ nr_stun_client_refresh_request_params refresh;
+
+ if (ctx->state != NR_TURN_CLIENT_STATE_ALLOCATED)
+ return(0);
+
+ r_log(NR_LOG_TURN, LOG_INFO, "TURN(%s): deallocating", ctx->label);
+
+ refresh.lifetime_secs = 0;
+
+ auth.username = ctx->username;
+ INIT_DATA(auth.password, ctx->password->data, ctx->password->len);
+
+ auth.realm = ctx->realm;
+ auth.nonce = ctx->nonce;
+
+ auth.authenticate = 1;
+
+ if ((r=nr_stun_build_refresh_request(&auth, &refresh, &aloc)))
+ ABORT(r);
+
+ // We are only sending a single request here because we are in the process of
+ // shutting everything down. Theoretically we should probably start a seperate
+ // STUN transaction which outlives the TURN context.
+ if ((r=nr_turn_client_send_stun_request(ctx, aloc, 0)))
+ ABORT(r);
+
+ ctx->state = NR_TURN_CLIENT_STATE_DEALLOCATING;
+
+ _status=0;
+abort:
+ nr_stun_message_destroy(&aloc);
+ return(_status);
+}
+
+static void nr_turn_client_fire_finished_cb(nr_turn_client_ctx *ctx)
+ {
+ if (ctx->finished_cb) {
+ NR_async_cb finished_cb=ctx->finished_cb;
+ ctx->finished_cb=0;
+ finished_cb(0, 0, ctx->cb_arg);
+ }
+ }
+
+int nr_turn_client_failed(nr_turn_client_ctx *ctx)
+{
+ if (ctx->state == NR_TURN_CLIENT_STATE_FAILED ||
+ ctx->state == NR_TURN_CLIENT_STATE_CANCELLED)
+ return(0);
+
+ r_log(NR_LOG_TURN, LOG_WARNING, "TURN(%s) failed", ctx->label);
+ nr_turn_client_cancel(ctx);
+ ctx->state = NR_TURN_CLIENT_STATE_FAILED;
+ nr_turn_client_fire_finished_cb(ctx);
+
+ return(0);
+}
+
+int nr_turn_client_get_relayed_address(nr_turn_client_ctx *ctx,
+ nr_transport_addr *relayed_address)
+{
+ int r, _status;
+
+ if (ctx->state != NR_TURN_CLIENT_STATE_ALLOCATED)
+ ABORT(R_FAILED);
+
+ if (r=nr_transport_addr_copy(relayed_address, &ctx->relay_addr))
+ ABORT(r);
+
+ _status=0;
+abort:
+ return(_status);
+}
+
+int nr_turn_client_get_mapped_address(nr_turn_client_ctx *ctx,
+ nr_transport_addr *mapped_address)
+{
+ int r, _status;
+
+ if (ctx->state != NR_TURN_CLIENT_STATE_ALLOCATED)
+ ABORT(R_FAILED);
+
+ if (r=nr_transport_addr_copy(mapped_address, &ctx->mapped_addr))
+ ABORT(r);
+
+ _status=0;
+abort:
+ return(_status);
+}
+
+static void nr_turn_client_allocate_cb(NR_SOCKET s, int how, void *arg)
+{
+ nr_turn_stun_ctx *ctx = (nr_turn_stun_ctx *)arg;
+ nr_turn_stun_ctx *refresh_ctx;
+ int r,_status;
+
+ ctx->tctx->state = NR_TURN_CLIENT_STATE_ALLOCATED;
+
+ if ((r=nr_transport_addr_copy(
+ &ctx->tctx->relay_addr,
+ &ctx->stun->results.allocate_response.relay_addr)))
+ ABORT(r);
+
+ if ((r=nr_transport_addr_copy(
+ &ctx->tctx->mapped_addr,
+ &ctx->stun->results.allocate_response.mapped_addr)))
+ ABORT(r);
+
+ if ((r=nr_turn_client_refresh_setup(ctx->tctx, &refresh_ctx)))
+ ABORT(r);
+
+ if ((r=nr_turn_client_start_refresh_timer(
+ ctx->tctx, refresh_ctx,
+ ctx->stun->results.allocate_response.lifetime_secs)))
+ ABORT(r);
+
+ r_log(NR_LOG_TURN, LOG_INFO,
+ "TURN(%s): Succesfully allocated addr %s lifetime=%u",
+ ctx->tctx->label,
+ ctx->tctx->relay_addr.as_string,
+ ctx->stun->results.allocate_response.lifetime_secs);
+
+ nr_turn_client_fire_finished_cb(ctx->tctx);
+ _status=0;
+abort:
+ if (_status) {
+ nr_turn_client_failed(ctx->tctx);
+ }
+}
+
+static void nr_turn_client_error_cb(NR_SOCKET s, int how, void *arg)
+{
+ nr_turn_stun_ctx *ctx = (nr_turn_stun_ctx *)arg;
+
+ r_log(NR_LOG_TURN, LOG_WARNING, "TURN(%s): mode %d, %s",
+ ctx->tctx->label, ctx->mode, __FUNCTION__);
+
+ nr_turn_client_failed(ctx->tctx);
+}
+
+static void nr_turn_client_permission_error_cb(NR_SOCKET s, int how, void *arg)
+{
+ nr_turn_stun_ctx *ctx = (nr_turn_stun_ctx *)arg;
+
+ if (ctx->last_error_code == 403) {
+ // track 403s for ice telemetry
+ nr_accumulate_count(&(ctx->tctx->cnt_403s), 1);
+ r_log(NR_LOG_TURN, LOG_WARNING, "TURN(%s): mode %d, permission denied",
+ ctx->tctx->label, ctx->mode);
+
+ } else{
+ nr_turn_client_error_cb(0, 0, ctx);
+ }
+}
+
+int nr_turn_client_allocate(nr_turn_client_ctx *ctx,
+ NR_async_cb finished_cb, void *cb_arg)
+{
+ nr_turn_stun_ctx *stun = 0;
+ int r,_status;
+
+ if(ctx->state == NR_TURN_CLIENT_STATE_FAILED ||
+ ctx->state == NR_TURN_CLIENT_STATE_CANCELLED){
+ /* TURN TCP contexts can fail before we ever try to form an allocation,
+ * since the TCP connection can fail. It is also conceivable that a TURN
+ * TCP context could be cancelled before we are done forming all
+ * allocations (although we do not do this at the time this code was
+ * written) */
+ assert(ctx->turn_server_addr.protocol == IPPROTO_TCP);
+ ABORT(R_NOT_FOUND);
+ }
+
+ assert(ctx->state == NR_TURN_CLIENT_STATE_INITTED);
+
+ ctx->finished_cb=finished_cb;
+ ctx->cb_arg=cb_arg;
+
+ if ((r=nr_turn_stun_ctx_create(ctx, NR_TURN_CLIENT_MODE_ALLOCATE_REQUEST,
+ nr_turn_client_allocate_cb,
+ nr_turn_client_error_cb,
+ &stun)))
+ ABORT(r);
+ stun->stun->params.allocate_request.lifetime_secs =
+ TURN_LIFETIME_REQUEST_SECONDS;
+
+ if (ctx->state == NR_TURN_CLIENT_STATE_INITTED) {
+ if ((r=nr_turn_stun_ctx_start(stun)))
+ ABORT(r);
+ ctx->state = NR_TURN_CLIENT_STATE_ALLOCATING;
+ } else {
+ ABORT(R_ALREADY);
+ }
+
+ _status=0;
+abort:
+ if (_status) {
+ nr_turn_client_failed(ctx);
+ }
+
+ return(_status);
+}
+
+int nr_turn_client_process_response(nr_turn_client_ctx *ctx,
+ UCHAR *msg, int len,
+ nr_transport_addr *turn_server_addr)
+{
+ int r, _status;
+ nr_turn_stun_ctx *sc1;
+
+ switch (ctx->state) {
+ case NR_TURN_CLIENT_STATE_ALLOCATING:
+ case NR_TURN_CLIENT_STATE_ALLOCATED:
+ break;
+ default:
+ ABORT(R_FAILED);
+ }
+
+ sc1 = STAILQ_FIRST(&ctx->stun_ctxs);
+ while (sc1) {
+ r = nr_stun_client_process_response(sc1->stun, msg, len, turn_server_addr);
+ if (!r)
+ break;
+ if (r==R_RETRY) /* Likely a 401 and we will retry */
+ break;
+ if (r != R_REJECTED)
+ ABORT(r);
+ sc1 = STAILQ_NEXT(sc1, entry);
+ }
+ if (!sc1)
+ ABORT(R_REJECTED);
+
+ _status=0;
+abort:
+ return(_status);
+}
+
+static int nr_turn_client_refresh_setup(nr_turn_client_ctx *ctx,
+ nr_turn_stun_ctx **sctx)
+{
+ nr_turn_stun_ctx *stun = 0;
+ int r,_status;
+
+ assert(ctx->state == NR_TURN_CLIENT_STATE_ALLOCATED);
+ if (ctx->state != NR_TURN_CLIENT_STATE_ALLOCATED)
+ ABORT(R_NOT_PERMITTED);
+
+ if ((r=nr_turn_stun_ctx_create(ctx, NR_TURN_CLIENT_MODE_REFRESH_REQUEST,
+ nr_turn_client_refresh_cb,
+ nr_turn_client_error_cb,
+ &stun)))
+ ABORT(r);
+
+ if ((r=nr_turn_stun_set_auth_params(stun, ctx->realm, ctx->nonce)))
+ ABORT(r);
+
+ stun->stun->params.refresh_request.lifetime_secs =
+ TURN_LIFETIME_REQUEST_SECONDS;
+
+
+ *sctx=stun;
+
+ _status=0;
+abort:
+ return(_status);
+}
+
+static int nr_turn_client_start_refresh_timer(nr_turn_client_ctx *tctx,
+ nr_turn_stun_ctx *sctx,
+ UINT4 lifetime)
+{
+ int _status;
+
+ assert(!tctx->refresh_timer_handle);
+
+ if (lifetime <= TURN_REFRESH_SLACK_SECONDS) {
+ r_log(NR_LOG_TURN, LOG_ERR, "Too short lifetime specified for turn %u", lifetime);
+ ABORT(R_BAD_DATA);
+ }
+
+ if (lifetime > 3600)
+ lifetime = 3600;
+
+ lifetime -= TURN_REFRESH_SLACK_SECONDS;
+
+ r_log(NR_LOG_TURN, LOG_DEBUG, "TURN(%s): Setting refresh timer for %u seconds",
+ tctx->label, lifetime);
+ NR_ASYNC_TIMER_SET(lifetime * 1000, nr_turn_client_refresh_timer_cb, sctx,
+ &tctx->refresh_timer_handle);
+
+ _status=0;
+abort:
+ if (_status) {
+ nr_turn_client_failed(tctx);
+ }
+ return _status;
+}
+
+static void nr_turn_client_refresh_timer_cb(NR_SOCKET s, int how, void *arg)
+{
+ nr_turn_stun_ctx *ctx = (nr_turn_stun_ctx *)arg;
+ int r,_status;
+
+ r_log(NR_LOG_TURN, LOG_DEBUG, "TURN(%s): Refresh timer fired",
+ ctx->tctx->label);
+
+ ctx->tctx->refresh_timer_handle=0;
+ if ((r=nr_turn_stun_ctx_start(ctx))) {
+ ABORT(r);
+ }
+
+ _status=0;
+abort:
+ if (_status) {
+ nr_turn_client_failed(ctx->tctx);
+ }
+ return;
+}
+
+static void nr_turn_client_refresh_cb(NR_SOCKET s, int how, void *arg)
+{
+ int r, _status;
+ nr_turn_stun_ctx *ctx = (nr_turn_stun_ctx *)arg;
+ /* Save lifetime from the reset */
+ UINT4 lifetime = ctx->stun->results.refresh_response.lifetime_secs;
+
+ r_log(NR_LOG_TURN, LOG_DEBUG, "TURN(%s): Refresh succeeded. lifetime=%u",
+ ctx->tctx->label, lifetime);
+
+ if ((r=nr_turn_client_start_refresh_timer(
+ ctx->tctx, ctx, lifetime)))
+ ABORT(r);
+
+ _status=0;
+
+abort:
+ if (_status) {
+ nr_turn_client_failed(ctx->tctx);
+ }
+}
+
+/* TODO(ekr@rtfm.com): We currently don't support channels.
+ We might in the future. Mozilla bug 857736 */
+int nr_turn_client_send_indication(nr_turn_client_ctx *ctx,
+ const UCHAR *msg, size_t len,
+ int flags, const nr_transport_addr *remote_addr)
+{
+ int r,_status;
+ nr_stun_client_send_indication_params params = { { 0 } };
+ nr_stun_message *ind = 0;
+
+ if (ctx->state != NR_TURN_CLIENT_STATE_ALLOCATED)
+ ABORT(R_FAILED);
+
+ r_log(NR_LOG_TURN, LOG_DEBUG, "TURN(%s): Send indication len=%zu",
+ ctx->label, len);
+
+ if ((r=nr_turn_client_ensure_perm(ctx, remote_addr)))
+ ABORT(r);
+
+ if ((r=nr_transport_addr_copy(&params.remote_addr, remote_addr)))
+ ABORT(r);
+
+ params.data.data = (UCHAR*)msg;
+ params.data.len = len;
+
+ if ((r=nr_stun_build_send_indication(&params, &ind)))
+ ABORT(r);
+
+ if ((r=nr_turn_client_send_stun_request(ctx, ind, flags)))
+ ABORT(r);
+
+ _status=0;
+abort:
+ nr_stun_message_destroy(&ind);
+ return(_status);
+}
+
+int nr_turn_client_parse_data_indication(nr_turn_client_ctx *ctx,
+ nr_transport_addr *source_addr,
+ UCHAR *msg, size_t len,
+ UCHAR *newmsg, size_t *newlen,
+ size_t newsize,
+ nr_transport_addr *remote_addr)
+{
+ int r,_status;
+ nr_stun_message *ind=0;
+ nr_stun_message_attribute *attr;
+ nr_turn_permission *perm;
+
+ if (nr_transport_addr_cmp(&ctx->turn_server_addr, source_addr,
+ NR_TRANSPORT_ADDR_CMP_MODE_ALL)) {
+ r_log(NR_LOG_TURN, LOG_WARNING,
+ "TURN(%s): Indication from unexpected source addr %s (expected %s)",
+ ctx->label, source_addr->as_string, ctx->turn_server_addr.as_string);
+ ABORT(R_REJECTED);
+ }
+
+ if ((r=nr_stun_message_create2(&ind, msg, len)))
+ ABORT(r);
+ if ((r=nr_stun_decode_message(ind, 0, 0)))
+ ABORT(r);
+
+ if (ind->header.type != NR_STUN_MSG_DATA_INDICATION)
+ ABORT(R_BAD_ARGS);
+
+ if (!nr_stun_message_has_attribute(ind, NR_STUN_ATTR_XOR_PEER_ADDRESS, &attr))
+ ABORT(R_BAD_ARGS);
+
+ if ((r=nr_turn_permission_find(ctx, &attr->u.xor_mapped_address.unmasked,
+ &perm))) {
+ if (r == R_NOT_FOUND) {
+ r_log(NR_LOG_TURN, LOG_WARNING,
+ "TURN(%s): Indication from peer addr %s with no permission",
+ ctx->label, attr->u.xor_mapped_address.unmasked.as_string);
+ }
+ ABORT(r);
+ }
+
+ if ((r=nr_transport_addr_copy(remote_addr,
+ &attr->u.xor_mapped_address.unmasked)))
+ ABORT(r);
+
+#if REFRESH_RESERVATION_ON_RECV
+ if ((r=nr_turn_client_ensure_perm(ctx, remote_addr))) {
+ ABORT(r);
+ }
+#endif
+
+ if (!nr_stun_message_has_attribute(ind, NR_STUN_ATTR_DATA, &attr)) {
+ ABORT(R_BAD_DATA);
+ }
+
+ assert(newsize >= attr->u.data.length);
+ if (newsize < attr->u.data.length)
+ ABORT(R_BAD_ARGS);
+
+ memcpy(newmsg, attr->u.data.data, attr->u.data.length);
+ *newlen = attr->u.data.length;
+
+ _status=0;
+abort:
+ nr_stun_message_destroy(&ind);
+ return(_status);
+}
+
+
+
+/* The permissions model is as follows:
+
+ - We keep a list of all the permissions we have ever requested
+ along with when they were last established.
+ - Whenever someone sends a packet, we automatically create/
+ refresh the permission.
+
+ This means that permissions automatically time out if
+ unused.
+
+*/
+int nr_turn_client_ensure_perm(nr_turn_client_ctx *ctx, const nr_transport_addr *addr)
+{
+ int r, _status;
+ nr_turn_permission *perm = 0;
+ UINT8 now;
+ UINT8 turn_permission_refresh = (TURN_PERMISSION_LIFETIME_SECONDS -
+ TURN_REFRESH_SLACK_SECONDS) * TURN_USECS_PER_S;
+
+ if ((r=nr_turn_permission_find(ctx, addr, &perm))) {
+ if (r == R_NOT_FOUND) {
+ if ((r=nr_turn_permission_create(ctx, addr, &perm)))
+ ABORT(r);
+ }
+ else {
+ ABORT(r);
+ }
+ }
+
+ assert(perm);
+
+ /* Now check that the permission is up-to-date */
+ now = r_gettimeint();
+
+ if ((now - perm->last_used) > turn_permission_refresh) {
+ r_log(NR_LOG_TURN, LOG_DEBUG, "TURN(%s): Permission for %s requires refresh",
+ ctx->label, perm->addr.as_string);
+
+ if ((r=nr_turn_stun_ctx_start(perm->stun)))
+ ABORT(r);
+
+ perm->last_used = now; /* Update the time now so we don't retry on
+ next packet */
+ }
+
+ _status=0;
+abort:
+ return(_status);
+}
+
+static int nr_turn_permission_create(nr_turn_client_ctx *ctx, const nr_transport_addr *addr,
+ nr_turn_permission **permp)
+{
+ int r, _status;
+ nr_turn_permission *perm = 0;
+
+ assert(ctx->state == NR_TURN_CLIENT_STATE_ALLOCATED);
+
+ r_log(NR_LOG_TURN, LOG_INFO, "TURN(%s): Creating permission for %s",
+ ctx->label, addr->as_string);
+
+ if (!(perm = RCALLOC(sizeof(nr_turn_permission))))
+ ABORT(R_NO_MEMORY);
+
+ if ((r=nr_transport_addr_copy(&perm->addr, addr)))
+ ABORT(r);
+
+ perm->last_used = 0;
+
+ if ((r=nr_turn_stun_ctx_create(ctx, NR_TURN_CLIENT_MODE_PERMISSION_REQUEST,
+ nr_turn_client_permissions_cb,
+ nr_turn_client_permission_error_cb,
+ &perm->stun)))
+ ABORT(r);
+
+ /* We want to authenticate on the first packet */
+ if ((r=nr_turn_stun_set_auth_params(perm->stun, ctx->realm, ctx->nonce)))
+ ABORT(r);
+
+ if ((r=nr_transport_addr_copy(
+ &perm->stun->stun->params.permission_request.remote_addr, addr)))
+ ABORT(r);
+ STAILQ_INSERT_TAIL(&ctx->permissions, perm, entry);
+
+ *permp = perm;
+
+ _status=0;
+abort:
+ if (_status) {
+ nr_turn_permission_destroy(&perm);
+ }
+ return(_status);
+}
+
+
+static int nr_turn_permission_find(nr_turn_client_ctx *ctx, const nr_transport_addr *addr,
+ nr_turn_permission **permp)
+{
+ nr_turn_permission *perm;
+ int _status;
+
+ perm = STAILQ_FIRST(&ctx->permissions);
+ while (perm) {
+ if (!nr_transport_addr_cmp(&perm->addr, addr,
+ NR_TRANSPORT_ADDR_CMP_MODE_ADDR))
+ break;
+
+ perm = STAILQ_NEXT(perm, entry);
+ }
+
+ if (!perm) {
+ ABORT(R_NOT_FOUND);
+ }
+ if (perm->stun->last_error_code == 403) {
+ ABORT(R_NOT_PERMITTED);
+ }
+ *permp = perm;
+
+ _status=0;
+abort:
+ return(_status);
+}
+
+static void nr_turn_client_permissions_cb(NR_SOCKET s, int how, void *arg)
+{
+ nr_turn_stun_ctx *ctx = (nr_turn_stun_ctx *)arg;
+ r_log(NR_LOG_TURN, LOG_DEBUG, "TURN(%s): Successfully refreshed permission",
+ ctx->tctx->label);
+}
+
+/* Note that we don't destroy the nr_turn_stun_ctx. That is owned by the
+ nr_turn_client_ctx. */
+static int nr_turn_permission_destroy(nr_turn_permission **permp)
+{
+ nr_turn_permission *perm;
+
+ if (!permp || !*permp)
+ return(0);
+
+ perm = *permp;
+ *permp = 0;
+
+ RFREE(perm);
+
+ return(0);
+}
+
+#endif /* USE_TURN */
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/stun/turn_client_ctx.h b/dom/media/webrtc/transport/third_party/nICEr/src/stun/turn_client_ctx.h
new file mode 100644
index 0000000000..2049f1bdfb
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/stun/turn_client_ctx.h
@@ -0,0 +1,161 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+Copyright (c) 2013, Mozilla
+
+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 Adobe Systems, Network Resonance, Mozilla 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
+OWNER 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 _turn_client_ctx_h
+#define _turn_client_ctx_h
+
+struct nr_ice_ctx_;
+
+typedef struct nr_transport_addr_listnode_ {
+ nr_transport_addr value;
+ STAILQ_ENTRY(nr_transport_addr_listnode_) entry;
+} nr_transport_addr_listnode;
+typedef STAILQ_HEAD(nr_transport_addr_listnode_head_, nr_transport_addr_listnode_) nr_transport_addr_listnode_head;
+
+/*
+ Represents a single set of STUN transactions, i.e.,
+ Allocate, Refresh, Permission. It automatically handles
+ permissions and restarts.
+ */
+typedef struct nr_turn_stun_ctx_ {
+ struct nr_turn_client_ctx_ *tctx;
+ int mode; /* From stun_client_ctx.h, NR_TURN_CLIENT_MODE_* */
+ int retry_ct;
+ nr_stun_client_ctx *stun;
+ char *nonce;
+ char *realm;
+ NR_async_cb success_cb;
+ NR_async_cb error_cb;
+ int last_error_code;
+
+ nr_transport_addr_listnode_head addresses_tried;
+
+ STAILQ_ENTRY(nr_turn_stun_ctx_) entry;
+} nr_turn_stun_ctx;
+typedef STAILQ_HEAD(nr_turn_stun_ctx_head_, nr_turn_stun_ctx_)
+ nr_turn_stun_ctx_head;
+
+/* Represents a single TURN permission */
+typedef struct nr_turn_permission_ {
+ nr_transport_addr addr;
+ nr_turn_stun_ctx *stun;
+ UINT8 last_used;
+
+ STAILQ_ENTRY(nr_turn_permission_) entry;
+} nr_turn_permission;
+typedef STAILQ_HEAD(nr_turn_permission_head_, nr_turn_permission_)
+ nr_turn_permission_head;
+
+/* A single connection to a TURN server. Use one
+ turn_client_ctx per socket/server pair. */
+typedef struct nr_turn_client_ctx_ {
+ int state;
+#define NR_TURN_CLIENT_STATE_INITTED 1
+#define NR_TURN_CLIENT_STATE_ALLOCATING 2
+#define NR_TURN_CLIENT_STATE_ALLOCATED 3
+#define NR_TURN_CLIENT_STATE_FAILED 4
+#define NR_TURN_CLIENT_STATE_CANCELLED 5
+#define NR_TURN_CLIENT_STATE_DEALLOCATING 6
+
+ char *label;
+ nr_socket *sock;
+
+ char *username;
+ Data *password;
+ char *nonce;
+ char *realm;
+
+ nr_transport_addr turn_server_addr;
+ nr_transport_addr relay_addr;
+ nr_transport_addr mapped_addr;
+
+ nr_turn_stun_ctx_head stun_ctxs;
+ nr_turn_permission_head permissions;
+
+ /* We need access to the socket factory to create new TCP sockets for handling
+ * STUN/300 responses. */
+ /* If we were to require TCP nr_sockets to allow multiple connect calls by
+ * disconnecting and re-connecting, we could avoid this requirement. */
+ struct nr_ice_ctx_* ctx;
+
+ NR_async_cb finished_cb;
+ void *cb_arg;
+
+ void *connected_timer_handle;
+ void *refresh_timer_handle;
+
+ // ice telemetry
+ UINT2 cnt_401s;
+ UINT2 cnt_403s;
+ UINT2 cnt_438s;
+} nr_turn_client_ctx;
+
+extern int NR_LOG_TURN;
+
+int nr_transport_addr_listnode_create(const nr_transport_addr *addr, nr_transport_addr_listnode **listnodep);
+void nr_transport_addr_listnode_destroy(nr_transport_addr_listnode **listnode);
+int nr_turn_client_ctx_create(const char* label, nr_socket* sock,
+ const char* username, Data* password,
+ nr_transport_addr* addr,
+ struct nr_ice_ctx_* ice_ctx,
+ nr_turn_client_ctx** ctxp);
+int nr_turn_client_ctx_destroy(nr_turn_client_ctx **ctxp);
+int nr_turn_client_allocate(nr_turn_client_ctx *ctx,
+ NR_async_cb finished_cb, void *cb_arg);
+int nr_turn_client_get_relayed_address(nr_turn_client_ctx *ctx,
+ nr_transport_addr *relayed_address);
+int nr_turn_client_get_mapped_address(nr_turn_client_ctx *ctx,
+ nr_transport_addr *mapped_address);
+int nr_turn_client_process_response(nr_turn_client_ctx *ctx,
+ UCHAR *msg, int len,
+ nr_transport_addr *turn_server_addr);
+int nr_turn_client_cancel(nr_turn_client_ctx *ctx);
+int nr_turn_client_failed(nr_turn_client_ctx *ctx);
+int nr_turn_client_deallocate(nr_turn_client_ctx *ctx);
+int nr_turn_client_send_indication(nr_turn_client_ctx *ctx,
+ const UCHAR *msg, size_t len,
+ int flags, const nr_transport_addr *remote_addr);
+int nr_turn_client_parse_data_indication(nr_turn_client_ctx *ctx,
+ nr_transport_addr *source_addr,
+ UCHAR *msg, size_t len,
+ UCHAR *newmsg, size_t *newlen,
+ size_t newsize,
+ nr_transport_addr *remote_addr);
+int nr_turn_client_ensure_perm(nr_turn_client_ctx *ctx,
+ const nr_transport_addr *addr);
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/util/cb_args.c b/dom/media/webrtc/transport/third_party/nICEr/src/util/cb_args.c
new file mode 100644
index 0000000000..81f7731d74
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/util/cb_args.c
@@ -0,0 +1,57 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+*/
+
+#include <stdarg.h>
+#include "nr_api.h"
+#include "cb_args.h"
+
+void **nr_pack_cb_args(int ct,...)
+ {
+ void **vlist;
+ va_list ap;
+ int i;
+
+ va_start(ap,ct);
+ if(!(vlist=RCALLOC(sizeof(void *)*ct+1)))
+ abort();
+
+ for(i=0;i<ct;i++){
+ vlist[i]=va_arg(ap, void *);
+ }
+
+ va_end(ap);
+
+ return(vlist);
+ }
+
+
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/util/cb_args.h b/dom/media/webrtc/transport/third_party/nICEr/src/util/cb_args.h
new file mode 100644
index 0000000000..83f7831ccc
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/util/cb_args.h
@@ -0,0 +1,41 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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 _cb_args_h
+#define _cb_args_h
+
+void **nr_pack_cb_args(int ct,...);
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/util/ice_util.c b/dom/media/webrtc/transport/third_party/nICEr/src/util/ice_util.c
new file mode 100644
index 0000000000..249cfe350f
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/util/ice_util.c
@@ -0,0 +1,71 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+*/
+
+#include <stdarg.h>
+#include <string.h>
+#include "nr_api.h"
+#include "ice_util.h"
+
+int nr_concat_strings(char **outp,...)
+ {
+ va_list ap;
+ char *s,*out=0;
+ int len=0;
+ int _status;
+
+ va_start(ap,outp);
+ while(s=va_arg(ap,char *)){
+ len+=strlen(s);
+ }
+ va_end(ap);
+
+
+ if(!(out=RMALLOC(len+1)))
+ ABORT(R_NO_MEMORY);
+
+ *outp=out;
+
+ va_start(ap,outp);
+ while(s=va_arg(ap,char *)){
+ len=strlen(s);
+ memcpy(out,s,len);
+ out+=len;
+ }
+ va_end(ap);
+
+ *out=0;
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
diff --git a/dom/media/webrtc/transport/third_party/nICEr/src/util/ice_util.h b/dom/media/webrtc/transport/third_party/nICEr/src/util/ice_util.h
new file mode 100644
index 0000000000..44751edaf8
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nICEr/src/util/ice_util.h
@@ -0,0 +1,41 @@
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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 _ice_util_h
+#define _ice_util_h
+
+int nr_concat_strings(char **outp,...);
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/COPYRIGHT b/dom/media/webrtc/transport/third_party/nrappkit/COPYRIGHT
new file mode 100644
index 0000000000..b0bb25595e
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/COPYRIGHT
@@ -0,0 +1,159 @@
+
+Copyright (C) 2006, Network Resonance, 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:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+2. 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.
+3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+This distribution also contains material from ssldump, tcpdump, and
+FreeBSD. The licenses are on the individual source files but follow
+here as well.
+
+SSLDUMP LICENSE
+Copyright (C) 1999-2001 RTFM, Inc.
+All Rights Reserved
+
+This package is a SSLv3/TLS protocol analyzer written by Eric Rescorla
+<ekr@rtfm.com> and licensed by RTFM, Inc.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+2. 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.
+3. All advertising materials mentioning features or use of this software
+ must display the following acknowledgement:
+
+ This product includes software developed by Eric Rescorla for
+ RTFM, Inc.
+
+4. Neither the name of RTFM, Inc. nor the name of Eric Rescorla may be
+ used to endorse or promote products derived from this
+ software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE ERIC RESCORLA AND RTFM ``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 REGENTS 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.
+
+TCPDUMP LICENSE
+The manual page for this software is partially excerpted from
+the tcpdump manual page, which is subject to the following license:
+Copyright (c) 1987, 1988, 1989, 1990, 1991, 1992, 1994, 1995, 1996, 1997
+ The Regents of the University of California. All rights reserved.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that: (1) source code distributions
+retain the above copyright notice and this paragraph in its entirety, (2)
+distributions including binary code include the above copyright notice and
+this paragraph in its entirety in the documentation or other materials
+provided with the distribution, and (3) all advertising materials mentioning
+features or use of this software display the following acknowledgement:
+``This product includes software developed by the University of California,
+Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
+the University 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 ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+
+
+The compilation of software known as FreeBSD is distributed under the
+following terms:
+
+Copyright (C) 1992-2004 The FreeBSD Project. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+2. 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.
+
+THIS SOFTWARE IS PROVIDED BY AUTHOR 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 AUTHOR 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.
+
+The 4.4BSD and 4.4BSD-Lite software is distributed under the following
+terms:
+
+All of the documentation and software included in the 4.4BSD and 4.4BSD-Lite
+Releases is copyrighted by The Regents of the University of California.
+
+Copyright 1979, 1980, 1983, 1986, 1988, 1989, 1991, 1992, 1993, 1994
+ The Regents of the University of California. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+2. 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.
+3. All advertising materials mentioning features or use of this software
+ must display the following acknowledgement:
+This product includes software developed by the University of
+California, Berkeley and its contributors.
+4. Neither the name of the University 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 REGENTS 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 REGENTS 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.
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/README b/dom/media/webrtc/transport/third_party/nrappkit/README
new file mode 100644
index 0000000000..9cf43319b3
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/README
@@ -0,0 +1,133 @@
+$Id: README,v 1.3 2007/11/21 00:09:10 adamcain Exp $
+
+nrappkit 1.0b2
+Copyright (C) 2006 Network Resonance, Inc.
+
+
+nrappkit is a toolkit for building standalone applications and
+appliances. It provides:
+
+- registry-based configuration (with change callbacks)
+- extensible command and configuration shell
+- extensible statistics system
+- configurable logging system
+- event and timer handling
+- generic plugin system
+- launcher daemon
+
+The contents of nrappkit were extracted from Network Resonance's
+product on the theory that they were generally useful for
+application developers.
+
+THIS PACKAGE DOES NOT GRANT A LICENSE OR RIGHT TO ANY OTHER NETWORK
+RESONANCE TECHNOLOGY OR SOFTWARE.
+
+
+
+BUILDING
+
+Builds are done semi-manually with port directories for each
+platform. There are pre-existing ports to FreeBSD, Linux (Ubuntu
+and Fedora Core), and Darwin (MacOSX). To build the system:
+
+ cd src/make/<platform>
+ gmake
+
+Some of the platforms come in several variants. Most notably,
+if a platform exists in "regular" and "-appliance" variant,
+this means that the regular variant just builds binaries intended
+to be run out of the make directory (for development) and the
+appliance variant is intended to be installed in a real system.
+
+By default we want to install things owned as user "pcecap".
+Either make this user or edit the Makefile to be a user you
+like (e.g., nobody).
+
+If you want to include the 'nrsh' command-line configuration
+tool in your build, you will need to make sure the line
+ BUILD_NRSH=yes
+appears (uncommented-out) in your platform Makefile. You will
+also need to to build OpenSSL and libedit and point your nrappkit
+Makefile to the correct paths. You can obtain these packages at:
+ openssl-0.9.7l
+ http://www.openssl.org/source/openssl-0.9.7l.tar.gz
+
+ libedit-20060829-2.9
+ http://freshmeat.net/redir/editline/53029/url_tgz/libedit-20060829-2.9.tar.gz
+
+
+INSTALLING
+If you're doing an appliance as opposed to a development build,
+you'll want to install it. This is easy:
+
+ su
+ gmake install
+
+Most binaries and libraries ends up in /usr/local/pcecap while
+data files are in /var/pcecap. However, you can tweak
+this in the Makefile. By default it's all owned by pcecap.
+
+To ensure that dynamic libraries are loaded correctly at runtime,
+you'd want to make sure the right directory is included in your
+LD_LIBRARY_PATH or via ldconfig.
+
+
+QUICK TOUR
+The build makes the following binaries that you may find useful:
+
+- captured -- the launcher (the name is historical)
+- registryd -- the registry daemon
+- nrregctl -- a registry control program
+- nrsh -- the command shell (when included in build)
+- nrstatsctl -- the stats control program
+
+Using the nrcapctl script is the easiest way to interact with
+the applications. It is run as "nrcapctl <command>" with the
+following commands recognized:
+
+ startup -- fires up captured, which in turn runs and
+ initializes the registry
+
+ shutdown -- kills captured and its child processes
+
+ status -- prints the running status of captured in
+ human-readable form
+
+ stat -- prints the running status of captured in
+ a form easily parsed by scripts
+
+ enable -- alters the mode.txt file so that captured
+ starts
+
+ disable -- alters the mode.txt file so that captured
+ does not start
+
+ clear-statistics -- equivalent to "nrstatsctl -z" (requires
+ that captured be running)
+
+Note: the "start" and "stop" nrcapctl commands do nothing as they
+use components not included in nrappkit. However the associated
+script logic in nrcapctl demonstrates how additional applications
+might be launched using nrcapctl and particular registry settings.
+
+
+EXTENDING
+When things come up, they're pretty dumb. You'll probably want to
+write your own applications, otherwise it's not clear why you're doing
+this. The general idea is that you write your application using the
+facilities that nrappkit provides and then write plugins to the
+nrappkit components as necessary. So, for example, say you want
+to write a network daemon. You would:
+
+ - configure the launcher to launch your daemon (using the registry,
+ naturally).
+ - make calls to the registry to get configuration data
+ - make calls to the logging system to log data
+ - implement a stats module to record statistics
+ - write a plugin to nrsh to let people configure your parameters
+
+Examples of some of this stuff can be found in examples/demo_plugin.
+Otherwise, read the source. More documentation will be on the way,
+hopefully.
+
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/README_MOZILLA b/dom/media/webrtc/transport/third_party/nrappkit/README_MOZILLA
new file mode 100644
index 0000000000..f7cdb4cabb
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/README_MOZILLA
@@ -0,0 +1,21 @@
+This import of nrappkit is a subset of the distribution at:
+
+ nrappkit.sourceforge.net
+
+
+The last revision included in this import was on Nov 25, 2008, version 1.0b2.
+
+The upstream project is now dead (no update since 2013), we can conisder this
+version as a fork.
+
+Out of the list in the README, we use:
+
+- registry-based configuration (with change callbacks)
+ [but without the registry daemon]
+- configurable logging system
+- event and timer handling
+ [though partly reimplemented]
+
+Also, we use a bunch of the generic utilities such as string handling,
+generic hash tables in C, yet another concrete type mapping, etc.
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/nrappkit.gyp b/dom/media/webrtc/transport/third_party/nrappkit/nrappkit.gyp
new file mode 100644
index 0000000000..c3f88af4bc
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/nrappkit.gyp
@@ -0,0 +1,251 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# nrappkit.gyp
+#
+#
+{
+ 'targets' : [
+ {
+ 'target_name' : 'nrappkit',
+ 'type' : 'static_library',
+
+ 'include_dirs' : [
+ # EXTERNAL
+ # INTERNAL
+ 'src/event',
+ 'src/log',
+ 'src/port/generic/include',
+ 'src/registry',
+ 'src/share',
+ 'src/stats',
+ 'src/util',
+ 'src/util/libekr',
+ ],
+
+ 'sources' : [
+ # Shared
+# './src/share/nr_api.h',
+ './src/share/nr_common.h',
+# './src/share/nr_dynlib.h',
+ './src/share/nr_reg_keys.h',
+# './src/share/nr_startup.c',
+# './src/share/nr_startup.h',
+# './src/share/nrappkit_static_plugins.c',
+ './src/port/generic/include'
+
+ # libekr
+ './src/util/libekr/assoc.h',
+# './src/util/libekr/debug.c',
+# './src/util/libekr/debug.h',
+ './src/util/libekr/r_assoc.c',
+ './src/util/libekr/r_assoc.h',
+# './src/util/libekr/r_assoc_test.c',
+ './src/util/libekr/r_common.h',
+ './src/util/libekr/r_crc32.c',
+ './src/util/libekr/r_crc32.h',
+ './src/util/libekr/r_data.c',
+ './src/util/libekr/r_data.h',
+ './src/util/libekr/r_defaults.h',
+ './src/util/libekr/r_errors.c',
+ './src/util/libekr/r_errors.h',
+ './src/util/libekr/r_includes.h',
+# './src/util/libekr/r_list.c',
+# './src/util/libekr/r_list.h',
+ './src/util/libekr/r_macros.h',
+ './src/util/libekr/r_memory.c',
+ './src/util/libekr/r_memory.h',
+ './src/util/libekr/r_replace.c',
+ './src/util/libekr/r_thread.h',
+ './src/util/libekr/r_time.c',
+ './src/util/libekr/r_time.h',
+ './src/util/libekr/r_types.h',
+
+ # Utilities
+ './src/util/byteorder.c',
+ './src/util/byteorder.h',
+ #'./src/util/escape.c',
+ #'./src/util/escape.h',
+ #'./src/util/filename.c',
+ #'./src/util/getopt.c',
+ #'./src/util/getopt.h',
+ './src/util/hex.c',
+ './src/util/hex.h',
+ #'./src/util/mem_util.c',
+ #'./src/util/mem_util.h',
+ #'./src/util/mutex.c',
+ #'./src/util/mutex.h',
+ './src/util/p_buf.c',
+ './src/util/p_buf.h',
+ #'./src/util/ssl_util.c',
+ #'./src/util/ssl_util.h',
+ './src/util/util.c',
+ './src/util/util.h',
+ #'./src/util/util_db.c',
+ #'./src/util/util_db.h',
+
+ # Events
+# './src/event/async_timer.c',
+ './src/event/async_timer.h',
+# './src/event/async_wait.c',
+ './src/event/async_wait.h',
+ './src/event/async_wait_int.h',
+
+ # Logging
+ './src/log/r_log.c',
+ './src/log/r_log.h',
+ #'./src/log/r_log_plugin.c',
+
+ # Registry
+ './src/registry/c2ru.c',
+ './src/registry/c2ru.h',
+ #'./src/registry/mod_registry/mod_registry.c',
+ #'./src/registry/nrregctl.c',
+ #'./src/registry/nrregistryctl.c',
+ './src/registry/registry.c',
+ './src/registry/registry.h',
+ './src/registry/registry_int.h',
+ './src/registry/registry_local.c',
+ #'./src/registry/registry_plugin.c',
+ './src/registry/registry_vtbl.h',
+ './src/registry/registrycb.c',
+ #'./src/registry/registryd.c',
+ #'./src/registry/regrpc.h',
+ #'./src/registry/regrpc_client.c',
+ #'./src/registry/regrpc_client.h',
+ #'./src/registry/regrpc_client_cb.c',
+ #'./src/registry/regrpc_clnt.c',
+ #'./src/registry/regrpc_server.c',
+ #'./src/registry/regrpc_svc.c',
+ #'./src/registry/regrpc_xdr.c',
+
+ # Statistics
+ #'./src/stats/nrstats.c',
+ #'./src/stats/nrstats.h',
+ #'./src/stats/nrstats_app.c',
+ #'./src/stats/nrstats_int.h',
+ #'./src/stats/nrstats_memory.c',
+ ],
+
+ 'defines' : [
+ 'SANITY_CHECKS',
+ 'R_PLATFORM_INT_TYPES=<stdint.h>',
+ 'R_DEFINED_INT2=int16_t',
+ 'R_DEFINED_UINT2=uint16_t',
+ 'R_DEFINED_INT4=int32_t',
+ 'R_DEFINED_UINT4=uint32_t',
+ 'R_DEFINED_INT8=int64_t',
+ 'R_DEFINED_UINT8=uint64_t',
+ ],
+
+ 'conditions' : [
+ ## Mac and BSDs
+ [ 'OS == "mac"', {
+ 'defines' : [
+ 'DARWIN',
+ ],
+ }],
+ [ 'os_bsd == 1', {
+ 'defines' : [
+ 'BSD',
+ ],
+ }],
+ [ 'OS == "mac" or OS == "ios" or os_bsd == 1', {
+ 'cflags_mozilla': [
+ '-Wall',
+ '-Wno-parentheses',
+ '-Wno-strict-prototypes',
+ '-Wmissing-prototypes',
+ '-Wno-format',
+ '-Wno-format-security',
+ ],
+ 'defines' : [
+ 'HAVE_LIBM=1',
+ 'HAVE_STRDUP=1',
+ 'HAVE_STRLCPY=1',
+ 'HAVE_SYS_TIME_H=1',
+ 'HAVE_VFPRINTF=1',
+ 'NEW_STDIO'
+ 'RETSIGTYPE=void',
+ 'TIME_WITH_SYS_TIME_H=1',
+ '__UNUSED__=__attribute__((unused))',
+ ],
+
+ 'include_dirs': [
+ 'src/port/darwin/include'
+ ],
+
+ 'sources': [
+ './src/port/darwin/include/csi_platform.h',
+ ],
+ }],
+
+ ## Win
+ [ 'OS == "win"', {
+ 'defines' : [
+ 'WIN',
+ '__UNUSED__=',
+ 'HAVE_STRDUP=1',
+ 'NO_REG_RPC'
+ ],
+
+ 'include_dirs': [
+ 'src/port/win32/include'
+ ],
+
+ 'sources': [
+ './src/port/win32/include/csi_platform.h',
+ ],
+ }],
+
+ # Windows, clang-cl build
+ [ 'clang_cl == 1', {
+ 'cflags_mozilla': [
+ '-Xclang',
+ '-Wall',
+ '-Xclang',
+ '-Wno-parentheses',
+ '-Wno-strict-prototypes',
+ '-Wmissing-prototypes',
+ '-Wno-format',
+ '-Wno-format-security',
+ ],
+ }],
+
+ ## Linux/Android
+ [ '(OS == "linux") or (OS == "android")', {
+ 'cflags_mozilla': [
+ '-Wall',
+ '-Wno-parentheses',
+ '-Wno-strict-prototypes',
+ '-Wmissing-prototypes',
+ '-Wno-format',
+ '-Wno-format-security',
+ ],
+ 'defines' : [
+ 'LINUX',
+ 'HAVE_LIBM=1',
+ 'HAVE_STRDUP=1',
+ 'HAVE_STRLCPY=1',
+ 'HAVE_SYS_TIME_H=1',
+ 'HAVE_VFPRINTF=1',
+ 'NEW_STDIO'
+ 'RETSIGTYPE=void',
+ 'TIME_WITH_SYS_TIME_H=1',
+ 'NO_REG_RPC=1',
+ '__UNUSED__=__attribute__((unused))',
+ ],
+
+ 'include_dirs': [
+ 'src/port/linux/include'
+ ],
+ 'sources': [
+ './src/port/linux/include/csi_platform.h',
+ ],
+ }]
+ ]
+ }]
+}
+
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/event/async_timer.h b/dom/media/webrtc/transport/third_party/nrappkit/src/event/async_timer.h
new file mode 100644
index 0000000000..544a3d44fd
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/event/async_timer.h
@@ -0,0 +1,54 @@
+/**
+ async_timer.h
+
+
+ Copyright (C) 2004, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ ekr@rtfm.com Sun Feb 22 19:35:24 2004
+ */
+
+
+#ifndef _async_timer_h
+#define _async_timer_h
+
+
+int NR_async_timer_init(void);
+int NR_async_timer_set(int delay_ms,NR_async_cb cb,void *cb_arg,char *function,int line,void **handle);
+int NR_async_timer_cancel(void *handle);
+int NR_async_timer_update_time(struct timeval *tv);
+int NR_async_timer_next_timeout(int *delay_ms);
+int NR_async_timer_sanity_check_for_cb_deleted(NR_async_cb cb,void *cb_arg);
+
+#define NR_ASYNC_TIMER_SET(d,c,a,hp) NR_async_timer_set(d,c,a,(char *)__FUNCTION__,__LINE__,hp)
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/event/async_wait.h b/dom/media/webrtc/transport/third_party/nrappkit/src/event/async_wait.h
new file mode 100644
index 0000000000..58bcd435eb
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/event/async_wait.h
@@ -0,0 +1,83 @@
+/**
+ async_wait.h
+
+
+ Copyright (C) 2001-2003, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ ekr@rtfm.com Thu Dec 20 20:14:49 2001
+ */
+
+
+#ifndef _async_wait_h
+#define _async_wait_h
+
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+#include <csi_platform.h>
+
+typedef void (*NR_async_cb)(NR_SOCKET resource,int how,void *arg);
+
+#define NR_ASYNC_WAIT_READ 0
+#define NR_ASYNC_WAIT_WRITE 1
+
+
+int NR_async_wait_init(void);
+int NR_async_wait(NR_SOCKET sock, int how, NR_async_cb cb,void *cb_arg,
+ char *function,int line);
+int NR_async_cancel(NR_SOCKET sock,int how);
+int NR_async_schedule(NR_async_cb cb,void *arg,char *function,int line);
+int NR_async_event_wait(int *eventsp);
+int NR_async_event_wait2(int *eventsp,struct timeval *tv);
+
+
+#ifdef NR_DEBUG_ASYNC
+#define NR_ASYNC_WAIT(s,h,cb,arg) do { \
+fprintf(stderr,"NR_async_wait(%d,%s,%s) at %s(%d)\n",s,#h,#cb,__FUNCTION__,__LINE__); \
+ NR_async_wait(s,h,cb,arg,(char *)__FUNCTION__,__LINE__); \
+} while (0)
+#define NR_ASYNC_SCHEDULE(cb,arg) do { \
+fprintf(stderr,"NR_async_schedule(%s) at %s(%d)\n",#cb,__FUNCTION__,__LINE__);\
+ NR_async_schedule(cb,arg,(char *)__FUNCTION__,__LINE__); \
+} while (0)
+#define NR_ASYNC_CANCEL(s,h) do { \
+ fprintf(stderr,"NR_async_cancel(%d,%s) at %s(%d)\n",s,#h,(char *)__FUNCTION__,__LINE__); \
+NR_async_cancel(s,h); \
+} while (0)
+#else
+#define NR_ASYNC_WAIT(a,b,c,d) NR_async_wait(a,b,c,d,(char *)__FUNCTION__,__LINE__)
+#define NR_ASYNC_SCHEDULE(a,b) NR_async_schedule(a,b,(char *)__FUNCTION__,__LINE__)
+#define NR_ASYNC_CANCEL NR_async_cancel
+#endif
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/event/async_wait_int.h b/dom/media/webrtc/transport/third_party/nrappkit/src/event/async_wait_int.h
new file mode 100644
index 0000000000..d17372d183
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/event/async_wait_int.h
@@ -0,0 +1,62 @@
+/**
+ async_wait_int.h
+
+
+ Copyright (C) 2004, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ ekr@rtfm.com Sun Feb 22 19:16:01 2004
+ */
+
+
+#ifndef _async_wait_int_h
+#define _async_wait_int_h
+
+
+typedef struct callback_ {
+ NR_async_cb cb;
+ void *arg;
+ int how;
+ int resource;
+ void *backptr;
+
+ char *func;
+ int free_func;
+ int line;
+ TAILQ_ENTRY(callback_) entry;
+} callback;
+
+int nr_async_create_cb(NR_async_cb cb,void *arg,int how,
+ int resource,char *func,int line,callback **cbp);
+int nr_async_destroy_cb(callback **cbp);
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/log/r_log.c b/dom/media/webrtc/transport/third_party/nrappkit/src/log/r_log.c
new file mode 100644
index 0000000000..09bb24749f
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/log/r_log.c
@@ -0,0 +1,696 @@
+/**
+ r_log.c
+
+
+ Copyright (C) 2001, RTFM, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ ekr@rtfm.com Mon Dec 3 15:24:38 2001
+ */
+
+#ifdef LINUX
+#define _BSD_SOURCE
+#define _DEFAULT_SOURCE
+#endif
+
+#include "r_log.h"
+#include "hex.h"
+
+#include <string.h>
+#include <errno.h>
+#ifndef _MSC_VER
+#include <strings.h>
+#include <syslog.h>
+#endif
+#include <registry.h>
+#include <time.h>
+
+
+#include "nr_common.h"
+#include "nr_reg_keys.h"
+
+
+#define LOGGING_DEFAULT_LEVEL 5
+
+int NR_LOG_LOGGING = 0;
+
+static char *log_level_strings[]={
+ "EMERG",
+ "ALERT",
+ "CRIT",
+ "ERR",
+ "WARNING",
+ "NOTICE",
+ "INFO",
+ "DEBUG"
+};
+
+static char *log_level_reg_strings[]={
+ "emergency",
+ "alert",
+ "critical",
+ "error",
+ "warning",
+ "notice",
+ "info",
+ "debug"
+};
+
+#define LOGGING_REG_PREFIX "logging"
+
+#define MAX_ERROR_STRING_SIZE 512
+
+#define R_LOG_INITTED1 1
+#define R_LOG_INITTED2 2
+
+#define LOG_NUM_DESTINATIONS 3
+
+typedef struct log_type_ {
+ char *facility_name;
+ int level[LOG_NUM_DESTINATIONS];
+ NR_registry dest_facility_key[LOG_NUM_DESTINATIONS];
+} log_type;
+
+#define MAX_LOG_TYPES 16
+
+static log_type log_types[MAX_LOG_TYPES];
+static int log_type_ct;
+
+
+typedef struct log_destination_ {
+ char *dest_name;
+ int enabled;
+ int default_level;
+ r_dest_vlog *dest_vlog;
+} log_destination;
+
+
+#define LOG_LEVEL_UNDEFINED -1
+#define LOG_LEVEL_NONE -2
+#define LOG_LEVEL_USE_DEST_DEFAULT -3
+
+static int stderr_vlog(int facility,int level,const char *format,va_list ap);
+static int syslog_vlog(int facility,int level,const char *format,va_list ap);
+static int noop_vlog(int facility,int level,const char *format,va_list ap);
+
+static log_destination log_destinations[LOG_NUM_DESTINATIONS]={
+ {
+ "stderr",
+ 0,
+ LOGGING_DEFAULT_LEVEL,
+ stderr_vlog,
+ },
+ {
+ "syslog",
+#ifndef WIN32
+ 1,
+#else
+ 0,
+#endif
+ LOGGING_DEFAULT_LEVEL,
+ syslog_vlog,
+ },
+ {
+ "extra",
+ 0,
+ LOGGING_DEFAULT_LEVEL,
+ noop_vlog,
+ },
+};
+
+static int r_log_level=LOGGING_DEFAULT_LEVEL;
+static int r_log_level_environment=0;
+static int r_log_initted=0;
+static int r_log_env_verbose=0;
+
+static void r_log_facility_change_cb(void *cb_arg, char action, NR_registry name);
+static void r_log_facility_delete_cb(void *cb_arg, char action, NR_registry name);
+static void r_log_destination_change_cb(void *cb_arg, char action, NR_registry name);
+static void r_log_default_level_change_cb(void *cb_arg, char action, NR_registry name);
+static int r_log_get_default_level(void);
+static int r_log_get_destinations(int usereg);
+static int r_logging_dest(int dest_index, int facility, int level);
+static int _r_log_init(int usereg);
+static int r_log_get_reg_level(NR_registry name, int *level);
+
+int r_log_register(char *facility_name,int *log_facility)
+ {
+ int i,j;
+ int level;
+ int r,_status;
+ char *buf=0;
+ NR_registry dest_prefix, dest_facility_prefix;
+
+ for(i=0;i<log_type_ct;i++){
+ if(!strcmp(facility_name,log_types[i].facility_name)){
+ *log_facility=i;
+ return(0);
+ }
+ }
+
+ if(log_type_ct==MAX_LOG_TYPES){
+ ABORT(R_INTERNAL);
+ }
+
+ i=log_type_ct;
+
+ /* Initial registration completed, increment log_type_ct */
+ log_types[i].facility_name=r_strdup(facility_name);
+ *log_facility=log_type_ct;
+ log_type_ct++;
+
+ for(j=0; j<LOG_NUM_DESTINATIONS; j++){
+ log_types[i].level[j]=LOG_LEVEL_UNDEFINED;
+
+ if(NR_reg_initted()){
+
+ if((size_t)snprintf(dest_prefix,sizeof(NR_registry),
+ "logging.%s.facility",log_destinations[j].dest_name)>=sizeof(NR_registry))
+ ABORT(R_INTERNAL);
+
+ if (r=NR_reg_make_registry(dest_prefix,facility_name,dest_facility_prefix))
+ ABORT(r);
+
+ if((size_t)snprintf(log_types[i].dest_facility_key[j],sizeof(NR_registry),
+ "%s.level",dest_facility_prefix)>=sizeof(NR_registry))
+ ABORT(R_INTERNAL);
+
+ if(!r_log_get_reg_level(log_types[i].dest_facility_key[j],&level)){
+ log_types[i].level[j]=level;
+ }
+
+ /* Set a callback for the facility's level */
+ if(r=NR_reg_register_callback(log_types[i].dest_facility_key[j],
+ NR_REG_CB_ACTION_ADD|NR_REG_CB_ACTION_CHANGE,
+ r_log_facility_change_cb,(void *)&(log_types[i].level[j])))
+ ABORT(r);
+ if(r=NR_reg_register_callback(log_types[i].dest_facility_key[j],
+ NR_REG_CB_ACTION_DELETE,
+ r_log_facility_delete_cb,(void *)&(log_types[i].level[j])))
+ ABORT(r);
+
+ }
+ }
+
+ _status=0;
+ abort:
+ if(_status)
+ RFREE(buf);
+ return(_status);
+ }
+
+int r_log_facility(int facility,char **typename)
+ {
+ if(facility >= 0 && facility < log_type_ct){
+ *typename=log_types[facility].facility_name;
+ return(0);
+ }
+ return(R_NOT_FOUND);
+ }
+
+static int r_log_get_reg_level(NR_registry name, int *out)
+ {
+ char level[32];
+ int r,_status;
+ int i;
+
+ if(r=NR_reg_get_string(name,level,sizeof(level)))
+ ABORT(r);
+
+ if(!strcasecmp(level,"none")){
+ *out=LOG_LEVEL_NONE;
+ return(0);
+ }
+
+ for(i=0;i<=LOG_DEBUG;i++){
+ if(!strcasecmp(level,log_level_reg_strings[i])){
+ *out=(int)i;
+ return(0);
+ }
+ }
+
+ if(i>LOG_DEBUG){
+ *out=LOG_LEVEL_UNDEFINED;
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+/* Handle the case where a value changes */
+static void r_log_facility_change_cb(void *cb_arg, char action, NR_registry name)
+ {
+ int *lt_level=(int *)cb_arg;
+ int level;
+ int r,_status;
+
+ if(r=r_log_get_reg_level(name,&level))
+ ABORT(r);
+
+ *lt_level=level;
+
+ _status=0;
+ abort:
+ (void)_status; // to avoid unused variable warning and still conform to
+ // pattern of using ABORT
+ return;
+ }
+
+/* Handle the case where a value is deleted */
+static void r_log_facility_delete_cb(void *cb_arg, char action, NR_registry name)
+ {
+ int *lt_level=(int *)cb_arg;
+
+ *lt_level=LOG_LEVEL_UNDEFINED;
+ }
+
+int r_log(int facility,int level,const char *format,...)
+ {
+ va_list ap;
+
+ va_start(ap,format);
+
+ r_vlog(facility,level,format,ap);
+ va_end(ap);
+
+ return(0);
+ }
+
+int r_dump(int facility,int level,char *name,char *data,int len)
+ {
+ char *hex = 0;
+ size_t unused;
+
+ if(!r_logging(facility,level))
+ return(0);
+
+ hex=RMALLOC((len*2)+1);
+ if (!hex)
+ return(R_FAILED);
+
+ if (nr_nbin2hex((UCHAR*)data, len, hex, len*2+1, &unused))
+ strcpy(hex, "?");
+
+ if(name)
+ r_log(facility,level,"%s[%d]=%s",name,len,hex);
+ else
+ r_log(facility,level,"%s",hex);
+
+ RFREE(hex);
+ return(0);
+ }
+
+// Some platforms (notably WIN32) do not have this
+#ifndef va_copy
+ #ifdef WIN32
+ #define va_copy(dest, src) ( (dest) = (src) )
+ #else // WIN32
+ #error va_copy undefined, and semantics of assignment on va_list unknown
+ #endif //WIN32
+#endif //va_copy
+
+int r_vlog(int facility,int level,const char *format,va_list ap)
+ {
+ char log_fmt_buf[MAX_ERROR_STRING_SIZE];
+ char *level_str="unknown";
+ char *facility_str="unknown";
+ char *fmt_str=(char *)format;
+ int i;
+
+ if(r_log_env_verbose){
+ if((level>=LOG_EMERG) && (level<=LOG_DEBUG))
+ level_str=log_level_strings[level];
+
+ if(facility >= 0 && facility < log_type_ct)
+ facility_str=log_types[facility].facility_name;
+
+ snprintf(log_fmt_buf, MAX_ERROR_STRING_SIZE, "(%s/%s) %s",
+ facility_str,level_str,format);
+
+ log_fmt_buf[MAX_ERROR_STRING_SIZE-1]=0;
+ fmt_str=log_fmt_buf;
+ }
+
+ for(i=0; i<LOG_NUM_DESTINATIONS; i++){
+ if(r_logging_dest(i,facility,level)){
+ // Some platforms do not react well when you use a va_list more than
+ // once
+ va_list copy;
+ va_copy(copy, ap);
+ log_destinations[i].dest_vlog(facility,level,fmt_str,copy);
+ va_end(copy);
+ }
+ }
+ return(0);
+ }
+
+int stderr_vlog(int facility,int level,const char *format,va_list ap)
+ {
+#if 0 /* remove time stamping, for now */
+ char cbuf[30];
+ time_t tt;
+
+ tt=time(0);
+
+ ctime_r(&tt,cbuf);
+ cbuf[strlen(cbuf)-1]=0;
+
+ fprintf(stderr,"%s: ",cbuf);
+#endif
+
+ vfprintf(stderr,format,ap);
+ fprintf(stderr,"\n");
+ return(0);
+ }
+
+int syslog_vlog(int facility,int level,const char *format,va_list ap)
+ {
+#ifndef WIN32
+ vsyslog(level|LOG_LOCAL0,format,ap);
+#endif
+ return(0);
+ }
+
+int noop_vlog(int facility,int level,const char *format,va_list ap)
+ {
+ return(0);
+ }
+
+int r_log_e(int facility,int level,const char *format,...)
+ {
+ va_list ap;
+
+ va_start(ap,format);
+ r_vlog_e(facility,level,format,ap);
+ va_end(ap);
+
+ return(0);
+ }
+
+int r_vlog_e(int facility,int level,const char *format,va_list ap)
+ {
+ char log_fmt_buf[MAX_ERROR_STRING_SIZE];
+ if(r_logging(facility,level)) {
+ int formatlen = strlen(format);
+
+ if(formatlen+2 > MAX_ERROR_STRING_SIZE)
+ return(1);
+
+ strncpy(log_fmt_buf, format, formatlen);
+ strcpy(&log_fmt_buf[formatlen], ": ");
+ snprintf(&log_fmt_buf[formatlen+2], MAX_ERROR_STRING_SIZE - formatlen - 2, "%s",
+#ifdef WIN32
+ strerror(WSAGetLastError()));
+#else
+ strerror(errno));
+#endif
+ log_fmt_buf[MAX_ERROR_STRING_SIZE-1]=0;
+
+ r_vlog(facility,level,log_fmt_buf,ap);
+ }
+ return(0);
+ }
+
+int r_log_nr(int facility,int level,int r,const char *format,...)
+ {
+ va_list ap;
+
+ va_start(ap,format);
+ r_vlog_nr(facility,level,r,format,ap);
+ va_end(ap);
+
+ return(0);
+ }
+
+int r_vlog_nr(int facility,int level,int r,const char *format,va_list ap)
+ {
+ char log_fmt_buf[MAX_ERROR_STRING_SIZE];
+ if(r_logging(facility,level)) {
+ int formatlen = strlen(format);
+
+ if(formatlen+2 > MAX_ERROR_STRING_SIZE)
+ return(1);
+ strncpy(log_fmt_buf, format, formatlen);
+ strcpy(&log_fmt_buf[formatlen], ": ");
+ snprintf(&log_fmt_buf[formatlen+2], MAX_ERROR_STRING_SIZE - formatlen - 2, "%s",
+ nr_strerror(r));
+
+ log_fmt_buf[MAX_ERROR_STRING_SIZE-1]=0;
+
+ r_vlog(facility,level,log_fmt_buf,ap);
+ }
+ return(0);
+ }
+
+static int r_logging_dest(int dest_index, int facility, int level)
+ {
+ int thresh;
+
+ _r_log_init(0);
+
+ if(!log_destinations[dest_index].enabled)
+ return(0);
+
+ if(level <= r_log_level_environment)
+ return(1);
+
+ if(r_log_initted<R_LOG_INITTED2)
+ return(level<=r_log_level);
+
+ if(facility < 0 || facility > log_type_ct)
+ thresh=r_log_level;
+ else{
+ if(log_types[facility].level[dest_index]==LOG_LEVEL_NONE)
+ return(0);
+
+ if(log_types[facility].level[dest_index]>=0)
+ thresh=log_types[facility].level[dest_index];
+ else if(log_destinations[dest_index].default_level!=LOG_LEVEL_UNDEFINED)
+ thresh=log_destinations[dest_index].default_level;
+ else
+ thresh=r_log_level;
+ }
+
+ if(level<=thresh)
+ return(1);
+
+ return(0);
+ }
+
+int r_logging(int facility, int level)
+ {
+ int i;
+
+ _r_log_init(0);
+
+ /* return 1 if logging is on for any dest */
+
+ for(i=0; i<LOG_NUM_DESTINATIONS; i++){
+ if(r_logging_dest(i,facility,level))
+ return(1);
+ }
+
+ return(0);
+ }
+
+
+static int r_log_get_default_level(void)
+ {
+ char *log;
+ int _status;
+
+ log=getenv("R_LOG_LEVEL");
+
+ if(log){
+ r_log_level=atoi(log);
+ r_log_level_environment=atoi(log);
+ }
+ else{
+ r_log_level=LOGGING_DEFAULT_LEVEL;
+ }
+
+ _status=0;
+ //abort:
+ return(_status);
+ }
+
+
+static int r_log_get_destinations(int usereg)
+ {
+ char *log;
+ int i;
+ int r,_status;
+
+ log=getenv("R_LOG_DESTINATION");
+ if(log){
+ for(i=0; i<LOG_NUM_DESTINATIONS; i++)
+ log_destinations[i].enabled=!strcmp(log,log_destinations[i].dest_name);
+ }
+ else if(usereg){
+ NR_registry reg_key;
+ int i;
+ int value;
+ char c;
+
+ /* Get the data out of the registry */
+ for(i=0; i<LOG_NUM_DESTINATIONS; i++){
+ /* set callback for default level */
+ if((size_t)snprintf(reg_key,sizeof(reg_key),"%s.%s.level",LOGGING_REG_PREFIX,
+ log_destinations[i].dest_name)>=sizeof(reg_key))
+ ABORT(R_INTERNAL);
+
+ NR_reg_register_callback(reg_key,
+ NR_REG_CB_ACTION_ADD|NR_REG_CB_ACTION_CHANGE|NR_REG_CB_ACTION_DELETE,
+ r_log_default_level_change_cb,0);
+
+ if(r=r_log_get_reg_level(reg_key,&value)){
+ if(r==R_NOT_FOUND)
+ log_destinations[i].default_level=LOG_LEVEL_UNDEFINED;
+ else
+ ABORT(R_INTERNAL);
+ }
+ else
+ log_destinations[i].default_level=value;
+
+ /* set callback for the enabled key for this logging dest */
+ if((size_t)snprintf(reg_key,sizeof(reg_key),"%s.%s.enabled",LOGGING_REG_PREFIX,
+ log_destinations[i].dest_name)>=sizeof(reg_key))
+ ABORT(R_INTERNAL);
+
+ NR_reg_register_callback(reg_key,
+ NR_REG_CB_ACTION_ADD|NR_REG_CB_ACTION_CHANGE|NR_REG_CB_ACTION_DELETE,
+ r_log_destination_change_cb,0);
+
+ if(r=NR_reg_get_char(reg_key,&c)){
+ if(r==R_NOT_FOUND)
+ log_destinations[i].enabled=0;
+ else
+ ABORT(r);
+ }
+ else
+ log_destinations[i].enabled=c;
+ }
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+static void r_log_destination_change_cb(void *cb_arg, char action, NR_registry name)
+ {
+ r_log_get_destinations(1);
+ }
+
+static void r_log_default_level_change_cb(void *cb_arg, char action, NR_registry name)
+ {
+ r_log_get_destinations(1);
+ }
+
+
+int r_log_init()
+ {
+ _r_log_init(1);
+
+ return 0;
+ }
+
+int _r_log_init(int use_reg)
+ {
+#ifndef WIN32
+ char *log;
+#endif
+
+ if(r_log_initted==0) {
+#ifdef WIN32
+ r_log_env_verbose=1;
+#else
+ log=getenv("R_LOG_VERBOSE");
+ if(log)
+ r_log_env_verbose=atoi(log);
+#endif
+
+ }
+
+ if(!use_reg){
+ if(r_log_initted<R_LOG_INITTED1){
+ r_log_get_default_level();
+ r_log_get_destinations(0);
+
+ r_log_initted=R_LOG_INITTED1;
+ }
+ }
+ else{
+ if(r_log_initted<R_LOG_INITTED2){
+ int facility;
+
+ r_log_get_default_level();
+ r_log_get_destinations(1);
+
+ r_log_register("generic",&facility);
+ r_log_register("logging",&NR_LOG_LOGGING);
+
+ r_log_initted=R_LOG_INITTED2;
+ }
+ }
+
+ return(0);
+ }
+
+int r_log_set_extra_destination(int default_level, r_dest_vlog *dest_vlog)
+ {
+ int i;
+ log_destination *dest = 0;
+
+ for(i=0; i<LOG_NUM_DESTINATIONS; i++){
+ if(!strcmp("extra",log_destinations[i].dest_name)){
+ dest=&log_destinations[i];
+ break;
+ }
+ }
+
+ if(!dest)
+ return(R_INTERNAL);
+
+ if (dest_vlog==0){
+ dest->enabled=0;
+ dest->dest_vlog=noop_vlog;
+ }
+ else{
+ dest->enabled=1;
+ dest->default_level=default_level;
+ dest->dest_vlog=dest_vlog;
+ }
+
+ return(0);
+ }
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/log/r_log.h b/dom/media/webrtc/transport/third_party/nrappkit/src/log/r_log.h
new file mode 100644
index 0000000000..a72dfa0667
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/log/r_log.h
@@ -0,0 +1,85 @@
+/**
+ r_log.h
+
+
+ Copyright (C) 2001, RTFM, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ ekr@rtfm.com Mon Dec 3 15:14:45 2001
+ */
+
+
+#ifndef _r_log_h
+#define _r_log_h
+
+#ifndef WIN32
+#include <syslog.h>
+#endif
+#include <stdarg.h>
+#include <r_common.h>
+
+int r_log(int facility,int level,const char *fmt,...)
+#ifdef __GNUC__
+ __attribute__ ((format (printf, 3, 4)))
+#endif
+;
+
+int r_vlog(int facility,int level,const char *fmt,va_list ap);
+int r_dump(int facility,int level,char *name,char *data,int len);
+
+int r_log_e(int facility,int level,const char *fmt,...)
+#ifdef __GNUC__
+ __attribute__ ((format (printf, 3, 4)))
+#endif
+;
+
+int r_vlog_e(int facility,int level,const char *fmt,va_list ap);
+int r_log_nr(int facility,int level,int r,const char *fmt,...)
+#ifdef __GNUC__
+ __attribute__ ((format (printf, 4, 5)))
+#endif
+;
+
+int r_vlog_nr(int facility,int level,int r,const char *fmt,va_list ap);
+
+int r_log_register(char *tipename,int *facility);
+int r_log_facility(int facility,char **tipename);
+int r_logging(int facility, int level);
+int r_log_init(void);
+
+#define LOG_GENERIC 0
+#define LOG_COMMON 0
+
+typedef int r_dest_vlog(int facility,int level,const char *format,va_list ap);
+int r_log_set_extra_destination(int default_level, r_dest_vlog *dest_vlog);
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/plugin/nr_plugin.h b/dom/media/webrtc/transport/third_party/nrappkit/src/plugin/nr_plugin.h
new file mode 100644
index 0000000000..56da0624a6
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/plugin/nr_plugin.h
@@ -0,0 +1,57 @@
+/**
+ nr_plugin.h
+
+
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ ekr@networkresonance.com Mon Jun 19 18:18:54 2006
+ */
+
+
+#ifndef _nr_plugin_h
+#define _nr_plugin_h
+
+typedef int (NR_plugin_hook)(void);
+
+typedef struct NR_plugin_hook_def_ {
+ char *type;
+ NR_plugin_hook *func;
+} NR_plugin_hook_def;
+
+typedef struct NR_plugin_def_ {
+ int api_version; // Should be 1
+ char *name;
+ char *version;
+ NR_plugin_hook_def *hooks;
+} NR_plugin_def;
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/port/android/include/android_funcs.h b/dom/media/webrtc/transport/third_party/nrappkit/src/port/android/include/android_funcs.h
new file mode 100644
index 0000000000..d48bbe1373
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/port/android/include/android_funcs.h
@@ -0,0 +1,62 @@
+/**
+ linux_funcs.h
+
+
+ Copyright (C) 2004, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ ekr@rtfm.com Mon Dec 13 16:28:22 2004
+ */
+
+
+#ifndef _android_funcs_h
+#define _android_funcs_h
+
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <unistd.h>
+
+#define ETHERTYPE_VLAN 0x8100
+
+#define STDIO_BYTES_BUFFERED(fp) (fp->_IO_read_end - fp->_IO_read_ptr)
+
+size_t strlcat(char *dst, const char *src, size_t siz);
+#ifndef strlcpy
+#define strlcpy(a,b,c) \
+ (strncpy((a),(b),(c)), \
+ ((c)<= 0 ? 0 : ((a)[(c)-1]='\0')), \
+ strlen((b)))
+#endif
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/port/android/include/csi_platform.h b/dom/media/webrtc/transport/third_party/nrappkit/src/port/android/include/csi_platform.h
new file mode 100644
index 0000000000..bcfb2bce77
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/port/android/include/csi_platform.h
@@ -0,0 +1,55 @@
+/**
+ platform.h
+
+
+ Copyright (C) 2004, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ ekr@rtfm.com Mon Dec 13 17:26:51 2004
+ */
+
+
+#ifndef _platform_h
+#define _platform_h
+
+#include <android_funcs.h>
+
+#ifdef NR_SOCKET_IS_VOID_PTR
+typedef void* NR_SOCKET;
+#else
+typedef int NR_SOCKET;
+#define NR_SOCKET_READ(sock,buf,count) read((sock),(buf),(count))
+#define NR_SOCKET_WRITE(sock,buf,count) write((sock),(buf),(count))
+#define NR_SOCKET_CLOSE(sock) close(sock)
+#endif
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/port/android/include/sys/ttycom.h b/dom/media/webrtc/transport/third_party/nrappkit/src/port/android/include/sys/ttycom.h
new file mode 100644
index 0000000000..852bf9103b
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/port/android/include/sys/ttycom.h
@@ -0,0 +1,38 @@
+/*
+ *
+ * Copyright (C) 2006, Network Resonance, 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:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of Network Resonance, Inc. nor the name of any
+ * contributors to this software 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 OWNER 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 __ANDROID_TTYCOM_H
+#define __ANDROID_TTYCOM_H
+
+#include <asm/ioctls.h>
+
+#endif
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/port/android/port-impl.mk b/dom/media/webrtc/transport/third_party/nrappkit/src/port/android/port-impl.mk
new file mode 100644
index 0000000000..6704cfedf9
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/port/android/port-impl.mk
@@ -0,0 +1,31 @@
+#
+# Copyright (C) 2006, Network Resonance, 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:
+#
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. 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.
+# 3. Neither the name of Network Resonance, Inc. nor the name of any
+# contributors to this software 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 OWNER 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.
+#
+#EXTATTR_IMPL=xattr
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/port/darwin/include/csi_platform.h b/dom/media/webrtc/transport/third_party/nrappkit/src/port/darwin/include/csi_platform.h
new file mode 100644
index 0000000000..41f1a5b097
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/port/darwin/include/csi_platform.h
@@ -0,0 +1,57 @@
+/**
+ platform.h
+
+
+ Copyright (C) 2005, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ briank@network-resonance.com Tue Mar 15 14:28:12 PST 2005
+ */
+
+
+#ifndef _platform_h
+#define _platform_h
+
+#include <unistd.h>
+
+#define STDIO_BYTES_BUFFERED(fp) (fp->_r)
+
+#ifdef NR_SOCKET_IS_VOID_PTR
+typedef void* NR_SOCKET;
+#else
+typedef int NR_SOCKET;
+#define NR_SOCKET_READ(sock,buf,count) read((sock),(buf),(count))
+#define NR_SOCKET_WRITE(sock,buf,count) write((sock),(buf),(count))
+#define NR_SOCKET_CLOSE(sock) close(sock)
+#endif
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/port/generic/include/sys/queue.h b/dom/media/webrtc/transport/third_party/nrappkit/src/port/generic/include/sys/queue.h
new file mode 100644
index 0000000000..4d7b998a34
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/port/generic/include/sys/queue.h
@@ -0,0 +1,562 @@
+/*
+ * Copyright (c) 1991, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 4. Neither the name of the University 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 REGENTS 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 REGENTS 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.
+ *
+ * @(#)queue.h 8.5 (Berkeley) 8/20/94
+ * $FreeBSD: src/sys/sys/queue.h,v 1.58 2004/04/07 04:19:49 imp Exp $
+ */
+
+#ifndef _SYS_QUEUE_H_
+#define _SYS_QUEUE_H_
+
+#include <stddef.h>
+
+#ifndef offsetof
+#define offsetof(type, field) ((size_t)(&((type *)0)->field))
+#endif
+
+#define STAILQ_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = STAILQ_FIRST((head)); \
+ (var) && ((tvar) = STAILQ_NEXT((var), field), 1); \
+ (var) = (tvar))
+
+/*
+ * This file defines four types of data structures: singly-linked lists,
+ * singly-linked tail queues, lists and tail queues.
+ *
+ * A singly-linked list is headed by a single forward pointer. The elements
+ * are singly linked for minimum space and pointer manipulation overhead at
+ * the expense of O(n) removal for arbitrary elements. New elements can be
+ * added to the list after an existing element or at the head of the list.
+ * Elements being removed from the head of the list should use the explicit
+ * macro for this purpose for optimum efficiency. A singly-linked list may
+ * only be traversed in the forward direction. Singly-linked lists are ideal
+ * for applications with large datasets and few or no removals or for
+ * implementing a LIFO queue.
+ *
+ * A singly-linked tail queue is headed by a pair of pointers, one to the
+ * head of the list and the other to the tail of the list. The elements are
+ * singly linked for minimum space and pointer manipulation overhead at the
+ * expense of O(n) removal for arbitrary elements. New elements can be added
+ * to the list after an existing element, at the head of the list, or at the
+ * end of the list. Elements being removed from the head of the tail queue
+ * should use the explicit macro for this purpose for optimum efficiency.
+ * A singly-linked tail queue may only be traversed in the forward direction.
+ * Singly-linked tail queues are ideal for applications with large datasets
+ * and few or no removals or for implementing a FIFO queue.
+ *
+ * A list is headed by a single forward pointer (or an array of forward
+ * pointers for a hash table header). The elements are doubly linked
+ * so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before
+ * or after an existing element or at the head of the list. A list
+ * may only be traversed in the forward direction.
+ *
+ * A tail queue is headed by a pair of pointers, one to the head of the
+ * list and the other to the tail of the list. The elements are doubly
+ * linked so that an arbitrary element can be removed without a need to
+ * traverse the list. New elements can be added to the list before or
+ * after an existing element, at the head of the list, or at the end of
+ * the list. A tail queue may be traversed in either direction.
+ *
+ * For details on the use of these macros, see the queue(3) manual page.
+ *
+ *
+ * SLIST LIST STAILQ TAILQ
+ * _HEAD + + + +
+ * _HEAD_INITIALIZER + + + +
+ * _ENTRY + + + +
+ * _INIT + + + +
+ * _EMPTY + + + +
+ * _FIRST + + + +
+ * _NEXT + + + +
+ * _PREV - - - +
+ * _LAST - - + +
+ * _FOREACH + + + +
+ * _FOREACH_SAFE + + + +
+ * _FOREACH_REVERSE - - - +
+ * _FOREACH_REVERSE_SAFE - - - +
+ * _INSERT_HEAD + + + +
+ * _INSERT_BEFORE - + - +
+ * _INSERT_AFTER + + + +
+ * _INSERT_TAIL - - + +
+ * _CONCAT - - + +
+ * _REMOVE_HEAD + - + -
+ * _REMOVE + + + +
+ *
+ */
+#define QUEUE_MACRO_DEBUG 0
+#if QUEUE_MACRO_DEBUG
+/* Store the last 2 places the queue element or head was altered */
+struct qm_trace {
+ char * lastfile;
+ int lastline;
+ char * prevfile;
+ int prevline;
+};
+
+#define TRACEBUF struct qm_trace trace;
+#define TRASHIT(x) do {(x) = (void *)-1;} while (0)
+
+#define QMD_TRACE_HEAD(head) do { \
+ (head)->trace.prevline = (head)->trace.lastline; \
+ (head)->trace.prevfile = (head)->trace.lastfile; \
+ (head)->trace.lastline = __LINE__; \
+ (head)->trace.lastfile = __FILE__; \
+} while (0)
+
+#define QMD_TRACE_ELEM(elem) do { \
+ (elem)->trace.prevline = (elem)->trace.lastline; \
+ (elem)->trace.prevfile = (elem)->trace.lastfile; \
+ (elem)->trace.lastline = __LINE__; \
+ (elem)->trace.lastfile = __FILE__; \
+} while (0)
+
+#else
+#define QMD_TRACE_ELEM(elem)
+#define QMD_TRACE_HEAD(head)
+#define TRACEBUF
+#define TRASHIT(x)
+#endif /* QUEUE_MACRO_DEBUG */
+
+/*
+ * Singly-linked List declarations.
+ */
+#define SLIST_HEAD(name, type) \
+struct name { \
+ struct type *slh_first; /* first element */ \
+}
+
+#define SLIST_HEAD_INITIALIZER(head) \
+ { NULL }
+
+#define SLIST_ENTRY(type) \
+struct { \
+ struct type *sle_next; /* next element */ \
+}
+
+/*
+ * Singly-linked List functions.
+ */
+#define SLIST_EMPTY(head) ((head)->slh_first == NULL)
+
+#define SLIST_FIRST(head) ((head)->slh_first)
+
+#define SLIST_FOREACH(var, head, field) \
+ for ((var) = SLIST_FIRST((head)); \
+ (var); \
+ (var) = SLIST_NEXT((var), field))
+
+#define SLIST_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = SLIST_FIRST((head)); \
+ (var) && ((tvar) = SLIST_NEXT((var), field), 1); \
+ (var) = (tvar))
+
+#define SLIST_FOREACH_PREVPTR(var, varp, head, field) \
+ for ((varp) = &SLIST_FIRST((head)); \
+ ((var) = *(varp)) != NULL; \
+ (varp) = &SLIST_NEXT((var), field))
+
+#define SLIST_INIT(head) do { \
+ SLIST_FIRST((head)) = NULL; \
+} while (0)
+
+#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \
+ SLIST_NEXT((elm), field) = SLIST_NEXT((slistelm), field); \
+ SLIST_NEXT((slistelm), field) = (elm); \
+} while (0)
+
+#define SLIST_INSERT_HEAD(head, elm, field) do { \
+ SLIST_NEXT((elm), field) = SLIST_FIRST((head)); \
+ SLIST_FIRST((head)) = (elm); \
+} while (0)
+
+#define SLIST_NEXT(elm, field) ((elm)->field.sle_next)
+
+#define SLIST_REMOVE(head, elm, type, field) do { \
+ if (SLIST_FIRST((head)) == (elm)) { \
+ SLIST_REMOVE_HEAD((head), field); \
+ } \
+ else { \
+ struct type *curelm = SLIST_FIRST((head)); \
+ while (SLIST_NEXT(curelm, field) != (elm)) \
+ curelm = SLIST_NEXT(curelm, field); \
+ SLIST_NEXT(curelm, field) = \
+ SLIST_NEXT(SLIST_NEXT(curelm, field), field); \
+ } \
+} while (0)
+
+#define SLIST_REMOVE_HEAD(head, field) do { \
+ SLIST_FIRST((head)) = SLIST_NEXT(SLIST_FIRST((head)), field); \
+} while (0)
+
+/*
+ * Singly-linked Tail queue declarations.
+ */
+#define STAILQ_HEAD(name, type) \
+struct name { \
+ struct type *stqh_first;/* first element */ \
+ struct type **stqh_last;/* addr of last next element */ \
+}
+
+#define STAILQ_HEAD_INITIALIZER(head) \
+ { NULL, &(head).stqh_first }
+
+#define STAILQ_ENTRY(type) \
+struct { \
+ struct type *stqe_next; /* next element */ \
+}
+
+/*
+ * Singly-linked Tail queue functions.
+ */
+#define STAILQ_CONCAT(head1, head2) do { \
+ if (!STAILQ_EMPTY((head2))) { \
+ *(head1)->stqh_last = (head2)->stqh_first; \
+ (head1)->stqh_last = (head2)->stqh_last; \
+ STAILQ_INIT((head2)); \
+ } \
+} while (0)
+
+#define STAILQ_EMPTY(head) ((head)->stqh_first == NULL)
+
+#define STAILQ_FIRST(head) ((head)->stqh_first)
+
+#define STAILQ_FOREACH(var, head, field) \
+ for((var) = STAILQ_FIRST((head)); \
+ (var); \
+ (var) = STAILQ_NEXT((var), field))
+
+
+#define STAILQ_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = STAILQ_FIRST((head)); \
+ (var) && ((tvar) = STAILQ_NEXT((var), field), 1); \
+ (var) = (tvar))
+
+#define STAILQ_INIT(head) do { \
+ STAILQ_FIRST((head)) = NULL; \
+ (head)->stqh_last = &STAILQ_FIRST((head)); \
+} while (0)
+
+#define STAILQ_INSERT_AFTER(head, tqelm, elm, field) do { \
+ if ((STAILQ_NEXT((elm), field) = STAILQ_NEXT((tqelm), field)) == NULL)\
+ (head)->stqh_last = &STAILQ_NEXT((elm), field); \
+ STAILQ_NEXT((tqelm), field) = (elm); \
+} while (0)
+
+#define STAILQ_INSERT_HEAD(head, elm, field) do { \
+ if ((STAILQ_NEXT((elm), field) = STAILQ_FIRST((head))) == NULL) \
+ (head)->stqh_last = &STAILQ_NEXT((elm), field); \
+ STAILQ_FIRST((head)) = (elm); \
+} while (0)
+
+#define STAILQ_INSERT_TAIL(head, elm, field) do { \
+ STAILQ_NEXT((elm), field) = NULL; \
+ *(head)->stqh_last = (elm); \
+ (head)->stqh_last = &STAILQ_NEXT((elm), field); \
+} while (0)
+
+#define STAILQ_LAST(head, type, field) \
+ (STAILQ_EMPTY((head)) ? \
+ NULL : \
+ ((struct type *) \
+ ((char *)((head)->stqh_last) - offsetof(struct type, field))))
+
+#define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next)
+
+#define STAILQ_REMOVE(head, elm, type, field) do { \
+ if (STAILQ_FIRST((head)) == (elm)) { \
+ STAILQ_REMOVE_HEAD((head), field); \
+ } \
+ else { \
+ struct type *curelm = STAILQ_FIRST((head)); \
+ while (STAILQ_NEXT(curelm, field) != (elm)) \
+ curelm = STAILQ_NEXT(curelm, field); \
+ if ((STAILQ_NEXT(curelm, field) = \
+ STAILQ_NEXT(STAILQ_NEXT(curelm, field), field)) == NULL)\
+ (head)->stqh_last = &STAILQ_NEXT((curelm), field);\
+ } \
+} while (0)
+
+#define STAILQ_REMOVE_HEAD(head, field) do { \
+ if ((STAILQ_FIRST((head)) = \
+ STAILQ_NEXT(STAILQ_FIRST((head)), field)) == NULL) \
+ (head)->stqh_last = &STAILQ_FIRST((head)); \
+} while (0)
+
+#define STAILQ_REMOVE_HEAD_UNTIL(head, elm, field) do { \
+ if ((STAILQ_FIRST((head)) = STAILQ_NEXT((elm), field)) == NULL) \
+ (head)->stqh_last = &STAILQ_FIRST((head)); \
+} while (0)
+
+/*
+ * List declarations.
+ */
+#define LIST_HEAD(name, type) \
+struct name { \
+ struct type *lh_first; /* first element */ \
+}
+
+#define LIST_HEAD_INITIALIZER(head) \
+ { NULL }
+
+#define LIST_ENTRY(type) \
+struct { \
+ struct type *le_next; /* next element */ \
+ struct type **le_prev; /* address of previous next element */ \
+}
+
+/*
+ * List functions.
+ */
+
+#define LIST_EMPTY(head) ((head)->lh_first == NULL)
+
+#define LIST_FIRST(head) ((head)->lh_first)
+
+#define LIST_FOREACH(var, head, field) \
+ for ((var) = LIST_FIRST((head)); \
+ (var); \
+ (var) = LIST_NEXT((var), field))
+
+#define LIST_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = LIST_FIRST((head)); \
+ (var) && ((tvar) = LIST_NEXT((var), field), 1); \
+ (var) = (tvar))
+
+#define LIST_INIT(head) do { \
+ LIST_FIRST((head)) = NULL; \
+} while (0)
+
+#define LIST_INSERT_AFTER(listelm, elm, field) do { \
+ if ((LIST_NEXT((elm), field) = LIST_NEXT((listelm), field)) != NULL)\
+ LIST_NEXT((listelm), field)->field.le_prev = \
+ &LIST_NEXT((elm), field); \
+ LIST_NEXT((listelm), field) = (elm); \
+ (elm)->field.le_prev = &LIST_NEXT((listelm), field); \
+} while (0)
+
+#define LIST_INSERT_BEFORE(listelm, elm, field) do { \
+ (elm)->field.le_prev = (listelm)->field.le_prev; \
+ LIST_NEXT((elm), field) = (listelm); \
+ *(listelm)->field.le_prev = (elm); \
+ (listelm)->field.le_prev = &LIST_NEXT((elm), field); \
+} while (0)
+
+#define LIST_INSERT_HEAD(head, elm, field) do { \
+ if ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL) \
+ LIST_FIRST((head))->field.le_prev = &LIST_NEXT((elm), field);\
+ LIST_FIRST((head)) = (elm); \
+ (elm)->field.le_prev = &LIST_FIRST((head)); \
+} while (0)
+
+#define LIST_NEXT(elm, field) ((elm)->field.le_next)
+
+#define LIST_REMOVE(elm, field) do { \
+ if (LIST_NEXT((elm), field) != NULL) \
+ LIST_NEXT((elm), field)->field.le_prev = \
+ (elm)->field.le_prev; \
+ *(elm)->field.le_prev = LIST_NEXT((elm), field); \
+} while (0)
+
+/*
+ * Tail queue declarations.
+ */
+#define TAILQ_HEAD(name, type) \
+struct name { \
+ struct type *tqh_first; /* first element */ \
+ struct type **tqh_last; /* addr of last next element */ \
+ TRACEBUF \
+}
+
+#define TAILQ_HEAD_INITIALIZER(head) \
+ { NULL, &(head).tqh_first }
+
+#define TAILQ_ENTRY(type) \
+struct { \
+ struct type *tqe_next; /* next element */ \
+ struct type **tqe_prev; /* address of previous next element */ \
+ TRACEBUF \
+}
+
+/*
+ * Tail queue functions.
+ */
+#define TAILQ_CONCAT(head1, head2, field) do { \
+ if (!TAILQ_EMPTY(head2)) { \
+ *(head1)->tqh_last = (head2)->tqh_first; \
+ (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \
+ (head1)->tqh_last = (head2)->tqh_last; \
+ TAILQ_INIT((head2)); \
+ QMD_TRACE_HEAD(head); \
+ QMD_TRACE_HEAD(head2); \
+ } \
+} while (0)
+
+#define TAILQ_EMPTY(head) ((head)->tqh_first == NULL)
+
+#define TAILQ_FIRST(head) ((head)->tqh_first)
+
+#define TAILQ_FOREACH(var, head, field) \
+ for ((var) = TAILQ_FIRST((head)); \
+ (var); \
+ (var) = TAILQ_NEXT((var), field))
+
+#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \
+ for ((var) = TAILQ_FIRST((head)); \
+ (var) && ((tvar) = TAILQ_NEXT((var), field), 1); \
+ (var) = (tvar))
+
+#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \
+ for ((var) = TAILQ_LAST((head), headname); \
+ (var); \
+ (var) = TAILQ_PREV((var), headname, field))
+
+#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \
+ for ((var) = TAILQ_LAST((head), headname); \
+ (var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \
+ (var) = (tvar))
+
+#define TAILQ_INIT(head) do { \
+ TAILQ_FIRST((head)) = NULL; \
+ (head)->tqh_last = &TAILQ_FIRST((head)); \
+ QMD_TRACE_HEAD(head); \
+} while (0)
+
+#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \
+ if ((TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) != NULL)\
+ TAILQ_NEXT((elm), field)->field.tqe_prev = \
+ &TAILQ_NEXT((elm), field); \
+ else { \
+ (head)->tqh_last = &TAILQ_NEXT((elm), field); \
+ QMD_TRACE_HEAD(head); \
+ } \
+ TAILQ_NEXT((listelm), field) = (elm); \
+ (elm)->field.tqe_prev = &TAILQ_NEXT((listelm), field); \
+ QMD_TRACE_ELEM(&(elm)->field); \
+ QMD_TRACE_ELEM(&listelm->field); \
+} while (0)
+
+#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \
+ (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \
+ TAILQ_NEXT((elm), field) = (listelm); \
+ *(listelm)->field.tqe_prev = (elm); \
+ (listelm)->field.tqe_prev = &TAILQ_NEXT((elm), field); \
+ QMD_TRACE_ELEM(&(elm)->field); \
+ QMD_TRACE_ELEM(&listelm->field); \
+} while (0)
+
+#define TAILQ_INSERT_HEAD(head, elm, field) do { \
+ if ((TAILQ_NEXT((elm), field) = TAILQ_FIRST((head))) != NULL) \
+ TAILQ_FIRST((head))->field.tqe_prev = \
+ &TAILQ_NEXT((elm), field); \
+ else \
+ (head)->tqh_last = &TAILQ_NEXT((elm), field); \
+ TAILQ_FIRST((head)) = (elm); \
+ (elm)->field.tqe_prev = &TAILQ_FIRST((head)); \
+ QMD_TRACE_HEAD(head); \
+ QMD_TRACE_ELEM(&(elm)->field); \
+} while (0)
+
+#define TAILQ_INSERT_TAIL(head, elm, field) do { \
+ TAILQ_NEXT((elm), field) = NULL; \
+ (elm)->field.tqe_prev = (head)->tqh_last; \
+ *(head)->tqh_last = (elm); \
+ (head)->tqh_last = &TAILQ_NEXT((elm), field); \
+ QMD_TRACE_HEAD(head); \
+ QMD_TRACE_ELEM(&(elm)->field); \
+} while (0)
+
+#define TAILQ_LAST(head, headname) \
+ (*(((struct headname *)((head)->tqh_last))->tqh_last))
+
+#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
+
+#define TAILQ_PREV(elm, headname, field) \
+ (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
+
+#define TAILQ_REMOVE(head, elm, field) do { \
+ if ((TAILQ_NEXT((elm), field)) != NULL) \
+ TAILQ_NEXT((elm), field)->field.tqe_prev = \
+ (elm)->field.tqe_prev; \
+ else { \
+ (head)->tqh_last = (elm)->field.tqe_prev; \
+ QMD_TRACE_HEAD(head); \
+ } \
+ *(elm)->field.tqe_prev = TAILQ_NEXT((elm), field); \
+ TRASHIT((elm)->field.tqe_next); \
+ TRASHIT((elm)->field.tqe_prev); \
+ QMD_TRACE_ELEM(&(elm)->field); \
+} while (0)
+
+
+#ifdef _KERNEL
+
+/*
+ * XXX insque() and remque() are an old way of handling certain queues.
+ * They bogusly assumes that all queue heads look alike.
+ */
+
+struct quehead {
+ struct quehead *qh_link;
+ struct quehead *qh_rlink;
+};
+
+#if defined(__GNUC__) || defined(__INTEL_COMPILER)
+
+static __inline void
+insque(void *a, void *b)
+{
+ struct quehead *element = (struct quehead *)a,
+ *head = (struct quehead *)b;
+
+ element->qh_link = head->qh_link;
+ element->qh_rlink = head;
+ head->qh_link = element;
+ element->qh_link->qh_rlink = element;
+}
+
+static __inline void
+remque(void *a)
+{
+ struct quehead *element = (struct quehead *)a;
+
+ element->qh_link->qh_rlink = element->qh_rlink;
+ element->qh_rlink->qh_link = element->qh_link;
+ element->qh_rlink = 0;
+}
+
+#else /* !(__GNUC__ || __INTEL_COMPILER) */
+
+void insque(void *a, void *b);
+void remque(void *a);
+
+#endif /* __GNUC__ || __INTEL_COMPILER */
+
+#endif /* _KERNEL */
+
+#endif /* !_SYS_QUEUE_H_ */
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/port/linux/include/csi_platform.h b/dom/media/webrtc/transport/third_party/nrappkit/src/port/linux/include/csi_platform.h
new file mode 100644
index 0000000000..8aefaf9249
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/port/linux/include/csi_platform.h
@@ -0,0 +1,55 @@
+/**
+ platform.h
+
+
+ Copyright (C) 2004, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ ekr@rtfm.com Mon Dec 13 17:26:51 2004
+ */
+
+
+#ifndef _platform_h
+#define _platform_h
+
+#include <linux_funcs.h>
+
+#ifdef NR_SOCKET_IS_VOID_PTR
+typedef void* NR_SOCKET;
+#else
+typedef int NR_SOCKET;
+#define NR_SOCKET_READ(sock,buf,count) read((sock),(buf),(count))
+#define NR_SOCKET_WRITE(sock,buf,count) write((sock),(buf),(count))
+#define NR_SOCKET_CLOSE(sock) close(sock)
+#endif
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/port/linux/include/linux_funcs.h b/dom/media/webrtc/transport/third_party/nrappkit/src/port/linux/include/linux_funcs.h
new file mode 100644
index 0000000000..1619136306
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/port/linux/include/linux_funcs.h
@@ -0,0 +1,62 @@
+/**
+ linux_funcs.h
+
+
+ Copyright (C) 2004, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ ekr@rtfm.com Mon Dec 13 16:28:22 2004
+ */
+
+
+#ifndef _linux_funcs_h
+#define _linux_funcs_h
+
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <unistd.h>
+
+#define ETHERTYPE_VLAN 0x8100
+
+#define STDIO_BYTES_BUFFERED(fp) (fp->_IO_read_end - fp->_IO_read_ptr)
+
+size_t strlcat(char *dst, const char *src, size_t siz);
+#ifndef strlcpy
+#define strlcpy(a,b,c) \
+ (strncpy((a),(b),(c)), \
+ ((c)<= 0 ? 0 : ((a)[(c)-1]='\0')), \
+ strlen((b)))
+#endif
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/port/linux/include/sys/ttycom.h b/dom/media/webrtc/transport/third_party/nrappkit/src/port/linux/include/sys/ttycom.h
new file mode 100644
index 0000000000..6f8a3066d6
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/port/linux/include/sys/ttycom.h
@@ -0,0 +1,38 @@
+/*
+ *
+ * Copyright (C) 2006, Network Resonance, 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:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of Network Resonance, Inc. nor the name of any
+ * contributors to this software 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 OWNER 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 __LINUX_TTYCOM_H
+#define __LINUX_TTYCOM_H
+
+#include <asm/ioctls.h>
+
+#endif
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/port/linux/port-impl.mk b/dom/media/webrtc/transport/third_party/nrappkit/src/port/linux/port-impl.mk
new file mode 100644
index 0000000000..6704cfedf9
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/port/linux/port-impl.mk
@@ -0,0 +1,31 @@
+#
+# Copyright (C) 2006, Network Resonance, 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:
+#
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. 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.
+# 3. Neither the name of Network Resonance, Inc. nor the name of any
+# contributors to this software 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 OWNER 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.
+#
+#EXTATTR_IMPL=xattr
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/port/win32/include/csi_platform.h b/dom/media/webrtc/transport/third_party/nrappkit/src/port/win32/include/csi_platform.h
new file mode 100644
index 0000000000..27c64c53d9
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/port/win32/include/csi_platform.h
@@ -0,0 +1,107 @@
+/**
+ platform.h
+
+
+ Copyright (C) 2005, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ briank@network-resonance.com Tue Mar 15 14:28:12 PST 2005
+ */
+
+
+#ifndef _csi_platform_h
+#define _csi_platform_h
+
+#ifndef _WIN32_WINNT
+#define _WIN32_WINNT 0x0400 // This prevents weird "'TryEnterCriticalSection': identifier not found"
+ // compiler errors when poco/win32_mutex.h is included
+#endif
+
+#define UINT8 UBLAH_IGNORE_ME_PLEASE
+#define INT8 BLAH_IGNORE_ME_PLEASE
+#include <winsock2.h>
+#undef UINT8
+#undef INT8
+#include <r_types.h>
+#include <errno.h>
+
+#define strcasecmp _stricmp
+#define strncasecmp _strnicmp
+
+#define strcasestr stristr
+
+/* Hack version of strlcpy (in util/util.c) */
+size_t strlcat(char *dst, const char *src, size_t siz);
+
+/* Hack version of getopt() (in util/getopt.c) */
+int getopt(int argc, char *argv[], char *opstring);
+extern char *optarg;
+extern int optind;
+extern int opterr;
+
+/* Hack version of gettimeofday() (in util/util.c) */
+int gettimeofday(struct timeval *tv, void *tz);
+
+#ifdef NR_SOCKET_IS_VOID_PTR
+typedef void* NR_SOCKET;
+#else
+typedef SOCKET NR_SOCKET;
+#define NR_SOCKET_READ(sock,buf,count) recv((sock),(buf),(count),0)
+#define NR_SOCKET_WRITE(sock,buf,count) send((sock),(buf),(count),0)
+#define NR_SOCKET_CLOSE(sock) closesocket(sock)
+#endif
+
+#ifndef EHOSTUNREACH
+#define EHOSTUNREACH WSAEHOSTUNREACH
+#endif
+
+#define LOG_EMERG 0
+#define LOG_ALERT 1
+#define LOG_CRIT 2
+#define LOG_ERR 3
+#define LOG_WARNING 4
+#define LOG_NOTICE 5
+#define LOG_INFO 6
+#define LOG_DEBUG 7
+
+// Until we refine the Windows port....
+
+#define in_addr_t UINT4
+
+#ifndef strlcpy
+#define strlcpy(a,b,c) \
+ (strncpy((a),(b),(c)), \
+ ((c)<= 0 ? 0 : ((a)[(c)-1]='\0')), \
+ strlen((b)))
+#endif
+
+#endif /* _csi_platform_h */
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/registry/c2ru.c b/dom/media/webrtc/transport/third_party/nrappkit/src/registry/c2ru.c
new file mode 100644
index 0000000000..3e71ca37d9
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/registry/c2ru.c
@@ -0,0 +1,320 @@
+/*
+ *
+ * c2ru.c
+ *
+ * $Source: /Users/ekr/tmp/nrappkit-dump/nrappkit/src/registry/c2ru.c,v $
+ * $Revision: 1.3 $
+ * $Date: 2007/06/26 22:37:50 $
+ *
+ * c2r utility methods
+ *
+ *
+ * Copyright (C) 2005, Network Resonance, Inc.
+ * Copyright (C) 2006, Network Resonance, 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:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of Network Resonance, Inc. nor the name of any
+ * contributors to this software 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 OWNER 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.
+ *
+ *
+ */
+
+#include <sys/queue.h>
+#include <string.h>
+#include <registry.h>
+#include "nr_common.h"
+#include <r_errors.h>
+#include <r_macros.h>
+#include <ctype.h>
+#include "c2ru.h"
+
+
+#define NRGET(func, type, get) \
+int \
+func(NR_registry parent, char *child, type **out) \
+{ \
+ int r, _status; \
+ NR_registry registry; \
+ type tmp; \
+ \
+ if ((r = nr_c2ru_make_registry(parent, child, registry))) \
+ ABORT(r); \
+ \
+ if ((r = get(registry, &tmp))) { \
+ if (r != R_NOT_FOUND) \
+ ABORT(r); \
+ *out = 0; \
+ } \
+ else { \
+ *out = RCALLOC(sizeof(tmp)); \
+ if (*out == 0) \
+ ABORT(R_NO_MEMORY); \
+ **out = tmp; \
+ } \
+ \
+ _status = 0; \
+abort: \
+ return (_status); \
+}
+
+int
+nr_c2ru_get_char(NR_registry parent, char *child, char **out)
+{
+ int r, _status;
+ NR_registry registry;
+ char tmp;
+
+ if ((r = nr_c2ru_make_registry(parent, child, registry)))
+ ABORT(r);
+
+ if ((r = NR_reg_get_char(registry, &tmp))) {
+ if (r != R_NOT_FOUND)
+ ABORT(r);
+ *out = 0;
+ }
+ else {
+ *out = RCALLOC(sizeof(tmp));
+ if (*out == 0)
+ ABORT(R_NO_MEMORY);
+ **out = tmp;
+ }
+
+ _status = 0;
+abort:
+ return (_status);
+}
+NRGET(nr_c2ru_get_uchar, UCHAR, NR_reg_get_uchar)
+NRGET(nr_c2ru_get_int2, INT2, NR_reg_get_int2)
+NRGET(nr_c2ru_get_uint2, UINT2, NR_reg_get_uint2)
+NRGET(nr_c2ru_get_int4, INT4, NR_reg_get_int4)
+NRGET(nr_c2ru_get_uint4, UINT4, NR_reg_get_uint4)
+NRGET(nr_c2ru_get_int8, INT8, NR_reg_get_int8)
+NRGET(nr_c2ru_get_uint8, UINT8, NR_reg_get_uint8)
+NRGET(nr_c2ru_get_double, double, NR_reg_get_double)
+NRGET(nr_c2ru_get_string, char*, NR_reg_alloc_string)
+NRGET(nr_c2ru_get_data, Data, NR_reg_alloc_data)
+
+
+#define NRSET(func, type, set) \
+int \
+func(NR_registry parent, char *child, type *in) \
+{ \
+ int r, _status; \
+ NR_registry registry; \
+ \
+ if (in == 0) \
+ return 0; \
+ \
+ if ((r = nr_c2ru_make_registry(parent, child, registry))) \
+ ABORT(r); \
+ \
+ if ((r = set(registry, *in))) \
+ ABORT(r); \
+ \
+ _status = 0; \
+abort: \
+ return (_status); \
+}
+
+NRSET(nr_c2ru_set_char, char, NR_reg_set_char)
+NRSET(nr_c2ru_set_uchar, UCHAR, NR_reg_set_uchar)
+NRSET(nr_c2ru_set_int2, INT2, NR_reg_set_int2)
+NRSET(nr_c2ru_set_uint2, UINT2, NR_reg_set_uint2)
+NRSET(nr_c2ru_set_int4, INT4, NR_reg_set_int4)
+NRSET(nr_c2ru_set_uint4, UINT4, NR_reg_set_uint4)
+NRSET(nr_c2ru_set_int8, INT8, NR_reg_set_int8)
+NRSET(nr_c2ru_set_uint8, UINT8, NR_reg_set_uint8)
+NRSET(nr_c2ru_set_double, double, NR_reg_set_double)
+NRSET(nr_c2ru_set_string, char*, NR_reg_set_string)
+
+int
+nr_c2ru_set_data(NR_registry parent, char *child, Data *in)
+{
+ int r, _status;
+ NR_registry registry;
+
+ if (in == 0)
+ return 0;
+
+ if ((r = nr_c2ru_make_registry(parent, child, registry)))
+ ABORT(r);
+
+ if ((r = NR_reg_set_bytes(registry, in->data, in->len)))
+ ABORT(r);
+
+ _status = 0;
+abort:
+ return (_status);
+}
+
+#define NRFREE(func, type) \
+int \
+func(type *in) \
+{ \
+ if (in) \
+ RFREE(in); \
+ return 0; \
+}
+
+NRFREE(nr_c2ru_free_char, char)
+NRFREE(nr_c2ru_free_uchar, UCHAR)
+NRFREE(nr_c2ru_free_int2, INT2)
+NRFREE(nr_c2ru_free_uint2, UINT2)
+NRFREE(nr_c2ru_free_int4, INT4)
+NRFREE(nr_c2ru_free_uint4, UINT4)
+NRFREE(nr_c2ru_free_int8, INT8)
+NRFREE(nr_c2ru_free_uint8, UINT8)
+NRFREE(nr_c2ru_free_double, double)
+
+
+int
+nr_c2ru_free_string(char **in)
+{
+ if (*in)
+ RFREE(*in);
+ if (in)
+ RFREE(in);
+ return 0;
+}
+
+int
+nr_c2ru_free_data(Data *in)
+{
+ int r, _status;
+
+ if (in) {
+ if ((r=r_data_destroy(&in)))
+ ABORT(r);
+ }
+
+ _status = 0;
+abort:
+ return (_status);
+}
+
+int
+nr_c2ru_get_children(NR_registry parent, char *child, void *ptr, size_t size, int (*get)(NR_registry, void*))
+{
+ int r, _status;
+ NR_registry registry;
+ unsigned int count;
+ unsigned int i;
+ NR_registry name;
+ struct entry { TAILQ_ENTRY(entry) entries; } *entry;
+ TAILQ_HEAD(, entry) *tailq = (void*)ptr;
+
+ TAILQ_INIT(tailq);
+
+ if ((r=nr_c2ru_make_registry(parent, child, registry)))
+ ABORT(r);
+
+ if ((r=NR_reg_get_child_count(registry, &count))) {
+ if (r != R_NOT_FOUND)
+ ABORT(r);
+ }
+ else {
+ for (i = 0; i < count; ++i) {
+ if ((r=NR_reg_get_child_registry(registry, i, name))) {
+ /* ignore R_NOT_FOUND errors */
+ if (r == R_NOT_FOUND)
+ continue;
+ else
+ ABORT(r);
+ }
+
+ if ((r=get(name, &entry))) {
+ /* ignore R_NOT_FOUND errors */
+ if (r == R_NOT_FOUND)
+ continue;
+ else
+ ABORT(r);
+ }
+
+ TAILQ_INSERT_TAIL(tailq, entry, entries);
+ }
+ }
+
+ _status = 0;
+abort:
+ return (_status);
+}
+
+int
+nr_c2ru_set_children(NR_registry parent, char *child, void *ptr, int (*set)(NR_registry, void*), int (*label)(NR_registry, void*, char[NR_REG_MAX_NR_REGISTRY_LEN]))
+{
+ int r, _status;
+ NR_registry registry;
+ int i;
+ NR_registry name;
+ char buffer[NR_REG_MAX_NR_REGISTRY_LEN];
+ struct entry { TAILQ_ENTRY(entry) entries; } *entry;
+ TAILQ_HEAD(, entry) *tailq = (void*)ptr;
+
+ if ((r=nr_c2ru_make_registry(parent, child, registry)))
+ ABORT(r);
+
+ (void)NR_reg_del(registry);
+
+ i = 0;
+ TAILQ_FOREACH(entry, tailq, entries) {
+ if (label == 0 || (r=label(registry, entry, buffer))) {
+ snprintf(buffer, sizeof(buffer), "%d", i);
+ }
+ if ((r=nr_c2ru_make_registry(registry, buffer, name)))
+ ABORT(r);
+
+ if ((r=set(name, entry)))
+ ABORT(r);
+
+ ++i;
+ }
+
+ _status = 0;
+abort:
+ return (_status);
+}
+
+int
+nr_c2ru_free_children(void *ptr, int (*free)(void*))
+{
+ struct entry { TAILQ_ENTRY(entry) entries; } *entry;
+ TAILQ_HEAD(, entry) *tailq = (void*)ptr;
+
+ while (! TAILQ_EMPTY(tailq)) {
+ entry = TAILQ_FIRST(tailq);
+ TAILQ_REMOVE(tailq, entry, entries);
+ (void)free(entry);
+ }
+
+ return 0;
+}
+
+/* requires parent already in legal form */
+int
+nr_c2ru_make_registry(NR_registry parent, char *child, NR_registry out)
+{
+ return NR_reg_make_registry(parent, child, out);
+}
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/registry/c2ru.h b/dom/media/webrtc/transport/third_party/nrappkit/src/registry/c2ru.h
new file mode 100644
index 0000000000..0f8c38ecc2
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/registry/c2ru.h
@@ -0,0 +1,96 @@
+/*
+ *
+ * c2ru.h
+ *
+ * $Source: /Users/ekr/tmp/nrappkit-dump/nrappkit/src/registry/c2ru.h,v $
+ * $Revision: 1.2 $
+ * $Date: 2006/08/16 19:39:13 $
+ *
+ * c2r utility methods
+ *
+ *
+ * Copyright (C) 2005, Network Resonance, Inc.
+ * Copyright (C) 2006, Network Resonance, 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:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of Network Resonance, Inc. nor the name of any
+ * contributors to this software 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 OWNER 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 __C2RU_H__
+#define __C2RU_H__
+
+#include <sys/types.h>
+#include <r_types.h>
+#include <r_data.h>
+#include "registry_int.h"
+
+int nr_c2ru_get_char(NR_registry parent, char *child, char **out);
+int nr_c2ru_get_uchar(NR_registry parent, char *child, UCHAR **out);
+int nr_c2ru_get_int2(NR_registry parent, char *child, INT2 **out);
+int nr_c2ru_get_uint2(NR_registry parent, char *child, UINT2 **out);
+int nr_c2ru_get_int4(NR_registry parent, char *child, INT4 **out);
+int nr_c2ru_get_uint4(NR_registry parent, char *child, UINT4 **out);
+int nr_c2ru_get_int8(NR_registry parent, char *child, INT8 **out);
+int nr_c2ru_get_uint8(NR_registry parent, char *child, UINT8 **out);
+int nr_c2ru_get_double(NR_registry parent, char *child, double **out);
+int nr_c2ru_get_data(NR_registry parent, char *child, Data **out);
+int nr_c2ru_get_string(NR_registry parent, char *child, char ***out);
+
+int nr_c2ru_set_char(NR_registry parent, char *child, char *data);
+int nr_c2ru_set_uchar(NR_registry parent, char *child, UCHAR *data);
+int nr_c2ru_set_int2(NR_registry parent, char *child, INT2 *data);
+int nr_c2ru_set_uint2(NR_registry parent, char *child, UINT2 *data);
+int nr_c2ru_set_int4(NR_registry parent, char *child, INT4 *data);
+int nr_c2ru_set_uint4(NR_registry parent, char *child, UINT4 *data);
+int nr_c2ru_set_int8(NR_registry parent, char *child, INT8 *data);
+int nr_c2ru_set_uint8(NR_registry parent, char *child, UINT8 *data);
+int nr_c2ru_set_double(NR_registry parent, char *child, double *data);
+int nr_c2ru_set_registry(NR_registry parent, char *child);
+int nr_c2ru_set_data(NR_registry parent, char *child, Data *data);
+int nr_c2ru_set_string(NR_registry parent, char *child, char **data);
+
+int nr_c2ru_free_char(char *data);
+int nr_c2ru_free_uchar(UCHAR *data);
+int nr_c2ru_free_int2(INT2 *data);
+int nr_c2ru_free_uint2(UINT2 *data);
+int nr_c2ru_free_int4(INT4 *data);
+int nr_c2ru_free_uint4(UINT4 *data);
+int nr_c2ru_free_int8(INT8 *data);
+int nr_c2ru_free_uint8(UINT8 *data);
+int nr_c2ru_free_double(double *data);
+int nr_c2ru_free_data(Data *data);
+int nr_c2ru_free_string(char **data);
+
+int nr_c2ru_get_children(NR_registry parent, char *child, void *ptr, size_t size, int (*get)(NR_registry, void*));
+int nr_c2ru_set_children(NR_registry parent, char *child, void *ptr, int (*set)(NR_registry, void*), int (*label)(NR_registry, void*, char[NR_REG_MAX_NR_REGISTRY_LEN]));
+int nr_c2ru_free_children(void *ptr, int (*free)(void*));
+
+int nr_c2ru_make_registry(NR_registry parent, char *child, NR_registry out);
+
+#endif
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/registry/registry.c b/dom/media/webrtc/transport/third_party/nrappkit/src/registry/registry.c
new file mode 100644
index 0000000000..709b1c3fb7
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/registry/registry.c
@@ -0,0 +1,604 @@
+/*
+ *
+ * registry.c
+ *
+ * $Source: /Users/ekr/tmp/nrappkit-dump/nrappkit/src/registry/registry.c,v $
+ * $Revision: 1.6 $
+ * $Date: 2007/11/21 00:09:12 $
+ *
+ * Datastore for tracking configuration and related info.
+ *
+ *
+ * Copyright (C) 2005, Network Resonance, Inc.
+ * Copyright (C) 2006, Network Resonance, 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:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of Network Resonance, Inc. nor the name of any
+ * contributors to this software 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 OWNER 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.
+ *
+ *
+ */
+
+#include <assert.h>
+#include <string.h>
+#ifndef _MSC_VER
+#include <strings.h>
+#include <sys/param.h>
+#include <netinet/in.h>
+#endif
+#ifdef OPENSSL
+#include <openssl/ssl.h>
+#endif
+#include <ctype.h>
+#include "registry.h"
+#include "registry_int.h"
+#include "registry_vtbl.h"
+#include "r_assoc.h"
+#include "nr_common.h"
+#include "r_log.h"
+#include "r_errors.h"
+#include "r_macros.h"
+#include "c2ru.h"
+
+/* vtbl used to switch hit between local and remote invocations */
+static nr_registry_module *reg_vtbl = 0;
+
+/* must be in the order the types are numbered */
+static char *typenames[] = { "char", "UCHAR", "INT2", "UINT2", "INT4", "UINT4", "INT8", "UINT8", "double", "Data", "string", "registry" };
+
+int NR_LOG_REGISTRY=0;
+
+NR_registry NR_TOP_LEVEL_REGISTRY = "";
+
+int
+NR_reg_init(void *mode)
+{
+ int r, _status;
+ nr_registry_module *module = (nr_registry_module*)mode;
+#ifdef SANITY_CHECKS
+ NR_registry registry;
+#endif
+
+ if (reg_vtbl) {
+ if (reg_vtbl != module) {
+ r_log(LOG_GENERIC,LOG_ERR,"Can't reinitialize registry in different mode");
+ ABORT(R_INTERNAL);
+ }
+
+ return(0);
+ }
+
+ reg_vtbl = module;
+
+ if ((r=reg_vtbl->vtbl->init(mode)))
+ ABORT(r);
+
+#ifdef SANITY_CHECKS
+ if ((r=NR_reg_get_registry(NR_TOP_LEVEL_REGISTRY, registry)))
+ ABORT(r);
+ assert(strcmp(registry, NR_TOP_LEVEL_REGISTRY) == 0);
+#endif
+
+ r_log_init();
+ r_log_register("registry",&NR_LOG_REGISTRY);
+
+ _status=0;
+ abort:
+ r_log(NR_LOG_REGISTRY,
+ (_status ? LOG_ERR : LOG_INFO),
+ (_status ? "Couldn't initialize registry" : "Initialized registry"));
+ return(_status);
+}
+
+int
+NR_reg_initted(void)
+{
+ return reg_vtbl!=0;
+}
+
+#define NRREGGET(func, method, type) \
+int \
+func(NR_registry name, type *out) \
+{ \
+ return reg_vtbl->vtbl->method(name, out); \
+}
+
+NRREGGET(NR_reg_get_char, get_char, char)
+NRREGGET(NR_reg_get_uchar, get_uchar, UCHAR)
+NRREGGET(NR_reg_get_int2, get_int2, INT2)
+NRREGGET(NR_reg_get_uint2, get_uint2, UINT2)
+NRREGGET(NR_reg_get_int4, get_int4, INT4)
+NRREGGET(NR_reg_get_uint4, get_uint4, UINT4)
+NRREGGET(NR_reg_get_int8, get_int8, INT8)
+NRREGGET(NR_reg_get_uint8, get_uint8, UINT8)
+NRREGGET(NR_reg_get_double, get_double, double)
+
+int
+NR_reg_get_registry(NR_registry name, NR_registry out)
+{
+ return reg_vtbl->vtbl->get_registry(name, out);
+}
+
+int
+NR_reg_get_bytes(NR_registry name, UCHAR *out, size_t size, size_t *length)
+{
+ return reg_vtbl->vtbl->get_bytes(name, out, size, length);
+}
+
+int
+NR_reg_get_string(NR_registry name, char *out, size_t size)
+{
+ return reg_vtbl->vtbl->get_string(name, out, size);
+}
+
+int
+NR_reg_get_length(NR_registry name, size_t *length)
+{
+ return reg_vtbl->vtbl->get_length(name, length);
+}
+
+int
+NR_reg_get_type(NR_registry name, NR_registry_type type)
+{
+ return reg_vtbl->vtbl->get_type(name, type);
+}
+
+#define NRREGSET(func, method, type) \
+int \
+func(NR_registry name, type data) \
+{ \
+ return reg_vtbl->vtbl->method(name, data); \
+}
+
+NRREGSET(NR_reg_set_char, set_char, char)
+NRREGSET(NR_reg_set_uchar, set_uchar, UCHAR)
+NRREGSET(NR_reg_set_int2, set_int2, INT2)
+NRREGSET(NR_reg_set_uint2, set_uint2, UINT2)
+NRREGSET(NR_reg_set_int4, set_int4, INT4)
+NRREGSET(NR_reg_set_uint4, set_uint4, UINT4)
+NRREGSET(NR_reg_set_int8, set_int8, INT8)
+NRREGSET(NR_reg_set_uint8, set_uint8, UINT8)
+NRREGSET(NR_reg_set_double, set_double, double)
+NRREGSET(NR_reg_set_string, set_string, char*)
+
+int
+NR_reg_set_registry(NR_registry name)
+{
+ return reg_vtbl->vtbl->set_registry(name);
+}
+
+int
+NR_reg_set_bytes(NR_registry name, unsigned char *data, size_t length)
+{
+ return reg_vtbl->vtbl->set_bytes(name, data, length);
+}
+
+
+int
+NR_reg_del(NR_registry name)
+{
+ return reg_vtbl->vtbl->del(name);
+}
+
+int
+NR_reg_fin(NR_registry name)
+{
+ return reg_vtbl->vtbl->fin(name);
+}
+
+int
+NR_reg_get_child_count(NR_registry parent, unsigned int *count)
+{
+ assert(sizeof(count) == sizeof(size_t));
+ return reg_vtbl->vtbl->get_child_count(parent, (size_t*)count);
+}
+
+int
+NR_reg_get_child_registry(NR_registry parent, unsigned int i, NR_registry child)
+{
+ int r, _status;
+ size_t count;
+ NR_registry *children=0;
+
+ if ((r=reg_vtbl->vtbl->get_child_count(parent, &count)))
+ ABORT(r);
+
+ if (i >= count)
+ ABORT(R_NOT_FOUND);
+ else {
+ count++;
+ children = (NR_registry *)RCALLOC(count * sizeof(NR_registry));
+ if (!children)
+ ABORT(R_NO_MEMORY);
+
+ if ((r=reg_vtbl->vtbl->get_children(parent, children, count, &count)))
+ ABORT(r);
+
+ if (i >= count)
+ ABORT(R_NOT_FOUND);
+
+ strncpy(child, children[i], sizeof(NR_registry));
+ }
+
+ _status=0;
+ abort:
+ RFREE(children);
+ return(_status);
+}
+
+int
+NR_reg_get_children(NR_registry parent, NR_registry *children, size_t size, size_t *length)
+{
+ return reg_vtbl->vtbl->get_children(parent, children, size, length);
+}
+
+int
+NR_reg_dump()
+{
+ int r, _status;
+
+ if ((r=reg_vtbl->vtbl->dump(0)))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ return(_status);
+}
+
+// convenience methods, call RFREE on the returned data
+int
+NR_reg_alloc_data(NR_registry name, Data *data)
+{
+ int r, _status;
+ size_t length;
+ UCHAR *tmp = 0;
+ size_t sanity_check;
+
+ if ((r=NR_reg_get_length(name, &length)))
+ ABORT(r);
+
+ if (!(tmp = (void*)RMALLOC(length)))
+ ABORT(R_NO_MEMORY);
+
+ if ((r=NR_reg_get_bytes(name, tmp, length, &sanity_check)))
+ ABORT(r);
+
+ assert(length == sanity_check);
+
+ data->len = length;
+ data->data = tmp;
+
+ _status=0;
+ abort:
+ if (_status) {
+ if (tmp) RFREE(tmp);
+ }
+ return(_status);
+}
+
+int
+NR_reg_alloc_string(NR_registry name, char **data)
+{
+ int r, _status;
+ size_t length;
+ char *tmp = 0;
+
+ if ((r=NR_reg_get_length(name, &length)))
+ ABORT(r);
+
+ if (!(tmp = (void*)RMALLOC(length+1)))
+ ABORT(R_NO_MEMORY);
+
+ if ((r=NR_reg_get_string(name, tmp, length+1)))
+ ABORT(r);
+
+ assert(length == strlen(tmp));
+
+ *data = tmp;
+
+ _status=0;
+ abort:
+ if (_status) {
+ if (tmp) RFREE(tmp);
+ }
+ return(_status);
+}
+
+
+char *
+nr_reg_type_name(int type)
+{
+ if ((type < NR_REG_TYPE_CHAR) || (type > NR_REG_TYPE_REGISTRY))
+ return(NULL);
+
+ return(typenames[type]);
+}
+
+int
+nr_reg_compute_type(char *typename, int *type)
+{
+ int _status;
+ size_t i;
+
+#ifdef SANITY_CHECKS
+ assert(!strcasecmp(typenames[NR_REG_TYPE_CHAR], "char"));
+ assert(!strcasecmp(typenames[NR_REG_TYPE_UCHAR], "UCHAR"));
+ assert(!strcasecmp(typenames[NR_REG_TYPE_INT2], "INT2"));
+ assert(!strcasecmp(typenames[NR_REG_TYPE_UINT2], "UINT2"));
+ assert(!strcasecmp(typenames[NR_REG_TYPE_INT4], "INT4"));
+ assert(!strcasecmp(typenames[NR_REG_TYPE_UINT4], "UINT4"));
+ assert(!strcasecmp(typenames[NR_REG_TYPE_INT8], "INT8"));
+ assert(!strcasecmp(typenames[NR_REG_TYPE_UINT8], "UINT8"));
+ assert(!strcasecmp(typenames[NR_REG_TYPE_DOUBLE], "double"));
+ assert(!strcasecmp(typenames[NR_REG_TYPE_BYTES], "Data"));
+ assert(!strcasecmp(typenames[NR_REG_TYPE_STRING], "string"));
+ assert(!strcasecmp(typenames[NR_REG_TYPE_REGISTRY], "registry"));
+ assert(sizeof(typenames)/sizeof(*typenames) == (NR_REG_TYPE_REGISTRY+1));
+#endif
+
+ for (i = 0; i < sizeof(typenames)/sizeof(*typenames); ++i) {
+ if (!strcasecmp(typenames[i], typename)) {
+ *type = i;
+ return 0;
+ }
+ }
+ ABORT(R_BAD_ARGS);
+
+ _status=0;
+ abort:
+ return(_status);
+}
+
+/* More convenience functions: the same as their parents but they
+ take a prefix and a suffix */
+#define NRGET2(func, type, get) \
+int \
+func(NR_registry parent, char *child, type *out) \
+{ \
+ int r, _status; \
+ NR_registry registry; \
+ \
+ if ((r = NR_reg_make_registry(parent, child, registry))) \
+ ABORT(r); \
+ \
+ if ((r = get(registry, out))) { \
+ ABORT(r); \
+ } \
+ \
+ _status = 0; \
+abort: \
+ return (_status); \
+}
+
+NRGET2(NR_reg_get2_char, char, NR_reg_get_char)
+NRGET2(NR_reg_get2_uchar, UCHAR, NR_reg_get_uchar)
+NRGET2(NR_reg_get2_int2, INT2, NR_reg_get_int2)
+NRGET2(NR_reg_get2_uint2, UINT2, NR_reg_get_uint2)
+NRGET2(NR_reg_get2_int4, INT4, NR_reg_get_int4)
+NRGET2(NR_reg_get2_uint4, UINT4, NR_reg_get_uint4)
+NRGET2(NR_reg_get2_int8, INT8, NR_reg_get_int8)
+NRGET2(NR_reg_get2_uint8, UINT8, NR_reg_get_uint8)
+NRGET2(NR_reg_get2_double, double, NR_reg_get_double)
+NRGET2(NR_reg_alloc2_string, char*, NR_reg_alloc_string)
+NRGET2(NR_reg_alloc2_data, Data, NR_reg_alloc_data)
+
+int
+NR_reg_get2_bytes(NR_registry parent, char *child, UCHAR *out, size_t size, size_t *length)
+{
+ int r, _status;
+ NR_registry registry;
+
+ if ((r=NR_reg_make_registry(parent, child, registry)))
+ ABORT(r);
+
+ if ((r=NR_reg_get_bytes(registry, out, size, length)))
+ ABORT(r);
+
+ _status = 0;
+abort:
+ return (_status);
+}
+
+int
+NR_reg_get2_string(NR_registry parent, char *child, char *out, size_t size)
+{
+ int r, _status;
+ NR_registry registry;
+
+ if ((r=NR_reg_make_registry(parent, child, registry)))
+ ABORT(r);
+
+ if ((r=NR_reg_get_string(registry, out, size)))
+ ABORT(r);
+
+ _status = 0;
+abort:
+ return (_status);
+}
+
+/* More convenience functions: the same as their parents but they
+ take a prefix and a suffix */
+#define NRSET2(func, type, set) \
+int \
+func(NR_registry parent, char *child, type in) \
+{ \
+ int r, _status; \
+ NR_registry registry; \
+ \
+ if ((r = NR_reg_make_registry(parent, child, registry))) \
+ ABORT(r); \
+ \
+ if ((r = set(registry, in))) { \
+ ABORT(r); \
+ } \
+ \
+ _status = 0; \
+abort: \
+ return (_status); \
+}
+
+NRSET2(NR_reg_set2_char, char, NR_reg_set_char)
+NRSET2(NR_reg_set2_uchar, UCHAR, NR_reg_set_uchar)
+NRSET2(NR_reg_set2_int2, INT2, NR_reg_set_int2)
+NRSET2(NR_reg_set2_uint2, UINT2, NR_reg_set_uint2)
+NRSET2(NR_reg_set2_int4, INT4, NR_reg_set_int4)
+NRSET2(NR_reg_set2_uint4, UINT4, NR_reg_set_uint4)
+NRSET2(NR_reg_set2_int8, INT8, NR_reg_set_int8)
+NRSET2(NR_reg_set2_uint8, UINT8, NR_reg_set_uint8)
+NRSET2(NR_reg_set2_double, double, NR_reg_set_double)
+NRSET2(NR_reg_set2_string, char*, NR_reg_set_string)
+
+int
+NR_reg_set2_bytes(NR_registry prefix, char *name, UCHAR *data, size_t length)
+{
+ int r, _status;
+ NR_registry registry;
+
+ if ((r = NR_reg_make_registry(prefix, name, registry)))
+ ABORT(r);
+
+ if ((r = NR_reg_set_bytes(registry, data, length)))
+ ABORT(r);
+
+ _status = 0;
+abort:
+ return (_status);
+}
+
+
+int
+NR_reg_make_child_registry(NR_registry parent, NR_registry descendant, unsigned int generation, NR_registry child)
+{
+ int _status;
+ size_t length;
+
+ length = strlen(parent);
+
+ if (strncasecmp(parent, descendant, length))
+ ABORT(R_BAD_ARGS);
+
+ while (descendant[length] != '\0') {
+ if (descendant[length] == '.') {
+ if (generation == 0)
+ break;
+
+ --generation;
+ }
+
+ ++length;
+ if (length >= sizeof(NR_registry))
+ ABORT(R_BAD_ARGS);
+ }
+
+ strncpy(child, descendant, length);
+ child[length] = '\0';
+
+ _status=0;
+ abort:
+ return(_status);
+}
+
+int
+NR_reg_get2_child_count(NR_registry base, NR_registry name, unsigned int *count)
+ {
+ int r, _status;
+ NR_registry registry;
+
+ if ((r=nr_c2ru_make_registry(base, name, registry)))
+ ABORT(r);
+
+ if (r=NR_reg_get_child_count(registry,count))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int
+NR_reg_get2_child_registry(NR_registry base, NR_registry name, unsigned int i, NR_registry child)
+ {
+ int r, _status;
+ NR_registry registry;
+
+ if ((r=nr_c2ru_make_registry(base, name, registry)))
+ ABORT(r);
+
+ if (r=NR_reg_get_child_registry(registry, i, child))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+
+/* requires parent already in legal form */
+int
+NR_reg_make_registry(NR_registry parent, char *child, NR_registry out)
+{
+ int r, _status;
+ size_t plen;
+ size_t clen;
+ char *c;
+ size_t i;
+
+ if ((r=nr_reg_is_valid(parent)))
+ ABORT(r);
+
+ if (*child == '.')
+ ABORT(R_BAD_ARGS);
+
+ clen = strlen(child);
+ if (!clen)
+ ABORT(R_BAD_ARGS);
+ plen = strlen(parent);
+ if ((plen + clen + 2) > sizeof(NR_registry))
+ ABORT(R_BAD_ARGS);
+
+ if (out != parent)
+ strcpy(out, parent);
+
+ c = &(out[plen]);
+
+ if (parent[0] != '\0') {
+ *c = '.';
+ ++c;
+ }
+
+ for (i = 0; i < clen; ++i, ++c) {
+ *c = child[i];
+ if (isspace(*c) || *c == '.' || *c == '/' || ! isprint(*c))
+ *c = '_';
+ }
+
+ *c = '\0';
+
+ _status = 0;
+abort:
+ return _status;
+}
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/registry/registry.h b/dom/media/webrtc/transport/third_party/nrappkit/src/registry/registry.h
new file mode 100644
index 0000000000..b48893ba72
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/registry/registry.h
@@ -0,0 +1,154 @@
+/*
+ *
+ * registry.h
+ *
+ * $Source: /Users/ekr/tmp/nrappkit-dump/nrappkit/src/registry/registry.h,v $
+ * $Revision: 1.3 $
+ * $Date: 2007/07/17 17:58:16 $
+ *
+ * Datastore for tracking configuration and related info.
+ *
+ *
+ * Copyright (C) 2005, Network Resonance, Inc.
+ * Copyright (C) 2006, Network Resonance, 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:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of Network Resonance, Inc. nor the name of any
+ * contributors to this software 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 OWNER 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 __REGISTRY_H__
+#define __REGISTRY_H__
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <r_types.h>
+#include <r_data.h>
+
+#define NR_REG_MAX_NR_REGISTRY_LEN 128
+#define NR_REG_MAX_TYPE_LEN 32
+
+typedef char NR_registry[NR_REG_MAX_NR_REGISTRY_LEN];
+typedef char NR_registry_type[NR_REG_MAX_TYPE_LEN];
+
+extern NR_registry NR_TOP_LEVEL_REGISTRY;
+
+extern void *NR_REG_MODE_LOCAL;
+extern void *NR_REG_MODE_REMOTE;
+
+int NR_reg_init(void *mode);
+
+int NR_reg_initted(void);
+
+int NR_reg_get_char(NR_registry name, char *out);
+int NR_reg_get_uchar(NR_registry name, UCHAR *out);
+int NR_reg_get_int2(NR_registry name, INT2 *out);
+int NR_reg_get_uint2(NR_registry name, UINT2 *out);
+int NR_reg_get_int4(NR_registry name, INT4 *out);
+int NR_reg_get_uint4(NR_registry name, UINT4 *out);
+int NR_reg_get_int8(NR_registry name, INT8 *out);
+int NR_reg_get_uint8(NR_registry name, UINT8 *out);
+int NR_reg_get_double(NR_registry name, double *out);
+int NR_reg_get_registry(NR_registry name, NR_registry out);
+
+int NR_reg_get_bytes(NR_registry name, UCHAR *out, size_t size, size_t *length);
+int NR_reg_get_string(NR_registry name, char *out, size_t size);
+int NR_reg_get_length(NR_registry name, size_t *length);
+int NR_reg_get_type(NR_registry name, NR_registry_type type);
+
+
+int NR_reg_get2_char(NR_registry prefix, char *name, char *);
+int NR_reg_get2_uchar(NR_registry prefix, char *name, UCHAR *);
+int NR_reg_get2_int2(NR_registry prefix, char *name, INT2 *);
+int NR_reg_get2_uint2(NR_registry prefix, char *name, UINT2 *);
+int NR_reg_get2_int4(NR_registry prefix, char *name, INT4 *);
+int NR_reg_get2_uint4(NR_registry prefix, char *name, UINT4 *);
+int NR_reg_get2_int8(NR_registry prefix, char *name, INT8 *);
+int NR_reg_get2_uint8(NR_registry prefix, char *name, UINT8 *);
+int NR_reg_get2_double(NR_registry prefix, char *name, double *);
+int NR_reg_get2_bytes(NR_registry prefix, char *name, UCHAR *out, size_t size, size_t *length);
+int NR_reg_get2_string(NR_registry prefix, char *name, char *out, size_t size);
+
+int NR_reg_alloc2_string(NR_registry prefix, char *name, char **);
+int NR_reg_alloc2_data(NR_registry prefix, char *name, Data *);
+
+int NR_reg_set_char(NR_registry name, char data);
+int NR_reg_set_uchar(NR_registry name, UCHAR data);
+int NR_reg_set_int2(NR_registry name, INT2 data);
+int NR_reg_set_uint2(NR_registry name, UINT2 data);
+int NR_reg_set_int4(NR_registry name, INT4 data);
+int NR_reg_set_uint4(NR_registry name, UINT4 data);
+int NR_reg_set_int8(NR_registry name, INT8 data);
+int NR_reg_set_uint8(NR_registry name, UINT8 data);
+int NR_reg_set_double(NR_registry name, double data);
+
+int NR_reg_set_registry(NR_registry name);
+
+int NR_reg_set_bytes(NR_registry name, UCHAR *data, size_t length);
+int NR_reg_set_string(NR_registry name, char *data);
+
+int NR_reg_set2_char(NR_registry prefix, char *name, char data);
+int NR_reg_set2_uchar(NR_registry prefix, char *name, UCHAR data);
+int NR_reg_set2_int2(NR_registry prefix, char *name, INT2 data);
+int NR_reg_set2_uint2(NR_registry prefix, char *name, UINT2 data);
+int NR_reg_set2_int4(NR_registry prefix, char *name, INT4 data);
+int NR_reg_set2_uint4(NR_registry prefix, char *name, UINT4 data);
+int NR_reg_set2_int8(NR_registry prefix, char *name, INT8 data);
+int NR_reg_set2_uint8(NR_registry prefix, char *name, UINT8 data);
+int NR_reg_set2_double(NR_registry prefix, char *name, double data);
+
+int NR_reg_set2_bytes(NR_registry prefix, char *name, UCHAR *data, size_t length);
+int NR_reg_set2_string(NR_registry prefix, char *name, char *data);
+
+int NR_reg_del(NR_registry name);
+
+int NR_reg_fin(NR_registry name);
+
+int NR_reg_get_child_count(NR_registry parent, unsigned int *count);
+int NR_reg_get_child_registry(NR_registry parent, unsigned int i, NR_registry child);
+int NR_reg_get2_child_count(NR_registry base, NR_registry name, unsigned int *count);
+int NR_reg_get2_child_registry(NR_registry base, NR_registry name, unsigned int i, NR_registry child);
+int NR_reg_get_children(NR_registry parent, NR_registry children[], size_t size, size_t *length);
+
+int NR_reg_dump(void);
+
+/* convenience methods, call RFREE on the returned data */
+int NR_reg_alloc_data(NR_registry name, Data *data);
+int NR_reg_alloc_string(NR_registry name, char **data);
+
+#define NR_REG_CB_ACTION_ADD (1<<0)
+#define NR_REG_CB_ACTION_CHANGE (1<<1)
+#define NR_REG_CB_ACTION_DELETE (1<<2)
+#define NR_REG_CB_ACTION_FINAL (1<<6)
+int NR_reg_register_callback(NR_registry name, char action, void (*cb)(void *cb_arg, char action, NR_registry name), void *cb_arg);
+int NR_reg_unregister_callback(NR_registry name, char action, void (*cb)(void *cb_arg, char action, NR_registry name));
+
+int NR_reg_make_registry(NR_registry parent, char *child, NR_registry out);
+int NR_reg_make_child_registry(NR_registry parent, NR_registry descendant, unsigned int generation, NR_registry child);
+
+#endif
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/registry/registry_int.h b/dom/media/webrtc/transport/third_party/nrappkit/src/registry/registry_int.h
new file mode 100644
index 0000000000..d6d412c543
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/registry/registry_int.h
@@ -0,0 +1,97 @@
+#if 0
+#define NR_LOG_REGISTRY BLAHBLAH()
+#define LOG_REGISTRY BLAHBLAH()
+static int BLAHBLAH() {
+int blahblah;
+r_log_register("registry",&blahblah);
+return blahblah;
+}
+#endif
+
+/*
+ *
+ * registry_int.h
+ *
+ * $Source: /Users/ekr/tmp/nrappkit-dump/nrappkit/src/registry/registry_int.h,v $
+ * $Revision: 1.3 $
+ * $Date: 2007/06/26 22:37:51 $
+ *
+ * Callback-related functions
+ *
+ *
+ * Copyright (C) 2005, Network Resonance, Inc.
+ * Copyright (C) 2006, Network Resonance, 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:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of Network Resonance, Inc. nor the name of any
+ * contributors to this software 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 OWNER 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 __REGISTRY_INT_H__
+#define __REGISTRY_INT_H__
+
+#include <sys/types.h>
+#include <r_types.h>
+#ifndef NO_REG_RPC
+#include <rpc/rpc.h>
+#endif
+
+extern int NR_LOG_REGISTRY;
+
+int nr_reg_is_valid(NR_registry name);
+
+#define NR_REG_TYPE_CHAR 0
+#define NR_REG_TYPE_UCHAR 1
+#define NR_REG_TYPE_INT2 2
+#define NR_REG_TYPE_UINT2 3
+#define NR_REG_TYPE_INT4 4
+#define NR_REG_TYPE_UINT4 5
+#define NR_REG_TYPE_INT8 6
+#define NR_REG_TYPE_UINT8 7
+#define NR_REG_TYPE_DOUBLE 8
+#define NR_REG_TYPE_BYTES 9
+#define NR_REG_TYPE_STRING 10
+#define NR_REG_TYPE_REGISTRY 11
+char *nr_reg_type_name(int type);
+int nr_reg_compute_type(char *type_name, int *type);
+
+char *nr_reg_action_name(int action);
+
+int nr_reg_cb_init(void);
+int nr_reg_client_cb_init(void);
+int nr_reg_register_for_callbacks(int fd, int connect_to_port);
+int nr_reg_raise_event(NR_registry name, int action);
+#ifndef NO_REG_RPC
+int nr_reg_get_client(CLIENT **client);
+#endif
+
+#define CALLBACK_SERVER_ADDR "127.0.0.1"
+#define CALLBACK_SERVER_PORT 8082
+#define CALLBACK_SERVER_BACKLOG 32
+
+#endif
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/registry/registry_local.c b/dom/media/webrtc/transport/third_party/nrappkit/src/registry/registry_local.c
new file mode 100644
index 0000000000..ed6e19aaa0
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/registry/registry_local.c
@@ -0,0 +1,1168 @@
+/*
+ *
+ * registry.c
+ *
+ * $Source: /Users/ekr/tmp/nrappkit-dump/nrappkit/src/registry/registry_local.c,v $
+ * $Revision: 1.4 $
+ * $Date: 2007/11/21 00:09:13 $
+ *
+ * Datastore for tracking configuration and related info.
+ *
+ *
+ * Copyright (C) 2005, Network Resonance, Inc.
+ * Copyright (C) 2006, Network Resonance, 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:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of Network Resonance, Inc. nor the name of any
+ * contributors to this software 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 OWNER 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.
+ *
+ *
+ */
+
+#include <assert.h>
+#include <string.h>
+#ifndef WIN32
+#include <strings.h>
+#include <sys/param.h>
+#include <netinet/in.h>
+#endif
+#ifdef OPENSSL
+#include <openssl/ssl.h>
+#endif
+#include <ctype.h>
+#include "registry.h"
+#include "registry_int.h"
+#include "registry_vtbl.h"
+#include "r_assoc.h"
+#include "nr_common.h"
+#include "r_log.h"
+#include "r_errors.h"
+#include "r_macros.h"
+
+/* if C were an object-oriented language, nr_scalar_registry_node and
+ * nr_array_registry_node would subclass nr_registry_node, but it isn't
+ * object-oriented language, so this is used in cases where the pointer
+ * could be of either type */
+typedef struct nr_registry_node_ {
+ unsigned char type;
+} nr_registry_node;
+
+typedef struct nr_scalar_registry_node_ {
+ unsigned char type;
+ union {
+ char _char;
+ UCHAR _uchar;
+ INT2 _nr_int2;
+ UINT2 _nr_uint2;
+ INT4 _nr_int4;
+ UINT4 _nr_uint4;
+ INT8 _nr_int8;
+ UINT8 _nr_uint8;
+ double _double;
+ } scalar;
+} nr_scalar_registry_node;
+
+/* string, bytes */
+typedef struct nr_array_registry_node_ {
+ unsigned char type;
+ struct {
+ unsigned int length;
+ unsigned char data[1];
+ } array;
+} nr_array_registry_node;
+
+static int nr_reg_local_init(nr_registry_module *me);
+static int nr_reg_local_get_char(NR_registry name, char *data);
+static int nr_reg_local_get_uchar(NR_registry name, UCHAR *data);
+static int nr_reg_local_get_int2(NR_registry name, INT2 *data);
+static int nr_reg_local_get_uint2(NR_registry name, UINT2 *data);
+static int nr_reg_local_get_int4(NR_registry name, INT4 *data);
+static int nr_reg_local_get_uint4(NR_registry name, UINT4 *data);
+static int nr_reg_local_get_int8(NR_registry name, INT8 *data);
+static int nr_reg_local_get_uint8(NR_registry name, UINT8 *data);
+static int nr_reg_local_get_double(NR_registry name, double *data);
+static int nr_reg_local_get_registry(NR_registry name, NR_registry data);
+static int nr_reg_local_get_bytes(NR_registry name, UCHAR *data, size_t size, size_t *length);
+static int nr_reg_local_get_string(NR_registry name, char *data, size_t size);
+static int nr_reg_local_get_length(NR_registry name, size_t *len);
+static int nr_reg_local_get_type(NR_registry name, NR_registry_type type);
+static int nr_reg_local_set_char(NR_registry name, char data);
+static int nr_reg_local_set_uchar(NR_registry name, UCHAR data);
+static int nr_reg_local_set_int2(NR_registry name, INT2 data);
+static int nr_reg_local_set_uint2(NR_registry name, UINT2 data);
+static int nr_reg_local_set_int4(NR_registry name, INT4 data);
+static int nr_reg_local_set_uint4(NR_registry name, UINT4 data);
+static int nr_reg_local_set_int8(NR_registry name, INT8 data);
+static int nr_reg_local_set_uint8(NR_registry name, UINT8 data);
+static int nr_reg_local_set_double(NR_registry name, double data);
+static int nr_reg_local_set_registry(NR_registry name);
+static int nr_reg_local_set_bytes(NR_registry name, UCHAR *data, size_t length);
+static int nr_reg_local_set_string(NR_registry name, char *data);
+static int nr_reg_local_del(NR_registry name);
+static int nr_reg_local_get_child_count(NR_registry parent, size_t *count);
+static int nr_reg_local_get_children(NR_registry parent, NR_registry *data, size_t size, size_t *length);
+static int nr_reg_local_fin(NR_registry name);
+static int nr_reg_local_dump(int sorted);
+static int nr_reg_insert_node(char *name, void *node);
+static int nr_reg_change_node(char *name, void *node, void *old);
+static int nr_reg_get(char *name, int type, void *out);
+static int nr_reg_get_data(NR_registry name, nr_scalar_registry_node *node, void *out);
+static int nr_reg_get_array(char *name, unsigned char type, UCHAR *out, size_t size, size_t *length);
+static int nr_reg_set(char *name, int type, void *data);
+static int nr_reg_set_array(char *name, unsigned char type, UCHAR *data, size_t length);
+static int nr_reg_set_parent_registries(char *name);
+
+/* make these static OLD_REGISTRY */
+#if 0
+static int nr_reg_fetch_node(char *name, unsigned char type, nr_registry_node **node, int *free_node);
+static char *nr_reg_alloc_node_data(char *name, nr_registry_node *node, int *freeit);
+#else
+int nr_reg_fetch_node(char *name, unsigned char type, nr_registry_node **node, int *free_node);
+char *nr_reg_alloc_node_data(char *name, nr_registry_node *node, int *freeit);
+#endif
+static int nr_reg_rfree(void *ptr);
+#if 0 /* Unused currently */
+static int nr_reg_noop(void *ptr);
+#endif
+static int nr_reg_compute_length(char *name, nr_registry_node *node, size_t *length);
+char *nr_reg_action_name(int action);
+
+/* the registry, containing mappings like "foo.bar.baz" to registry
+ * nodes, which are either of type nr_scalar_registry_node or
+ * nr_array_registry_node */
+static r_assoc *nr_registry = 0;
+
+#if 0 /* Unused currently */
+static nr_array_registry_node nr_top_level_node;
+#endif
+
+typedef struct nr_reg_find_children_arg_ {
+ size_t size;
+ NR_registry *children;
+ size_t length;
+} nr_reg_find_children_arg;
+
+static int nr_reg_local_iter(NR_registry prefix, int (*action)(void *ptr, r_assoc_iterator *iter, char *prefix, char *name, nr_registry_node *node), void *ptr);
+static int nr_reg_local_iter_delete(void *ptr, r_assoc_iterator *iter, char *prefix, char *name, nr_registry_node *node);
+static int nr_reg_local_find_children(void *ptr, r_assoc_iterator *iter, char *prefix, char *name, nr_registry_node *node);
+static int nr_reg_local_count_children(void *ptr, r_assoc_iterator *iter, char *prefix, char *name, nr_registry_node *node);
+static int nr_reg_local_dump_print(void *ptr, r_assoc_iterator *iter, char *prefix, char *name, nr_registry_node *node);
+
+
+
+int
+nr_reg_local_iter(NR_registry prefix, int (*action)(void *ptr, r_assoc_iterator *iter, char *prefix, char *name, nr_registry_node *node), void *ptr)
+{
+ int r, _status;
+ r_assoc_iterator iter;
+ char *name;
+ int namel;
+ nr_registry_node *node;
+ int prefixl;
+
+ if (prefix == 0)
+ ABORT(R_INTERNAL);
+
+ if ((r=r_assoc_init_iter(nr_registry, &iter)))
+ ABORT(r);
+
+ prefixl = strlen(prefix);
+
+ for (;;) {
+ if ((r=r_assoc_iter(&iter, (void*)&name, &namel, (void*)&node))) {
+ if (r == R_EOD)
+ break;
+ else
+ ABORT(r);
+ }
+
+ /* subtract to remove the '\0' character from the string length */
+ --namel;
+
+ /* sanity check that the name is null-terminated */
+ assert(namel >= 0);
+ assert(name[namel] == '\0');
+
+ if (namel < 0 || name[namel] != '\0' || node == 0)
+ break;
+
+ /* 3 cases where action will be called:
+ * 1) prefix == ""
+ * 2) prefix == name
+ * 3) name == prefix + '.'
+ */
+ if (prefixl == 0
+ || ((namel == prefixl || (namel > prefixl && name[prefixl] == '.'))
+ && !strncmp(prefix, name, prefixl))) {
+ if ((r=action(ptr, &iter, prefix, name, node)))
+ ABORT(r);
+ }
+ }
+
+ _status=0;
+ abort:
+
+ return(_status);
+}
+
+int
+nr_reg_local_iter_delete(void *ptr, r_assoc_iterator *iter, char *prefix, char *name, nr_registry_node *node)
+{
+ int r, _status;
+
+ if ((r=r_assoc_iter_delete(iter)))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ return(_status);
+}
+
+int
+nr_reg_local_find_children(void *ptr, r_assoc_iterator *iter, char *prefix, char *name, nr_registry_node *node)
+{
+ int _status;
+ int prefixl = strlen(prefix);
+ char *dot;
+ nr_reg_find_children_arg *arg = (void*)ptr;
+
+ assert(sizeof(*(arg->children)) == sizeof(NR_registry));
+
+ /* only grovel through immediate children */
+ if (prefixl == 0 || name[prefixl] == '.') {
+ if (name[prefixl] != '\0') {
+ dot = strchr(&name[prefixl+1], '.');
+ if (dot == 0) {
+ strncpy(arg->children[arg->length], name, sizeof(NR_registry)-1);
+ ++arg->length;
+
+ /* only grab as many as there are room for */
+ if (arg->length >= arg->size)
+ ABORT(R_INTERRUPTED);
+ }
+ }
+ }
+
+ _status = 0;
+ abort:
+ return _status;
+}
+
+int
+nr_reg_local_count_children(void *ptr, r_assoc_iterator *iter, char *prefix, char *name, nr_registry_node *node)
+{
+ int prefixl = strlen(prefix);
+ char *dot;
+
+ /* only count children */
+ if (name[prefixl] == '.') {
+ dot = strchr(&name[prefixl+1], '.');
+ if (dot == 0)
+ ++(*(unsigned int *)ptr);
+ }
+ else if (name[0] != '\0') {
+ if (prefixl == 0)
+ ++(*(unsigned int *)ptr);
+ }
+
+ return 0;
+}
+
+int
+nr_reg_local_dump_print(void *ptr, r_assoc_iterator *iter, char *prefix, char *name, nr_registry_node *node)
+{
+ int _status;
+ int freeit = 0;
+ char *data;
+
+ /* only print leaf nodes */
+ if (node->type != NR_REG_TYPE_REGISTRY) {
+ data = nr_reg_alloc_node_data(name, node, &freeit);
+ if (ptr)
+ fprintf((FILE*)ptr, "%s: %s\n", name, data);
+ else
+ r_log(NR_LOG_REGISTRY, LOG_INFO, "%s: %s", name, data);
+ if (freeit)
+ RFREE(data);
+ }
+
+ _status=0;
+ //abort:
+ return(_status);
+}
+
+
+#if 0 /* Unused currently */
+int
+nr_reg_noop(void *ptr)
+{
+ return 0;
+}
+#endif
+
+int
+nr_reg_rfree(void *ptr)
+{
+ RFREE(ptr);
+ return 0;
+}
+
+int
+nr_reg_fetch_node(char *name, unsigned char type, nr_registry_node **node, int *free_node)
+{
+ int r, _status;
+
+ *node = 0;
+ *free_node = 0;
+
+ if ((r=nr_reg_is_valid(name)))
+ ABORT(r);
+
+ if ((r=r_assoc_fetch(nr_registry, name, strlen(name)+1, (void*)node)))
+ ABORT(r);
+
+ if ((*node)->type != type)
+ ABORT(R_FAILED);
+
+ _status=0;
+ abort:
+ if (_status) {
+ if (*node)
+ r_log(NR_LOG_REGISTRY, LOG_DEBUG, "Couldn't fetch node '%s' ('%s'), found '%s' instead",
+ name, nr_reg_type_name(type), nr_reg_type_name((*node)->type));
+ else
+ r_log(NR_LOG_REGISTRY, LOG_DEBUG, "Couldn't fetch node '%s' ('%s')",
+ name, nr_reg_type_name(type));
+ }
+ else {
+ r_log(NR_LOG_REGISTRY, LOG_DEBUG, "Fetched node '%s' ('%s')",
+ name, nr_reg_type_name(type));
+ }
+ return(_status);
+}
+
+int
+nr_reg_insert_node(char *name, void *node)
+{
+ int r, _status;
+
+ if ((r=nr_reg_is_valid(name)))
+ ABORT(r);
+
+ /* since the registry application is not multi-threaded, a node being
+ * inserted should always be a new node because the registry app must
+ * have looked for a node with this key but not found it, so it is
+ * being created/inserted now using R_ASSOC_NEW */
+ if ((r=r_assoc_insert(nr_registry, name, strlen(name)+1, node, 0, nr_reg_rfree, R_ASSOC_NEW)))
+ ABORT(r);
+
+ if ((r=nr_reg_set_parent_registries(name)))
+ ABORT(r);
+
+ if ((r=nr_reg_raise_event(name, NR_REG_CB_ACTION_ADD)))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ if (r_logging(NR_LOG_REGISTRY, LOG_INFO)) {
+ int freeit;
+ char *data = nr_reg_alloc_node_data(name, (void*)node, &freeit);
+ r_log(NR_LOG_REGISTRY, LOG_INFO,
+ "insert '%s' (%s) %s: %s", name,
+ nr_reg_type_name(((nr_registry_node*)node)->type),
+ (_status ? "FAILED" : "succeeded"), data);
+ if (freeit)
+ RFREE(data);
+ }
+ return(_status);
+}
+
+int
+nr_reg_change_node(char *name, void *node, void *old)
+{
+ int r, _status;
+
+ if ((r=nr_reg_is_valid(name)))
+ ABORT(r);
+
+ if (old != node) {
+ if ((r=r_assoc_insert(nr_registry, name, strlen(name)+1, node, 0, nr_reg_rfree, R_ASSOC_REPLACE)))
+ ABORT(r);
+ }
+
+ if ((r=nr_reg_raise_event(name, NR_REG_CB_ACTION_CHANGE)))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ if (r_logging(NR_LOG_REGISTRY, LOG_INFO)) {
+ int freeit;
+ char *data = nr_reg_alloc_node_data(name, (void*)node, &freeit);
+ r_log(NR_LOG_REGISTRY, LOG_INFO,
+ "change '%s' (%s) %s: %s", name,
+ nr_reg_type_name(((nr_registry_node*)node)->type),
+ (_status ? "FAILED" : "succeeded"), data);
+ if (freeit)
+ RFREE(data);
+ }
+ return(_status);
+}
+
+char *
+nr_reg_alloc_node_data(char *name, nr_registry_node *node, int *freeit)
+{
+ char *s = 0;
+ int len;
+ int alloc = 0;
+ unsigned int i;
+
+ *freeit = 0;
+
+ switch (node->type) {
+ default:
+ alloc = 100; /* plenty of room for any of the scalar types */
+ break;
+ case NR_REG_TYPE_REGISTRY:
+ alloc = strlen(name) + 1;
+ break;
+ case NR_REG_TYPE_BYTES:
+ alloc = (2 * ((nr_array_registry_node*)node)->array.length) + 1;
+ break;
+ case NR_REG_TYPE_STRING:
+ alloc = 0;
+ break;
+ }
+
+ if (alloc > 0) {
+ s = (void*)RMALLOC(alloc);
+ if (!s)
+ return "";
+
+ *freeit = 1;
+ }
+
+ len = alloc;
+
+ switch (node->type) {
+ case NR_REG_TYPE_CHAR:
+ i = ((nr_scalar_registry_node*)node)->scalar._char;
+ if (isprint(i) && ! isspace(i))
+ snprintf(s, len, "%c", (char)i);
+ else
+ snprintf(s, len, "\\%03o", (char)i);
+ break;
+ case NR_REG_TYPE_UCHAR:
+ snprintf(s, len, "0x%02x", ((nr_scalar_registry_node*)node)->scalar._uchar);
+ break;
+ case NR_REG_TYPE_INT2:
+ snprintf(s, len, "%d", ((nr_scalar_registry_node*)node)->scalar._nr_int2);
+ break;
+ case NR_REG_TYPE_UINT2:
+ snprintf(s, len, "%u", ((nr_scalar_registry_node*)node)->scalar._nr_uint2);
+ break;
+ case NR_REG_TYPE_INT4:
+ snprintf(s, len, "%d", ((nr_scalar_registry_node*)node)->scalar._nr_int4);
+ break;
+ case NR_REG_TYPE_UINT4:
+ snprintf(s, len, "%u", ((nr_scalar_registry_node*)node)->scalar._nr_uint4);
+ break;
+ case NR_REG_TYPE_INT8:
+ snprintf(s, len, "%lld", ((nr_scalar_registry_node*)node)->scalar._nr_int8);
+ break;
+ case NR_REG_TYPE_UINT8:
+ snprintf(s, len, "%llu", ((nr_scalar_registry_node*)node)->scalar._nr_uint8);
+ break;
+ case NR_REG_TYPE_DOUBLE:
+ snprintf(s, len, "%#f", ((nr_scalar_registry_node*)node)->scalar._double);
+ break;
+ case NR_REG_TYPE_REGISTRY:
+ snprintf(s, len, "%s", name);
+ break;
+ case NR_REG_TYPE_BYTES:
+ for (i = 0; i < ((nr_array_registry_node*)node)->array.length; ++i) {
+ sprintf(&s[2*i], "%02x", ((nr_array_registry_node*)node)->array.data[i]);
+ }
+ break;
+ case NR_REG_TYPE_STRING:
+ s = (char*)((nr_array_registry_node*)node)->array.data;
+ break;
+ default:
+ assert(0); /* bad value */
+ *freeit = 0;
+ s = "";
+ break;
+ }
+
+ return s;
+}
+
+int
+nr_reg_get(char *name, int type, void *out)
+{
+ int r, _status;
+ nr_scalar_registry_node *node = 0;
+ int free_node = 0;
+
+ if ((r=nr_reg_fetch_node(name, type, (void*)&node, &free_node)))
+ ABORT(r);
+
+ if ((r=nr_reg_get_data(name, node, out)))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ if (free_node) RFREE(node);
+ return(_status);
+}
+
+int
+nr_reg_get_data(NR_registry name, nr_scalar_registry_node *node, void *out)
+{
+ int _status;
+
+ switch (node->type) {
+ case NR_REG_TYPE_CHAR:
+ *(char*)out = node->scalar._char;
+ break;
+ case NR_REG_TYPE_UCHAR:
+ *(UCHAR*)out = node->scalar._uchar;
+ break;
+ case NR_REG_TYPE_INT2:
+ *(INT2*)out = node->scalar._nr_int2;
+ break;
+ case NR_REG_TYPE_UINT2:
+ *(UINT2*)out = node->scalar._nr_uint2;
+ break;
+ case NR_REG_TYPE_INT4:
+ *(INT4*)out = node->scalar._nr_int4;
+ break;
+ case NR_REG_TYPE_UINT4:
+ *(UINT4*)out = node->scalar._nr_uint4;
+ break;
+ case NR_REG_TYPE_INT8:
+ *(INT8*)out = node->scalar._nr_int8;
+ break;
+ case NR_REG_TYPE_UINT8:
+ *(UINT8*)out = node->scalar._nr_uint8;
+ break;
+ case NR_REG_TYPE_DOUBLE:
+ *(double*)out = node->scalar._double;
+ break;
+ default:
+ ABORT(R_INTERNAL);
+ break;
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+}
+
+int
+nr_reg_get_array(char *name, unsigned char type, unsigned char *out, size_t size, size_t *length)
+{
+ int r, _status;
+ nr_array_registry_node *node = 0;
+ int free_node = 0;
+
+ if ((r=nr_reg_fetch_node(name, type, (void*)&node, &free_node)))
+ ABORT(r);
+
+ if (size < node->array.length)
+ ABORT(R_BAD_ARGS);
+
+ if (out != 0)
+ memcpy(out, node->array.data, node->array.length);
+ if (length != 0)
+ *length = node->array.length;
+
+ _status=0;
+ abort:
+ if (node && free_node) RFREE(node);
+ return(_status);
+}
+
+int
+nr_reg_set(char *name, int type, void *data)
+{
+ int r, _status;
+ nr_scalar_registry_node *node = 0;
+ int create_node = 0;
+ int changed = 0;
+ int free_node = 0;
+
+ if ((r=nr_reg_fetch_node(name, type, (void*)&node, &free_node)))
+ if (r == R_NOT_FOUND) {
+ create_node = 1;
+ free_node = 1;
+ }
+ else
+ ABORT(r);
+
+ if (create_node) {
+ if (!(node=(void*)RCALLOC(sizeof(nr_scalar_registry_node))))
+ ABORT(R_NO_MEMORY);
+
+ node->type = type;
+ }
+ else {
+ if (node->type != type)
+ ABORT(R_BAD_ARGS);
+ }
+
+ switch (type) {
+#define CASE(TYPE, _name, type) \
+ case TYPE: \
+ if (node->scalar._name != *(type*)data) { \
+ node->scalar._name = *(type*)data; \
+ if (! create_node) \
+ changed = 1; \
+ } \
+ break;
+ CASE(NR_REG_TYPE_CHAR, _char, char)
+ CASE(NR_REG_TYPE_UCHAR, _uchar, UCHAR)
+ CASE(NR_REG_TYPE_INT2, _nr_int2, INT2)
+ CASE(NR_REG_TYPE_UINT2, _nr_uint2, UINT2)
+ CASE(NR_REG_TYPE_INT4, _nr_int4, INT4)
+ CASE(NR_REG_TYPE_UINT4, _nr_uint4, UINT4)
+ CASE(NR_REG_TYPE_INT8, _nr_int8, INT8)
+ CASE(NR_REG_TYPE_UINT8, _nr_uint8, UINT8)
+ CASE(NR_REG_TYPE_DOUBLE, _double, double)
+#undef CASE
+
+ case NR_REG_TYPE_REGISTRY:
+ /* do nothing */
+ break;
+
+ default:
+ ABORT(R_INTERNAL);
+ break;
+ }
+
+ if (create_node) {
+ if ((r=nr_reg_insert_node(name, node)))
+ ABORT(r);
+ free_node = 0;
+ }
+ else {
+ if (changed) {
+ if ((r=nr_reg_change_node(name, node, node)))
+ ABORT(r);
+ free_node = 0;
+ }
+ }
+
+ _status=0;
+ abort:
+ if (_status) {
+ if (node && free_node) RFREE(node);
+ }
+ return(_status);
+}
+
+int
+nr_reg_set_array(char *name, unsigned char type, UCHAR *data, size_t length)
+{
+ int r, _status;
+ nr_array_registry_node *old = 0;
+ nr_array_registry_node *node = 0;
+ int free_node = 0;
+ int added = 0;
+ int changed = 0;
+
+ if ((r=nr_reg_fetch_node(name, type, (void*)&old, &free_node))) {
+ if (r != R_NOT_FOUND)
+ ABORT(r);
+ }
+ else {
+ assert(free_node == 0);
+ }
+
+ if (old) {
+ if (old->type != type)
+ ABORT(R_BAD_ARGS);
+
+ if (old->array.length != length
+ || memcmp(old->array.data, data, length)) {
+ changed = 1;
+
+ if (old->array.length < length) {
+ if (!(node=(void*)RCALLOC(sizeof(nr_array_registry_node)+length)))
+ ABORT(R_NO_MEMORY);
+ }
+ else {
+ node = old;
+ }
+ }
+ }
+ else {
+ if (!(node=(void*)RCALLOC(sizeof(nr_array_registry_node)+length)))
+ ABORT(R_NO_MEMORY);
+
+ added = 1;
+ }
+
+ if (added || changed) {
+ node->type = type;
+ node->array.length = length;
+ memcpy(node->array.data, data, length);
+ }
+
+ if (added) {
+ if ((r=nr_reg_insert_node(name, node)))
+ ABORT(r);
+ }
+ else if (changed) {
+ if ((r=nr_reg_change_node(name, node, old)))
+ ABORT(r);
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+}
+
+int
+nr_reg_set_parent_registries(char *name)
+{
+ int r, _status;
+ char *parent = 0;
+ char *dot;
+
+ if ((parent = r_strdup(name)) == 0)
+ ABORT(R_NO_MEMORY);
+
+ if ((dot = strrchr(parent, '.')) != 0) {
+ *dot = '\0';
+ if ((r=NR_reg_set_registry(parent)))
+ ABORT(r);
+ }
+
+ _status=0;
+ abort:
+ if (parent) RFREE(parent);
+ return(_status);
+}
+
+
+
+
+
+/* NON-STATIC METHODS */
+
+int
+nr_reg_is_valid(NR_registry name)
+{
+ int _status;
+ unsigned int length;
+ unsigned int i;
+
+ if (name == 0)
+ ABORT(R_BAD_ARGS);
+
+ /* make sure the key is null-terminated */
+ if (memchr(name, '\0', sizeof(NR_registry)) == 0)
+ ABORT(R_BAD_ARGS);
+
+ length = strlen(name);
+
+ /* cannot begin or end with a period */
+ if (name[0] == '.')
+ ABORT(R_BAD_ARGS);
+ if (strlen(name) > 0 && name[length-1] == '.')
+ ABORT(R_BAD_ARGS);
+
+ /* all characters cannot be space, and must be printable and not / */
+ for (i = 0; i < length; ++i) {
+ if (isspace(name[i]) || !isprint(name[i]) || name[i] == '/')
+ ABORT(R_BAD_ARGS);
+ }
+
+ _status=0;
+ abort:
+ if (_status) {
+ r_log(NR_LOG_REGISTRY, LOG_DEBUG, "invalid name '%s'", name);
+ }
+ return(_status);
+}
+
+
+int
+nr_reg_compute_length(char *name, nr_registry_node *in, size_t *length)
+{
+ int _status;
+ nr_array_registry_node *node = (nr_array_registry_node*)in;
+
+ switch (node->type) {
+ case NR_REG_TYPE_STRING:
+ *length = node->array.length - 1;
+ break;
+ case NR_REG_TYPE_BYTES:
+ *length = node->array.length;
+ break;
+ case NR_REG_TYPE_CHAR:
+ *length = sizeof(char);
+ break;
+ case NR_REG_TYPE_UCHAR:
+ *length = sizeof(UCHAR);
+ break;
+ case NR_REG_TYPE_INT2:
+ case NR_REG_TYPE_UINT2:
+ *length = 2;
+ break;
+ case NR_REG_TYPE_INT4:
+ case NR_REG_TYPE_UINT4:
+ *length = 4;
+ break;
+ case NR_REG_TYPE_INT8:
+ case NR_REG_TYPE_UINT8:
+ *length = 8;
+ break;
+ case NR_REG_TYPE_DOUBLE:
+ *length = sizeof(double);
+ break;
+ case NR_REG_TYPE_REGISTRY:
+ *length = strlen(name);
+ break;
+ default:
+ ABORT(R_INTERNAL);
+ break;
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+}
+
+
+/* VTBL METHODS */
+
+int
+nr_reg_local_init(nr_registry_module *me)
+{
+ int r, _status;
+
+ if (nr_registry == 0) {
+ if ((r=r_assoc_create(&nr_registry, r_assoc_crc32_hash_compute, 12)))
+ ABORT(r);
+
+ if ((r=nr_reg_cb_init()))
+ ABORT(r);
+
+ /* make sure NR_TOP_LEVEL_REGISTRY always exists */
+ if ((r=nr_reg_local_set_registry(NR_TOP_LEVEL_REGISTRY)))
+ ABORT(r);
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+}
+
+#define NRREGLOCALGET(func, TYPE, type) \
+int \
+func(NR_registry name, type *out) \
+{ \
+ return nr_reg_get(name, TYPE, out); \
+}
+
+NRREGLOCALGET(nr_reg_local_get_char, NR_REG_TYPE_CHAR, char)
+NRREGLOCALGET(nr_reg_local_get_uchar, NR_REG_TYPE_UCHAR, UCHAR)
+NRREGLOCALGET(nr_reg_local_get_int2, NR_REG_TYPE_INT2, INT2)
+NRREGLOCALGET(nr_reg_local_get_uint2, NR_REG_TYPE_UINT2, UINT2)
+NRREGLOCALGET(nr_reg_local_get_int4, NR_REG_TYPE_INT4, INT4)
+NRREGLOCALGET(nr_reg_local_get_uint4, NR_REG_TYPE_UINT4, UINT4)
+NRREGLOCALGET(nr_reg_local_get_int8, NR_REG_TYPE_INT8, INT8)
+NRREGLOCALGET(nr_reg_local_get_uint8, NR_REG_TYPE_UINT8, UINT8)
+NRREGLOCALGET(nr_reg_local_get_double, NR_REG_TYPE_DOUBLE, double)
+
+int
+nr_reg_local_get_registry(NR_registry name, NR_registry out)
+{
+ int r, _status;
+ nr_scalar_registry_node *node = 0;
+ int free_node = 0;
+
+ if ((r=nr_reg_fetch_node(name, NR_REG_TYPE_REGISTRY, (void*)&node, &free_node)))
+ ABORT(r);
+
+ strncpy(out, name, sizeof(NR_registry));
+
+ _status=0;
+ abort:
+ if (free_node) RFREE(node);
+ return(_status);
+
+}
+
+int
+nr_reg_local_get_bytes(NR_registry name, UCHAR *out, size_t size, size_t *length)
+{
+ return nr_reg_get_array(name, NR_REG_TYPE_BYTES, out, size, length);
+}
+
+int
+nr_reg_local_get_string(NR_registry name, char *out, size_t size)
+{
+ return nr_reg_get_array(name, NR_REG_TYPE_STRING, (UCHAR*)out, size, 0);
+}
+
+int
+nr_reg_local_get_length(NR_registry name, size_t *length)
+{
+ int r, _status;
+ nr_registry_node *node = 0;
+
+ if ((r=nr_reg_is_valid(name)))
+ ABORT(r);
+
+ if ((r=r_assoc_fetch(nr_registry, name, strlen(name)+1, (void*)&node)))
+ ABORT(r);
+
+ if ((r=nr_reg_compute_length(name, node, length)))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ return(_status);
+}
+
+int
+nr_reg_local_get_type(NR_registry name, NR_registry_type type)
+{
+ int r, _status;
+ nr_registry_node *node = 0;
+ char *str;
+
+ if ((r=nr_reg_is_valid(name)))
+ ABORT(r);
+
+ if ((r=r_assoc_fetch(nr_registry, name, strlen(name)+1, (void*)&node)))
+ ABORT(r);
+
+ str = nr_reg_type_name(node->type);
+ if (! str)
+ ABORT(R_BAD_ARGS);
+
+ strncpy(type, str, sizeof(NR_registry_type));
+
+ _status=0;
+ abort:
+ return(_status);
+}
+
+
+#define NRREGLOCALSET(func, TYPE, type) \
+int \
+func(NR_registry name, type data) \
+{ \
+ return nr_reg_set(name, TYPE, &data); \
+}
+
+NRREGLOCALSET(nr_reg_local_set_char, NR_REG_TYPE_CHAR, char)
+NRREGLOCALSET(nr_reg_local_set_uchar, NR_REG_TYPE_UCHAR, UCHAR)
+NRREGLOCALSET(nr_reg_local_set_int2, NR_REG_TYPE_INT2, INT2)
+NRREGLOCALSET(nr_reg_local_set_uint2, NR_REG_TYPE_UINT2, UINT2)
+NRREGLOCALSET(nr_reg_local_set_int4, NR_REG_TYPE_INT4, INT4)
+NRREGLOCALSET(nr_reg_local_set_uint4, NR_REG_TYPE_UINT4, UINT4)
+NRREGLOCALSET(nr_reg_local_set_int8, NR_REG_TYPE_INT8, INT8)
+NRREGLOCALSET(nr_reg_local_set_uint8, NR_REG_TYPE_UINT8, UINT8)
+NRREGLOCALSET(nr_reg_local_set_double, NR_REG_TYPE_DOUBLE, double)
+
+int
+nr_reg_local_set_registry(NR_registry name)
+{
+ return nr_reg_set(name, NR_REG_TYPE_REGISTRY, 0);
+}
+
+int
+nr_reg_local_set_bytes(NR_registry name, unsigned char *data, size_t length)
+{
+ return nr_reg_set_array(name, NR_REG_TYPE_BYTES, data, length);
+}
+
+int
+nr_reg_local_set_string(NR_registry name, char *data)
+{
+ return nr_reg_set_array(name, NR_REG_TYPE_STRING, (UCHAR*)data, strlen(data)+1);
+}
+
+int
+nr_reg_local_del(NR_registry name)
+{
+ int r, _status;
+
+ if ((r=nr_reg_is_valid(name)))
+ ABORT(r);
+
+ /* delete from NR_registry */
+ if ((r=nr_reg_local_iter(name, nr_reg_local_iter_delete, 0)))
+ ABORT(r);
+
+ if ((r=nr_reg_raise_event(name, NR_REG_CB_ACTION_DELETE)))
+ ABORT(r);
+
+ /* if deleting from the root, re-insert the root */
+ if (! strcasecmp(name, NR_TOP_LEVEL_REGISTRY)) {
+ if ((r=nr_reg_local_set_registry(NR_TOP_LEVEL_REGISTRY)))
+ ABORT(r);
+ }
+
+ _status=0;
+ abort:
+ r_log(NR_LOG_REGISTRY,
+ (_status ? LOG_INFO : LOG_INFO),
+ "delete of '%s' %s", name,
+ (_status ? "FAILED" : "succeeded"));
+ return(_status);
+}
+
+int
+nr_reg_local_get_child_count(NR_registry parent, size_t *count)
+{
+ int r, _status;
+ nr_registry_node *ignore1;
+ int ignore2;
+
+
+ if ((r=nr_reg_is_valid(parent)))
+ ABORT(r);
+
+ /* test to see whether it is present */
+ if ((r=nr_reg_fetch_node(parent, NR_REG_TYPE_REGISTRY, &ignore1, &ignore2)))
+ ABORT(r);
+
+ /* sanity check that there isn't any memory to free */
+ assert(ignore2 == 0);
+
+ *count = 0;
+
+ if ((r=nr_reg_local_iter(parent, nr_reg_local_count_children, count)))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ return(_status);
+}
+
+int
+nr_reg_local_get_children(NR_registry parent, NR_registry *data, size_t size, size_t *length)
+{
+ int r, _status;
+ nr_reg_find_children_arg arg;
+
+ if ((r=nr_reg_is_valid(parent)))
+ ABORT(r);
+
+ arg.children = data;
+ arg.size = size;
+ arg.length = 0;
+
+ if ((r=nr_reg_local_iter(parent, nr_reg_local_find_children, (void*)&arg))) {
+ if (r == R_INTERRUPTED)
+ ABORT(R_BAD_ARGS);
+ else
+ ABORT(r);
+ }
+
+ assert(sizeof(*arg.children) == sizeof(NR_registry));
+ qsort(arg.children, arg.length, sizeof(*arg.children), (void*)strcasecmp);
+
+ *length = arg.length;
+
+ _status = 0;
+ abort:
+ return(_status);
+}
+
+int
+nr_reg_local_fin(NR_registry name)
+{
+ int r, _status;
+
+ if ((r=nr_reg_raise_event(name, NR_REG_CB_ACTION_FINAL)))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ return(_status);
+}
+
+int
+nr_reg_local_dump(int sorted)
+{
+ int r, _status;
+
+ if ((r=nr_reg_local_iter(NR_TOP_LEVEL_REGISTRY, nr_reg_local_dump_print, 0)))
+ ABORT(r);
+
+ _status=0;
+ abort:
+ return(_status);
+}
+
+
+
+static nr_registry_module_vtbl nr_reg_local_vtbl = {
+ nr_reg_local_init,
+ nr_reg_local_get_char,
+ nr_reg_local_get_uchar,
+ nr_reg_local_get_int2,
+ nr_reg_local_get_uint2,
+ nr_reg_local_get_int4,
+ nr_reg_local_get_uint4,
+ nr_reg_local_get_int8,
+ nr_reg_local_get_uint8,
+ nr_reg_local_get_double,
+ nr_reg_local_get_registry,
+ nr_reg_local_get_bytes,
+ nr_reg_local_get_string,
+ nr_reg_local_get_length,
+ nr_reg_local_get_type,
+ nr_reg_local_set_char,
+ nr_reg_local_set_uchar,
+ nr_reg_local_set_int2,
+ nr_reg_local_set_uint2,
+ nr_reg_local_set_int4,
+ nr_reg_local_set_uint4,
+ nr_reg_local_set_int8,
+ nr_reg_local_set_uint8,
+ nr_reg_local_set_double,
+ nr_reg_local_set_registry,
+ nr_reg_local_set_bytes,
+ nr_reg_local_set_string,
+ nr_reg_local_del,
+ nr_reg_local_get_child_count,
+ nr_reg_local_get_children,
+ nr_reg_local_fin,
+ nr_reg_local_dump
+};
+
+static nr_registry_module nr_reg_local_module = { 0, &nr_reg_local_vtbl };
+
+void *NR_REG_MODE_LOCAL = &nr_reg_local_module;
+
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/registry/registry_vtbl.h b/dom/media/webrtc/transport/third_party/nrappkit/src/registry/registry_vtbl.h
new file mode 100644
index 0000000000..0f75a03b95
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/registry/registry_vtbl.h
@@ -0,0 +1,96 @@
+/*
+ *
+ * registry_vtbl.h
+ *
+ * $Source: /Users/ekr/tmp/nrappkit-dump/nrappkit/src/registry/registry_vtbl.h,v $
+ * $Revision: 1.2 $
+ * $Date: 2006/08/16 19:39:14 $
+ *
+ *
+ * Copyright (C) 2006, Network Resonance, 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:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of Network Resonance, Inc. nor the name of any
+ * contributors to this software 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 OWNER 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 __REGISTRY_VTBL_H__
+#define __REGISTRY_VTBL_H__
+
+typedef struct nr_registry_module_ nr_registry_module;
+
+typedef struct nr_registry_module_vtbl_ {
+ int (*init)(nr_registry_module*);
+
+ int (*get_char)(NR_registry name, char *out);
+ int (*get_uchar)(NR_registry name, UCHAR *out);
+ int (*get_int2)(NR_registry name, INT2 *out);
+ int (*get_uint2)(NR_registry name, UINT2 *out);
+ int (*get_int4)(NR_registry name, INT4 *out);
+ int (*get_uint4)(NR_registry name, UINT4 *out);
+ int (*get_int8)(NR_registry name, INT8 *out);
+ int (*get_uint8)(NR_registry name, UINT8 *out);
+ int (*get_double)(NR_registry name, double *out);
+ int (*get_registry)(NR_registry name, NR_registry out);
+
+ int (*get_bytes)(NR_registry name, UCHAR *out, size_t size, size_t *length);
+ int (*get_string)(NR_registry name, char *out, size_t size);
+ int (*get_length)(NR_registry name, size_t *length);
+ int (*get_type)(NR_registry name, NR_registry_type type);
+
+ int (*set_char)(NR_registry name, char data);
+ int (*set_uchar)(NR_registry name, UCHAR data);
+ int (*set_int2)(NR_registry name, INT2 data);
+ int (*set_uint2)(NR_registry name, UINT2 data);
+ int (*set_int4)(NR_registry name, INT4 data);
+ int (*set_uint4)(NR_registry name, UINT4 data);
+ int (*set_int8)(NR_registry name, INT8 data);
+ int (*set_uint8)(NR_registry name, UINT8 data);
+ int (*set_double)(NR_registry name, double data);
+ int (*set_registry)(NR_registry name);
+
+ int (*set_bytes)(NR_registry name, UCHAR *data, size_t length);
+ int (*set_string)(NR_registry name, char *data);
+
+ int (*del)(NR_registry name);
+
+ int (*get_child_count)(NR_registry parent, size_t *count);
+ int (*get_children)(NR_registry parent, NR_registry *data, size_t size, size_t *length);
+
+ int (*fin)(NR_registry name);
+
+ int (*dump)(int sorted);
+} nr_registry_module_vtbl;
+
+struct nr_registry_module_ {
+ void *handle;
+ nr_registry_module_vtbl *vtbl;
+};
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/registry/registrycb.c b/dom/media/webrtc/transport/third_party/nrappkit/src/registry/registrycb.c
new file mode 100644
index 0000000000..4b326a1ee2
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/registry/registrycb.c
@@ -0,0 +1,440 @@
+/*
+ *
+ * registrycb.c
+ *
+ * $Source: /Users/ekr/tmp/nrappkit-dump/nrappkit/src/registry/registrycb.c,v $
+ * $Revision: 1.3 $
+ * $Date: 2007/06/26 22:37:51 $
+ *
+ * Callback-related functions
+ *
+ *
+ * Copyright (C) 2005, Network Resonance, Inc.
+ * Copyright (C) 2006, Network Resonance, 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:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of Network Resonance, Inc. nor the name of any
+ * contributors to this software 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 OWNER 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.
+ *
+ *
+ */
+
+#include <assert.h>
+#include <string.h>
+#include "registry.h"
+#include "registry_int.h"
+#include "r_assoc.h"
+#include "r_errors.h"
+#include "nr_common.h"
+#include "r_log.h"
+#include "r_macros.h"
+
+static char CB_ACTIONS[] = { NR_REG_CB_ACTION_ADD,
+ NR_REG_CB_ACTION_DELETE,
+ NR_REG_CB_ACTION_CHANGE,
+ NR_REG_CB_ACTION_FINAL };
+
+typedef struct nr_reg_cb_info_ {
+ char action;
+ void (*cb)(void *cb_arg, char action, NR_registry name);
+ void *cb_arg;
+ NR_registry name;
+} nr_reg_cb_info;
+
+/* callbacks that are registered, a mapping from names like "foo.bar.baz"
+ * to an r_assoc which contains possibly several nr_reg_cb_info*'s */
+static r_assoc *nr_registry_callbacks = 0;
+
+//static size_t SIZEOF_CB_ID = (sizeof(void (*)()) + 1);
+#define SIZEOF_CB_ID (sizeof(void (*)()) + 1)
+
+static int nr_reg_validate_action(char action);
+static int nr_reg_assoc_destroy(void *ptr);
+static int compute_cb_id(void *cb, char action, unsigned char cb_id[SIZEOF_CB_ID]);
+static int nr_reg_info_free(void *ptr);
+static int nr_reg_raise_event_recurse(char *name, char *tmp, int action);
+static int nr_reg_register_callback(NR_registry name, char action, void (*cb)(void *cb_arg, char action, NR_registry name), void *cb_arg);
+static int nr_reg_unregister_callback(char *name, char action, void (*cb)(void *cb_arg, char action, NR_registry name));
+
+int
+nr_reg_cb_init()
+{
+ int r, _status;
+
+ if (nr_registry_callbacks == 0) {
+ if ((r=r_assoc_create(&nr_registry_callbacks, r_assoc_crc32_hash_compute, 12)))
+ ABORT(r);
+ }
+
+ _status=0;
+ abort:
+ if (_status) {
+ r_log(NR_LOG_REGISTRY, LOG_DEBUG, "Couldn't init notifications: %s", nr_strerror(_status));
+ }
+ return(_status);
+}
+
+int
+nr_reg_validate_action(char action)
+{
+ int _status;
+ size_t i;
+
+ for (i = 0; i < sizeof(CB_ACTIONS); ++i) {
+ if (action == CB_ACTIONS[i])
+ return 0;
+ }
+ ABORT(R_BAD_ARGS);
+
+ _status=0;
+ abort:
+ return(_status);
+}
+
+int
+nr_reg_register_callback(NR_registry name, char action, void (*cb)(void *cb_arg, char action, NR_registry name), void *cb_arg)
+{
+ int r, _status;
+ r_assoc *assoc;
+ int create_assoc = 0;
+ nr_reg_cb_info *info;
+ int create_info = 0;
+ unsigned char cb_id[SIZEOF_CB_ID];
+
+ if (name == 0 || cb == 0)
+ ABORT(R_BAD_ARGS);
+
+ if (nr_registry_callbacks == 0)
+ ABORT(R_FAILED);
+
+ if ((r=nr_reg_is_valid(name)))
+ ABORT(r);
+
+ if ((r=nr_reg_validate_action(action)))
+ ABORT(r);
+
+ if ((r=r_assoc_fetch(nr_registry_callbacks, name, strlen(name)+1, (void*)&assoc))) {
+ if (r == R_NOT_FOUND)
+ create_assoc = 1;
+ else
+ ABORT(r);
+ }
+
+ if (create_assoc) {
+ if ((r=r_assoc_create(&assoc, r_assoc_crc32_hash_compute, 5)))
+ ABORT(r);
+
+ if ((r=r_assoc_insert(nr_registry_callbacks, name, strlen(name)+1, assoc, 0, nr_reg_assoc_destroy, R_ASSOC_NEW)))
+ ABORT(r);
+ }
+
+ if ((r=compute_cb_id(cb, action, cb_id)))
+ ABORT(r);
+
+ if ((r=r_assoc_fetch(assoc, (char*)cb_id, SIZEOF_CB_ID, (void*)&info))) {
+ if (r == R_NOT_FOUND)
+ create_info = 1;
+ else
+ ABORT(r);
+ }
+
+ if (create_info) {
+ if (!(info=(void*)RCALLOC(sizeof(*info))))
+ ABORT(R_NO_MEMORY);
+ }
+
+ strncpy(info->name, name, sizeof(info->name));
+ info->action = action;
+ info->cb = cb;
+ info->cb_arg = cb_arg;
+
+ if (create_info) {
+ if ((r=r_assoc_insert(assoc, (char*)cb_id, SIZEOF_CB_ID, info, 0, nr_reg_info_free, R_ASSOC_NEW)))
+ ABORT(r);
+ }
+
+ _status=0;
+ abort:
+ r_log(NR_LOG_REGISTRY, LOG_DEBUG, "register callback %p on '%s' for '%s' %s", cb, name, nr_reg_action_name(action), (_status ? "FAILED" : "succeeded"));
+
+ if (_status) {
+ if (create_info && info) RFREE(info);
+ if (create_assoc && assoc) nr_reg_assoc_destroy(&assoc);
+ }
+ return(_status);
+}
+
+int
+nr_reg_unregister_callback(char *name, char action, void (*cb)(void *cb_arg, char action, NR_registry name))
+{
+ int r, _status;
+ r_assoc *assoc;
+ int size;
+ unsigned char cb_id[SIZEOF_CB_ID];
+
+ if (name == 0 || cb == 0)
+ ABORT(R_BAD_ARGS);
+
+ if (nr_registry_callbacks == 0)
+ ABORT(R_FAILED);
+
+ if ((r=nr_reg_is_valid(name)))
+ ABORT(r);
+
+ if ((r=nr_reg_validate_action(action)))
+ ABORT(r);
+
+ if ((r=r_assoc_fetch(nr_registry_callbacks, name, strlen(name)+1, (void*)&assoc))) {
+ if (r != R_NOT_FOUND)
+ ABORT(r);
+ }
+ else {
+ if ((r=compute_cb_id(cb, action, cb_id)))
+ ABORT(r);
+
+ if ((r=r_assoc_delete(assoc, (char*)cb_id, SIZEOF_CB_ID))) {
+ if (r != R_NOT_FOUND)
+ ABORT(r);
+ }
+
+ if ((r=r_assoc_num_elements(assoc, &size)))
+ ABORT(r);
+
+ if (size == 0) {
+ if ((r=r_assoc_delete(nr_registry_callbacks, name, strlen(name)+1)))
+ ABORT(r);
+ }
+ }
+
+ _status=0;
+ abort:
+ r_log(NR_LOG_REGISTRY, LOG_DEBUG, "unregister callback %p on '%s' for '%s' %s", cb, name, nr_reg_action_name(action), (_status ? "FAILED" : "succeeded"));
+
+ return(_status);
+}
+
+int
+compute_cb_id(void *cb, char action, unsigned char cb_id[SIZEOF_CB_ID])
+{
+ /* callbacks are identified by the pointer to the cb function plus
+ * the action being watched */
+ assert(sizeof(cb) == sizeof(void (*)()));
+ assert(sizeof(cb) == (SIZEOF_CB_ID - 1));
+
+ memcpy(cb_id, &(cb), sizeof(cb));
+ cb_id[SIZEOF_CB_ID-1] = action;
+
+ return 0;
+}
+
+char *
+nr_reg_action_name(int action)
+{
+ char *name = "*Unknown*";
+
+ switch (action) {
+ case NR_REG_CB_ACTION_ADD: name = "add"; break;
+ case NR_REG_CB_ACTION_DELETE: name = "delete"; break;
+ case NR_REG_CB_ACTION_CHANGE: name = "change"; break;
+ case NR_REG_CB_ACTION_FINAL: name = "final"; break;
+ }
+
+ return name;
+}
+
+int
+nr_reg_assoc_destroy(void *ptr)
+{
+ return r_assoc_destroy((r_assoc**)&ptr);
+}
+
+int
+nr_reg_info_free(void *ptr)
+{
+ RFREE(ptr);
+ return 0;
+}
+
+/* call with tmp=0 */
+int
+nr_reg_raise_event_recurse(char *name, char *tmp, int action)
+{
+ int r, _status;
+ r_assoc *assoc;
+ nr_reg_cb_info *info;
+ r_assoc_iterator iter;
+ char *key;
+ int keyl;
+ char *c;
+ int free_tmp = 0;
+ int count;
+
+ if (tmp == 0) {
+ if (!(tmp = (char*)r_strdup(name)))
+ ABORT(R_NO_MEMORY);
+ free_tmp = 1;
+ }
+
+ if ((r=r_assoc_fetch(nr_registry_callbacks, tmp, strlen(tmp)+1, (void*)&assoc))) {
+ if (r != R_NOT_FOUND)
+ ABORT(r);
+
+ r_log(NR_LOG_REGISTRY, LOG_DEBUG, "No callbacks found on '%s'", tmp);
+ }
+ else {
+ if (!r_assoc_num_elements(assoc, &count)) {
+ r_log(NR_LOG_REGISTRY, LOG_DEBUG, "%d callback%s found on '%s'",
+ count, ((count == 1) ? "" : "s"), tmp);
+ }
+
+ if ((r=r_assoc_init_iter(assoc, &iter)))
+ ABORT(r);
+
+ for (;;) {
+ if ((r=r_assoc_iter(&iter, (void*)&key, &keyl, (void*)&info))) {
+ if (r == R_EOD)
+ break;
+ else
+ ABORT(r);
+ }
+
+ if (info->action == action) {
+ r_log(NR_LOG_REGISTRY, LOG_DEBUG,
+ "Invoking callback %p for '%s'",
+ info->cb,
+ nr_reg_action_name(info->action));
+
+ (void)info->cb(info->cb_arg, action, name);
+ }
+ else {
+ r_log(NR_LOG_REGISTRY, LOG_DEBUG,
+ "Skipping callback %p for '%s'",
+ info->cb,
+ nr_reg_action_name(info->action));
+ }
+ }
+ }
+
+ if (strlen(tmp) > 0) {
+ c = strrchr(tmp, '.');
+ if (c != 0)
+ *c = '\0';
+ else
+ tmp[0] = '\0';
+
+ if ((r=nr_reg_raise_event_recurse(name, tmp, action)))
+ ABORT(r);
+ }
+
+ _status=0;
+ abort:
+ if (free_tmp && tmp != 0) RFREE(tmp);
+ return(_status);
+}
+
+
+/* NON-STATIC METHODS */
+
+int
+nr_reg_raise_event(NR_registry name, int action)
+{
+ int r, _status;
+ int count;
+ char *event = nr_reg_action_name(action);
+
+ r_log(NR_LOG_REGISTRY, LOG_DEBUG, "raising event '%s' on '%s'", event, name);
+
+ if (name == 0)
+ ABORT(R_BAD_ARGS);
+
+ if ((r=nr_reg_validate_action(action)))
+ ABORT(r);
+
+ if ((r=r_assoc_num_elements(nr_registry_callbacks, &count)))
+ ABORT(r);
+
+ if (count > 0) {
+ if ((r=nr_reg_raise_event_recurse(name, 0, action)))
+ ABORT(r);
+ }
+ else {
+ r_log(NR_LOG_REGISTRY, LOG_DEBUG, "No callbacks found");
+ return 0;
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+}
+
+
+/* PUBLIC METHODS */
+
+int
+NR_reg_register_callback(NR_registry name, char action, void (*cb)(void *cb_arg, char action, NR_registry name), void *cb_arg)
+{
+ int r, _status;
+ size_t i;
+
+ for (i = 0; i < sizeof(CB_ACTIONS); ++i) {
+ if (action & CB_ACTIONS[i]) {
+ if ((r=nr_reg_register_callback(name, CB_ACTIONS[i], cb, cb_arg)))
+ ABORT(r);
+
+ action &= ~(CB_ACTIONS[i]);
+ }
+ }
+
+ if (action)
+ ABORT(R_BAD_ARGS);
+
+ _status=0;
+ abort:
+ return(_status);
+}
+
+int
+NR_reg_unregister_callback(NR_registry name, char action, void (*cb)(void *cb_arg, char action, NR_registry name))
+{
+ int r, _status;
+ size_t i;
+
+ for (i = 0; i < sizeof(CB_ACTIONS); ++i) {
+ if (action & CB_ACTIONS[i]) {
+ if ((r=nr_reg_unregister_callback(name, CB_ACTIONS[i], cb)))
+ ABORT(r);
+
+ action &= ~(CB_ACTIONS[i]);
+ }
+ }
+
+ if (action)
+ ABORT(R_BAD_ARGS);
+
+ _status=0;
+ abort:
+ return(_status);
+}
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/share/nr_api.h b/dom/media/webrtc/transport/third_party/nrappkit/src/share/nr_api.h
new file mode 100644
index 0000000000..ce6055f0d9
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/share/nr_api.h
@@ -0,0 +1,51 @@
+/**
+ nr_pce.h
+
+
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ ekr@networkresonance.com Wed Jul 19 13:18:39 2006
+ */
+
+
+#ifndef _nr_pce_h
+#define _nr_pce_h
+
+#include <sys/queue.h>
+#include <csi_platform.h>
+#include <r_common.h>
+#include <r_log.h>
+#include <nrstats.h>
+#include <nr_plugin.h>
+#include <async_wait.h>
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/share/nr_common.h b/dom/media/webrtc/transport/third_party/nrappkit/src/share/nr_common.h
new file mode 100644
index 0000000000..ac3ff44004
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/share/nr_common.h
@@ -0,0 +1,108 @@
+/**
+ nr_common.h
+
+
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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 _nr_common_h
+#define _nr_common_h
+
+#include <csi_platform.h>
+
+#ifdef USE_MPATROL
+#define USEDEBUG 1
+#include <mpatrol.h>
+#endif
+
+#ifdef USE_DMALLOC
+#include <dmalloc.h>
+#endif
+
+#include <string.h>
+#include <time.h>
+
+#ifdef WIN32
+#include <winsock2.h>
+#include <errno.h>
+#else
+#include <sys/errno.h>
+#endif
+
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <r_log.h>
+
+extern int NR_LOG_REASSD;
+
+#include "registry.h"
+#include "nrstats.h"
+
+typedef struct nr_captured_packet_ {
+ UCHAR cap_interface; /* 1 for primary, 2 for secondary */
+ struct timeval ts; /* The time this packet was captured */
+ UINT4 len; /* The length of the packet */
+ UINT8 packet_number; /* The listener's packet index */
+} nr_captured_packet;
+
+#ifndef NR_ROOT_PATH
+#define NR_ROOT_PATH "/usr/local/ctc/"
+#endif
+
+#define NR_ARCHIVE_DIR NR_ROOT_PATH "archive/"
+#define NR_TEMP_DIR NR_ROOT_PATH "tmp/"
+#define NR_ARCHIVE_STATEFILE NR_ROOT_PATH "archive/state"
+#define NR_CAPTURED_PID_FILENAME NR_ROOT_PATH "captured.pid"
+#define NR_REASSD_PID_FILENAME NR_ROOT_PATH "reassd.pid"
+#define NR_MODE_FILENAME NR_ROOT_PATH "mode.txt"
+
+char *nr_revision_number(void);
+
+
+
+
+/* Memory buckets for CTC memory types */
+#define NR_MEM_TCP 1
+#define NR_MEM_HTTP 2
+#define NR_MEM_DELIVERY 3
+#define NR_MEM_OUT_HM 4
+#define NR_MEM_OUT_SSL 5
+#define NR_MEM_SSL 7
+#define NR_MEM_COMMON 8
+#define NR_MEM_CODEC 9
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/share/nr_reg_keys.h b/dom/media/webrtc/transport/third_party/nrappkit/src/share/nr_reg_keys.h
new file mode 100644
index 0000000000..b051f51d4a
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/share/nr_reg_keys.h
@@ -0,0 +1,167 @@
+/*
+ *
+ * nr_reg_keys.h
+ *
+ * $Source: /Users/ekr/tmp/nrappkit-dump/nrappkit/src/share/nr_reg_keys.h,v $
+ * $Revision: 1.3 $
+ * $Date: 2008/01/29 00:34:00 $
+ *
+ *
+ * Copyright (C) 2006, Network Resonance, 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:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of Network Resonance, Inc. nor the name of any
+ * contributors to this software 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 OWNER 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 __NR_REG_KEYS_H__
+#define __NR_REG_KEYS_H__
+
+#include <stdio.h>
+
+#define NR_REG_NAME_LENGTH_MIN 1
+#define NR_REG_NAME_LENGTH_MAX 32
+
+#define NR_REG_HOSTNAME "hostname"
+#define NR_REG_ADDRESS "address"
+#define NR_REG_NETMASKSIZE "netmasksize"
+#define NR_REG_ADDRESS_NETMASKSIZE "address/netmasksize" /* use only in clic files */
+#define NR_REG_PORT "port"
+
+#define NR_REG_LOGGING_SYSLOG_ENABLED "logging.syslog.enabled"
+#define NR_REG_LOGGING_SYSLOG_SERVERS "logging.syslog.servers"
+#define NR_REG_LOGGING_SYSLOG_FACILITY "logging.syslog.facility"
+#define NR_REG_LOGGING_SYSLOG_LEVEL "logging.syslog.level"
+
+#define NR_REG_CAPTURED_DAEMONS "captured.daemons"
+
+#define NR_REG_LISTEND_ENABLED "listend.enabled"
+
+#define NR_REG_LISTEND_MAX_INPUT_BUFFER_SIZE "listend.max_input_buffer_size"
+#define NR_REG_LISTEND_MAX_INPUT_BUFFER_SIZE_MIN 1 // 1 byte?
+#define NR_REG_LISTEND_MAX_INPUT_BUFFER_SIZE_MAX (10ULL*1024*1024*1024) // 10 GB
+
+#define NR_REG_LISTEND_INTERFACE "listend.interface"
+#define NR_REG_LISTEND_INTERFACE_PRIMARY "listend.interface.primary"
+#define NR_REG_LISTEND_INTERFACE_SECONDARY "listend.interface.secondary"
+#define NR_REG_LISTEND_LISTEN_ON_BOTH_INTERFACES "listend.interface.listen_on_both_interfaces"
+#define NR_REG_LISTEND_ENABLE_VLAN "listend.enable_vlan"
+
+#define NR_REG_LISTEND_LISTEN_TO "listend.listen_to"
+#define NR_REG_LISTEND_IGNORE "listend.ignore"
+
+#define NR_REG_LISTEND_PORT_MIN 0
+#define NR_REG_LISTEND_PORT_MAX 65535
+
+#define NR_REG_REASSD_MAX_MEMORY_CONSUMPTION "reassd.max_memory_consumption"
+#define NR_REG_REASSD_MAX_MEMORY_CONSUMPTION_MIN (10*1024) // 100 KB
+#define NR_REG_REASSD_MAX_MEMORY_CONSUMPTION_MAX (10ULL*1024*1024*1024) // 10 GB
+
+#define NR_REG_REASSD_DECODER_TCP_IGNORE_CHECKSUMS "reassd.decoder.tcp.ignore_checksums"
+
+#define NR_REG_REASSD_DECODER_TCP_MAX_CONNECTIONS_IN_SYN_STATE "reassd.decoder.tcp.max_connections_in_syn_state"
+#define NR_REG_REASSD_DECODER_TCP_MAX_CONNECTIONS_IN_SYN_STATE_MIN 1
+#define NR_REG_REASSD_DECODER_TCP_MAX_CONNECTIONS_IN_SYN_STATE_MAX 500000
+
+#define NR_REG_REASSD_DECODER_TCP_MAX_SIMULTANEOUS_CONNECTIONS "reassd.decoder.tcp.max_simultaneous_connections"
+#define NR_REG_REASSD_DECODER_TCP_MAX_SIMULTANEOUS_CONNECTIONS_MIN 1
+#define NR_REG_REASSD_DECODER_TCP_MAX_SIMULTANEOUS_CONNECTIONS_MAX 1000000 // 1 million
+
+#define NR_REG_REASSD_DECODER_SSL_MAX_SESSION_CACHE_SIZE "reassd.decoder.ssl.max_session_cache_size"
+#define NR_REG_REASSD_DECODER_SSL_MAX_SESSION_CACHE_SIZE_MIN 0
+#define NR_REG_REASSD_DECODER_SSL_MAX_SESSION_CACHE_SIZE_MAX (1ULL*1024*1024*1024) // 1GB
+
+#define NR_REG_REASSD_DECODER_SSL_REVEAL_LOCAL_KEYS "reassd.decoder.ssl.reveal.local.keys"
+
+#define NR_REG_REASSD_DECODER_HTTP_HANGING_RESPONSE_TIMEOUT "reassd.decoder.http.hanging_response_timeout"
+#define NR_REG_REASSD_DECODER_HTTP_HANGING_RESPONSE_TIMEOUT_MIN 1
+#define NR_REG_REASSD_DECODER_HTTP_HANGING_RESPONSE_TIMEOUT_MAX 1023
+
+#define NR_REG_REASSD_DECODER_HTTP_HANGING_TRANSMISSION_TIMEOUT "reassd.decoder.http.hanging_transmission_timeout"
+#define NR_REG_REASSD_DECODER_HTTP_HANGING_TRANSMISSION_TIMEOUT_MIN 1
+#define NR_REG_REASSD_DECODER_HTTP_HANGING_TRANSMISSION_TIMEOUT_MAX 1023
+
+#define NR_REG_REASSD_DECODER_HTTP_MAX_HTTP_MESSAGE_SIZE "reassd.decoder.http.max_http_message_size"
+#define NR_REG_REASSD_DECODER_HTTP_MAX_HTTP_MESSAGE_SIZE_MIN 0
+#define NR_REG_REASSD_DECODER_HTTP_MAX_HTTP_MESSAGE_SIZE_MAX (10ULL*1024*1024*1024)
+
+/* PCE-only: */
+#define NR_REG_LISTEND_ARCHIIVE "listend.archive"
+#define NR_REG_LISTEND_ARCHIVE_MAX_SIZE "listend.archive.max_size"
+#define NR_REG_LISTEND_ARCHIVE_MAX_SIZE_MIN 0
+#define NR_REG_LISTEND_ARCHIVE_MAX_SIZE_MAX (10ULL*1024*1024*1024*1024) // 10 TB
+
+#define NR_REG_LISTEND_ARCHIVE_RECORDING_ENABLED "listend.archive.recording_enabled"
+
+#define NR_REG_REASSD_DECODER_NET_DELIVER_BATCH_INTERVAL "reassd.decoder.net_deliver.batch_interval"
+#define NR_REG_REASSD_DECODER_NET_DELIVER_BATCH_INTERVAL_MIN 0
+#define NR_REG_REASSD_DECODER_NET_DELIVER_BATCH_INTERVAL_MAX 1023
+
+#define NR_REG_REASSD_DECODER_NET_DELIVER_MAX_QUEUE_DEPTH "reassd.decoder.net_deliver.max_queue_depth"
+#define NR_REG_REASSD_DECODER_NET_DELIVER_MAX_QUEUE_DEPTH_MIN 0
+#define NR_REG_REASSD_DECODER_NET_DELIVER_MAX_QUEUE_DEPTH_MAX (1ULL*1024*1024*1024) // 1 GB
+
+#define NR_REG_REASSD_DECODER_NET_DELIVER_MY_KEY_CERTIFICATE "reassd.decoder.net_deliver.my_key.certificate"
+#define NR_REG_REASSD_DECODER_NET_DELIVER_MY_KEY_PRIVATE_KEY "reassd.decoder.net_deliver.my_key.private_key"
+#define NR_REG_REASSD_DECODER_NET_DELIVER_PEER "reassd.decoder.net_deliver.peer"
+#define NR_REG_REASSD_DECODER_NET_DELIVER_PEER_PORT_MIN 0
+#define NR_REG_REASSD_DECODER_NET_DELIVER_PEER_PORT_MAX 65535
+
+#define NR_REG_REASSD_DECODER_NET_DELIVER_POLLING_INTERVAL "reassd.decoder.net_deliver.polling_interval"
+#define NR_REG_REASSD_DECODER_NET_DELIVER_POLLING_INTERVAL_MIN 1
+#define NR_REG_REASSD_DECODER_NET_DELIVER_POLLING_INTERVAL_MAX 1023
+
+#define NR_REG_REASSD_DECODER_NET_DELIVER_STATELESS "reassd.decoder.net_deliver.stateless"
+#define NR_REG_REASSD_DECODER_NET_DELIVER_WATCHDOG_TIMER "reassd.decoder.net_deliver.watchdog_timer"
+#define NR_REG_REASSD_DECODER_NET_DELIVER_WATCHDOG_TIMER_MIN 0
+#define NR_REG_REASSD_DECODER_NET_DELIVER_WATCHDOG_TIMER_MAX 666
+
+/* ASA-only: */
+#define NR_REG_MIGRATE_ENABLED "migrate.enabled"
+
+#define NR_REG_MIGRATE_INACTIVITY_TIMEOUT "migrate.inactivity_timeout"
+#define NR_REG_MIGRATE_INACTIVITY_TIMEOUT_MIN 0
+#define NR_REG_MIGRATE_INACTIVITY_TIMEOUT_MAX 1023
+
+#define NR_REG_MIGRATE_MIN_LOCAL_SIZE "migrate.min_local_size"
+#define NR_REG_MIGRATE_MIN_OVERLAP_SIZE "migrate.min_overlap_size"
+#define NR_REG_MIGRATE_RETRANSMIT_FREQUENCY "migrate.retransmit_frequency"
+#define NR_REG_MIGRATE_RETRIES "migrate.retries"
+#define NR_MIGRATE_LOCATION_NUMBER_MIN 0
+#define NR_MIGRATE_LOCATION_NUMBER_MAX 255
+
+#define NR_REG_REVELATION_ENABLE "revelation.enabled"
+#define NR_REG_REVELATION_MAX_PER_HOUR "revelation.max_per_hour"
+#define NR_REG_REVELATION_MAX_PER_HOUR_PER_PORTAL "revelation.max_per_hour_per_portal"
+
+/* Appliance-only: */
+#define NR_REG_SNMP_ENABLED "snmp.enabled"
+#define NR_REG_CLOCK_TIMEZONE "clock.timezone"
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/stats/nrstats.h b/dom/media/webrtc/transport/third_party/nrappkit/src/stats/nrstats.h
new file mode 100644
index 0000000000..0b6c2bc3c3
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/stats/nrstats.h
@@ -0,0 +1,118 @@
+/*
+ *
+ * nrstats.h
+ *
+ * $Source: /Users/ekr/tmp/nrappkit-dump/nrappkit/src/stats/nrstats.h,v $
+ * $Revision: 1.4 $
+ * $Date: 2007/06/26 22:37:55 $
+ *
+ * API for keeping and sharing statistics
+ *
+ *
+ * Copyright (C) 2003-2005, Network Resonance, Inc.
+ * Copyright (C) 2006, Network Resonance, 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:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. Neither the name of Network Resonance, Inc. nor the name of any
+ * contributors to this software 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 OWNER 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 __NRSTATS_H__
+#define __NRSTATS_H__
+
+#include <sys/types.h>
+#ifdef WIN32
+#include <time.h>
+#else
+#include <sys/time.h>
+#endif
+#include <r_types.h>
+
+#ifndef CAPTURE_USER
+#define CAPTURE_USER "pcecap"
+#endif
+
+#define NR_MAX_STATS_TYPES 256 /* max number of stats objects */
+#define NR_MAX_STATS_TYPE_NAME 26
+
+typedef struct NR_stats_type_ {
+ char name[NR_MAX_STATS_TYPE_NAME];
+ int (*reset)(void *stats);
+ int (*print)(void *stats, char *stat_namespace, void (*output)(void *handle, const char *fmt, ...), void *handle);
+ int (*get_lib_name)(char **libname);
+ unsigned int size;
+} NR_stats_type;
+
+typedef struct NR_stats_app_ {
+ time_t last_counter_reset;
+ time_t last_restart;
+ UINT8 total_restarts;
+ char version[64];
+} NR_stats_app;
+
+extern NR_stats_type *NR_stats_type_app;
+
+/* everything measured in bytes */
+typedef struct NR_stats_memory_ {
+ UINT8 current_size;
+ UINT8 max_size;
+ UINT8 in_use;
+ UINT8 in_use_max;
+} NR_stats_memory;
+
+extern NR_stats_type *NR_stats_type_memory;
+
+/* all functions below return 0 on success, else they return an
+ * error code */
+
+/* if errprintf is null, warnings and errors will be sent to /dev/null */
+extern int NR_stats_startup(char *app_name, char *user_name, void (*errprintf)(void *handle, const char *fmt, ...), void *errhandle);
+extern int NR_stats_shutdown(void);
+#define NR_STATS_CREATE (1<<0)
+extern int NR_stats_get(char *module_name, NR_stats_type *type, int flag, void **stats);
+extern int NR_stats_clear(void *stats); /* zeroizes */
+extern int NR_stats_reset(void *stats); /* zeros "speedometers" */
+extern int NR_stats_register(NR_stats_type *type);
+extern int NR_stats_acquire_mutex(void *stats);
+extern int NR_stats_release_mutex(void *stats);
+// TODO: should _get_names take an app_name argument (0==all)????
+extern int NR_stats_get_names(unsigned int *nnames, char ***names);
+extern int NR_stats_get_by_name(char *name, NR_stats_type **type, void **stats);
+extern int NR_stats_get_lib_name(void *stats, char **lib_name);
+extern int NR_stats_rmids(void);
+
+extern char *NR_prefix_to_stats_module(char *prefix);
+
+#define NR_INCREMENT_STAT(stat) do { \
+ stat++; if(stat>stat##_max) stat##_max=stat; \
+ } while (0)
+#define NR_UPDATE_STAT(stat,newval) do { \
+ stat=newval; if(stat>stat##_max) stat##_max=stat; \
+ } while (0)
+
+#endif
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/byteorder.c b/dom/media/webrtc/transport/third_party/nrappkit/src/util/byteorder.c
new file mode 100644
index 0000000000..64689accfa
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/byteorder.c
@@ -0,0 +1,73 @@
+/**
+ byteorder.c
+
+
+ Copyright (C) 2007, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ briank@networkresonance.com Wed May 16 16:46:00 PDT 2007
+ */
+
+#include "nr_common.h"
+#ifndef WIN32
+#include <arpa/inet.h>
+#endif
+#include "r_types.h"
+#include "byteorder.h"
+
+#define IS_BIG_ENDIAN (htonl(0x1) == 0x1)
+
+#define BYTE(n,i) (((UCHAR*)&(n))[(i)])
+#define SWAP(n,x,y) tmp=BYTE((n),(x)), \
+ BYTE((n),(x))=BYTE((n),(y)), \
+ BYTE((n),(y))=tmp
+
+UINT8
+nr_htonll(UINT8 hostlonglong)
+{
+ UINT8 netlonglong = hostlonglong;
+ UCHAR tmp;
+
+ if (!IS_BIG_ENDIAN) {
+ SWAP(netlonglong, 0, 7);
+ SWAP(netlonglong, 1, 6);
+ SWAP(netlonglong, 2, 5);
+ SWAP(netlonglong, 3, 4);
+ }
+
+ return netlonglong;
+}
+
+UINT8
+nr_ntohll(UINT8 netlonglong)
+{
+ return nr_htonll(netlonglong);
+}
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/byteorder.h b/dom/media/webrtc/transport/third_party/nrappkit/src/util/byteorder.h
new file mode 100644
index 0000000000..8df0589a63
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/byteorder.h
@@ -0,0 +1,47 @@
+/**
+ byteorder.h
+
+
+ Copyright (C) 2007, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ briank@networkresonance.com Wed May 16 16:46:00 PDT 2007
+ */
+
+
+#ifndef _byteorder_h
+#define _byteorder_h
+
+UINT8 nr_htonll(UINT8 hostlonglong);
+
+UINT8 nr_ntohll(UINT8 netlonglong);
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/hex.c b/dom/media/webrtc/transport/third_party/nrappkit/src/util/hex.c
new file mode 100644
index 0000000000..8212988eda
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/hex.c
@@ -0,0 +1,109 @@
+/**
+ hex.c
+
+
+ Copyright (C) 2001-2003, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ briank@network-resonance.com
+ */
+
+#include <sys/types.h>
+#include <string.h>
+#include <ctype.h>
+#include "r_common.h"
+#include "hex.h"
+#include "r_log.h"
+
+static char bin2hex_map[][3] = { "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0a", "0b", "0c", "0d", "0e", "0f", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "1a", "1b", "1c", "1d", "1e", "1f", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "2a", "2b", "2c", "2d", "2e", "2f", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "3a", "3b", "3c", "3d", "3e", "3f", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "4a", "4b", "4c", "4d", "4e", "4f", "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "5a", "5b", "5c", "5d", "5e", "5f", "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "6a", "6b", "6c", "6d", "6e", "6f", "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "7a", "7b", "7c", "7d", "7e", "7f", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "8a", "8b", "8c", "8d", "8e", "8f", "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "9a", "9b", "9c", "9d", "9e", "9f", "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "aa", "ab", "ac", "ad", "ae", "af", "b0", "b1", "b2", "b3", "b4", "b5", "b6", "b7", "b8", "b9", "ba", "bb", "bc", "bd", "be", "bf", "c0", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "ca", "cb", "cc", "cd", "ce", "cf", "d0", "d1", "d2", "d3", "d4", "d5", "d6", "d7", "d8", "d9", "da", "db", "dc", "dd", "de", "df", "e0", "e1", "e2", "e3", "e4", "e5", "e6", "e7", "e8", "e9", "ea", "eb", "ec", "ed", "ee", "ef", "f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "fa", "fb", "fc", "fd", "fe", "ff" };
+
+static int hex2bin_map[] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0 /* '0' */, 1 /* '1' */, 2 /* '2' */, 3 /* '3' */, 4 /* '4' */, 5 /* '5' */, 6 /* '6' */, 7 /* '7' */, 8 /* '8' */, 9 /* '9' */, -1, -1, -1, -1, -1, -1, -1, 10 /* 'A' */, 11 /* 'B' */, 12 /* 'C' */, 13 /* 'D' */, 14 /* 'E' */, 15 /* 'F' */, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 10 /* 'a' */, 11 /* 'b' */, 12 /* 'c' */, 13 /* 'd' */, 14 /* 'e' */, 15 /* 'f' */, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 };
+
+int
+nr_nbin2hex(UCHAR *bin, size_t binlen, char hex[], size_t size, size_t *len)
+{
+ int _status;
+ size_t i;
+ size_t hexlen;
+
+ hexlen = 2*binlen;
+ if (size < hexlen)
+ ABORT(R_BAD_ARGS);
+
+ for (i = 0; i < binlen; ++i) {
+ *hex++ = bin2hex_map[bin[i]][0];
+ *hex++ = bin2hex_map[bin[i]][1];
+ }
+
+ if (size >= hexlen+1)
+ *hex = '\0';
+
+ *len = hexlen;
+
+ _status=0;
+ abort:
+ return(_status);
+}
+
+
+int
+nr_nhex2bin(char *hex, size_t hexlen, UCHAR bin[], size_t size, size_t *len)
+{
+ int _status;
+ size_t binlen;
+ int h1;
+ int h2;
+ size_t i;
+
+ if (hexlen % 2)
+ ABORT(R_BAD_ARGS);
+
+ binlen = hexlen/2;
+
+ if (size < binlen)
+ ABORT(R_BAD_ARGS);
+
+ for (i = 0; i < binlen; ++i) {
+ h1 = hex2bin_map[(int)*hex++];
+ h2 = hex2bin_map[(int)*hex++];
+
+ if (h1 == -1 || h2 == -1)
+ ABORT(R_BAD_ARGS);
+
+ bin[i] = (h1 << 4) | h2;
+ }
+
+ *len = binlen;
+
+ _status=0;
+ abort:
+ return(_status);
+}
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/hex.h b/dom/media/webrtc/transport/third_party/nrappkit/src/util/hex.h
new file mode 100644
index 0000000000..8c493b533b
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/hex.h
@@ -0,0 +1,47 @@
+/**
+ hex.h
+
+
+ Copyright (C) 2001-2003, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ briank@network-resonance.com
+ */
+
+
+#ifndef _hex_h
+#define _hex_h
+
+int nr_nbin2hex(UCHAR *bin, size_t binlen, char hex[], size_t size, size_t *len);
+int nr_nhex2bin(char *hex, size_t hexlen, UCHAR bin[], size_t size, size_t *len);
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/assoc.h b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/assoc.h
new file mode 100644
index 0000000000..013e788685
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/assoc.h
@@ -0,0 +1,90 @@
+/**
+ assoc.h
+
+
+ Copyright (C) 2002-2003, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ */
+
+/**
+ assoc.h
+
+
+ Copyright (C) 1999-2000 RTFM, Inc.
+ All Rights Reserved
+
+ This package is a SSLv3/TLS protocol analyzer written by Eric Rescorla
+ <ekr@rtfm.com> and licensed by RTFM, Inc.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. All advertising materials mentioning features or use of this software
+ must display the following acknowledgement:
+
+ This product includes software developed by Eric Rescorla for
+ RTFM, Inc.
+
+ 4. Neither the name of RTFM, Inc. nor the name of Eric Rescorla may be
+ used to endorse or promote products derived from this
+ software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY ERIC RESCORLA AND RTFM, INC. ``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 REGENTS 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 SUCH DAMAGE.
+
+ $Id: assoc.h,v 1.2 2006/08/16 19:39:17 adamcain Exp $
+
+
+ ekr@rtfm.com Sun Jan 17 17:56:35 1999
+ */
+
+
+#ifndef _assoc_h
+#define _assoc_h
+
+typedef struct assoc_ assoc;
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/debug.c b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/debug.c
new file mode 100644
index 0000000000..fa796217ea
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/debug.c
@@ -0,0 +1,127 @@
+/**
+ debug.c
+
+
+ Copyright (C) 2002-2003, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ */
+
+/**
+ debug.c
+
+
+ Copyright (C) 1999-2000 RTFM, Inc.
+ All Rights Reserved
+
+ This package is a SSLv3/TLS protocol analyzer written by Eric Rescorla
+ <ekr@rtfm.com> and licensed by RTFM, Inc.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. All advertising materials mentioning features or use of this software
+ must display the following acknowledgement:
+
+ This product includes software developed by Eric Rescorla for
+ RTFM, Inc.
+
+ 4. Neither the name of RTFM, Inc. nor the name of Eric Rescorla may be
+ used to endorse or promote products derived from this
+ software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY ERIC RESCORLA AND RTFM, INC. ``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 REGENTS 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 SUCH DAMAGE.
+
+ $Id: debug.c,v 1.3 2007/06/26 22:37:57 adamcain Exp $
+
+
+ ekr@rtfm.com Wed Jan 6 17:08:58 1999
+ */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include "r_common.h"
+#include "debug.h"
+
+int nr_debug(int class,char *format,...)
+ {
+ va_list ap;
+
+ va_start(ap,format);
+#ifdef WIN32
+ vprintf(format,ap);
+ printf("\n");
+#else
+ vfprintf(stderr,format,ap);
+ fprintf(stderr,"\n");
+#endif
+ return(0);
+ }
+
+int nr_xdump(name,data,len)
+ char *name;
+ UCHAR *data;
+ int len;
+ {
+ int i;
+
+ if(name){
+ printf("%s[%d]=\n",name,len);
+ }
+ for(i=0;i<len;i++){
+
+ if((len>8) && i && !(i%12)){
+ printf("\n");
+ }
+ printf("%.2x ",data[i]&255);
+ }
+ if(i%12)
+ printf("\n");
+ return(0);
+ }
+
+
+
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/debug.h b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/debug.h
new file mode 100644
index 0000000000..34f7b2fb54
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/debug.h
@@ -0,0 +1,94 @@
+/**
+ debug.h
+
+
+ Copyright (C) 2002-2003, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ */
+
+/**
+ debug.h
+
+
+ Copyright (C) 1999-2000 RTFM, Inc.
+ All Rights Reserved
+
+ This package is a SSLv3/TLS protocol analyzer written by Eric Rescorla
+ <ekr@rtfm.com> and licensed by RTFM, Inc.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. All advertising materials mentioning features or use of this software
+ must display the following acknowledgement:
+
+ This product includes software developed by Eric Rescorla for
+ RTFM, Inc.
+
+ 4. Neither the name of RTFM, Inc. nor the name of Eric Rescorla may be
+ used to endorse or promote products derived from this
+ software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY ERIC RESCORLA AND RTFM, INC. ``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 REGENTS 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 SUCH DAMAGE.
+
+ $Id: debug.h,v 1.2 2006/08/16 19:39:17 adamcain Exp $
+
+
+ ekr@rtfm.com Wed Jan 6 17:13:00 1999
+ */
+
+
+#ifndef _debug_h
+#define _debug_h
+
+// Remove debugging mess from ssldump
+// #define DBG(a)
+
+int nr_debug(int class,char *format,...);
+int nr_xdump(char *name,UCHAR *data, int len);
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_assoc.c b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_assoc.c
new file mode 100644
index 0000000000..25b3827d50
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_assoc.c
@@ -0,0 +1,539 @@
+/**
+ r_assoc.c
+
+
+ Copyright (C) 2002-2003, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ */
+
+/**
+ r_assoc.c
+
+ This is an associative array implementation, using an open-chained
+ hash bucket technique.
+
+ Note that this implementation permits each data entry to have
+ separate copy constructors and destructors. This currently wastes
+ space, but could be implemented while saving space by using
+ the high order bit of the length value or somesuch.
+
+ The major problem with this code is it's not resizable, though it
+ could be made so.
+
+
+ Copyright (C) 1999-2000 RTFM, Inc.
+ All Rights Reserved
+
+ This package is a SSLv3/TLS protocol analyzer written by Eric Rescorla
+ <ekr@rtfm.com> and licensed by RTFM, Inc.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. All advertising materials mentioning features or use of this software
+ must display the following acknowledgement:
+
+ This product includes software developed by Eric Rescorla for
+ RTFM, Inc.
+
+ 4. Neither the name of RTFM, Inc. nor the name of Eric Rescorla may be
+ used to endorse or promote products derived from this
+ software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY ERIC RESCORLA AND RTFM, INC. ``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 REGENTS 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 SUCH DAMAGE.
+
+ $Id: r_assoc.c,v 1.4 2007/06/08 17:41:49 adamcain Exp $
+
+ ekr@rtfm.com Sun Jan 17 17:57:15 1999
+ */
+
+#include <r_common.h>
+#include <string.h>
+#include "r_assoc.h"
+
+typedef struct r_assoc_el_ {
+ char *key;
+ int key_len;
+ void *data;
+ struct r_assoc_el_ *prev;
+ struct r_assoc_el_ *next;
+ int (*copy)(void **n,void *old);
+ int (*destroy)(void *ptr);
+} r_assoc_el;
+
+struct r_assoc_ {
+ int size;
+ int bits;
+ int (*hash_func)(char *key,int len,int size);
+ r_assoc_el **chains;
+ UINT4 num_elements;
+};
+
+#define DEFAULT_TABLE_BITS 5
+
+static int destroy_assoc_chain(r_assoc_el *chain);
+static int r_assoc_fetch_bucket(r_assoc *assoc,
+ char *key,int len,r_assoc_el **bucketp);
+static int copy_assoc_chain(r_assoc_el **knewp, r_assoc_el *old);
+
+int r_assoc_create(assocp,hash_func,bits)
+ r_assoc **assocp;
+ int (*hash_func)(char *key,int len,int size);
+ int bits;
+ {
+ r_assoc *assoc=0;
+ int _status;
+
+ if(!(assoc=(r_assoc *)RCALLOC(sizeof(r_assoc))))
+ ABORT(R_NO_MEMORY);
+ assoc->size=(1<<bits);
+ assoc->bits=bits;
+ assoc->hash_func=hash_func;
+
+ if(!(assoc->chains=(r_assoc_el **)RCALLOC(sizeof(r_assoc_el *)*
+ assoc->size)))
+ ABORT(R_NO_MEMORY);
+
+ *assocp=assoc;
+
+ _status=0;
+ abort:
+ if(_status){
+ r_assoc_destroy(&assoc);
+ }
+ return(_status);
+ }
+
+int r_assoc_destroy(assocp)
+ r_assoc **assocp;
+ {
+ r_assoc *assoc;
+ int i;
+
+ if(!assocp || !*assocp)
+ return(0);
+
+ assoc=*assocp;
+ for(i=0;i<assoc->size;i++)
+ destroy_assoc_chain(assoc->chains[i]);
+
+ RFREE(assoc->chains);
+ RFREE(*assocp);
+
+ return(0);
+ }
+
+static int destroy_assoc_chain(chain)
+ r_assoc_el *chain;
+ {
+ r_assoc_el *nxt;
+
+ while(chain){
+ nxt=chain->next;
+
+ if(chain->destroy)
+ chain->destroy(chain->data);
+
+ RFREE(chain->key);
+
+ RFREE(chain);
+ chain=nxt;
+ }
+
+ return(0);
+ }
+
+static int copy_assoc_chain(knewp,old)
+ r_assoc_el **knewp;
+ r_assoc_el *old;
+ {
+ r_assoc_el *knew=0,*ptr,*tmp;
+ int r,_status;
+
+ ptr=0; /* Pacify GCC's uninitialized warning.
+ It's not correct */
+ if(!old) {
+ *knewp=0;
+ return(0);
+ }
+ for(;old;old=old->next){
+ if(!(tmp=(r_assoc_el *)RCALLOC(sizeof(r_assoc_el))))
+ ABORT(R_NO_MEMORY);
+
+ if(!knew){
+ knew=tmp;
+ ptr=knew;
+ }
+ else{
+ ptr->next=tmp;
+ tmp->prev=ptr;
+ ptr=tmp;
+ }
+
+ ptr->destroy=old->destroy;
+ ptr->copy=old->copy;
+
+ if(old->copy){
+ if(r=old->copy(&ptr->data,old->data))
+ ABORT(r);
+ }
+ else
+ ptr->data=old->data;
+
+ if(!(ptr->key=(char *)RMALLOC(old->key_len)))
+ ABORT(R_NO_MEMORY);
+ memcpy(ptr->key,old->key,ptr->key_len=old->key_len);
+ }
+
+ *knewp=knew;
+
+ _status=0;
+ abort:
+ if(_status){
+ destroy_assoc_chain(knew);
+ }
+ return(_status);
+ }
+
+static int r_assoc_fetch_bucket(assoc,key,len,bucketp)
+ r_assoc *assoc;
+ char *key;
+ int len;
+ r_assoc_el **bucketp;
+ {
+ UINT4 hash_value;
+ r_assoc_el *bucket;
+
+ hash_value=assoc->hash_func(key,len,assoc->bits);
+
+ for(bucket=assoc->chains[hash_value];bucket;bucket=bucket->next){
+ if(bucket->key_len == len && !memcmp(bucket->key,key,len)){
+ *bucketp=bucket;
+ return(0);
+ }
+ }
+
+ return(R_NOT_FOUND);
+ }
+
+int r_assoc_fetch(assoc,key,len,datap)
+ r_assoc *assoc;
+ char *key;
+ int len;
+ void **datap;
+ {
+ r_assoc_el *bucket;
+ int r;
+
+ if(r=r_assoc_fetch_bucket(assoc,key,len,&bucket)){
+ if(r!=R_NOT_FOUND)
+ ERETURN(r);
+ return(r);
+ }
+
+ *datap=bucket->data;
+ return(0);
+ }
+
+int r_assoc_insert(assoc,key,len,data,copy,destroy,how)
+ r_assoc *assoc;
+ char *key;
+ int len;
+ void *data;
+ int (*copy)(void **knew,void *old);
+ int (*destroy)(void *ptr);
+ int how;
+ {
+ r_assoc_el *bucket,*new_bucket=0;
+ int r,_status;
+
+ if(r=r_assoc_fetch_bucket(assoc,key,len,&bucket)){
+ /*Note that we compute the hash value twice*/
+ UINT4 hash_value;
+
+ if(r!=R_NOT_FOUND)
+ ABORT(r);
+ hash_value=assoc->hash_func(key,len,assoc->bits);
+
+ if(!(new_bucket=(r_assoc_el *)RCALLOC(sizeof(r_assoc_el))))
+ ABORT(R_NO_MEMORY);
+ if(!(new_bucket->key=(char *)RMALLOC(len)))
+ ABORT(R_NO_MEMORY);
+ memcpy(new_bucket->key,key,len);
+ new_bucket->key_len=len;
+
+ /*Insert at the list head. Is FIFO a good algorithm?*/
+ if(assoc->chains[hash_value])
+ assoc->chains[hash_value]->prev=new_bucket;
+ new_bucket->next=assoc->chains[hash_value];
+ assoc->chains[hash_value]=new_bucket;
+ bucket=new_bucket;
+ }
+ else{
+ if(!(how&R_ASSOC_REPLACE))
+ ABORT(R_ALREADY);
+
+ if(bucket->destroy)
+ bucket->destroy(bucket->data);
+ }
+
+ bucket->data=data;
+ bucket->copy=copy;
+ bucket->destroy=destroy;
+ assoc->num_elements++;
+
+ _status=0;
+ abort:
+ if(_status && new_bucket){
+ RFREE(new_bucket->key);
+ RFREE(new_bucket);
+ }
+ return(_status);
+ }
+
+int r_assoc_delete(assoc,key,len)
+ r_assoc *assoc;
+ char *key;
+ int len;
+ {
+ int r;
+ r_assoc_el *bucket;
+ UINT4 hash_value;
+
+ if(r=r_assoc_fetch_bucket(assoc,key,len,&bucket)){
+ if(r!=R_NOT_FOUND)
+ ERETURN(r);
+ return(r);
+ }
+
+ /* Now remove the element from the hash chain */
+ if(bucket->prev){
+ bucket->prev->next=bucket->next;
+ }
+ else {
+ hash_value=assoc->hash_func(key,len,assoc->bits);
+ assoc->chains[hash_value]=bucket->next;
+ }
+
+ if(bucket->next)
+ bucket->next->prev=bucket->prev;
+
+ /* Remove the data */
+ if(bucket->destroy)
+ bucket->destroy(bucket->data);
+
+ RFREE(bucket->key);
+ RFREE(bucket);
+ assoc->num_elements--;
+
+ return(0);
+ }
+
+int r_assoc_copy(knewp,old)
+ r_assoc **knewp;
+ r_assoc *old;
+ {
+ int r,_status,i;
+ r_assoc *knew;
+
+ if(!(knew=(r_assoc *)RCALLOC(sizeof(r_assoc))))
+ ABORT(R_NO_MEMORY);
+ knew->size=old->size;
+ knew->bits=old->bits;
+ knew->hash_func=old->hash_func;
+
+ if(!(knew->chains=(r_assoc_el **)RCALLOC(sizeof(r_assoc_el)*old->size)))
+ ABORT(R_NO_MEMORY);
+ for(i=0;i<knew->size;i++){
+ if(r=copy_assoc_chain(knew->chains+i,old->chains[i]))
+ ABORT(r);
+ }
+ knew->num_elements=old->num_elements;
+
+ *knewp=knew;
+
+ _status=0;
+ abort:
+ if(_status){
+ r_assoc_destroy(&knew);
+ }
+ return(_status);
+ }
+
+int r_assoc_num_elements(r_assoc *assoc,int *sizep)
+ {
+ *sizep=assoc->num_elements;
+
+ return(0);
+ }
+
+int r_assoc_init_iter(assoc,iter)
+ r_assoc *assoc;
+ r_assoc_iterator *iter;
+ {
+ int i;
+
+ iter->assoc=assoc;
+ iter->prev_chain=-1;
+ iter->prev=0;
+
+ iter->next_chain=assoc->size;
+ iter->next=0;
+
+ for(i=0;i<assoc->size;i++){
+ if(assoc->chains[i]!=0){
+ iter->next_chain=i;
+ iter->next=assoc->chains[i];
+ break;
+ }
+ }
+
+ return(0);
+ }
+
+int r_assoc_iter(iter,key,keyl,val)
+ r_assoc_iterator *iter;
+ void **key;
+ int *keyl;
+ void **val;
+ {
+ int i;
+ r_assoc_el *ret;
+
+ if(!iter->next)
+ return(R_EOD);
+ ret=iter->next;
+
+ *key=ret->key;
+ *keyl=ret->key_len;
+ *val=ret->data;
+
+ /* Now increment */
+ iter->prev_chain=iter->next_chain;
+ iter->prev=iter->next;
+
+ /* More on this chain */
+ if(iter->next->next){
+ iter->next=iter->next->next;
+ }
+ else{
+ iter->next=0;
+
+ /* FInd the next occupied chain*/
+ for(i=iter->next_chain+1;i<iter->assoc->size;i++){
+ if(iter->assoc->chains[i]){
+ iter->next_chain=i;
+ iter->next=iter->assoc->chains[i];
+ break;
+ }
+ }
+ }
+
+ return(0);
+ }
+
+/* Delete the last returned value*/
+int r_assoc_iter_delete(iter)
+ r_assoc_iterator *iter;
+ {
+ /* First unhook it from the list*/
+ if(!iter->prev->prev){
+ /* First element*/
+ iter->assoc->chains[iter->prev_chain]=iter->prev->next;
+ }
+ else{
+ iter->prev->prev->next=iter->prev->next;
+ }
+
+ if(iter->prev->next){
+ iter->prev->next->prev=iter->prev->prev;
+ }
+
+ if (iter->prev->destroy)
+ iter->prev->destroy(iter->prev->data);
+
+ iter->assoc->num_elements--;
+ RFREE(iter->prev->key);
+ RFREE(iter->prev);
+ return(0);
+ }
+
+
+/*This is a hack from AMS. Supposedly, it's pretty good for strings, even
+ though it doesn't take into account all the data*/
+int r_assoc_simple_hash_compute(key,len,bits)
+ char *key;
+ int len;
+ int bits;
+ {
+ UINT4 h=0;
+
+ h=key[0] +(key[len-1] * len);
+
+ h &= (1<<bits) - 1;
+
+ return(h);
+ }
+
+
+int r_crc32(char *data,int len,UINT4 *crcval);
+
+int r_assoc_crc32_hash_compute(data,len,bits)
+ char *data;
+ int len;
+ int bits;
+ {
+ UINT4 res;
+ UINT4 mask;
+
+ /* First compute the CRC value */
+ if(r_crc32(data,len,&res))
+ ERETURN(R_INTERNAL);
+
+ mask=~(0xffffffff<<bits);
+
+ return(res & mask);
+ }
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_assoc.h b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_assoc.h
new file mode 100644
index 0000000000..5311542c9d
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_assoc.h
@@ -0,0 +1,126 @@
+/**
+ r_assoc.h
+
+
+ Copyright (C) 2002-2003, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ */
+
+/**
+ r_assoc.h
+
+ Associative array code. This code has the advantage that different
+ elements can have different create and destroy operators. Unfortunately,
+ this can waste space.
+
+
+ Copyright (C) 1999-2000 RTFM, Inc.
+ All Rights Reserved
+
+ This package is a SSLv3/TLS protocol analyzer written by Eric Rescorla
+ <ekr@rtfm.com> and licensed by RTFM, Inc.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. All advertising materials mentioning features or use of this software
+ must display the following acknowledgement:
+
+ This product includes software developed by Eric Rescorla for
+ RTFM, Inc.
+
+ 4. Neither the name of RTFM, Inc. nor the name of Eric Rescorla may be
+ used to endorse or promote products derived from this
+ software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY ERIC RESCORLA AND RTFM, INC. ``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 REGENTS 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 SUCH DAMAGE.
+
+ $Id: r_assoc.h,v 1.3 2007/06/08 22:16:08 adamcain Exp $
+
+
+ ekr@rtfm.com Sun Jan 17 17:57:18 1999
+ */
+
+
+#ifndef _r_assoc_h
+#define _r_assoc_h
+
+typedef struct r_assoc_ r_assoc;
+
+int r_assoc_create(r_assoc **assocp,
+ int (*hash_func)(char *,int,int),
+ int bits);
+int r_assoc_insert(r_assoc *assoc,char *key,int len,
+ void *value,int (*copy)(void **knew,void *old),
+ int (*destroy)(void *ptr),int how);
+#define R_ASSOC_REPLACE 0x1
+#define R_ASSOC_NEW 0x2
+
+int r_assoc_fetch(r_assoc *assoc,char *key, int len, void **value);
+int r_assoc_delete(r_assoc *assoc,char *key, int len);
+
+int r_assoc_copy(r_assoc **knew,r_assoc *old);
+int r_assoc_destroy(r_assoc **assocp);
+int r_assoc_simple_hash_compute(char *key, int len,int bits);
+int r_assoc_crc32_hash_compute(char *key, int len,int bits);
+
+/*We need iterators, but I haven't written them yet*/
+typedef struct r_assoc_iterator_ {
+ r_assoc *assoc;
+ int prev_chain;
+ struct r_assoc_el_ *prev;
+ int next_chain;
+ struct r_assoc_el_ *next;
+} r_assoc_iterator;
+
+int r_assoc_init_iter(r_assoc *assoc,r_assoc_iterator *);
+int r_assoc_iter(r_assoc_iterator *iter,void **key,int *keyl, void **val);
+int r_assoc_iter_delete(r_assoc_iterator *);
+
+int r_assoc_num_elements(r_assoc *assoc,int *sizep);
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_common.h b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_common.h
new file mode 100644
index 0000000000..c11bb3d0fd
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_common.h
@@ -0,0 +1,100 @@
+/**
+ r_common.h
+
+
+ Copyright (C) 2002-2003, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ */
+
+/**
+ r_common.h
+
+
+ Copyright (C) 1999-2000 RTFM, Inc.
+ All Rights Reserved
+
+ This package is a SSLv3/TLS protocol analyzer written by Eric Rescorla
+ <ekr@rtfm.com> and licensed by RTFM, Inc.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. All advertising materials mentioning features or use of this software
+ must display the following acknowledgement:
+
+ This product includes software developed by Eric Rescorla for
+ RTFM, Inc.
+
+ 4. Neither the name of RTFM, Inc. nor the name of Eric Rescorla may be
+ used to endorse or promote products derived from this
+ software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY ERIC RESCORLA AND RTFM, INC. ``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 REGENTS 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 SUCH DAMAGE.
+
+ $Id: r_common.h,v 1.2 2006/08/16 19:39:17 adamcain Exp $
+
+
+ ekr@rtfm.com Tue Dec 22 10:40:07 1998
+ */
+
+
+#ifndef _r_common_h
+#define _r_common_h
+
+#include "r_defaults.h"
+#include "r_includes.h"
+#include "r_types.h"
+#include "r_macros.h"
+#include "r_errors.h"
+#include "r_data.h"
+
+/* defines for possibly replaced functions */
+#ifndef HAVE_STRDUP
+char *strdup(char *in);
+#endif
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_crc32.c b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_crc32.c
new file mode 100644
index 0000000000..38d3e4da38
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_crc32.c
@@ -0,0 +1,175 @@
+/**
+ r_crc32.c
+
+
+ Copyright (C) 2002-2003, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ */
+
+/**
+ crc32.c
+
+ Copyright (C) 2003, RTFM, Inc.
+ All Rights Reserved.
+
+ ekr@rtfm.com Fri Jan 31 13:57:43 2003
+
+ THE FOLLOWING CODE WAS EXTRACTED FROM FreeBSD
+ The comment below was the original header
+
+ The main function was modified to process a buffer
+ rather than a file
+ */
+
+/*
+ * This code implements the AUTODIN II polynomial used by Ethernet,
+ * and can be used to calculate multicast address hash indices.
+ * It assumes that the low order bits will be transmitted first,
+ * and consequently the low byte should be sent first when
+ * the crc computation is finished. The crc should be complemented
+ * before transmission.
+ * The variable corresponding to the macro argument "crc" should
+ * be an unsigned long and should be preset to all ones for Ethernet
+ * use. An error-free packet will leave 0xDEBB20E3 in the crc.
+ * Spencer Garrett <srg@quick.com>
+ */
+
+
+#include <sys/types.h>
+#include <r_types.h>
+
+#ifdef WIN32
+#define u_int32_t UINT4
+#endif
+
+#define CRC(crc, ch) (crc = (crc >> 8) ^ crctab[(crc ^ (ch)) & 0xff])
+
+/* generated using the AUTODIN II polynomial
+ * x^32 + x^26 + x^23 + x^22 + x^16 +
+ * x^12 + x^11 + x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x^1 + 1
+ */
+static const u_int32_t crctab[256] = {
+ 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,
+ 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
+ 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
+ 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
+ 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
+ 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
+ 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
+ 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
+ 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
+ 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
+ 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,
+ 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
+ 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,
+ 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
+ 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
+ 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
+ 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,
+ 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
+ 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,
+ 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
+ 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
+ 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
+ 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,
+ 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
+ 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
+ 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
+ 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
+ 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
+ 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,
+ 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
+ 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,
+ 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
+ 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
+ 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
+ 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
+ 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
+ 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,
+ 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
+ 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
+ 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
+ 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,
+ 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
+ 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,
+ 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
+ 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
+ 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
+ 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,
+ 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
+ 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
+ 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
+ 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
+ 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
+ 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,
+ 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
+ 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
+ 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
+ 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
+ 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
+ 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,
+ 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
+ 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,
+ 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
+ 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
+ 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
+};
+
+#include <stdio.h>
+#include <sys/types.h>
+
+
+int r_crc32 (char *buf,int dlen,u_int32_t *cval);
+
+int
+r_crc32(buf, dlen, cval)
+ char *buf;
+ int dlen;
+ u_int32_t *cval;
+{
+ u_int32_t crc = ~0;
+ char *p ;
+ int i;
+ u_int32_t crc32_total = 0 ;
+
+ p=buf;
+
+ for(i=0;i<dlen;i++){
+ CRC(crc, *p) ;
+ CRC(crc32_total, *p) ;
+ p++;
+ }
+
+ *cval = ~crc ;
+
+ return 0 ;
+}
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_crc32.h b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_crc32.h
new file mode 100644
index 0000000000..c11be4e04d
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_crc32.h
@@ -0,0 +1,14 @@
+/**
+ r_crc32.h
+
+ Copyright (C) 2002-2003, Network Resonance, Inc.
+ All Rights Reserved.
+
+ */
+
+#ifndef _r_crc32_
+#define _r_crc32_
+
+int r_crc32 (char *buf,int dlen, UINT4 *cval);
+
+#endif
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_data.c b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_data.c
new file mode 100644
index 0000000000..dfb7af2d5c
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_data.c
@@ -0,0 +1,248 @@
+/**
+ r_data.c
+
+
+ Copyright (C) 2002-2003, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ */
+
+/**
+ r_data.c
+
+
+ Copyright (C) 1999-2000 RTFM, Inc.
+ All Rights Reserved
+
+ This package is a SSLv3/TLS protocol analyzer written by Eric Rescorla
+ <ekr@rtfm.com> and licensed by RTFM, Inc.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. All advertising materials mentioning features or use of this software
+ must display the following acknowledgement:
+
+ This product includes software developed by Eric Rescorla for
+ RTFM, Inc.
+
+ 4. Neither the name of RTFM, Inc. nor the name of Eric Rescorla may be
+ used to endorse or promote products derived from this
+ software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY ERIC RESCORLA AND RTFM, INC. ``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 REGENTS 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 SUCH DAMAGE.
+
+ $Id: r_data.c,v 1.2 2006/08/16 19:39:17 adamcain Exp $
+
+ ekr@rtfm.com Tue Aug 17 15:39:50 1999
+ */
+
+#include <string.h>
+#include <r_common.h>
+#include <r_data.h>
+#include <string.h>
+
+int r_data_create(dp,d,l)
+ Data **dp;
+ const UCHAR *d;
+ size_t l;
+ {
+ Data *d_=0;
+ int _status;
+
+ if(!(d_=(Data *)RCALLOC(sizeof(Data))))
+ ABORT(R_NO_MEMORY);
+ if(!(d_->data=(UCHAR *)RMALLOC(l)))
+ ABORT(R_NO_MEMORY);
+
+ if (d) {
+ memcpy(d_->data,d,l);
+ }
+ d_->len=l;
+
+ *dp=d_;
+
+ _status=0;
+ abort:
+ if(_status)
+ r_data_destroy(&d_);
+
+ return(_status);
+ }
+
+
+int r_data_alloc_mem(d,l)
+ Data *d;
+ size_t l;
+ {
+ int _status;
+
+ if(!(d->data=(UCHAR *)RMALLOC(l)))
+ ABORT(R_NO_MEMORY);
+ d->len=l;
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int r_data_alloc(dp,l)
+ Data **dp;
+ size_t l;
+ {
+ Data *d_=0;
+ int _status;
+
+ if(!(d_=(Data *)RCALLOC(sizeof(Data))))
+ ABORT(R_NO_MEMORY);
+ if(!(d_->data=(UCHAR *)RCALLOC(l)))
+ ABORT(R_NO_MEMORY);
+
+ d_->len=l;
+
+ *dp=d_;
+ _status=0;
+ abort:
+ if(_status)
+ r_data_destroy(&d_);
+
+ return(_status);
+ }
+
+int r_data_make(dp,d,l)
+ Data *dp;
+ const UCHAR *d;
+ size_t l;
+ {
+ if(!(dp->data=(UCHAR *)RMALLOC(l)))
+ ERETURN(R_NO_MEMORY);
+
+ memcpy(dp->data,d,l);
+ dp->len=l;
+
+ return(0);
+ }
+
+int r_data_destroy(dp)
+ Data **dp;
+ {
+ if(!dp || !*dp)
+ return(0);
+
+ if((*dp)->data)
+ RFREE((*dp)->data);
+
+ RFREE(*dp);
+ *dp=0;
+
+ return(0);
+ }
+
+int r_data_destroy_v(v)
+ void *v;
+ {
+ Data *d;
+
+ if(!v)
+ return(0);
+
+ d=(Data *)v;
+ r_data_zfree(d);
+
+ RFREE(d);
+
+ return(0);
+ }
+
+int r_data_destroy_vp(v)
+ void **v;
+ {
+ Data *d;
+
+ if(!v || !*v)
+ return(0);
+
+ d=(Data *)*v;
+ r_data_zfree(d);
+
+ *v=0;
+ RFREE(d);
+
+ return(0);
+ }
+
+int r_data_copy(dst,src)
+ Data *dst;
+ Data *src;
+ {
+ if(!(dst->data=(UCHAR *)RMALLOC(src->len)))
+ ERETURN(R_NO_MEMORY);
+ memcpy(dst->data,src->data,dst->len=src->len);
+ return(0);
+ }
+
+int r_data_zfree(d)
+ Data *d;
+ {
+ if(!d)
+ return(0);
+ if(!d->data)
+ return(0);
+ memset(d->data,0,d->len);
+ RFREE(d->data);
+ return(0);
+ }
+
+int r_data_compare(d1,d2)
+ Data *d1;
+ Data *d2;
+ {
+ if(d1->len<d2->len)
+ return(-1);
+ if(d2->len<d1->len)
+ return(-1);
+ return(memcmp(d1->data,d2->data,d1->len));
+ }
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_data.h b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_data.h
new file mode 100644
index 0000000000..3edf7b287a
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_data.h
@@ -0,0 +1,108 @@
+/**
+ r_data.h
+
+
+ Copyright (C) 2002-2003, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ */
+
+/**
+ r_data.h
+
+
+ Copyright (C) 1999-2000 RTFM, Inc.
+ All Rights Reserved
+
+ This package is a SSLv3/TLS protocol analyzer written by Eric Rescorla
+ <ekr@rtfm.com> and licensed by RTFM, Inc.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. All advertising materials mentioning features or use of this software
+ must display the following acknowledgement:
+
+ This product includes software developed by Eric Rescorla for
+ RTFM, Inc.
+
+ 4. Neither the name of RTFM, Inc. nor the name of Eric Rescorla may be
+ used to endorse or promote products derived from this
+ software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY ERIC RESCORLA AND RTFM, INC. ``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 REGENTS 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 SUCH DAMAGE.
+
+ $Id: r_data.h,v 1.2 2006/08/16 19:39:17 adamcain Exp $
+
+
+ ekr@rtfm.com Wed Feb 10 14:18:19 1999
+ */
+
+
+#ifndef _r_data_h
+#define _r_data_h
+
+typedef struct Data_ {
+ UCHAR *data;
+ size_t len;
+} Data;
+
+int r_data_create(Data **dp,const UCHAR *d,size_t l);
+int r_data_alloc(Data **dp, size_t l);
+int r_data_make(Data *dp, const UCHAR *d,size_t l);
+int r_data_alloc_mem(Data *d,size_t l);
+int r_data_destroy(Data **dp);
+int r_data_destroy_v(void *v);
+int r_data_destroy_vp(void **vp);
+int r_data_copy(Data *dst,Data *src);
+int r_data_zfree(Data *d);
+int r_data_compare(Data *d1,Data *d2);
+
+#define INIT_DATA(a,b,c) (a).data=b; (a).len=c
+#define ATTACH_DATA(a,b) (a).data=b; (a).len=sizeof(b)
+#define ZERO_DATA(a) (a).data=0; (a).len=0
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_defaults.h b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_defaults.h
new file mode 100644
index 0000000000..0ec91a0331
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_defaults.h
@@ -0,0 +1,91 @@
+/**
+ r_defaults.h
+
+
+ Copyright (C) 2002-2003, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ */
+
+/**
+ r_defaults.h
+
+
+ Copyright (C) 1999-2000 RTFM, Inc.
+ All Rights Reserved
+
+ This package is a SSLv3/TLS protocol analyzer written by Eric Rescorla
+ <ekr@rtfm.com> and licensed by RTFM, Inc.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. All advertising materials mentioning features or use of this software
+ must display the following acknowledgement:
+
+ This product includes software developed by Eric Rescorla for
+ RTFM, Inc.
+
+ 4. Neither the name of RTFM, Inc. nor the name of Eric Rescorla may be
+ used to endorse or promote products derived from this
+ software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY ERIC RESCORLA AND RTFM, INC. ``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 REGENTS 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 SUCH DAMAGE.
+
+ $Id: r_defaults.h,v 1.2 2006/08/16 19:39:17 adamcain Exp $
+
+
+ ekr@rtfm.com Tue Dec 22 10:39:14 1998
+ */
+
+
+#ifndef _r_defaults_h
+#define _r_defaults_h
+
+/*The needs defines don't belong here*/
+#define R_NEEDS_STDLIB_H
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_errors.c b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_errors.c
new file mode 100644
index 0000000000..e770e02438
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_errors.c
@@ -0,0 +1,136 @@
+/**
+ r_errors.c
+
+
+ Copyright (C) 2002-2003, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ */
+
+/**
+ r_errors.c
+
+
+ Copyright (C) 1999-2000 RTFM, Inc.
+ All Rights Reserved
+
+ This package is a SSLv3/TLS protocol analyzer written by Eric Rescorla
+ <ekr@rtfm.com> and licensed by RTFM, Inc.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. All advertising materials mentioning features or use of this software
+ must display the following acknowledgement:
+
+ This product includes software developed by Eric Rescorla for
+ RTFM, Inc.
+
+ 4. Neither the name of RTFM, Inc. nor the name of Eric Rescorla may be
+ used to endorse or promote products derived from this
+ software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY ERIC RESCORLA AND RTFM, INC. ``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 REGENTS 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 SUCH DAMAGE.
+
+ $Id: r_errors.c,v 1.5 2008/11/26 03:22:02 adamcain Exp $
+
+
+ ekr@rtfm.com Tue Feb 16 16:37:05 1999
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include "r_common.h"
+#include "r_errors.h"
+
+static struct {
+ int errnum;
+ char *str;
+} errors[] = NR_ERROR_MAPPING;
+
+int nr_verr_exit(char *fmt,...)
+ {
+ va_list ap;
+
+ va_start(ap,fmt);
+ vfprintf(stderr,fmt,ap);
+
+ if (fmt[0] != '\0' && fmt[strlen(fmt)-1] != '\n')
+ fprintf(stderr,"\n");
+
+ exit(1);
+ }
+
+char *
+nr_strerror(int errnum)
+{
+ static char unknown_error[256];
+ size_t i;
+ char *error = 0;
+
+ for (i = 0; i < sizeof(errors)/sizeof(*errors); ++i) {
+ if (errnum == errors[i].errnum) {
+ error = errors[i].str;
+ break;
+ }
+ }
+
+ if (! error) {
+ snprintf(unknown_error, sizeof(unknown_error), "Unknown error: %d", errnum);
+ error = unknown_error;
+ }
+
+ return error;
+}
+
+int
+nr_strerror_r(int errnum, char *strerrbuf, size_t buflen)
+{
+ char *error = nr_strerror(errnum);
+ snprintf(strerrbuf, buflen, "%s", error);
+ return 0;
+}
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_errors.h b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_errors.h
new file mode 100644
index 0000000000..f52375b98d
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_errors.h
@@ -0,0 +1,127 @@
+/**
+ r_errors.h
+
+
+ Copyright (C) 2002-2003, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ */
+
+/**
+ r_errors.h
+
+
+ Copyright (C) 1999-2000 RTFM, Inc.
+ All Rights Reserved
+
+ This package is a SSLv3/TLS protocol analyzer written by Eric Rescorla
+ <ekr@rtfm.com> and licensed by RTFM, Inc.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. All advertising materials mentioning features or use of this software
+ must display the following acknowledgement:
+
+ This product includes software developed by Eric Rescorla for
+ RTFM, Inc.
+
+ 4. Neither the name of RTFM, Inc. nor the name of Eric Rescorla may be
+ used to endorse or promote products derived from this
+ software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY ERIC RESCORLA AND RTFM, INC. ``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 REGENTS 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 SUCH DAMAGE.
+
+ $Id: r_errors.h,v 1.4 2007/10/12 20:53:24 adamcain Exp $
+
+
+ ekr@rtfm.com Tue Dec 22 10:59:49 1998
+ */
+
+
+#ifndef _r_errors_h
+#define _r_errors_h
+
+#define R_NO_MEMORY 1 /*out of memory*/
+#define R_NOT_FOUND 2 /*Item not found*/
+#define R_INTERNAL 3 /*Unspecified internal error*/
+#define R_ALREADY 4 /*Action already done*/
+#define R_EOD 5 /*end of data*/
+#define R_BAD_ARGS 6 /*Bad arguments*/
+#define R_BAD_DATA 7 /*Bad data*/
+#define R_WOULDBLOCK 8 /*Operation would block */
+#define R_QUEUED 9 /*Operation was queued */
+#define R_FAILED 10 /*Operation failed */
+#define R_REJECTED 11 /* We don't care about this */
+#define R_INTERRUPTED 12 /* Operation interrupted */
+#define R_IO_ERROR 13 /* I/O Error */
+#define R_NOT_PERMITTED 14 /* Permission denied */
+#define R_RETRY 15 /* Retry possible */
+
+#define NR_ERROR_MAPPING {\
+ { R_NO_MEMORY, "Cannot allocate memory" },\
+ { R_NOT_FOUND, "Item not found" },\
+ { R_INTERNAL, "Internal failure" },\
+ { R_ALREADY, "Action already performed" },\
+ { R_EOD, "End of data" },\
+ { R_BAD_ARGS, "Invalid argument" },\
+ { R_BAD_DATA, "Invalid data" },\
+ { R_WOULDBLOCK, "Operation would block" },\
+ { R_QUEUED, "Operation queued" },\
+ { R_FAILED, "Operation failed" },\
+ { R_REJECTED, "Operation rejected" },\
+ { R_INTERRUPTED, "Operation interrupted" },\
+ { R_IO_ERROR, "I/O error" },\
+ { R_NOT_PERMITTED, "Permission Denied" },\
+ { R_RETRY, "Retry may be possible" },\
+ }
+
+int nr_verr_exit(char *fmt,...);
+
+char *nr_strerror(int errnum);
+int nr_strerror_r(int errnum, char *strerrbuf, size_t buflen);
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_includes.h b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_includes.h
new file mode 100644
index 0000000000..4fb7af5643
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_includes.h
@@ -0,0 +1,98 @@
+/**
+ r_includes.h
+
+
+ Copyright (C) 2002-2003, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ */
+
+/**
+ r_includes.h
+
+
+ Copyright (C) 1999-2000 RTFM, Inc.
+ All Rights Reserved
+
+ This package is a SSLv3/TLS protocol analyzer written by Eric Rescorla
+ <ekr@rtfm.com> and licensed by RTFM, Inc.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. All advertising materials mentioning features or use of this software
+ must display the following acknowledgement:
+
+ This product includes software developed by Eric Rescorla for
+ RTFM, Inc.
+
+ 4. Neither the name of RTFM, Inc. nor the name of Eric Rescorla may be
+ used to endorse or promote products derived from this
+ software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY ERIC RESCORLA AND RTFM, INC. ``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 REGENTS 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 SUCH DAMAGE.
+
+ $Id: r_includes.h,v 1.2 2006/08/16 19:39:17 adamcain Exp $
+
+
+ ekr@rtfm.com Tue Dec 22 11:38:50 1998
+ */
+
+
+#ifndef _r_includes_h
+#define _r_includes_h
+
+#ifdef R_NEEDS_STDLIB_H
+#include <stdlib.h>
+#endif
+
+#ifdef R_NEEDS_MEMORY_H
+#include <memory.h>
+#endif
+
+#include <stdio.h>
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_list.c b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_list.c
new file mode 100644
index 0000000000..4e71d67030
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_list.c
@@ -0,0 +1,273 @@
+/**
+ r_list.c
+
+
+ Copyright (C) 2002-2003, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ */
+
+/**
+ r_list.c
+
+
+ Copyright (C) 1999-2000 RTFM, Inc.
+ All Rights Reserved
+
+ This package is a SSLv3/TLS protocol analyzer written by Eric Rescorla
+ <ekr@rtfm.com> and licensed by RTFM, Inc.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. All advertising materials mentioning features or use of this software
+ must display the following acknowledgement:
+
+ This product includes software developed by Eric Rescorla for
+ RTFM, Inc.
+
+ 4. Neither the name of RTFM, Inc. nor the name of Eric Rescorla may be
+ used to endorse or promote products derived from this
+ software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY ERIC RESCORLA AND RTFM, INC. ``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 REGENTS 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 SUCH DAMAGE.
+
+ $Id: r_list.c,v 1.2 2006/08/16 19:39:17 adamcain Exp $
+
+
+ ekr@rtfm.com Tue Jan 19 08:36:39 1999
+ */
+
+#include <r_common.h>
+#include "r_list.h"
+
+typedef struct r_list_el_ {
+ void *data;
+ struct r_list_el_ *next;
+ struct r_list_el_ *prev;
+ int (*copy)(void **new,void *old);
+ int (*destroy)(void **ptr);
+} r_list_el;
+
+struct r_list_ {
+ struct r_list_el_ *first;
+ struct r_list_el_ *last;
+};
+
+int r_list_create(listp)
+ r_list **listp;
+ {
+ r_list *list=0;
+ int _status;
+
+ if(!(list=(r_list *)RCALLOC(sizeof(r_list))))
+ ABORT(R_NO_MEMORY);
+
+ list->first=0;
+ list->last=0;
+ *listp=list;
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int r_list_destroy(listp)
+ r_list **listp;
+ {
+ r_list *list;
+ r_list_el *el;
+
+ if(!listp || !*listp)
+ return(0);
+ list=*listp;
+
+ el=list->first;
+
+ while(el){
+ r_list_el *el_t;
+
+ if(el->destroy && el->data)
+ el->destroy(&el->data);
+ el_t=el;
+ el=el->next;
+ RFREE(el_t);
+ }
+
+ RFREE(list);
+ *listp=0;
+
+ return(0);
+ }
+
+int r_list_copy(outp,in)
+ r_list**outp;
+ r_list *in;
+ {
+ r_list *out=0;
+ r_list_el *el,*el2,*last=0;
+ int r, _status;
+
+ if(!in){
+ *outp=0;
+ return(0);
+ }
+
+ if(r=r_list_create(&out))
+ ABORT(r);
+
+ for(el=in->first;el;el=el->next){
+ if(!(el2=(r_list_el *)RCALLOC(sizeof(r_list_el))))
+ ABORT(R_NO_MEMORY);
+
+ if(el->copy && el->data){
+ if(r=el->copy(&el2->data,el->data))
+ ABORT(r);
+ }
+
+ el2->copy=el->copy;
+ el2->destroy=el->destroy;
+
+ if(!(out->first))
+ out->first=el2;
+
+ el2->prev=last;
+ if(last) last->next=el2;
+ last=el2;
+ }
+
+ out->last=last;
+
+ *outp=out;
+
+ _status=0;
+ abort:
+ if(_status)
+ r_list_destroy(&out);
+ return(_status);
+ }
+
+int r_list_insert(list,value,copy,destroy)
+ r_list *list;
+ void *value;
+ int (*copy)(void **out, void *in);
+ int (*destroy)(void **val);
+ {
+ r_list_el *el=0;
+ int _status;
+
+ if(!(el=(r_list_el *)RCALLOC(sizeof(r_list_el))))
+ ABORT(R_NO_MEMORY);
+ el->data=value;
+ el->copy=copy;
+ el->destroy=destroy;
+
+ el->prev=0;
+ el->next=list->first;
+ if(list->first){
+ list->first->prev=el;
+ }
+ list->first=el;
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int r_list_append(list,value,copy,destroy)
+ r_list *list;
+ void *value;
+ int (*copy)(void **out, void *in);
+ int (*destroy)(void **val);
+ {
+ r_list_el *el=0;
+ int _status;
+
+ if(!(el=(r_list_el *)RCALLOC(sizeof(r_list_el))))
+ ABORT(R_NO_MEMORY);
+ el->data=value;
+ el->copy=copy;
+ el->destroy=destroy;
+
+ el->prev=list->last;
+ el->next=0;
+
+ if(list->last) list->last->next=el;
+ else list->first=el;
+
+ list->last=el;
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int r_list_init_iter(list,iter)
+ r_list *list;
+ r_list_iterator *iter;
+ {
+ iter->list=list;
+ iter->ptr=list->first;
+
+ return(0);
+ }
+
+int r_list_iter(iter,val)
+ r_list_iterator *iter;
+ void **val;
+ {
+ if(!iter->ptr)
+ return(R_EOD);
+
+ *val=iter->ptr->data;
+ iter->ptr=iter->ptr->next;
+
+ return(0);
+ }
+
+
+
+
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_list.h b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_list.h
new file mode 100644
index 0000000000..bbefc6c39f
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_list.h
@@ -0,0 +1,106 @@
+/**
+ r_list.h
+
+
+ Copyright (C) 2002-2003, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ */
+
+/**
+ r_list.h
+
+
+ Copyright (C) 1999-2000 RTFM, Inc.
+ All Rights Reserved
+
+ This package is a SSLv3/TLS protocol analyzer written by Eric Rescorla
+ <ekr@rtfm.com> and licensed by RTFM, Inc.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. All advertising materials mentioning features or use of this software
+ must display the following acknowledgement:
+
+ This product includes software developed by Eric Rescorla for
+ RTFM, Inc.
+
+ 4. Neither the name of RTFM, Inc. nor the name of Eric Rescorla may be
+ used to endorse or promote products derived from this
+ software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY ERIC RESCORLA AND RTFM, INC. ``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 REGENTS 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 SUCH DAMAGE.
+
+ $Id: r_list.h,v 1.3 2007/06/08 17:41:49 adamcain Exp $
+
+
+ ekr@rtfm.com Tue Jan 19 08:36:48 1999
+ */
+
+
+#ifndef _r_list_h
+#define _r_list_h
+
+typedef struct r_list_ r_list;
+
+typedef struct r_list_iterator_ {
+ r_list *list;
+ struct r_list_el_ *ptr;
+} r_list_iterator;
+
+int r_list_create(r_list **listp);
+int r_list_destroy(r_list **listp);
+int r_list_copy(r_list **out,r_list *in);
+int r_list_insert(r_list *list,void *value,
+ int (*copy)(void **knew,void *old),
+ int (*destroy)(void **ptr));
+int r_list_append(r_list *list,void *value,
+ int (*copy)(void **knew,void *old),
+ int (*destroy)(void **ptr));
+int r_list_init_iter(r_list *list,r_list_iterator *iter);
+int r_list_iter(r_list_iterator *iter,void **val);
+
+#endif
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_macros.h b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_macros.h
new file mode 100644
index 0000000000..ddfedd3c36
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_macros.h
@@ -0,0 +1,137 @@
+/**
+ r_macros.h
+
+
+ Copyright (C) 2002-2003, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ */
+
+/**
+ r_macros.h
+
+
+ Copyright (C) 1999-2000 RTFM, Inc.
+ All Rights Reserved
+
+ This package is a SSLv3/TLS protocol analyzer written by Eric Rescorla
+ <ekr@rtfm.com> and licensed by RTFM, Inc.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. All advertising materials mentioning features or use of this software
+ must display the following acknowledgement:
+
+ This product includes software developed by Eric Rescorla for
+ RTFM, Inc.
+
+ 4. Neither the name of RTFM, Inc. nor the name of Eric Rescorla may be
+ used to endorse or promote products derived from this
+ software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY ERIC RESCORLA AND RTFM, INC. ``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 REGENTS 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 SUCH DAMAGE.
+
+ $Id: r_macros.h,v 1.3 2007/06/26 22:37:57 adamcain Exp $
+
+ ekr@rtfm.com Tue Dec 22 10:37:32 1998
+ */
+
+
+#ifndef _r_macros_h
+#define _r_macros_h
+
+/* Haven't removed all PROTO_LIST defs yet */
+#define PROTO_LIST(a) a
+
+#ifndef WIN32
+#ifndef __GNUC__
+#define __FUNCTION__ "unknown"
+#endif
+#endif
+
+#ifdef R_TRACE_ERRORS
+#ifdef WIN32
+#define REPORT_ERROR_(caller,a) printf("%s: error %d at %s:%d (function %s)\n", \
+ caller,a,__FILE__,__LINE__,__FUNCTION__)
+#else
+#define REPORT_ERROR_(caller,a) fprintf(stderr,"%s: error %d at %s:%d (function %s)\n", \
+ caller,a,__FILE__,__LINE__,__FUNCTION__)
+#endif
+#else
+#define REPORT_ERROR_(caller,a)
+#endif
+
+#ifndef ERETURN
+#define ERETURN(a) do {int _r=a; if(!_r) _r=-1; REPORT_ERROR_("ERETURN",_r); return(_r);} while(0)
+#endif
+
+#ifndef ABORT
+#define ABORT(a) do { int _r=a; if(!_r) _r=-1; REPORT_ERROR_("ABORT",_r); _status=_r; goto abort;} while(0)
+#endif
+
+#ifndef FREE
+#define FREE(a) if(a) free(a)
+#endif
+#ifndef MIN
+#define MIN(a,b) ((a)>(b))?(b):(a)
+#endif
+
+#ifndef MAX
+#define MAX(a,b) ((b)>(a))?(b):(a)
+#endif
+
+#ifdef DEBUG
+#define DBG(a) debug a
+int debug(int cls, char *format,...);
+#else
+#define DBG(a)
+#endif
+
+#define NR_UNIMPLEMENTED do { fprintf(stderr,"%s:%d Function %s unimplemented\n",__FILE__,__LINE__,__FUNCTION__); abort(); } while(0)
+
+#include "r_memory.h"
+
+#endif
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_memory.c b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_memory.c
new file mode 100644
index 0000000000..53846fc019
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_memory.c
@@ -0,0 +1,198 @@
+/**
+ r_memory.c
+
+
+ Copyright (C) 2004, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ ekr@rtfm.com Thu Apr 22 20:40:45 2004
+ */
+
+#include <string.h>
+#include <stddef.h>
+#include <assert.h>
+#include "r_common.h"
+#include "r_memory.h"
+
+typedef struct r_malloc_chunk_ {
+#ifdef SANITY_CHECKS
+ UINT4 hdr;
+#endif
+ UCHAR type;
+ UINT4 size;
+ UCHAR memory[1];
+} r_malloc_chunk;
+
+#define CHUNK_MEMORY_OFFSET offsetof(struct r_malloc_chunk_, memory)
+#define GET_CHUNK_ADDR_FROM_MEM_ADDR(memp) \
+ ((struct r_malloc_chunk *)(((unsigned char*)(memp))-CHUNK_MEMORY_OFFSET))
+#define CHUNK_SIZE(size) (size+sizeof(r_malloc_chunk))
+
+#define HDR_FLAG 0x464c4147
+
+static UINT4 mem_usage; /* Includes our header */
+static UINT4 mem_stats[256]; /* Does not include our header */
+
+void *r_malloc(type,size)
+ int type;
+ size_t size;
+ {
+ size_t total;
+ r_malloc_chunk *chunk;
+
+ total=size+sizeof(r_malloc_chunk);
+
+ if(!(chunk=malloc(total)))
+ return(0);
+
+#ifdef SANITY_CHECKS
+ chunk->hdr=HDR_FLAG;
+#endif
+ chunk->type=type;
+ chunk->size=size;
+
+ mem_usage+=CHUNK_SIZE(size);
+ mem_stats[type]+=size;
+
+ return(chunk->memory);
+ }
+
+void *r_calloc(type,number,size)
+ int type;
+ size_t number;
+ size_t size;
+ {
+ void *ret;
+ size_t total;
+
+ total=number*size;
+
+ if(!(ret=r_malloc(type,total)))
+ return(0);
+
+ memset(ret,0,size);
+
+ return(ret);
+ }
+
+void r_free(ptr)
+ void *ptr;
+ {
+ r_malloc_chunk *chunk;
+
+ if(!ptr) return;
+
+ chunk=(r_malloc_chunk *)GET_CHUNK_ADDR_FROM_MEM_ADDR(ptr);
+#ifdef SANITY_CHECKS
+ assert(chunk->hdr==HDR_FLAG);
+#endif
+
+ mem_usage-=CHUNK_SIZE(chunk->size);
+ mem_stats[chunk->type]-=chunk->size;
+
+ free(chunk);
+ }
+
+void *r_realloc(ptr,size)
+ void *ptr;
+ size_t size;
+ {
+ r_malloc_chunk *chunk,*nchunk;
+ size_t total;
+
+ if(!ptr) return(r_malloc(255,size));
+
+ chunk=(r_malloc_chunk *)GET_CHUNK_ADDR_FROM_MEM_ADDR(ptr);
+#ifdef SANITY_CHECKS
+ assert(chunk->hdr==HDR_FLAG);
+#endif
+
+ total=size + sizeof(r_malloc_chunk);
+
+ if(!(nchunk=realloc(chunk,total)))
+ return(0);
+
+ mem_usage-=CHUNK_SIZE(nchunk->size);
+ mem_stats[nchunk->type]-=nchunk->size;
+
+ nchunk->size=size;
+ mem_usage+=CHUNK_SIZE(nchunk->size);
+ mem_stats[nchunk->type]+=nchunk->size;
+
+ return(nchunk->memory);
+ }
+
+char *r_strdup(str)
+ const char *str;
+ {
+ int len;
+ char *nstr;
+
+ if(!str)
+ return(0);
+
+ len=strlen(str)+1;
+
+ if(!(nstr=r_malloc(0,len)))
+ return(0);
+
+ memcpy(nstr,str,len);
+
+ return(nstr);
+ }
+
+int r_mem_get_usage(usagep)
+ UINT4 *usagep;
+ {
+ *usagep=mem_usage;
+
+ return(0);
+ }
+
+int r_memory_dump_stats()
+ {
+ int i;
+
+ printf("Total memory usage: %d\n",mem_usage);
+ printf("Memory usage by bucket\n");
+ for(i=0;i<256;i++){
+ if(mem_stats[i]){
+ printf("%d\t%d\n",i,mem_stats[i]);
+ }
+ }
+ return(0);
+ }
+
+void *r_malloc_compat(size)
+ size_t size;
+ {
+ return(r_malloc(255,size));
+ }
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_memory.h b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_memory.h
new file mode 100644
index 0000000000..4357070767
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_memory.h
@@ -0,0 +1,101 @@
+/**
+ r_memory.h
+
+
+ Copyright (C) 2004, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ ekr@rtfm.com Sat Apr 24 08:30:00 2004
+ */
+
+
+#ifndef _r_memory_h
+#define _r_memory_h
+
+#define R_MALLOC_X 2
+
+#include "r_types.h"
+
+void *r_malloc(int type, size_t size);
+void *r_malloc_compat(size_t size);
+void *r_calloc(int type,size_t number,size_t size);
+void r_free (void *ptr);
+void *r_realloc(void *ptr,size_t size);
+char *r_strdup(const char *str);
+int r_mem_get_usage(UINT4 *usage);
+int r_memory_dump_stats(void);
+
+#ifdef NO_MALLOC_REPLACE
+
+#ifndef RMALLOC
+#define RMALLOC(a) malloc(a)
+#endif
+
+#ifndef RCALLOC
+#define RCALLOC(a) calloc(1,a)
+#endif
+
+#ifndef RFREE
+#define RFREE(a) if(a) free(a)
+#endif
+
+#ifndef RREALLOC
+#define RREALLOC(a,b) realloc(a,b)
+#endif
+
+#else
+
+
+#ifndef R_MALLOC_TYPE
+#define R_MALLOC_TYPE 0
+#endif
+
+#ifndef RMALLOC
+#define RMALLOC(a) r_malloc(R_MALLOC_TYPE,a)
+#endif
+
+#ifndef RCALLOC
+#define RCALLOC(a) r_calloc(R_MALLOC_TYPE,1,a)
+#endif
+
+#ifndef RFREE
+#define RFREE(a) if(a) r_free(a)
+#endif
+
+#ifndef RREALLOC
+#define RREALLOC(a,b) r_realloc(a,b)
+#endif
+
+#endif
+
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_replace.c b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_replace.c
new file mode 100644
index 0000000000..8916b884cc
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_replace.c
@@ -0,0 +1,107 @@
+/**
+ r_replace.c
+
+
+ Copyright (C) 2002-2003, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ */
+
+/**
+ r_replace.c
+
+
+ Copyright (C) 1999-2000 RTFM, Inc.
+ All Rights Reserved
+
+ This package is a SSLv3/TLS protocol analyzer written by Eric Rescorla
+ <ekr@rtfm.com> and licensed by RTFM, Inc.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. All advertising materials mentioning features or use of this software
+ must display the following acknowledgement:
+
+ This product includes software developed by Eric Rescorla for
+ RTFM, Inc.
+
+ 4. Neither the name of RTFM, Inc. nor the name of Eric Rescorla may be
+ used to endorse or promote products derived from this
+ software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY ERIC RESCORLA AND RTFM, INC. ``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 REGENTS 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 SUCH DAMAGE.
+
+ $Id: r_replace.c,v 1.2 2006/08/16 19:39:17 adamcain Exp $
+
+
+ ekr@rtfm.com Sun Oct 1 11:18:49 2000
+ */
+
+#include "r_common.h"
+
+#ifndef HAVE_STRDUP
+
+char *strdup(str)
+ char *str;
+ {
+ int len=strlen(str);
+ char *n;
+
+ if(!(n=(char *)malloc(len+1)))
+ return(0);
+
+ memcpy(n,str,len+1);
+
+ return(n);
+ }
+#endif
+
+
+#ifdef SUPPLY_ATEXIT
+int atexit(void (*func)(void)){
+ ;
+}
+#endif
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_thread.h b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_thread.h
new file mode 100644
index 0000000000..212900bcc4
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_thread.h
@@ -0,0 +1,68 @@
+/**
+ r_thread.h
+
+
+ Copyright (C) 1999, RTFM, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ ekr@rtfm.com Tue Feb 23 14:58:36 1999
+ */
+
+
+#ifndef _r_thread_h
+#define _r_thread_h
+
+typedef void *r_thread;
+typedef void *r_rwlock;
+typedef void * r_cond;
+
+int r_thread_fork (void (*func)(void *),void *arg,
+ r_thread *tid);
+int r_thread_destroy (r_thread tid);
+int r_thread_yield (void);
+int r_thread_exit (void);
+int r_thread_wait_last (void);
+int r_thread_self (void);
+
+int r_rwlock_create (r_rwlock **lockp);
+int r_rwlock_destroy (r_rwlock **lock);
+int r_rwlock_lock (r_rwlock *lock,int action);
+
+int r_cond_init (r_cond *cond);
+int r_cond_wait (r_cond cond);
+int r_cond_signal (r_cond cond);
+
+#define R_RWLOCK_UNLOCK 0
+#define R_RWLOCK_RLOCK 1
+#define R_RWLOCK_WLOCK 2
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_time.c b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_time.c
new file mode 100644
index 0000000000..f873585c4b
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_time.c
@@ -0,0 +1,235 @@
+/**
+ r_time.c
+
+
+ Copyright (C) 2002-2003, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ */
+
+/**
+ r_time.c
+
+
+ Copyright (C) 1999-2000 RTFM, Inc.
+ All Rights Reserved
+
+ This package is a SSLv3/TLS protocol analyzer written by Eric Rescorla
+ <ekr@rtfm.com> and licensed by RTFM, Inc.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. All advertising materials mentioning features or use of this software
+ must display the following acknowledgement:
+
+ This product includes software developed by Eric Rescorla for
+ RTFM, Inc.
+
+ 4. Neither the name of RTFM, Inc. nor the name of Eric Rescorla may be
+ used to endorse or promote products derived from this
+ software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY ERIC RESCORLA AND RTFM, INC. ``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 REGENTS 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 SUCH DAMAGE.
+
+ $Id: r_time.c,v 1.5 2008/11/26 03:22:02 adamcain Exp $
+
+ ekr@rtfm.com Thu Mar 4 08:43:46 1999
+ */
+
+#include <r_common.h>
+#include <r_time.h>
+
+/*Note that t1 must be > t0 */
+int r_timeval_diff(t1,t0,diff)
+ struct timeval *t1;
+ struct timeval *t0;
+ struct timeval *diff;
+ {
+ long d;
+
+ if(t0->tv_sec > t1->tv_sec)
+ ERETURN(R_BAD_ARGS);
+ if((t0->tv_sec == t1->tv_sec) && (t0->tv_usec > t1->tv_usec))
+ ERETURN(R_BAD_ARGS);
+
+ /*Easy case*/
+ if(t0->tv_usec <= t1->tv_usec){
+ diff->tv_sec=t1->tv_sec - t0->tv_sec;
+ diff->tv_usec=t1->tv_usec - t0->tv_usec;
+ return(0);
+ }
+
+ /*Hard case*/
+ d=t0->tv_usec - t1->tv_usec;
+ if(t1->tv_sec < (t0->tv_sec + 1))
+ ERETURN(R_BAD_ARGS);
+ diff->tv_sec=t1->tv_sec - (t0->tv_sec + 1);
+ diff->tv_usec=1000000 - d;
+
+ return(0);
+ }
+
+int r_timeval_add(t1,t2,sum)
+ struct timeval *t1;
+ struct timeval *t2;
+ struct timeval *sum;
+ {
+ long tv_sec,tv_usec,d;
+
+ tv_sec=t1->tv_sec + t2->tv_sec;
+
+ d=t1->tv_usec + t2->tv_usec;
+ if(d>1000000){
+ tv_sec++;
+ tv_usec=d-1000000;
+ }
+ else{
+ tv_usec=d;
+ }
+
+ sum->tv_sec=tv_sec;
+ sum->tv_usec=tv_usec;
+
+ return(0);
+ }
+
+int r_timeval_cmp(t1,t2)
+ struct timeval *t1;
+ struct timeval *t2;
+ {
+ if(t1->tv_sec>t2->tv_sec)
+ return(1);
+ if(t1->tv_sec<t2->tv_sec)
+ return(-1);
+ if(t1->tv_usec>t2->tv_usec)
+ return(1);
+ if(t1->tv_usec<t2->tv_usec)
+ return(-1);
+ return(0);
+ }
+
+
+UINT8 r_timeval2int(tv)
+ struct timeval *tv;
+ {
+ UINT8 r=0;
+
+ r=(tv->tv_sec);
+ r*=1000000;
+ r+=tv->tv_usec;
+
+ return r;
+ }
+
+int r_int2timeval(UINT8 t,struct timeval *tv)
+ {
+ tv->tv_sec=t/1000000;
+ tv->tv_usec=t%1000000;
+
+ return(0);
+ }
+
+UINT8 r_gettimeint()
+ {
+ struct timeval tv;
+
+ gettimeofday(&tv,0);
+
+ return r_timeval2int(&tv);
+ }
+
+/* t1-t0 in microseconds */
+int r_timeval_diff_usec(struct timeval *t1, struct timeval *t0, INT8 *diff)
+ {
+ int r,_status;
+ int sign;
+ struct timeval tmp;
+
+ sign = 1;
+ if (r=r_timeval_diff(t1, t0, &tmp)) {
+ if (r == R_BAD_ARGS) {
+ sign = -1;
+ if (r=r_timeval_diff(t0, t1, &tmp))
+ ABORT(r);
+ }
+ }
+
+ /* 1 second = 1000 milliseconds
+ * 1 milliseconds = 1000 microseconds */
+
+ *diff = ((tmp.tv_sec * (1000*1000)) + tmp.tv_usec) * sign;
+
+ _status = 0;
+ abort:
+ return(_status);
+ }
+
+/* t1-t0 in milliseconds */
+int r_timeval_diff_ms(struct timeval *t1, struct timeval *t0, INT8 *diff)
+ {
+ int r,_status;
+ int sign;
+ struct timeval tmp;
+
+ sign = 1;
+ if (r=r_timeval_diff(t1, t0, &tmp)) {
+ if (r == R_BAD_ARGS) {
+ sign = -1;
+ if (r=r_timeval_diff(t0, t1, &tmp))
+ ABORT(r);
+ }
+ }
+
+ /* 1 second = 1000 milliseconds
+ * 1 milliseconds = 1000 microseconds */
+
+ *diff = ((tmp.tv_sec * 1000) + (tmp.tv_usec / 1000)) * sign;
+
+ _status = 0;
+ abort:
+ return(_status);
+ }
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_time.h b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_time.h
new file mode 100644
index 0000000000..1ee1e778fe
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_time.h
@@ -0,0 +1,109 @@
+/**
+ r_time.h
+
+
+ Copyright (C) 2002-2003, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ */
+
+/**
+ r_time.h
+
+
+ Copyright (C) 1999-2000 RTFM, Inc.
+ All Rights Reserved
+
+ This package is a SSLv3/TLS protocol analyzer written by Eric Rescorla
+ <ekr@rtfm.com> and licensed by RTFM, Inc.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. All advertising materials mentioning features or use of this software
+ must display the following acknowledgement:
+
+ This product includes software developed by Eric Rescorla for
+ RTFM, Inc.
+
+ 4. Neither the name of RTFM, Inc. nor the name of Eric Rescorla may be
+ used to endorse or promote products derived from this
+ software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY ERIC RESCORLA AND RTFM, INC. ``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 REGENTS 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 SUCH DAMAGE.
+
+ $Id: r_time.h,v 1.4 2007/06/26 22:37:57 adamcain Exp $
+
+
+ ekr@rtfm.com Thu Mar 4 08:45:41 1999
+ */
+
+
+#ifndef _r_time_h
+#define _r_time_h
+
+#include <csi_platform.h>
+
+#ifndef WIN32
+#include <sys/time.h>
+#include <time.h>
+#endif
+
+int r_timeval_diff(struct timeval *t1,struct timeval *t0, struct timeval *diff);
+int r_timeval_add(struct timeval *t1,struct timeval *t2, struct timeval *sum);
+int r_timeval_cmp(struct timeval *t1,struct timeval *t2);
+
+UINT8 r_timeval2int(struct timeval *tv);
+int r_int2timeval(UINT8 t,struct timeval *tv);
+UINT8 r_gettimeint(void);
+
+/* t1-t0 in microseconds */
+int r_timeval_diff_usec(struct timeval *t1, struct timeval *t0, INT8 *diff);
+
+/* t1-t0 in milliseconds */
+int r_timeval_diff_ms(struct timeval *t1, struct timeval *t0, INT8 *diff);
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_types.h b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_types.h
new file mode 100644
index 0000000000..d00810a7a2
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/libekr/r_types.h
@@ -0,0 +1,213 @@
+/**
+ r_types.h
+
+
+ Copyright (C) 2002-2003, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ */
+
+/**
+ r_types.h
+
+
+ Copyright (C) 1999-2000 RTFM, Inc.
+ All Rights Reserved
+
+ This package is a SSLv3/TLS protocol analyzer written by Eric Rescorla
+ <ekr@rtfm.com> and licensed by RTFM, Inc.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. All advertising materials mentioning features or use of this software
+ must display the following acknowledgement:
+
+ This product includes software developed by Eric Rescorla for
+ RTFM, Inc.
+
+ 4. Neither the name of RTFM, Inc. nor the name of Eric Rescorla may be
+ used to endorse or promote products derived from this
+ software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY ERIC RESCORLA AND RTFM, INC. ``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 REGENTS 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 SUCH DAMAGE.
+
+ $Id: r_types.h,v 1.2 2006/08/16 19:39:18 adamcain Exp $
+
+
+ ekr@rtfm.com Tue Dec 22 10:36:02 1998
+ */
+
+
+#ifndef _r_types_h
+#define _r_types_h
+
+/* Either define R_PLATFORM_INT_TYPES or be on a platform that
+ has stdint.h */
+#ifdef R_PLATFORM_INT_TYPES
+#include R_PLATFORM_INT_TYPES
+#else
+#include <stdint.h>
+#endif
+
+#ifndef R_DEFINED_INT2
+#ifndef SIZEOF_INT
+typedef short INT2;
+#else
+# if (SIZEOF_INT==2)
+typedef int INT2;
+# elif (SIZEOF_SHORT==2)
+typedef short INT2;
+# elif (SIZEOF_LONG==2)
+typedef long INT2;
+# else
+# error no type for INT2
+# endif
+#endif
+#else
+typedef R_DEFINED_INT2 INT2;
+#endif
+
+#ifndef R_DEFINED_UINT2
+#ifndef SIZEOF_UNSIGNED_INT
+typedef unsigned short UINT2;
+#else
+# if (SIZEOF_UNSIGNED_INT==2)
+typedef unsigned int UINT2;
+# elif (SIZEOF_UNSIGNED_SHORT==2)
+typedef unsigned short UINT2;
+# elif (SIZEOF_UNSIGNED_LONG==2)
+typedef unsigned long UINT2;
+# else
+# error no type for UINT2
+# endif
+#endif
+#else
+typedef R_DEFINED_UINT2 UINT2;
+#endif
+
+#ifndef R_DEFINED_INT4
+#ifndef SIZEOF_INT
+typedef int INT4;
+#else
+# if (SIZEOF_INT==4)
+typedef int INT4;
+# elif (SIZEOF_SHORT==4)
+typedef short INT4;
+# elif (SIZEOF_LONG==4)
+typedef long INT4;
+# else
+# error no type for INT4
+# endif
+#endif
+#else
+typedef R_DEFINED_INT4 INT4;
+#endif
+
+#ifndef R_DEFINED_UINT4
+#ifndef SIZEOF_UNSIGNED_INT
+typedef unsigned int UINT4;
+#else
+# if (SIZEOF_UNSIGNED_INT==4)
+typedef unsigned int UINT4;
+# elif (SIZEOF_UNSIGNED_SHORT==4)
+typedef unsigned short UINT4;
+# elif (SIZEOF_UNSIGNED_LONG==4)
+typedef unsigned long UINT4;
+# else
+# error no type for UINT4
+# endif
+#endif
+#else
+typedef R_DEFINED_UINT4 UINT4;
+#endif
+
+#ifndef R_DEFINED_INT8
+#ifndef SIZEOF_INT
+typedef long long INT8;
+#else
+# if (SIZEOF_INT==8)
+typedef int INT8;
+# elif (SIZEOF_SHORT==8)
+typedef short INT8;
+# elif (SIZEOF_LONG==8)
+typedef long INT8;
+# elif (SIZEOF_LONG_LONG==8)
+typedef long long INT8;
+# else
+# error no type for INT8
+# endif
+#endif
+#else
+typedef R_DEFINED_INT8 INT8;
+#endif
+
+#ifndef R_DEFINED_UINT8
+#ifndef SIZEOF_UNSIGNED_INT
+typedef unsigned long long UINT8;
+#else
+# if (SIZEOF_UNSIGNED_INT==8)
+typedef unsigned int UINT8;
+# elif (SIZEOF_UNSIGNED_SHORT==8)
+typedef unsigned short UINT8;
+# elif (SIZEOF_UNSIGNED_LONG==8)
+typedef unsigned long UINT8;
+# elif (SIZEOF_UNSIGNED_LONG_LONG==8)
+typedef unsigned long long UINT8;
+# else
+# error no type for UINT8
+# endif
+#endif
+#else
+typedef R_DEFINED_UINT8 UINT8;
+#endif
+
+#ifndef R_DEFINED_UCHAR
+typedef unsigned char UCHAR;
+#else
+typedef R_DEFINED_UCHAR UCHAR;
+#endif
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/p_buf.c b/dom/media/webrtc/transport/third_party/nrappkit/src/util/p_buf.c
new file mode 100644
index 0000000000..459baecdda
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/p_buf.c
@@ -0,0 +1,215 @@
+/**
+ p_buf.c
+
+
+ Copyright (C) 2003, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ All Rights Reserved.
+
+ ekr@rtfm.com Tue Nov 25 16:33:08 2003
+ */
+
+#include <string.h>
+#include <stddef.h>
+#include "nr_common.h"
+#include "p_buf.h"
+
+
+static int nr_p_buf_destroy_chain(nr_p_buf_head *head);
+static int nr_p_buf_destroy(nr_p_buf *buf);
+
+int nr_p_buf_ctx_create(size,ctxp)
+ int size;
+ nr_p_buf_ctx **ctxp;
+ {
+ int _status;
+ nr_p_buf_ctx *ctx=0;
+
+ if(!(ctx=(nr_p_buf_ctx *)RCALLOC(sizeof(nr_p_buf_ctx))))
+ ABORT(R_NO_MEMORY);
+
+ ctx->buf_size=size;
+ STAILQ_INIT(&ctx->free_list);
+
+ *ctxp=ctx;
+ _status=0;
+ abort:
+ if(_status){
+ nr_p_buf_ctx_destroy(&ctx);
+ }
+ return(_status);
+ }
+
+int nr_p_buf_ctx_destroy(ctxp)
+ nr_p_buf_ctx **ctxp;
+ {
+ nr_p_buf_ctx *ctx;
+
+ if(!ctxp || !*ctxp)
+ return(0);
+
+ ctx=*ctxp;
+
+ nr_p_buf_destroy_chain(&ctx->free_list);
+
+ RFREE(ctx);
+ *ctxp=0;
+
+ return(0);
+ }
+
+int nr_p_buf_alloc(ctx,bufp)
+ nr_p_buf_ctx *ctx;
+ nr_p_buf **bufp;
+ {
+ int _status;
+ nr_p_buf *buf=0;
+
+ if(!STAILQ_EMPTY(&ctx->free_list)){
+ buf=STAILQ_FIRST(&ctx->free_list);
+ STAILQ_REMOVE_HEAD(&ctx->free_list,entry);
+ goto ok;
+ }
+ else {
+ if(!(buf=(nr_p_buf *)RCALLOC(sizeof(nr_p_buf))))
+ ABORT(R_NO_MEMORY);
+ if(!(buf->data=(UCHAR *)RMALLOC(ctx->buf_size)))
+ ABORT(R_NO_MEMORY);
+ buf->size=ctx->buf_size;
+ }
+
+ ok:
+ buf->r_offset=0;
+ buf->length=0;
+
+ *bufp=buf;
+ _status=0;
+ abort:
+ if(_status){
+ nr_p_buf_destroy(buf);
+ }
+ return(_status);
+ }
+
+int nr_p_buf_free(ctx,buf)
+ nr_p_buf_ctx *ctx;
+ nr_p_buf *buf;
+ {
+ STAILQ_INSERT_TAIL(&ctx->free_list,buf,entry);
+
+ return(0);
+ }
+
+int nr_p_buf_free_chain(ctx,head)
+ nr_p_buf_ctx *ctx;
+ nr_p_buf_head *head;
+ {
+ nr_p_buf *n1,*n2;
+
+ n1=STAILQ_FIRST(head);
+ while(n1){
+ n2=STAILQ_NEXT(n1,entry);
+
+ nr_p_buf_free(ctx,n1);
+
+ n1=n2;
+ }
+
+ return(0);
+ }
+
+
+int nr_p_buf_write_to_chain(ctx,chain,data,len)
+ nr_p_buf_ctx *ctx;
+ nr_p_buf_head *chain;
+ UCHAR *data;
+ UINT4 len;
+ {
+ int r,_status;
+ nr_p_buf *buf;
+
+ buf=STAILQ_LAST(chain,nr_p_buf_,entry);
+ while(len){
+ int towrite;
+
+ if(!buf){
+ if(r=nr_p_buf_alloc(ctx,&buf))
+ ABORT(r);
+ STAILQ_INSERT_TAIL(chain,buf,entry);
+ }
+
+ towrite=MIN(len,(buf->size-(buf->length+buf->r_offset)));
+
+ memcpy(buf->data+buf->length+buf->r_offset,data,towrite);
+ len-=towrite;
+ data+=towrite;
+ buf->length+=towrite;
+
+ r_log(LOG_COMMON,LOG_DEBUG,"Wrote %d bytes to buffer %p",towrite,buf);
+ buf=0;
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+static int nr_p_buf_destroy_chain(head)
+ nr_p_buf_head *head;
+ {
+ nr_p_buf *n1,*n2;
+
+ n1=STAILQ_FIRST(head);
+ while(n1){
+ n2=STAILQ_NEXT(n1,entry);
+
+ nr_p_buf_destroy(n1);
+
+ n1=n2;
+ }
+
+ return(0);
+ }
+
+static int nr_p_buf_destroy(buf)
+ nr_p_buf *buf;
+ {
+ if(!buf)
+ return(0);
+
+ RFREE(buf->data);
+ RFREE(buf);
+
+ return(0);
+ }
+
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/p_buf.h b/dom/media/webrtc/transport/third_party/nrappkit/src/util/p_buf.h
new file mode 100644
index 0000000000..29881960c6
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/p_buf.h
@@ -0,0 +1,72 @@
+/**
+ p_buf.h
+
+
+ Copyright (C) 2003, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ ekr@rtfm.com Tue Nov 25 15:58:37 2003
+ */
+
+
+#ifndef _p_buf_h
+#define _p_buf_h
+
+typedef struct nr_p_buf_ {
+ UCHAR *data; /*The pointer to the buffer where the data lives */
+ UINT4 size; /*The size of the buffer */
+ UINT4 r_offset; /*The offset into the buffer where the data starts
+ when reading */
+ UINT4 length; /*The length of the data portion */
+
+ STAILQ_ENTRY(nr_p_buf_) entry;
+} nr_p_buf;
+
+typedef STAILQ_HEAD(nr_p_buf_head_,nr_p_buf_) nr_p_buf_head;
+
+
+typedef struct nr_p_buf_ctx_ {
+ int buf_size;
+
+ nr_p_buf_head free_list;
+} nr_p_buf_ctx;
+
+int nr_p_buf_ctx_create(int size,nr_p_buf_ctx **ctxp);
+int nr_p_buf_ctx_destroy(nr_p_buf_ctx **ctxp);
+int nr_p_buf_alloc(nr_p_buf_ctx *ctx,nr_p_buf **bufp);
+int nr_p_buf_free(nr_p_buf_ctx *ctx,nr_p_buf *buf);
+int nr_p_buf_free_chain(nr_p_buf_ctx *ctx,nr_p_buf_head *chain);
+int nr_p_buf_write_to_chain(nr_p_buf_ctx *ctx,
+ nr_p_buf_head *chain,
+ UCHAR *data,UINT4 len);
+
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/util.c b/dom/media/webrtc/transport/third_party/nrappkit/src/util/util.c
new file mode 100644
index 0000000000..17d49639fd
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/util.c
@@ -0,0 +1,775 @@
+/**
+ util.c
+
+
+ Copyright (C) 2001-2003, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ ekr@rtfm.com Wed Dec 26 17:19:36 2001
+ */
+
+#ifndef WIN32
+#include <sys/uio.h>
+#include <pwd.h>
+#include <dirent.h>
+#endif
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <sys/stat.h>
+#ifdef OPENSSL
+#include <openssl/evp.h>
+#endif
+#include "nr_common.h"
+#include "r_common.h"
+#include "registry.h"
+#include "util.h"
+#include "r_log.h"
+
+int nr_util_default_log_facility=LOG_COMMON;
+
+int nr_get_filename(base,name,namep)
+ char *base;
+ char *name;
+ char **namep;
+ {
+ int len=strlen(base)+strlen(name)+2;
+ char *ret=0;
+ int _status;
+
+ if(!(ret=(char *)RMALLOC(len)))
+ ABORT(R_NO_MEMORY);
+ if(base[strlen(base)-1]!='/'){
+ sprintf(ret,"%s/%s",base,name);
+ }
+ else{
+ sprintf(ret,"%s%s",base,name);
+ }
+ *namep=ret;
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+#if 0
+int read_RSA_private_key(base,name,keyp)
+ char *base;
+ char *name;
+ RSA **keyp;
+ {
+ char *keyfile=0;
+ BIO *bio=0;
+ FILE *fp=0;
+ RSA *rsa=0;
+ int r,_status;
+
+ /* Load the keyfile */
+ if(r=get_filename(base,name,&keyfile))
+ ABORT(r);
+ if(!(fp=fopen(keyfile,"r")))
+ ABORT(R_NOT_FOUND);
+ if(!(bio=BIO_new(BIO_s_file())))
+ ABORT(R_NO_MEMORY);
+ BIO_set_fp(bio,fp,BIO_NOCLOSE);
+
+ if(!(rsa=PEM_read_bio_RSAPrivateKey(bio,0,0,0)))
+ ABORT(R_NOT_FOUND);
+
+ *keyp=rsa;
+ _status=0;
+ abort:
+ return(_status);
+ }
+#endif
+
+
+void nr_errprintf_log(const char *format,...)
+ {
+ va_list ap;
+
+ va_start(ap,format);
+
+ r_vlog(nr_util_default_log_facility,LOG_ERR,format,ap);
+
+ va_end(ap);
+ }
+
+void nr_errprintf_log2(void *ignore, const char *format,...)
+ {
+ va_list ap;
+
+ va_start(ap,format);
+
+ r_vlog(nr_util_default_log_facility,LOG_ERR,format,ap);
+
+ va_end(ap);
+ }
+
+
+int nr_fwrite_all(FILE *fp,UCHAR *buf,int len)
+ {
+ int r,_status;
+
+ while(len){
+ r=fwrite(buf,1,len,fp);
+ if(r==0)
+ ABORT(R_IO_ERROR);
+
+ len-=r;
+ buf+=r;
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+int nr_read_data(fd,buf,len)
+ int fd;
+ char *buf;
+ int len;
+ {
+ int r,_status;
+
+ while(len){
+ r=NR_SOCKET_READ(fd,buf,len);
+ if(r<=0)
+ ABORT(R_EOD);
+
+ buf+=r;
+ len-=r;
+ }
+
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+
+#ifdef WIN32
+ // TODO
+#else
+int nr_drop_privileges(char *username)
+ {
+ int _status;
+
+ /* Drop privileges */
+ if ((getuid() == 0) || geteuid()==0) {
+ struct passwd *passwd;
+
+ if ((passwd = getpwnam(CAPTURE_USER)) == 0){
+ r_log(LOG_GENERIC,LOG_EMERG,"Couldn't get user %s",CAPTURE_USER);
+ ABORT(R_INTERNAL);
+ }
+
+ if(setuid(passwd->pw_uid)!=0){
+ r_log(LOG_GENERIC,LOG_EMERG,"Couldn't drop privileges");
+ ABORT(R_INTERNAL);
+ }
+ }
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+#endif
+
+int nr_bin2hex(UCHAR *in,int len,UCHAR *out)
+ {
+ while(len){
+ sprintf((char*)out,"%.2x",in[0] & 0xff);
+
+ in+=1;
+ out+=2;
+
+ len--;
+ }
+
+ return(0);
+ }
+
+int nr_hex_ascii_dump(Data *data)
+ {
+ UCHAR *ptr=data->data;
+ int len=data->len;
+
+ while(len){
+ int i;
+ int bytes=MIN(len,16);
+
+ for(i=0;i<bytes;i++)
+ printf("%.2x ",ptr[i]&255);
+ /* Fill */
+ for(i=0;i<(16-bytes);i++)
+ printf(" ");
+ printf(" ");
+
+ for(i=0;i<bytes;i++){
+ if(isprint(ptr[i]))
+ printf("%c",ptr[i]);
+ else
+ printf(".");
+ }
+ printf("\n");
+
+ len-=bytes;
+ ptr+=bytes;
+ }
+ return(0);
+ }
+
+#ifdef OPENSSL
+int nr_sha1_file(char *filename,UCHAR *out)
+ {
+ EVP_MD_CTX md_ctx;
+ FILE *fp=0;
+ int r,_status;
+ UCHAR buf[1024];
+ int out_len;
+
+ EVP_MD_CTX_init(&md_ctx);
+
+ if(!(fp=fopen(filename,"r"))){
+ r_log(LOG_COMMON,LOG_ERR,"Couldn't open file %s",filename);
+ ABORT(R_NOT_FOUND);
+ }
+
+ EVP_DigestInit_ex(&md_ctx,EVP_sha1(),0);
+
+ while(1){
+ r=fread(buf,1,sizeof(buf),fp);
+
+ if(r<0){
+ r_log(LOG_COMMON,LOG_ERR,"Error reading from %s",filename);
+ ABORT(R_INTERNAL);
+ }
+
+ if(!r)
+ break;
+
+ EVP_DigestUpdate(&md_ctx,buf,r);
+ }
+
+ EVP_DigestFinal(&md_ctx,out,(unsigned int*)&out_len);
+ if(out_len!=20)
+ ABORT(R_INTERNAL);
+
+ _status=0;
+ abort:
+ EVP_MD_CTX_cleanup(&md_ctx);
+ if(fp) fclose(fp);
+
+ return(_status);
+ }
+
+#endif
+
+#ifdef WIN32
+ // TODO
+#else
+
+#if 0
+
+#include <fts.h>
+
+int nr_rm_tree(char *path)
+ {
+ FTS *fts=0;
+ FTSENT *p;
+ int failed=0;
+ int _status;
+ char *argv[2];
+
+ argv[0]=path;
+ argv[1]=0;
+
+ if(!(fts=fts_open(argv,0,NULL))){
+ r_log_e(LOG_COMMON,LOG_ERR,"Couldn't open directory %s",path);
+ ABORT(R_FAILED);
+ }
+
+ while(p=fts_read(fts)){
+ switch(p->fts_info){
+ case FTS_D:
+ break;
+ case FTS_DOT:
+ break;
+ case FTS_ERR:
+ r_log_e(LOG_COMMON,LOG_ERR,"Problem reading %s",p->fts_path);
+ break;
+ default:
+ r_log(LOG_COMMON,LOG_DEBUG,"Removing %s",p->fts_path);
+ errno=0;
+ if(remove(p->fts_path)){
+ r_log_e(LOG_COMMON,LOG_ERR,"Problem removing %s",p->fts_path);
+ failed=1;
+ }
+ }
+ }
+
+ if(failed)
+ ABORT(R_FAILED);
+
+ _status=0;
+ abort:
+ if(fts) fts_close(fts);
+ return(_status);
+ }
+#endif
+
+int nr_write_pid_file(char *pid_filename)
+ {
+ FILE *fp;
+ int _status;
+
+ if(!pid_filename)
+ ABORT(R_BAD_ARGS);
+
+ unlink(pid_filename);
+
+ if(!(fp=fopen(pid_filename,"w"))){
+ r_log(LOG_GENERIC,LOG_CRIT,"Couldn't open PID file: %s",strerror(errno));
+ ABORT(R_NOT_FOUND);
+ }
+
+ fprintf(fp,"%d\n",getpid());
+
+ fclose(fp);
+
+ chmod(pid_filename,S_IRUSR | S_IRGRP | S_IROTH);
+
+ _status=0;
+ abort:
+ return(_status);
+ }
+#endif
+
+int nr_reg_uint4_fetch_and_check(NR_registry key, UINT4 min, UINT4 max, int log_fac, int die, UINT4 *val)
+ {
+ int r,_status;
+ UINT4 my_val;
+
+ if(r=NR_reg_get_uint4(key,&my_val)){
+ r_log(log_fac,LOG_ERR,"Couldn't get key '%s', error %d",key,r);
+ ABORT(r);
+ }
+
+ if((min>0) && (my_val<min)){
+ r_log(log_fac,LOG_ERR,"Invalid value for key '%s'=%lu, (min = %lu)",key,(unsigned long)my_val,(unsigned long)min);
+ ABORT(R_BAD_DATA);
+ }
+
+ if(my_val>max){
+ r_log(log_fac,LOG_ERR,"Invalid value for key '%s'=%lu, (max = %lu)",key,(unsigned long)my_val,(unsigned long)max);
+ ABORT(R_BAD_DATA);
+ }
+
+ *val=my_val;
+ _status=0;
+
+ abort:
+ if(die && _status){
+ r_log(log_fac,LOG_CRIT,"Exiting due to invalid configuration (key '%s')",key);
+ exit(1);
+ }
+ return(_status);
+ }
+
+int nr_reg_uint8_fetch_and_check(NR_registry key, UINT8 min, UINT8 max, int log_fac, int die, UINT8 *val)
+ {
+ int r,_status;
+ UINT8 my_val;
+
+ if(r=NR_reg_get_uint8(key,&my_val)){
+ r_log(log_fac,LOG_ERR,"Couldn't get key '%s', error %d",key,r);
+ ABORT(r);
+ }
+
+ if(my_val<min){
+ r_log(log_fac,LOG_ERR,"Invalid value for key '%s'=%llu, (min = %llu)",key,my_val,min);
+ ABORT(R_BAD_DATA);
+ }
+
+ if(my_val>max){
+ r_log(log_fac,LOG_ERR,"Invalid value for key '%s'=%llu, (max = %llu)",key,my_val,max);
+ ABORT(R_BAD_DATA);
+ }
+
+ *val=my_val;
+ _status=0;
+
+ abort:
+ if(die && _status){
+ r_log(log_fac,LOG_CRIT,"Exiting due to invalid configuration (key '%s')",key);
+ exit(1);
+ }
+ return(_status);
+ }
+
+#if defined(LINUX) || defined(WIN32)
+/*-
+ * Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED ``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 AUTHOR 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.
+ */
+
+
+/*
+ * Appends src to string dst of size siz (unlike strncat, siz is the
+ * full size of dst, not space left). At most siz-1 characters
+ * will be copied. Always NUL terminates (unless siz <= strlen(dst)).
+ * Returns strlen(src) + MIN(siz, strlen(initial dst)).
+ * If retval >= siz, truncation occurred.
+ */
+size_t
+strlcat(dst, src, siz)
+ char *dst;
+ const char *src;
+ size_t siz;
+{
+ char *d = dst;
+ const char *s = src;
+ size_t n = siz;
+ size_t dlen;
+
+ /* Find the end of dst and adjust bytes left but don't go past end */
+ while (n-- != 0 && *d != '\0')
+ d++;
+ dlen = d - dst;
+ n = siz - dlen;
+
+ if (n == 0)
+ return(dlen + strlen(s));
+ while (*s != '\0') {
+ if (n != 1) {
+ *d++ = *s;
+ n--;
+ }
+ s++;
+ }
+ *d = '\0';
+
+ return(dlen + (s - src)); /* count does not include NUL */
+}
+
+#endif /* LINUX or WIN32 */
+
+#if defined(USE_OWN_INET_NTOP) || (defined(WIN32) && WINVER < 0x0600)
+#include <errno.h>
+#ifdef WIN32
+#include <Ws2ipdef.h>
+#ifndef EAFNOSUPPORT
+#define EAFNOSUPPORT WSAEAFNOSUPPORT
+#endif
+#else
+#include <sys/socket.h>
+#endif
+#define INET6
+
+/* inet_ntop implementation from NetBSD */
+
+/*
+ * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (c) 1996-1999 by Internet Software Consortium.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#if !defined(NS_INADDRSZ)
+# define NS_INADDRSZ 4
+#endif
+#if !defined(NS_IN6ADDRSZ)
+# define NS_IN6ADDRSZ 16
+#endif
+#if !defined(NS_INT16SZ)
+# define NS_INT16SZ 2
+#endif
+
+/*
+ * WARNING: Don't even consider trying to compile this on a system where
+ * sizeof(int) < 4. sizeof(int) > 4 is fine; all the world's not a VAX.
+ */
+
+static const char *inet_ntop4(const unsigned char *src, char *dst, size_t size);
+#ifdef INET6
+static const char *inet_ntop6(const unsigned char *src, char *dst, size_t size);
+#endif /* INET6 */
+
+/* char *
+ * inet_ntop(af, src, dst, size)
+ * convert a network format address to presentation format.
+ * return:
+ * pointer to presentation format address (`dst'), or NULL (see errno).
+ * author:
+ * Paul Vixie, 1996.
+ */
+const char *
+inet_ntop(int af, const void *src, char *dst, size_t size)
+{
+
+ switch (af) {
+ case AF_INET:
+ return (inet_ntop4(src, dst, size));
+#ifdef INET6
+ case AF_INET6:
+ return (inet_ntop6(src, dst, size));
+#endif /* INET6 */
+ default:
+ errno = EAFNOSUPPORT;
+ return (NULL);
+ }
+ /* NOTREACHED */
+}
+
+/* const char *
+ * inet_ntop4(src, dst, size)
+ * format an IPv4 address, more or less like inet_ntoa()
+ * return:
+ * `dst' (as a const)
+ * notes:
+ * (1) uses no statics
+ * (2) takes a unsigned char* not an in_addr as input
+ * author:
+ * Paul Vixie, 1996.
+ */
+static const char *
+inet_ntop4(const unsigned char *src, char *dst, size_t size)
+{
+ char tmp[sizeof "255.255.255.255"];
+ int l;
+
+ l = snprintf(tmp, sizeof(tmp), "%u.%u.%u.%u",
+ src[0], src[1], src[2], src[3]);
+ if (l <= 0 || (size_t) l >= size) {
+ errno = ENOSPC;
+ return (NULL);
+ }
+ strlcpy(dst, tmp, size);
+ return (dst);
+}
+
+#ifdef INET6
+/* const char *
+ * inet_ntop6(src, dst, size)
+ * convert IPv6 binary address into presentation (printable) format
+ * author:
+ * Paul Vixie, 1996.
+ */
+static const char *
+inet_ntop6(const unsigned char *src, char *dst, size_t size)
+{
+ /*
+ * Note that int32_t and int16_t need only be "at least" large enough
+ * to contain a value of the specified size. On some systems, like
+ * Crays, there is no such thing as an integer variable with 16 bits.
+ * Keep this in mind if you think this function should have been coded
+ * to use pointer overlays. All the world's not a VAX.
+ */
+ char tmp[sizeof "ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255"];
+ char *tp, *ep;
+ struct { int base, len; } best, cur;
+ unsigned int words[NS_IN6ADDRSZ / NS_INT16SZ];
+ int i;
+ int advance;
+
+ /*
+ * Preprocess:
+ * Copy the input (bytewise) array into a wordwise array.
+ * Find the longest run of 0x00's in src[] for :: shorthanding.
+ */
+ memset(words, '\0', sizeof words);
+ for (i = 0; i < NS_IN6ADDRSZ; i++)
+ words[i / 2] |= (src[i] << ((1 - (i % 2)) << 3));
+ best.base = -1;
+ cur.base = -1;
+ best.len = -1; /* XXX gcc */
+ cur.len = -1; /* XXX gcc */
+ for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++) {
+ if (words[i] == 0) {
+ if (cur.base == -1)
+ cur.base = i, cur.len = 1;
+ else
+ cur.len++;
+ } else {
+ if (cur.base != -1) {
+ if (best.base == -1 || cur.len > best.len)
+ best = cur;
+ cur.base = -1;
+ }
+ }
+ }
+ if (cur.base != -1) {
+ if (best.base == -1 || cur.len > best.len)
+ best = cur;
+ }
+ if (best.base != -1 && best.len < 2)
+ best.base = -1;
+
+ /*
+ * Format the result.
+ */
+ tp = tmp;
+ ep = tmp + sizeof(tmp);
+ for (i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++) {
+ /* Are we inside the best run of 0x00's? */
+ if (best.base != -1 && i >= best.base &&
+ i < (best.base + best.len)) {
+ if (i == best.base)
+ *tp++ = ':';
+ continue;
+ }
+ /* Are we following an initial run of 0x00s or any real hex? */
+ if (i != 0) {
+ if (tp + 1 >= ep)
+ return (NULL);
+ *tp++ = ':';
+ }
+ /* Is this address an encapsulated IPv4? */
+ if (i == 6 && best.base == 0 &&
+ (best.len == 6 ||
+ (best.len == 7 && words[7] != 0x0001) ||
+ (best.len == 5 && words[5] == 0xffff))) {
+ if (!inet_ntop4(src+12, tp, (size_t)(ep - tp)))
+ return (NULL);
+ tp += strlen(tp);
+ break;
+ }
+ advance = snprintf(tp, (size_t)(ep - tp), "%x", words[i]);
+ if (advance <= 0 || advance >= ep - tp)
+ return (NULL);
+ tp += advance;
+ }
+ /* Was it a trailing run of 0x00's? */
+ if (best.base != -1 && (best.base + best.len) ==
+ (NS_IN6ADDRSZ / NS_INT16SZ)) {
+ if (tp + 1 >= ep)
+ return (NULL);
+ *tp++ = ':';
+ }
+ if (tp + 1 >= ep)
+ return (NULL);
+ *tp++ = '\0';
+
+ /*
+ * Check for overflow, copy, and we're done.
+ */
+ if ((size_t)(tp - tmp) > size) {
+ errno = ENOSPC;
+ return (NULL);
+ }
+ strlcpy(dst, tmp, size);
+ return (dst);
+}
+#endif /* INET6 */
+
+#ifdef WIN32
+/* Not exactly, will forgive stuff like <addr>:<port> */
+int inet_pton(int af, const char *src, void *dst)
+{
+ struct sockaddr_storage ss;
+ int addrlen = sizeof(ss);
+
+ if (af != AF_INET && af != AF_INET6) {
+ return -1;
+ }
+
+ if (!WSAStringToAddressA(src, af, NULL, (struct sockaddr*)&ss, &addrlen)) {
+ if (af == AF_INET) {
+ struct sockaddr_in *in = (struct sockaddr_in*)&ss;
+ memcpy(dst, &in->sin_addr, sizeof(struct in_addr));
+ } else {
+ struct sockaddr_in6 *in6 = (struct sockaddr_in6*)&ss;
+ memcpy(dst, &in6->sin6_addr, sizeof(struct in6_addr));
+ }
+ return 1;
+ }
+ return 0;
+}
+#endif /* WIN32 */
+
+#endif
+
+#ifdef WIN32
+#include <time.h>
+/* this is only millisecond-accurate, but that should be OK */
+
+int gettimeofday(struct timeval *tv, void *tz)
+ {
+ SYSTEMTIME st;
+ FILETIME ft;
+ ULARGE_INTEGER u;
+
+ GetLocalTime (&st);
+
+ /* strangely, the FILETIME is the number of 100 nanosecond (0.1 us) intervals
+ * since the Epoch */
+ SystemTimeToFileTime(&st, &ft);
+ u.HighPart = ft.dwHighDateTime;
+ u.LowPart = ft.dwLowDateTime;
+
+ tv->tv_sec = (long) (u.QuadPart / 10000000L);
+ tv->tv_usec = (long) (st.wMilliseconds * 1000);;
+
+ return 0;
+ }
+#endif
+
diff --git a/dom/media/webrtc/transport/third_party/nrappkit/src/util/util.h b/dom/media/webrtc/transport/third_party/nrappkit/src/util/util.h
new file mode 100644
index 0000000000..975baa4aa2
--- /dev/null
+++ b/dom/media/webrtc/transport/third_party/nrappkit/src/util/util.h
@@ -0,0 +1,73 @@
+/**
+ util.h
+
+
+ Copyright (C) 2001-2003, Network Resonance, Inc.
+ Copyright (C) 2006, Network Resonance, 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:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. 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.
+ 3. Neither the name of Network Resonance, Inc. nor the name of any
+ contributors to this software 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 OWNER 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.
+
+
+ ekr@rtfm.com Wed Dec 26 17:20:23 2001
+ */
+
+
+#ifndef _util_h
+#define _util_h
+
+#include "registry.h"
+
+int nr_get_filename(char *base,char *name, char **namep);
+#if 0
+#include <openssl/ssl.h>
+
+int read_RSA_private_key(char *base, char *name,RSA **keyp);
+#endif
+void nr_errprintf_log(const char *fmt,...);
+void nr_errprintf_log2(void *ignore, const char *fmt,...);
+extern int nr_util_default_log_facility;
+
+int nr_read_data(int fd,char *buf,int len);
+int nr_drop_privileges(char *username);
+int nr_hex_ascii_dump(Data *data);
+int nr_fwrite_all(FILE *fp,UCHAR *buf,int len);
+int nr_sha1_file(char *filename,UCHAR *out);
+int nr_bin2hex(UCHAR *in,int len,UCHAR *out);
+int nr_rm_tree(char *path);
+int nr_write_pid_file(char *pid_filename);
+
+int nr_reg_uint4_fetch_and_check(NR_registry key, UINT4 min, UINT4 max, int log_fac, int die, UINT4 *val);
+int nr_reg_uint8_fetch_and_check(NR_registry key, UINT8 min, UINT8 max, int log_fac, int die, UINT8 *val);
+
+#if defined(WIN32) && WINVER < 0x0600
+const char *inet_ntop(int af, const void *src, char *dst, size_t size);
+int inet_pton(int af, const char *src, void *dst);
+#endif
+
+#endif
+
diff --git a/dom/media/webrtc/transport/transportflow.cpp b/dom/media/webrtc/transport/transportflow.cpp
new file mode 100644
index 0000000000..8b263c8cfe
--- /dev/null
+++ b/dom/media/webrtc/transport/transportflow.cpp
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+#include <deque>
+
+#include "transportflow.h"
+#include "transportlayer.h"
+
+namespace mozilla {
+
+NS_IMPL_ISUPPORTS0(TransportFlow)
+
+// There are some hacks here to allow destruction off of
+// the main thread.
+TransportFlow::~TransportFlow() {
+ // Push the destruction onto the STS thread. Note that there
+ // is still some possibility that someone is accessing this
+ // object simultaneously, but as long as smart pointer discipline
+ // is maintained, it shouldn't be possible to access and
+ // destroy it simultaneously. The conversion to a UniquePtr
+ // ensures automatic destruction of the queue at exit of
+ // DestroyFinal.
+ CheckThread();
+ ClearLayers(layers_.get());
+}
+
+void TransportFlow::DestroyFinal(
+ UniquePtr<std::deque<TransportLayer*>> layers) {
+ ClearLayers(layers.get());
+}
+
+void TransportFlow::ClearLayers(std::deque<TransportLayer*>* layers) {
+ while (!layers->empty()) {
+ delete layers->front();
+ layers->pop_front();
+ }
+}
+
+void TransportFlow::PushLayer(TransportLayer* layer) {
+ CheckThread();
+ layers_->push_front(layer);
+ EnsureSameThread(layer);
+ layer->SetFlowId(id_);
+}
+
+TransportLayer* TransportFlow::GetLayer(const std::string& id) const {
+ CheckThread();
+
+ if (layers_) {
+ for (TransportLayer* layer : *layers_) {
+ if (layer->id() == id) return layer;
+ }
+ }
+
+ return nullptr;
+}
+
+void TransportFlow::EnsureSameThread(TransportLayer* layer) {
+ // Enforce that if any of the layers have a thread binding,
+ // they all have the same binding.
+ if (target_) {
+ const nsCOMPtr<nsIEventTarget>& lthread = layer->GetThread();
+
+ if (lthread && (lthread != target_)) MOZ_CRASH();
+ } else {
+ target_ = layer->GetThread();
+ }
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/transport/transportflow.h b/dom/media/webrtc/transport/transportflow.h
new file mode 100644
index 0000000000..43253a260b
--- /dev/null
+++ b/dom/media/webrtc/transport/transportflow.h
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+
+#ifndef transportflow_h__
+#define transportflow_h__
+
+#include <deque>
+#include <string>
+
+#include "nscore.h"
+#include "nsISupportsImpl.h"
+#include "mozilla/UniquePtr.h"
+#include "transportlayer.h"
+#include "m_cpp_utils.h"
+
+// A stack of transport layers acts as a flow.
+// Generally, one reads and writes to the top layer.
+
+// This code has a confusing hybrid threading model which
+// probably needs some eventual refactoring.
+// TODO(ekr@rtfm.com): Bug 844891
+//
+// TransportFlows are not inherently bound to a thread *but*
+// TransportLayers can be. If any layer in a flow is bound
+// to a given thread, then all layers in the flow MUST be
+// bound to that thread and you can only manipulate the
+// flow (push layers, write, etc.) on that thread.
+//
+// The sole official exception to this is that you are
+// allowed to *destroy* a flow off the bound thread provided
+// that there are no listeners on its signals. This exception
+// is designed to allow idioms where you create the flow
+// and then something goes wrong and you destroy it and
+// you don't want to bother with a thread dispatch.
+//
+// Eventually we hope to relax the "no listeners"
+// restriction by thread-locking the signals, but previous
+// attempts have caused deadlocks.
+//
+// Most of these invariants are enforced by hard asserts
+// (i.e., those which fire even in production builds).
+
+namespace mozilla {
+
+class TransportFlow final : public nsISupports {
+ public:
+ TransportFlow()
+ : id_("(anonymous)"), layers_(new std::deque<TransportLayer*>) {}
+ explicit TransportFlow(const std::string id)
+ : id_(id), layers_(new std::deque<TransportLayer*>) {}
+
+ const std::string& id() const { return id_; }
+
+ // Layer management. Note PushLayer() is not thread protected, so
+ // either:
+ // (a) Do it in the thread handling the I/O
+ // (b) Do it before you activate the I/O system
+ //
+ // The flow takes ownership of the layers after a successful
+ // push.
+ void PushLayer(TransportLayer* layer);
+
+ TransportLayer* GetLayer(const std::string& id) const;
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ private:
+ ~TransportFlow();
+
+ DISALLOW_COPY_ASSIGN(TransportFlow);
+
+ // Check if we are on the right thread
+ void CheckThread() const {
+ if (!CheckThreadInt()) MOZ_CRASH();
+ }
+
+ bool CheckThreadInt() const {
+ bool on;
+
+ if (!target_) // OK if no thread set.
+ return true;
+ if (NS_FAILED(target_->IsOnCurrentThread(&on))) return false;
+
+ return on;
+ }
+
+ void EnsureSameThread(TransportLayer* layer);
+
+ static void DestroyFinal(UniquePtr<std::deque<TransportLayer*>> layers);
+
+ // Overload needed because we use deque internally and queue externally.
+ static void ClearLayers(std::deque<TransportLayer*>* layers);
+
+ std::string id_;
+ UniquePtr<std::deque<TransportLayer*>> layers_;
+ nsCOMPtr<nsIEventTarget> target_;
+};
+
+} // namespace mozilla
+#endif
diff --git a/dom/media/webrtc/transport/transportlayer.cpp b/dom/media/webrtc/transport/transportlayer.cpp
new file mode 100644
index 0000000000..8816f24754
--- /dev/null
+++ b/dom/media/webrtc/transport/transportlayer.cpp
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+#include "logging.h"
+#include "transportlayer.h"
+
+// Logging context
+namespace mozilla {
+
+MOZ_MTLOG_MODULE("mtransport")
+
+nsresult TransportLayer::Init() {
+ if (state_ != TS_NONE) return state_ == TS_ERROR ? NS_ERROR_FAILURE : NS_OK;
+
+ nsresult rv = InitInternal();
+
+ if (!NS_SUCCEEDED(rv)) {
+ state_ = TS_ERROR;
+ return rv;
+ }
+ state_ = TS_INIT;
+
+ return NS_OK;
+}
+
+void TransportLayer::Chain(TransportLayer* downward) {
+ downward_ = downward;
+ MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "Inserted: downward='"
+ << (downward ? downward->id() : "none")
+ << "'");
+
+ WasInserted();
+}
+
+void TransportLayer::SetState(State state, const char* file, unsigned line) {
+ if (state != state_) {
+ MOZ_MTLOG(state == TS_ERROR ? ML_ERROR : ML_DEBUG,
+ file << ":" << line << ": " << LAYER_INFO << "state " << state_
+ << "->" << state);
+ state_ = state;
+ SignalStateChange(this, state);
+ }
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/transport/transportlayer.h b/dom/media/webrtc/transport/transportlayer.h
new file mode 100644
index 0000000000..47287ab4e0
--- /dev/null
+++ b/dom/media/webrtc/transport/transportlayer.h
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+
+#ifndef transportlayer_h__
+#define transportlayer_h__
+
+#include "sigslot.h"
+
+#include "nsCOMPtr.h"
+#include "nsIEventTarget.h"
+
+#include "m_cpp_utils.h"
+#include "mediapacket.h"
+
+namespace mozilla {
+
+class TransportFlow;
+
+typedef int TransportResult;
+
+enum { TE_WOULDBLOCK = -1, TE_ERROR = -2, TE_INTERNAL = -3 };
+
+#define TRANSPORT_LAYER_ID(name) \
+ const std::string id() const override { return name; } \
+ static std::string ID() { return name; }
+
+// Abstract base class for network transport layers.
+class TransportLayer : public sigslot::has_slots<> {
+ public:
+ // The state of the transport flow
+ // We can't use "ERROR" because Windows has a macro named "ERROR"
+ enum State { TS_NONE, TS_INIT, TS_CONNECTING, TS_OPEN, TS_CLOSED, TS_ERROR };
+
+ // Is this a stream or datagram flow
+ TransportLayer() : state_(TS_NONE), flow_id_(), downward_(nullptr) {}
+
+ virtual ~TransportLayer() = default;
+
+ // Called to initialize
+ nsresult Init(); // Called by Insert() to set up -- do not override
+ virtual nsresult InitInternal() { return NS_OK; } // Called by Init
+
+ void SetFlowId(const std::string& flow_id) { flow_id_ = flow_id; }
+
+ virtual void Chain(TransportLayer* downward);
+
+ // Downward interface
+ TransportLayer* downward() { return downward_; }
+
+ // Get the state
+ State state() const { return state_; }
+ // Must be implemented by derived classes
+ virtual TransportResult SendPacket(MediaPacket& packet) = 0;
+
+ // Get the thread.
+ const nsCOMPtr<nsIEventTarget> GetThread() const { return target_; }
+
+ // Event definitions that one can register for
+ // State has changed
+ sigslot::signal2<TransportLayer*, State> SignalStateChange;
+ // Data received on the flow
+ sigslot::signal2<TransportLayer*, MediaPacket&> SignalPacketReceived;
+
+ // Return the layer id for this layer
+ virtual const std::string id() const = 0;
+
+ // The id of the flow
+ const std::string& flow_id() const { return flow_id_; }
+
+ protected:
+ virtual void WasInserted() {}
+ virtual void SetState(State state, const char* file, unsigned line);
+ // Check if we are on the right thread
+ void CheckThread() const { MOZ_ASSERT(CheckThreadInt(), "Wrong thread"); }
+
+ State state_;
+ std::string flow_id_;
+ TransportLayer* downward_; // The next layer in the stack
+ nsCOMPtr<nsIEventTarget> target_;
+
+ private:
+ DISALLOW_COPY_ASSIGN(TransportLayer);
+
+ bool CheckThreadInt() const {
+ bool on;
+
+ if (!target_) // OK if no thread set.
+ return true;
+
+ NS_ENSURE_SUCCESS(target_->IsOnCurrentThread(&on), false);
+ NS_ENSURE_TRUE(on, false);
+
+ return true;
+ }
+};
+
+#define LAYER_INFO \
+ "Flow[" << flow_id() << "(none)" \
+ << "]; Layer[" << id() << "]: "
+#define TL_SET_STATE(x) SetState((x), __FILE__, __LINE__)
+
+} // namespace mozilla
+#endif
diff --git a/dom/media/webrtc/transport/transportlayerdtls.cpp b/dom/media/webrtc/transport/transportlayerdtls.cpp
new file mode 100644
index 0000000000..4ab8aaa029
--- /dev/null
+++ b/dom/media/webrtc/transport/transportlayerdtls.cpp
@@ -0,0 +1,1558 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+
+#include "transportlayerdtls.h"
+
+#include <algorithm>
+#include <queue>
+#include <sstream>
+
+#include "dtlsidentity.h"
+#include "keyhi.h"
+#include "logging.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "nsCOMPtr.h"
+#include "nsNetCID.h"
+#include "nsServiceManagerUtils.h"
+#include "sslexp.h"
+#include "sslproto.h"
+
+namespace mozilla {
+
+MOZ_MTLOG_MODULE("mtransport")
+
+static PRDescIdentity transport_layer_identity = PR_INVALID_IO_LAYER;
+
+// TODO: Implement a mode for this where
+// the channel is not ready until confirmed externally
+// (e.g., after cert check).
+
+#define UNIMPLEMENTED \
+ MOZ_MTLOG(ML_ERROR, "Call to unimplemented function " << __FUNCTION__); \
+ MOZ_ASSERT(false); \
+ PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0)
+
+#define MAX_ALPN_LENGTH 255
+
+// We need to adapt the NSPR/libssl model to the TransportFlow model.
+// The former wants pull semantics and TransportFlow wants push.
+//
+// - A TransportLayerDtls assumes it is sitting on top of another
+// TransportLayer, which means that events come in asynchronously.
+// - NSS (libssl) wants to sit on top of a PRFileDesc and poll.
+// - The TransportLayerNSPRAdapter is a PRFileDesc containing a
+// FIFO.
+// - When TransportLayerDtls.PacketReceived() is called, we insert
+// the packets in the FIFO and then do a PR_Recv() on the NSS
+// PRFileDesc, which eventually reads off the FIFO.
+//
+// All of this stuff is assumed to happen solely in a single thread
+// (generally the SocketTransportService thread)
+
+void TransportLayerNSPRAdapter::PacketReceived(MediaPacket& packet) {
+ if (enabled_) {
+ input_.push(new MediaPacket(std::move(packet)));
+ }
+}
+
+int32_t TransportLayerNSPRAdapter::Recv(void* buf, int32_t buflen) {
+ if (input_.empty()) {
+ PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
+ return -1;
+ }
+
+ MediaPacket* front = input_.front();
+ int32_t count = static_cast<int32_t>(front->len());
+
+ if (buflen < count) {
+ MOZ_ASSERT(false, "Not enough buffer space to receive into");
+ PR_SetError(PR_BUFFER_OVERFLOW_ERROR, 0);
+ return -1;
+ }
+
+ memcpy(buf, front->data(), count);
+
+ input_.pop();
+ delete front;
+
+ return count;
+}
+
+int32_t TransportLayerNSPRAdapter::Write(const void* buf, int32_t length) {
+ if (!enabled_) {
+ MOZ_MTLOG(ML_WARNING, "Writing to disabled transport layer");
+ return -1;
+ }
+
+ MediaPacket packet;
+ // Copies. Oh well.
+ packet.Copy(static_cast<const uint8_t*>(buf), static_cast<size_t>(length));
+ packet.SetType(MediaPacket::DTLS);
+
+ TransportResult r = output_->SendPacket(packet);
+ if (r >= 0) {
+ return r;
+ }
+
+ if (r == TE_WOULDBLOCK) {
+ PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
+ } else {
+ PR_SetError(PR_IO_ERROR, 0);
+ }
+
+ return -1;
+}
+
+// Implementation of NSPR methods
+static PRStatus TransportLayerClose(PRFileDesc* f) {
+ f->dtor(f);
+ return PR_SUCCESS;
+}
+
+static int32_t TransportLayerRead(PRFileDesc* f, void* buf, int32_t length) {
+ UNIMPLEMENTED;
+ return -1;
+}
+
+static int32_t TransportLayerWrite(PRFileDesc* f, const void* buf,
+ int32_t length) {
+ TransportLayerNSPRAdapter* io =
+ reinterpret_cast<TransportLayerNSPRAdapter*>(f->secret);
+ return io->Write(buf, length);
+}
+
+static int32_t TransportLayerAvailable(PRFileDesc* f) {
+ UNIMPLEMENTED;
+ return -1;
+}
+
+int64_t TransportLayerAvailable64(PRFileDesc* f) {
+ UNIMPLEMENTED;
+ return -1;
+}
+
+static PRStatus TransportLayerSync(PRFileDesc* f) {
+ UNIMPLEMENTED;
+ return PR_FAILURE;
+}
+
+static int32_t TransportLayerSeek(PRFileDesc* f, int32_t offset,
+ PRSeekWhence how) {
+ UNIMPLEMENTED;
+ return -1;
+}
+
+static int64_t TransportLayerSeek64(PRFileDesc* f, int64_t offset,
+ PRSeekWhence how) {
+ UNIMPLEMENTED;
+ return -1;
+}
+
+static PRStatus TransportLayerFileInfo(PRFileDesc* f, PRFileInfo* info) {
+ UNIMPLEMENTED;
+ return PR_FAILURE;
+}
+
+static PRStatus TransportLayerFileInfo64(PRFileDesc* f, PRFileInfo64* info) {
+ UNIMPLEMENTED;
+ return PR_FAILURE;
+}
+
+static int32_t TransportLayerWritev(PRFileDesc* f, const PRIOVec* iov,
+ int32_t iov_size, PRIntervalTime to) {
+ UNIMPLEMENTED;
+ return -1;
+}
+
+static PRStatus TransportLayerConnect(PRFileDesc* f, const PRNetAddr* addr,
+ PRIntervalTime to) {
+ UNIMPLEMENTED;
+ return PR_FAILURE;
+}
+
+static PRFileDesc* TransportLayerAccept(PRFileDesc* sd, PRNetAddr* addr,
+ PRIntervalTime to) {
+ UNIMPLEMENTED;
+ return nullptr;
+}
+
+static PRStatus TransportLayerBind(PRFileDesc* f, const PRNetAddr* addr) {
+ UNIMPLEMENTED;
+ return PR_FAILURE;
+}
+
+static PRStatus TransportLayerListen(PRFileDesc* f, int32_t depth) {
+ UNIMPLEMENTED;
+ return PR_FAILURE;
+}
+
+static PRStatus TransportLayerShutdown(PRFileDesc* f, int32_t how) {
+ // This is only called from NSS when we are the server and the client refuses
+ // to provide a certificate. In this case, the handshake is destined for
+ // failure, so we will just let this pass.
+ TransportLayerNSPRAdapter* io =
+ reinterpret_cast<TransportLayerNSPRAdapter*>(f->secret);
+ io->SetEnabled(false);
+ return PR_SUCCESS;
+}
+
+// This function does not support peek, or waiting until `to`
+static int32_t TransportLayerRecv(PRFileDesc* f, void* buf, int32_t buflen,
+ int32_t flags, PRIntervalTime to) {
+ MOZ_ASSERT(flags == 0);
+ if (flags != 0) {
+ PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
+ return -1;
+ }
+
+ TransportLayerNSPRAdapter* io =
+ reinterpret_cast<TransportLayerNSPRAdapter*>(f->secret);
+ return io->Recv(buf, buflen);
+}
+
+// Note: this is always nonblocking and assumes a zero timeout.
+static int32_t TransportLayerSend(PRFileDesc* f, const void* buf,
+ int32_t amount, int32_t flags,
+ PRIntervalTime to) {
+ int32_t written = TransportLayerWrite(f, buf, amount);
+ return written;
+}
+
+static int32_t TransportLayerRecvfrom(PRFileDesc* f, void* buf, int32_t amount,
+ int32_t flags, PRNetAddr* addr,
+ PRIntervalTime to) {
+ UNIMPLEMENTED;
+ return -1;
+}
+
+static int32_t TransportLayerSendto(PRFileDesc* f, const void* buf,
+ int32_t amount, int32_t flags,
+ const PRNetAddr* addr, PRIntervalTime to) {
+ UNIMPLEMENTED;
+ return -1;
+}
+
+static int16_t TransportLayerPoll(PRFileDesc* f, int16_t in_flags,
+ int16_t* out_flags) {
+ UNIMPLEMENTED;
+ return -1;
+}
+
+static int32_t TransportLayerAcceptRead(PRFileDesc* sd, PRFileDesc** nd,
+ PRNetAddr** raddr, void* buf,
+ int32_t amount, PRIntervalTime t) {
+ UNIMPLEMENTED;
+ return -1;
+}
+
+static int32_t TransportLayerTransmitFile(PRFileDesc* sd, PRFileDesc* f,
+ const void* headers, int32_t hlen,
+ PRTransmitFileFlags flags,
+ PRIntervalTime t) {
+ UNIMPLEMENTED;
+ return -1;
+}
+
+static PRStatus TransportLayerGetpeername(PRFileDesc* f, PRNetAddr* addr) {
+ // TODO: Modify to return unique names for each channel
+ // somehow, as opposed to always the same static address. The current
+ // implementation messes up the session cache, which is why it's off
+ // elsewhere
+ addr->inet.family = PR_AF_INET;
+ addr->inet.port = 0;
+ addr->inet.ip = 0;
+
+ return PR_SUCCESS;
+}
+
+static PRStatus TransportLayerGetsockname(PRFileDesc* f, PRNetAddr* addr) {
+ UNIMPLEMENTED;
+ return PR_FAILURE;
+}
+
+static PRStatus TransportLayerGetsockoption(PRFileDesc* f,
+ PRSocketOptionData* opt) {
+ switch (opt->option) {
+ case PR_SockOpt_Nonblocking:
+ opt->value.non_blocking = PR_TRUE;
+ return PR_SUCCESS;
+ default:
+ UNIMPLEMENTED;
+ break;
+ }
+
+ return PR_FAILURE;
+}
+
+// Imitate setting socket options. These are mostly noops.
+static PRStatus TransportLayerSetsockoption(PRFileDesc* f,
+ const PRSocketOptionData* opt) {
+ switch (opt->option) {
+ case PR_SockOpt_Nonblocking:
+ return PR_SUCCESS;
+ case PR_SockOpt_NoDelay:
+ return PR_SUCCESS;
+ default:
+ UNIMPLEMENTED;
+ break;
+ }
+
+ return PR_FAILURE;
+}
+
+static int32_t TransportLayerSendfile(PRFileDesc* out, PRSendFileData* in,
+ PRTransmitFileFlags flags,
+ PRIntervalTime to) {
+ UNIMPLEMENTED;
+ return -1;
+}
+
+static PRStatus TransportLayerConnectContinue(PRFileDesc* f, int16_t flags) {
+ UNIMPLEMENTED;
+ return PR_FAILURE;
+}
+
+static int32_t TransportLayerReserved(PRFileDesc* f) {
+ UNIMPLEMENTED;
+ return -1;
+}
+
+static const struct PRIOMethods TransportLayerMethods = {
+ PR_DESC_LAYERED,
+ TransportLayerClose,
+ TransportLayerRead,
+ TransportLayerWrite,
+ TransportLayerAvailable,
+ TransportLayerAvailable64,
+ TransportLayerSync,
+ TransportLayerSeek,
+ TransportLayerSeek64,
+ TransportLayerFileInfo,
+ TransportLayerFileInfo64,
+ TransportLayerWritev,
+ TransportLayerConnect,
+ TransportLayerAccept,
+ TransportLayerBind,
+ TransportLayerListen,
+ TransportLayerShutdown,
+ TransportLayerRecv,
+ TransportLayerSend,
+ TransportLayerRecvfrom,
+ TransportLayerSendto,
+ TransportLayerPoll,
+ TransportLayerAcceptRead,
+ TransportLayerTransmitFile,
+ TransportLayerGetsockname,
+ TransportLayerGetpeername,
+ TransportLayerReserved,
+ TransportLayerReserved,
+ TransportLayerGetsockoption,
+ TransportLayerSetsockoption,
+ TransportLayerSendfile,
+ TransportLayerConnectContinue,
+ TransportLayerReserved,
+ TransportLayerReserved,
+ TransportLayerReserved,
+ TransportLayerReserved};
+
+TransportLayerDtls::~TransportLayerDtls() {
+ // Destroy the NSS instance first so it can still send out an alert before
+ // we disable the nspr_io_adapter_.
+ ssl_fd_ = nullptr;
+ nspr_io_adapter_->SetEnabled(false);
+ if (timer_) {
+ timer_->Cancel();
+ }
+}
+
+nsresult TransportLayerDtls::InitInternal() {
+ // Get the transport service as an event target
+ nsresult rv;
+ target_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+
+ if (NS_FAILED(rv)) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't get socket transport service");
+ return rv;
+ }
+
+ timer_ = NS_NewTimer();
+ if (!timer_) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't get timer");
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void TransportLayerDtls::WasInserted() {
+ // Connect to the lower layers
+ if (!Setup()) {
+ TL_SET_STATE(TS_ERROR);
+ }
+}
+
+// Set the permitted and default ALPN identifiers.
+// The default is here to allow for peers that don't want to negotiate ALPN
+// in that case, the default string will be reported from GetNegotiatedAlpn().
+// Setting the default to the empty string causes the transport layer to fail
+// if ALPN is not negotiated.
+// Note: we only support Unicode strings here, which are encoded into UTF-8,
+// even though ALPN ostensibly allows arbitrary octet sequences.
+nsresult TransportLayerDtls::SetAlpn(const std::set<std::string>& alpn_allowed,
+ const std::string& alpn_default) {
+ alpn_allowed_ = alpn_allowed;
+ alpn_default_ = alpn_default;
+
+ return NS_OK;
+}
+
+nsresult TransportLayerDtls::SetVerificationAllowAll() {
+ // Defensive programming
+ if (verification_mode_ != VERIFY_UNSET) return NS_ERROR_ALREADY_INITIALIZED;
+
+ verification_mode_ = VERIFY_ALLOW_ALL;
+
+ return NS_OK;
+}
+
+nsresult TransportLayerDtls::SetVerificationDigest(const DtlsDigest& digest) {
+ // Defensive programming
+ if (verification_mode_ != VERIFY_UNSET &&
+ verification_mode_ != VERIFY_DIGEST) {
+ return NS_ERROR_ALREADY_INITIALIZED;
+ }
+
+ digests_.push_back(digest);
+ verification_mode_ = VERIFY_DIGEST;
+ return NS_OK;
+}
+
+void TransportLayerDtls::SetMinMaxVersion(Version min_version,
+ Version max_version) {
+ if (min_version < Version::DTLS_1_0 || min_version > Version::DTLS_1_3 ||
+ max_version < Version::DTLS_1_0 || max_version > Version::DTLS_1_3 ||
+ min_version > max_version || max_version < min_version) {
+ return;
+ }
+ minVersion_ = min_version;
+ maxVersion_ = max_version;
+}
+
+// These are the named groups that we will allow.
+static const SSLNamedGroup NamedGroupPreferences[] = {
+ ssl_grp_ec_curve25519, ssl_grp_ec_secp256r1, ssl_grp_ec_secp384r1,
+ ssl_grp_ffdhe_2048, ssl_grp_ffdhe_3072};
+
+// TODO: make sure this is called from STS. Otherwise
+// we have thread safety issues
+bool TransportLayerDtls::Setup() {
+ CheckThread();
+ SECStatus rv;
+
+ if (!downward_) {
+ MOZ_MTLOG(ML_ERROR, "DTLS layer with nothing below. This is useless");
+ return false;
+ }
+ nspr_io_adapter_ = MakeUnique<TransportLayerNSPRAdapter>(downward_);
+
+ if (!identity_) {
+ MOZ_MTLOG(ML_ERROR, "Can't start DTLS without an identity");
+ return false;
+ }
+
+ if (verification_mode_ == VERIFY_UNSET) {
+ MOZ_MTLOG(ML_ERROR,
+ "Can't start DTLS without specifying a verification mode");
+ return false;
+ }
+
+ if (transport_layer_identity == PR_INVALID_IO_LAYER) {
+ transport_layer_identity = PR_GetUniqueIdentity("nssstreamadapter");
+ }
+
+ UniquePRFileDesc pr_fd(
+ PR_CreateIOLayerStub(transport_layer_identity, &TransportLayerMethods));
+ MOZ_ASSERT(pr_fd != nullptr);
+ if (!pr_fd) return false;
+ pr_fd->secret = reinterpret_cast<PRFilePrivate*>(nspr_io_adapter_.get());
+
+ UniquePRFileDesc ssl_fd(DTLS_ImportFD(nullptr, pr_fd.get()));
+ MOZ_ASSERT(ssl_fd != nullptr); // This should never happen
+ if (!ssl_fd) {
+ return false;
+ }
+
+ Unused << pr_fd.release(); // ownership transfered to ssl_fd;
+
+ if (role_ == CLIENT) {
+ MOZ_MTLOG(ML_INFO, "Setting up DTLS as client");
+ rv = SSL_GetClientAuthDataHook(ssl_fd.get(), GetClientAuthDataHook, this);
+ if (rv != SECSuccess) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't set identity");
+ return false;
+ }
+
+ if (maxVersion_ >= Version::DTLS_1_3) {
+ MOZ_MTLOG(ML_INFO, "Setting DTLS1.3 supported_versions workaround");
+ rv = SSL_SetDtls13VersionWorkaround(ssl_fd.get(), PR_TRUE);
+ if (rv != SECSuccess) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't set DTLS1.3 workaround");
+ return false;
+ }
+ }
+ } else {
+ MOZ_MTLOG(ML_INFO, "Setting up DTLS as server");
+ // Server side
+ rv = SSL_ConfigSecureServer(ssl_fd.get(), identity_->cert().get(),
+ identity_->privkey().get(),
+ identity_->auth_type());
+ if (rv != SECSuccess) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't set identity");
+ return false;
+ }
+
+ UniqueCERTCertList zero_certs(CERT_NewCertList());
+ rv = SSL_SetTrustAnchors(ssl_fd.get(), zero_certs.get());
+ if (rv != SECSuccess) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't set trust anchors");
+ return false;
+ }
+
+ // Insist on a certificate from the client
+ rv = SSL_OptionSet(ssl_fd.get(), SSL_REQUEST_CERTIFICATE, PR_TRUE);
+ if (rv != SECSuccess) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't request certificate");
+ return false;
+ }
+
+ rv = SSL_OptionSet(ssl_fd.get(), SSL_REQUIRE_CERTIFICATE, PR_TRUE);
+ if (rv != SECSuccess) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't require certificate");
+ return false;
+ }
+ }
+
+ SSLVersionRange version_range = {static_cast<PRUint16>(minVersion_),
+ static_cast<PRUint16>(maxVersion_)};
+
+ rv = SSL_VersionRangeSet(ssl_fd.get(), &version_range);
+ if (rv != SECSuccess) {
+ MOZ_MTLOG(ML_ERROR, "Can't disable SSLv3");
+ return false;
+ }
+
+ rv = SSL_OptionSet(ssl_fd.get(), SSL_ENABLE_SESSION_TICKETS, PR_FALSE);
+ if (rv != SECSuccess) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't disable session tickets");
+ return false;
+ }
+
+ rv = SSL_OptionSet(ssl_fd.get(), SSL_NO_CACHE, PR_TRUE);
+ if (rv != SECSuccess) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't disable session caching");
+ return false;
+ }
+
+ rv = SSL_OptionSet(ssl_fd.get(), SSL_ENABLE_DEFLATE, PR_FALSE);
+ if (rv != SECSuccess) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't disable deflate");
+ return false;
+ }
+
+ rv = SSL_OptionSet(ssl_fd.get(), SSL_ENABLE_RENEGOTIATION,
+ SSL_RENEGOTIATE_NEVER);
+ if (rv != SECSuccess) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't disable renegotiation");
+ return false;
+ }
+
+ rv = SSL_OptionSet(ssl_fd.get(), SSL_ENABLE_FALSE_START, PR_FALSE);
+ if (rv != SECSuccess) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't disable false start");
+ return false;
+ }
+
+ rv = SSL_OptionSet(ssl_fd.get(), SSL_NO_LOCKS, PR_TRUE);
+ if (rv != SECSuccess) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't disable locks");
+ return false;
+ }
+
+ rv = SSL_OptionSet(ssl_fd.get(), SSL_REUSE_SERVER_ECDHE_KEY, PR_FALSE);
+ if (rv != SECSuccess) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't disable ECDHE key reuse");
+ return false;
+ }
+
+ if (!SetupCipherSuites(ssl_fd)) {
+ return false;
+ }
+
+ rv = SSL_NamedGroupConfig(ssl_fd.get(), NamedGroupPreferences,
+ mozilla::ArrayLength(NamedGroupPreferences));
+ if (rv != SECSuccess) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't set named groups");
+ return false;
+ }
+
+ // Certificate validation
+ rv = SSL_AuthCertificateHook(ssl_fd.get(), AuthCertificateHook,
+ reinterpret_cast<void*>(this));
+ if (rv != SECSuccess) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't set certificate validation hook");
+ return false;
+ }
+
+ if (!SetupAlpn(ssl_fd)) {
+ return false;
+ }
+
+ // Now start the handshake
+ rv = SSL_ResetHandshake(ssl_fd.get(), role_ == SERVER ? PR_TRUE : PR_FALSE);
+ if (rv != SECSuccess) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't reset handshake");
+ return false;
+ }
+ ssl_fd_ = std::move(ssl_fd);
+
+ // Finally, get ready to receive data
+ downward_->SignalStateChange.connect(this, &TransportLayerDtls::StateChange);
+ downward_->SignalPacketReceived.connect(this,
+ &TransportLayerDtls::PacketReceived);
+
+ if (downward_->state() == TS_OPEN) {
+ TL_SET_STATE(TS_CONNECTING);
+ Handshake();
+ }
+
+ return true;
+}
+
+bool TransportLayerDtls::SetupAlpn(UniquePRFileDesc& ssl_fd) const {
+ if (alpn_allowed_.empty()) {
+ return true;
+ }
+
+ SECStatus rv = SSL_OptionSet(ssl_fd.get(), SSL_ENABLE_NPN, PR_FALSE);
+ if (rv != SECSuccess) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't disable NPN");
+ return false;
+ }
+
+ rv = SSL_OptionSet(ssl_fd.get(), SSL_ENABLE_ALPN, PR_TRUE);
+ if (rv != SECSuccess) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't enable ALPN");
+ return false;
+ }
+
+ unsigned char buf[MAX_ALPN_LENGTH];
+ size_t offset = 0;
+ for (const auto& tag : alpn_allowed_) {
+ if ((offset + 1 + tag.length()) >= sizeof(buf)) {
+ MOZ_MTLOG(ML_ERROR, "ALPN too long");
+ return false;
+ }
+ buf[offset++] = tag.length();
+ memcpy(buf + offset, tag.c_str(), tag.length());
+ offset += tag.length();
+ }
+ rv = SSL_SetNextProtoNego(ssl_fd.get(), buf, offset);
+ if (rv != SECSuccess) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't set ALPN string");
+ return false;
+ }
+ return true;
+}
+
+// Ciphers we need to enable. These are on by default in standard firefox
+// builds, but can be disabled with prefs and they aren't on in our unit tests
+// since that uses NSS default configuration.
+//
+// Only override prefs to comply with MUST statements in the security-arch doc.
+// Anything outside this list is governed by the usual combination of policy
+// and user preferences.
+static const uint32_t EnabledCiphers[] = {
+ TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
+ TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA};
+
+// Disable all NSS suites modes without PFS or with old and rusty ciphersuites.
+// Anything outside this list is governed by the usual combination of policy
+// and user preferences.
+static const uint32_t DisabledCiphers[] = {
+ // Bug 1310061: disable all SHA384 ciphers until fixed
+ TLS_AES_256_GCM_SHA384,
+ TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
+ TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+ TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,
+ TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,
+ TLS_DHE_RSA_WITH_AES_256_GCM_SHA384,
+ TLS_DHE_DSS_WITH_AES_256_GCM_SHA384,
+
+ TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
+ TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
+
+ TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,
+ TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
+ TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
+ TLS_ECDHE_RSA_WITH_RC4_128_SHA,
+
+ TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA,
+ TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA,
+ TLS_DHE_DSS_WITH_RC4_128_SHA,
+
+ TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,
+ TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,
+ TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,
+ TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,
+ TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,
+ TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA,
+ TLS_ECDH_ECDSA_WITH_RC4_128_SHA,
+ TLS_ECDH_RSA_WITH_RC4_128_SHA,
+
+ TLS_RSA_WITH_AES_128_GCM_SHA256,
+ TLS_RSA_WITH_AES_256_GCM_SHA384,
+ TLS_RSA_WITH_AES_128_CBC_SHA,
+ TLS_RSA_WITH_AES_128_CBC_SHA256,
+ TLS_RSA_WITH_CAMELLIA_128_CBC_SHA,
+ TLS_RSA_WITH_AES_256_CBC_SHA,
+ TLS_RSA_WITH_AES_256_CBC_SHA256,
+ TLS_RSA_WITH_CAMELLIA_256_CBC_SHA,
+ TLS_RSA_WITH_SEED_CBC_SHA,
+ TLS_RSA_WITH_3DES_EDE_CBC_SHA,
+ TLS_RSA_WITH_RC4_128_SHA,
+ TLS_RSA_WITH_RC4_128_MD5,
+
+ TLS_DHE_RSA_WITH_DES_CBC_SHA,
+ TLS_DHE_DSS_WITH_DES_CBC_SHA,
+ TLS_RSA_WITH_DES_CBC_SHA,
+
+ TLS_ECDHE_ECDSA_WITH_NULL_SHA,
+ TLS_ECDHE_RSA_WITH_NULL_SHA,
+ TLS_ECDH_ECDSA_WITH_NULL_SHA,
+ TLS_ECDH_RSA_WITH_NULL_SHA,
+ TLS_RSA_WITH_NULL_SHA,
+ TLS_RSA_WITH_NULL_SHA256,
+ TLS_RSA_WITH_NULL_MD5,
+};
+
+bool TransportLayerDtls::SetupCipherSuites(UniquePRFileDesc& ssl_fd) {
+ SECStatus rv;
+
+ // Set the SRTP ciphers
+ if (!enabled_srtp_ciphers_.empty()) {
+ rv = SSL_InstallExtensionHooks(ssl_fd.get(), ssl_use_srtp_xtn,
+ TransportLayerDtls::WriteSrtpXtn, this,
+ TransportLayerDtls::HandleSrtpXtn, this);
+ if (rv != SECSuccess) {
+ MOZ_MTLOG(ML_ERROR, LAYER_INFO << "unable to set SRTP extension handler");
+ return false;
+ }
+ }
+
+ for (const auto& cipher : EnabledCiphers) {
+ MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "Enabling: " << cipher);
+ rv = SSL_CipherPrefSet(ssl_fd.get(), cipher, PR_TRUE);
+ if (rv != SECSuccess) {
+ MOZ_MTLOG(ML_ERROR, LAYER_INFO << "Unable to enable suite: " << cipher);
+ return false;
+ }
+ }
+
+ for (const auto& cipher : DisabledCiphers) {
+ MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "Disabling: " << cipher);
+
+ PRBool enabled = false;
+ rv = SSL_CipherPrefGet(ssl_fd.get(), cipher, &enabled);
+ if (rv != SECSuccess) {
+ MOZ_MTLOG(ML_NOTICE, LAYER_INFO << "Unable to check if suite is enabled: "
+ << cipher);
+ return false;
+ }
+ if (enabled) {
+ rv = SSL_CipherPrefSet(ssl_fd.get(), cipher, PR_FALSE);
+ if (rv != SECSuccess) {
+ MOZ_MTLOG(ML_NOTICE,
+ LAYER_INFO << "Unable to disable suite: " << cipher);
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+nsresult TransportLayerDtls::GetCipherSuite(uint16_t* cipherSuite) const {
+ CheckThread();
+ if (!cipherSuite) {
+ MOZ_MTLOG(ML_ERROR, LAYER_INFO << "GetCipherSuite passed a nullptr");
+ return NS_ERROR_NULL_POINTER;
+ }
+ if (state_ != TS_OPEN) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ SSLChannelInfo info;
+ SECStatus rv = SSL_GetChannelInfo(ssl_fd_.get(), &info, sizeof(info));
+ if (rv != SECSuccess) {
+ MOZ_MTLOG(ML_NOTICE, LAYER_INFO << "GetCipherSuite can't get channel info");
+ return NS_ERROR_FAILURE;
+ }
+ *cipherSuite = info.cipherSuite;
+ return NS_OK;
+}
+
+std::vector<uint16_t> TransportLayerDtls::GetDefaultSrtpCiphers() {
+ std::vector<uint16_t> ciphers;
+
+ ciphers.push_back(kDtlsSrtpAeadAes128Gcm);
+ // Since we don't support DTLS 1.3 or SHA384 ciphers (see bug 1312976)
+ // we don't really enough entropy to prefer this over 128 bit
+ ciphers.push_back(kDtlsSrtpAeadAes256Gcm);
+ ciphers.push_back(kDtlsSrtpAes128CmHmacSha1_80);
+#ifndef NIGHTLY_BUILD
+ // To support bug 1491583 lets try to find out if we get bug reports if we no
+ // longer offer this in Nightly builds.
+ ciphers.push_back(kDtlsSrtpAes128CmHmacSha1_32);
+#endif
+
+ return ciphers;
+}
+
+void TransportLayerDtls::StateChange(TransportLayer* layer, State state) {
+ switch (state) {
+ case TS_NONE:
+ MOZ_ASSERT(false); // Can't happen
+ break;
+
+ case TS_INIT:
+ MOZ_MTLOG(ML_ERROR,
+ LAYER_INFO << "State change of lower layer to INIT forbidden");
+ TL_SET_STATE(TS_ERROR);
+ break;
+
+ case TS_CONNECTING:
+ MOZ_MTLOG(ML_INFO, LAYER_INFO << "Lower layer is connecting.");
+ break;
+
+ case TS_OPEN:
+ if (timer_) {
+ MOZ_MTLOG(ML_INFO,
+ LAYER_INFO << "Lower layer is now open; starting TLS");
+ timer_->Cancel();
+ timer_->SetTarget(target_);
+ // Async, since the ICE layer might need to send a STUN response, and we
+ // don't want the handshake to start until that is sent.
+ timer_->InitWithNamedFuncCallback(TimerCallback, this, 0,
+ nsITimer::TYPE_ONE_SHOT,
+ "TransportLayerDtls::TimerCallback");
+ TL_SET_STATE(TS_CONNECTING);
+ } else {
+ // We have already completed DTLS. Can happen if the ICE layer failed
+ // due to a loss of network, and then recovered.
+ TL_SET_STATE(TS_OPEN);
+ }
+ break;
+
+ case TS_CLOSED:
+ MOZ_MTLOG(ML_INFO, LAYER_INFO << "Lower layer is now closed");
+ TL_SET_STATE(TS_CLOSED);
+ break;
+
+ case TS_ERROR:
+ MOZ_MTLOG(ML_ERROR, LAYER_INFO << "Lower layer experienced an error");
+ TL_SET_STATE(TS_ERROR);
+ break;
+ }
+}
+
+void TransportLayerDtls::Handshake() {
+ if (!timer_) {
+ // We are done with DTLS, regardless of the state changes of lower layers
+ return;
+ }
+
+ // Clear the retransmit timer
+ timer_->Cancel();
+
+ MOZ_ASSERT(state_ == TS_CONNECTING);
+
+ SECStatus rv = SSL_ForceHandshake(ssl_fd_.get());
+
+ if (rv == SECSuccess) {
+ MOZ_MTLOG(ML_NOTICE, LAYER_INFO << "****** SSL handshake completed ******");
+ if (!cert_ok_) {
+ MOZ_MTLOG(ML_ERROR, LAYER_INFO << "Certificate check never occurred");
+ TL_SET_STATE(TS_ERROR);
+ return;
+ }
+ if (!CheckAlpn()) {
+ // Despite connecting, the connection doesn't have a valid ALPN label.
+ // Forcibly close the connection so that the peer isn't left hanging
+ // (assuming the close_notify isn't dropped).
+ ssl_fd_ = nullptr;
+ TL_SET_STATE(TS_ERROR);
+ return;
+ }
+
+ TL_SET_STATE(TS_OPEN);
+
+ RecordTlsTelemetry();
+ timer_ = nullptr;
+ } else {
+ int32_t err = PR_GetError();
+ switch (err) {
+ case SSL_ERROR_RX_MALFORMED_HANDSHAKE:
+ MOZ_MTLOG(ML_ERROR, LAYER_INFO << "Malformed DTLS message; ignoring");
+ // If this were TLS (and not DTLS), this would be fatal, but
+ // here we're required to ignore bad messages, so fall through
+ [[fallthrough]];
+ case PR_WOULD_BLOCK_ERROR:
+ MOZ_MTLOG(ML_NOTICE, LAYER_INFO << "Handshake would have blocked");
+ PRIntervalTime timeout;
+ rv = DTLS_GetHandshakeTimeout(ssl_fd_.get(), &timeout);
+ if (rv == SECSuccess) {
+ uint32_t timeout_ms = PR_IntervalToMilliseconds(timeout);
+
+ MOZ_MTLOG(ML_DEBUG,
+ LAYER_INFO << "Setting DTLS timeout to " << timeout_ms);
+ timer_->SetTarget(target_);
+ timer_->InitWithNamedFuncCallback(
+ TimerCallback, this, timeout_ms, nsITimer::TYPE_ONE_SHOT,
+ "TransportLayerDtls::TimerCallback");
+ }
+ break;
+ default:
+ const char* err_msg = PR_ErrorToName(err);
+ MOZ_MTLOG(ML_ERROR, LAYER_INFO << "DTLS handshake error " << err << " ("
+ << err_msg << ")");
+ TL_SET_STATE(TS_ERROR);
+ break;
+ }
+ }
+}
+
+// Checks if ALPN was negotiated correctly and returns false if it wasn't.
+// After this returns successfully, alpn_ will be set to the negotiated
+// protocol.
+bool TransportLayerDtls::CheckAlpn() {
+ if (alpn_allowed_.empty()) {
+ return true;
+ }
+
+ SSLNextProtoState alpnState;
+ char chosenAlpn[MAX_ALPN_LENGTH];
+ unsigned int chosenAlpnLen;
+ SECStatus rv = SSL_GetNextProto(ssl_fd_.get(), &alpnState,
+ reinterpret_cast<unsigned char*>(chosenAlpn),
+ &chosenAlpnLen, sizeof(chosenAlpn));
+ if (rv != SECSuccess) {
+ MOZ_MTLOG(ML_ERROR, LAYER_INFO << "ALPN error");
+ return false;
+ }
+ switch (alpnState) {
+ case SSL_NEXT_PROTO_SELECTED:
+ case SSL_NEXT_PROTO_NEGOTIATED:
+ break; // OK
+
+ case SSL_NEXT_PROTO_NO_SUPPORT:
+ MOZ_MTLOG(ML_NOTICE,
+ LAYER_INFO << "ALPN not negotiated, "
+ << (alpn_default_.empty() ? "failing"
+ : "selecting default"));
+ alpn_ = alpn_default_;
+ return !alpn_.empty();
+
+ case SSL_NEXT_PROTO_NO_OVERLAP:
+ // This only happens if there is a custom NPN/ALPN callback installed and
+ // that callback doesn't properly handle ALPN.
+ MOZ_MTLOG(ML_ERROR, LAYER_INFO << "error in ALPN selection callback");
+ return false;
+
+ case SSL_NEXT_PROTO_EARLY_VALUE:
+ MOZ_CRASH("Unexpected 0-RTT ALPN value");
+ return false;
+ }
+
+ // Warning: NSS won't null terminate the ALPN string for us.
+ std::string chosen(chosenAlpn, chosenAlpnLen);
+ MOZ_MTLOG(ML_NOTICE, LAYER_INFO << "Selected ALPN string: " << chosen);
+ if (alpn_allowed_.find(chosen) == alpn_allowed_.end()) {
+ // Maybe our peer chose a protocol we didn't offer (when we are client), or
+ // something is seriously wrong.
+ std::ostringstream ss;
+ for (auto i = alpn_allowed_.begin(); i != alpn_allowed_.end(); ++i) {
+ ss << (i == alpn_allowed_.begin() ? " '" : ", '") << *i << "'";
+ }
+ MOZ_MTLOG(ML_ERROR, LAYER_INFO << "Bad ALPN string: '" << chosen
+ << "'; permitted:" << ss.str());
+ return false;
+ }
+ alpn_ = chosen;
+ return true;
+}
+
+void TransportLayerDtls::PacketReceived(TransportLayer* layer,
+ MediaPacket& packet) {
+ CheckThread();
+ MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "PacketReceived(" << packet.len() << ")");
+
+ if (state_ != TS_CONNECTING && state_ != TS_OPEN) {
+ MOZ_MTLOG(ML_DEBUG,
+ LAYER_INFO << "Discarding packet in inappropriate state");
+ return;
+ }
+
+ if (!packet.data()) {
+ // Something ate this, probably the SRTP layer
+ return;
+ }
+
+ if (packet.type() != MediaPacket::DTLS) {
+ return;
+ }
+
+ nspr_io_adapter_->PacketReceived(packet);
+ GetDecryptedPackets();
+}
+
+void TransportLayerDtls::GetDecryptedPackets() {
+ // If we're still connecting, try to handshake
+ if (state_ == TS_CONNECTING) {
+ Handshake();
+ }
+
+ // Now try a recv if we're open, since there might be data left
+ if (state_ == TS_OPEN) {
+ int32_t rv;
+ // One packet might contain several DTLS packets
+ do {
+ // nICEr uses a 9216 bytes buffer to allow support for jumbo frames
+ // Can we peek to get a better idea of the actual size?
+ static const size_t kBufferSize = 9216;
+ auto buffer = MakeUnique<uint8_t[]>(kBufferSize);
+ rv = PR_Recv(ssl_fd_.get(), buffer.get(), kBufferSize, 0,
+ PR_INTERVAL_NO_WAIT);
+ if (rv > 0) {
+ // We have data
+ MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "Read " << rv << " bytes from NSS");
+ MediaPacket packet;
+ packet.SetType(MediaPacket::SCTP);
+ packet.Take(std::move(buffer), static_cast<size_t>(rv));
+ SignalPacketReceived(this, packet);
+ } else if (rv == 0) {
+ TL_SET_STATE(TS_CLOSED);
+ } else {
+ int32_t err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR) {
+ // This gets ignored
+ MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "Receive would have blocked");
+ } else {
+ MOZ_MTLOG(ML_NOTICE, LAYER_INFO << "NSS Error " << err);
+ TL_SET_STATE(TS_ERROR);
+ }
+ }
+ } while (rv > 0);
+ }
+}
+
+void TransportLayerDtls::SetState(State state, const char* file,
+ unsigned line) {
+ if (timer_) {
+ switch (state) {
+ case TS_NONE:
+ case TS_INIT:
+ MOZ_ASSERT(false);
+ break;
+ case TS_CONNECTING:
+ break;
+ case TS_OPEN:
+ case TS_CLOSED:
+ case TS_ERROR:
+ timer_->Cancel();
+ break;
+ }
+ }
+
+ TransportLayer::SetState(state, file, line);
+}
+
+TransportResult TransportLayerDtls::SendPacket(MediaPacket& packet) {
+ CheckThread();
+ if (state_ != TS_OPEN) {
+ MOZ_MTLOG(ML_ERROR,
+ LAYER_INFO << "Can't call SendPacket() in state " << state_);
+ return TE_ERROR;
+ }
+
+ int32_t rv = PR_Send(ssl_fd_.get(), packet.data(), packet.len(), 0,
+ PR_INTERVAL_NO_WAIT);
+
+ if (rv > 0) {
+ // We have data
+ MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "Wrote " << rv << " bytes to SSL Layer");
+ return rv;
+ }
+
+ if (rv == 0) {
+ TL_SET_STATE(TS_CLOSED);
+ return 0;
+ }
+
+ int32_t err = PR_GetError();
+
+ if (err == PR_WOULD_BLOCK_ERROR) {
+ // This gets ignored
+ MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "Send would have blocked");
+ return TE_WOULDBLOCK;
+ }
+
+ MOZ_MTLOG(ML_NOTICE, LAYER_INFO << "NSS Error " << err);
+ TL_SET_STATE(TS_ERROR);
+ return TE_ERROR;
+}
+
+SECStatus TransportLayerDtls::GetClientAuthDataHook(
+ void* arg, PRFileDesc* fd, CERTDistNames* caNames,
+ CERTCertificate** pRetCert, SECKEYPrivateKey** pRetKey) {
+ MOZ_MTLOG(ML_DEBUG, "Server requested client auth");
+
+ TransportLayerDtls* stream = reinterpret_cast<TransportLayerDtls*>(arg);
+ stream->CheckThread();
+
+ if (!stream->identity_) {
+ MOZ_MTLOG(ML_ERROR, "No identity available");
+ PR_SetError(SSL_ERROR_NO_CERTIFICATE, 0);
+ return SECFailure;
+ }
+
+ *pRetCert = CERT_DupCertificate(stream->identity_->cert().get());
+ if (!*pRetCert) {
+ PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
+ return SECFailure;
+ }
+
+ *pRetKey = SECKEY_CopyPrivateKey(stream->identity_->privkey().get());
+ if (!*pRetKey) {
+ CERT_DestroyCertificate(*pRetCert);
+ *pRetCert = nullptr;
+ PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
+ return SECFailure;
+ }
+
+ return SECSuccess;
+}
+
+nsresult TransportLayerDtls::SetSrtpCiphers(
+ const std::vector<uint16_t>& ciphers) {
+ enabled_srtp_ciphers_ = std::move(ciphers);
+ return NS_OK;
+}
+
+nsresult TransportLayerDtls::GetSrtpCipher(uint16_t* cipher) const {
+ CheckThread();
+ if (srtp_cipher_ == 0) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ *cipher = srtp_cipher_;
+ return NS_OK;
+}
+
+static uint8_t* WriteUint16(uint8_t* cursor, uint16_t v) {
+ *cursor++ = v >> 8;
+ *cursor++ = v & 0xff;
+ return cursor;
+}
+
+static SSLHandshakeType SrtpXtnServerMessage(PRFileDesc* fd) {
+ SSLPreliminaryChannelInfo preinfo;
+ SECStatus rv = SSL_GetPreliminaryChannelInfo(fd, &preinfo, sizeof(preinfo));
+ if (rv != SECSuccess) {
+ MOZ_ASSERT(false, "Can't get version info");
+ return ssl_hs_client_hello;
+ }
+ return (preinfo.protocolVersion >= SSL_LIBRARY_VERSION_TLS_1_3)
+ ? ssl_hs_encrypted_extensions
+ : ssl_hs_server_hello;
+}
+
+/* static */
+PRBool TransportLayerDtls::WriteSrtpXtn(PRFileDesc* fd,
+ SSLHandshakeType message, uint8_t* data,
+ unsigned int* len, unsigned int max_len,
+ void* arg) {
+ auto self = reinterpret_cast<TransportLayerDtls*>(arg);
+
+ // ClientHello: send all supported versions.
+ if (message == ssl_hs_client_hello) {
+ MOZ_ASSERT(self->role_ == CLIENT);
+ MOZ_ASSERT(self->enabled_srtp_ciphers_.size(), "Haven't enabled SRTP");
+ // We will take 2 octets for each cipher, plus a 2 octet length and 1 octet
+ // for the length of the empty MKI.
+ if (max_len < self->enabled_srtp_ciphers_.size() * 2 + 3) {
+ MOZ_ASSERT(false, "Not enough space to send SRTP extension");
+ return false;
+ }
+ uint8_t* cursor = WriteUint16(data, self->enabled_srtp_ciphers_.size() * 2);
+ for (auto cs : self->enabled_srtp_ciphers_) {
+ cursor = WriteUint16(cursor, cs);
+ }
+ *cursor++ = 0; // MKI is empty
+ *len = cursor - data;
+ return true;
+ }
+
+ if (message == SrtpXtnServerMessage(fd)) {
+ MOZ_ASSERT(self->role_ == SERVER);
+ if (!self->srtp_cipher_) {
+ // Not negotiated. Definitely bad, but the connection can fail later.
+ return false;
+ }
+ if (max_len < 5) {
+ MOZ_ASSERT(false, "Not enough space to send SRTP extension");
+ return false;
+ }
+
+ uint8_t* cursor = WriteUint16(data, 2); // Length = 2.
+ cursor = WriteUint16(cursor, self->srtp_cipher_);
+ *cursor++ = 0; // No MKI
+ *len = cursor - data;
+ return true;
+ }
+
+ return false;
+}
+
+class TlsParser {
+ public:
+ TlsParser(const uint8_t* data, size_t len) : cursor_(data), remaining_(len) {}
+
+ bool error() const { return error_; }
+ size_t remaining() const { return remaining_; }
+
+ template <typename T,
+ class = typename std::enable_if<std::is_unsigned<T>::value>::type>
+ void Read(T* v, size_t sz = sizeof(T)) {
+ MOZ_ASSERT(sz <= sizeof(T),
+ "Type is too small to hold the value requested");
+ if (remaining_ < sz) {
+ error_ = true;
+ return;
+ }
+
+ T result = 0;
+ for (size_t i = 0; i < sz; ++i) {
+ result = (result << 8) | *cursor_++;
+ remaining_--;
+ }
+ *v = result;
+ }
+
+ template <typename T,
+ class = typename std::enable_if<std::is_unsigned<T>::value>::type>
+ void ReadVector(std::vector<T>* v, size_t w) {
+ MOZ_ASSERT(v->empty(), "vector needs to be empty");
+
+ uint32_t len;
+ Read(&len, w);
+ if (error_ || len % sizeof(T) != 0 || len > remaining_) {
+ error_ = true;
+ return;
+ }
+
+ size_t count = len / sizeof(T);
+ v->reserve(count);
+ for (T i = 0; !error_ && i < count; ++i) {
+ T item;
+ Read(&item);
+ if (!error_) {
+ v->push_back(item);
+ }
+ }
+ }
+
+ void Skip(size_t n) {
+ if (remaining_ < n) {
+ error_ = true;
+ } else {
+ cursor_ += n;
+ remaining_ -= n;
+ }
+ }
+
+ size_t SkipVector(size_t w) {
+ uint32_t len = 0;
+ Read(&len, w);
+ Skip(len);
+ return len;
+ }
+
+ private:
+ const uint8_t* cursor_;
+ size_t remaining_;
+ bool error_ = false;
+};
+
+/* static */
+SECStatus TransportLayerDtls::HandleSrtpXtn(
+ PRFileDesc* fd, SSLHandshakeType message, const uint8_t* data,
+ unsigned int len, SSLAlertDescription* alert, void* arg) {
+ static const uint8_t kTlsAlertHandshakeFailure = 40;
+ static const uint8_t kTlsAlertIllegalParameter = 47;
+ static const uint8_t kTlsAlertDecodeError = 50;
+ static const uint8_t kTlsAlertUnsupportedExtension = 110;
+
+ auto self = reinterpret_cast<TransportLayerDtls*>(arg);
+
+ // Parse the extension.
+ TlsParser parser(data, len);
+ std::vector<uint16_t> advertised;
+ parser.ReadVector(&advertised, 2);
+ size_t mki_len = parser.SkipVector(1);
+ if (parser.error() || parser.remaining() > 0) {
+ *alert = kTlsAlertDecodeError;
+ return SECFailure;
+ }
+
+ if (message == ssl_hs_client_hello) {
+ MOZ_ASSERT(self->role_ == SERVER);
+ if (self->enabled_srtp_ciphers_.empty()) {
+ // We don't have SRTP enabled, which is probably bad, but no sense in
+ // having the handshake fail at this point, let the client decide if this
+ // is a problem.
+ return SECSuccess;
+ }
+
+ for (auto supported : self->enabled_srtp_ciphers_) {
+ auto it = std::find(advertised.begin(), advertised.end(), supported);
+ if (it != advertised.end()) {
+ self->srtp_cipher_ = supported;
+ return SECSuccess;
+ }
+ }
+
+ // No common cipher.
+ *alert = kTlsAlertHandshakeFailure;
+ return SECFailure;
+ }
+
+ if (message == SrtpXtnServerMessage(fd)) {
+ MOZ_ASSERT(self->role_ == CLIENT);
+ if (advertised.size() != 1 || mki_len > 0) {
+ *alert = kTlsAlertIllegalParameter;
+ return SECFailure;
+ }
+ self->srtp_cipher_ = advertised[0];
+ return SECSuccess;
+ }
+
+ *alert = kTlsAlertUnsupportedExtension;
+ return SECFailure;
+}
+
+nsresult TransportLayerDtls::ExportKeyingMaterial(const std::string& label,
+ bool use_context,
+ const std::string& context,
+ unsigned char* out,
+ unsigned int outlen) {
+ CheckThread();
+ if (state_ != TS_OPEN) {
+ MOZ_ASSERT(false, "Transport must be open for ExportKeyingMaterial");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ SECStatus rv = SSL_ExportKeyingMaterial(
+ ssl_fd_.get(), label.c_str(), label.size(), use_context,
+ reinterpret_cast<const unsigned char*>(context.c_str()), context.size(),
+ out, outlen);
+ if (rv != SECSuccess) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't export SSL keying material");
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+SECStatus TransportLayerDtls::AuthCertificateHook(void* arg, PRFileDesc* fd,
+ PRBool checksig,
+ PRBool isServer) {
+ TransportLayerDtls* stream = reinterpret_cast<TransportLayerDtls*>(arg);
+ stream->CheckThread();
+ return stream->AuthCertificateHook(fd, checksig, isServer);
+}
+
+SECStatus TransportLayerDtls::CheckDigest(
+ const DtlsDigest& digest, UniqueCERTCertificate& peer_cert) const {
+ DtlsDigest computed_digest(digest.algorithm_);
+
+ MOZ_MTLOG(ML_DEBUG,
+ LAYER_INFO << "Checking digest, algorithm=" << digest.algorithm_);
+ nsresult res = DtlsIdentity::ComputeFingerprint(peer_cert, &computed_digest);
+ if (NS_FAILED(res)) {
+ MOZ_MTLOG(ML_ERROR, "Could not compute peer fingerprint for digest "
+ << digest.algorithm_);
+ // Go to end
+ PR_SetError(SSL_ERROR_BAD_CERTIFICATE, 0);
+ return SECFailure;
+ }
+
+ if (computed_digest != digest) {
+ MOZ_MTLOG(ML_ERROR, "Digest does not match");
+ PR_SetError(SSL_ERROR_BAD_CERTIFICATE, 0);
+ return SECFailure;
+ }
+
+ return SECSuccess;
+}
+
+SECStatus TransportLayerDtls::AuthCertificateHook(PRFileDesc* fd,
+ PRBool checksig,
+ PRBool isServer) {
+ CheckThread();
+ UniqueCERTCertificate peer_cert(SSL_PeerCertificate(fd));
+
+ // We are not set up to take this being called multiple
+ // times. Change this if we ever add renegotiation.
+ MOZ_ASSERT(!auth_hook_called_);
+ if (auth_hook_called_) {
+ PR_SetError(PR_UNKNOWN_ERROR, 0);
+ return SECFailure;
+ }
+ auth_hook_called_ = true;
+
+ MOZ_ASSERT(verification_mode_ != VERIFY_UNSET);
+
+ switch (verification_mode_) {
+ case VERIFY_UNSET:
+ // Break out to error exit
+ PR_SetError(PR_UNKNOWN_ERROR, 0);
+ break;
+
+ case VERIFY_ALLOW_ALL:
+ cert_ok_ = true;
+ return SECSuccess;
+
+ case VERIFY_DIGEST: {
+ MOZ_ASSERT(!digests_.empty());
+ // Check all the provided digests
+
+ // Checking functions call PR_SetError()
+ SECStatus rv = SECFailure;
+ for (auto digest : digests_) {
+ rv = CheckDigest(digest, peer_cert);
+
+ // Matches a digest, we are good to go
+ if (rv == SECSuccess) {
+ cert_ok_ = true;
+ return SECSuccess;
+ }
+ }
+ } break;
+ default:
+ MOZ_CRASH(); // Can't happen
+ }
+
+ return SECFailure;
+}
+
+void TransportLayerDtls::TimerCallback(nsITimer* timer, void* arg) {
+ TransportLayerDtls* dtls = reinterpret_cast<TransportLayerDtls*>(arg);
+
+ MOZ_MTLOG(ML_DEBUG, "DTLS timer expired");
+
+ dtls->Handshake();
+}
+
+void TransportLayerDtls::RecordTlsTelemetry() {
+ MOZ_ASSERT(state_ == TS_OPEN);
+ SSLChannelInfo info;
+ SECStatus ss = SSL_GetChannelInfo(ssl_fd_.get(), &info, sizeof(info));
+ if (ss != SECSuccess) {
+ MOZ_MTLOG(ML_NOTICE,
+ LAYER_INFO << "RecordTlsTelemetry failed to get channel info");
+ return;
+ }
+
+ uint16_t telemetry_cipher = 0;
+
+ switch (info.cipherSuite) {
+ /* Old DHE ciphers: candidates for removal, see bug 1227519 */
+ case TLS_DHE_RSA_WITH_AES_128_CBC_SHA:
+ telemetry_cipher = 1;
+ break;
+ case TLS_DHE_RSA_WITH_AES_256_CBC_SHA:
+ telemetry_cipher = 2;
+ break;
+ /* Current ciphers */
+ case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA:
+ telemetry_cipher = 3;
+ break;
+ case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:
+ telemetry_cipher = 4;
+ break;
+ case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA:
+ telemetry_cipher = 5;
+ break;
+ case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+ telemetry_cipher = 6;
+ break;
+ case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+ telemetry_cipher = 7;
+ break;
+ case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+ telemetry_cipher = 8;
+ break;
+ case TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+ telemetry_cipher = 9;
+ break;
+ case TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
+ telemetry_cipher = 10;
+ break;
+ /* TLS 1.3 ciphers */
+ case TLS_AES_128_GCM_SHA256:
+ telemetry_cipher = 11;
+ break;
+ case TLS_CHACHA20_POLY1305_SHA256:
+ telemetry_cipher = 12;
+ break;
+ case TLS_AES_256_GCM_SHA384:
+ telemetry_cipher = 13;
+ break;
+ }
+
+ Telemetry::Accumulate(Telemetry::WEBRTC_DTLS_CIPHER, telemetry_cipher);
+
+ uint16_t cipher;
+ nsresult rv = GetSrtpCipher(&cipher);
+
+ if (NS_FAILED(rv)) {
+ MOZ_MTLOG(ML_DEBUG, "No SRTP cipher suite");
+ return;
+ }
+
+ auto cipher_label = mozilla::Telemetry::LABELS_WEBRTC_SRTP_CIPHER::Unknown;
+
+ switch (cipher) {
+ case kDtlsSrtpAes128CmHmacSha1_80:
+ cipher_label = Telemetry::LABELS_WEBRTC_SRTP_CIPHER::Aes128CmHmacSha1_80;
+ break;
+ case kDtlsSrtpAes128CmHmacSha1_32:
+ cipher_label = Telemetry::LABELS_WEBRTC_SRTP_CIPHER::Aes128CmHmacSha1_32;
+ break;
+ case kDtlsSrtpAeadAes128Gcm:
+ cipher_label = Telemetry::LABELS_WEBRTC_SRTP_CIPHER::AeadAes128Gcm;
+ break;
+ case kDtlsSrtpAeadAes256Gcm:
+ cipher_label = Telemetry::LABELS_WEBRTC_SRTP_CIPHER::AeadAes256Gcm;
+ break;
+ }
+
+ Telemetry::AccumulateCategorical(cipher_label);
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/transport/transportlayerdtls.h b/dom/media/webrtc/transport/transportlayerdtls.h
new file mode 100644
index 0000000000..078b266e8b
--- /dev/null
+++ b/dom/media/webrtc/transport/transportlayerdtls.h
@@ -0,0 +1,187 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+
+#ifndef transportlayerdtls_h__
+#define transportlayerdtls_h__
+
+#include <queue>
+#include <set>
+
+#ifdef XP_MACOSX
+// ensure that Apple Security kit enum goes before "sslproto.h"
+# include <CoreFoundation/CFAvailability.h>
+# include <Security/CipherSuite.h>
+#endif
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "nsCOMPtr.h"
+#include "nsITimer.h"
+#include "ScopedNSSTypes.h"
+#include "m_cpp_utils.h"
+#include "dtlsidentity.h"
+#include "transportlayer.h"
+#include "ssl.h"
+#include "sslproto.h"
+
+namespace mozilla {
+
+// RFC 5764 (we don't support the NULL cipher)
+static const uint16_t kDtlsSrtpAes128CmHmacSha1_80 = 0x0001;
+static const uint16_t kDtlsSrtpAes128CmHmacSha1_32 = 0x0002;
+// RFC 7714
+static const uint16_t kDtlsSrtpAeadAes128Gcm = 0x0007;
+static const uint16_t kDtlsSrtpAeadAes256Gcm = 0x0008;
+
+struct Packet;
+
+class TransportLayerNSPRAdapter {
+ public:
+ explicit TransportLayerNSPRAdapter(TransportLayer* output)
+ : output_(output), input_(), enabled_(true) {}
+
+ void PacketReceived(MediaPacket& packet);
+ int32_t Recv(void* buf, int32_t buflen);
+ int32_t Write(const void* buf, int32_t length);
+ void SetEnabled(bool enabled) { enabled_ = enabled; }
+
+ private:
+ DISALLOW_COPY_ASSIGN(TransportLayerNSPRAdapter);
+
+ TransportLayer* output_;
+ std::queue<MediaPacket*> input_;
+ bool enabled_;
+};
+
+class TransportLayerDtls final : public TransportLayer {
+ public:
+ TransportLayerDtls() = default;
+
+ virtual ~TransportLayerDtls();
+
+ enum Role { CLIENT, SERVER };
+ enum Verification { VERIFY_UNSET, VERIFY_ALLOW_ALL, VERIFY_DIGEST };
+
+ // DTLS-specific operations
+ void SetRole(Role role) { role_ = role; }
+ Role role() { return role_; }
+
+ enum class Version : uint16_t {
+ DTLS_1_0 = SSL_LIBRARY_VERSION_DTLS_1_0,
+ DTLS_1_2 = SSL_LIBRARY_VERSION_DTLS_1_2,
+ DTLS_1_3 = SSL_LIBRARY_VERSION_DTLS_1_3
+ };
+ void SetMinMaxVersion(Version min_version, Version max_version);
+
+ void SetIdentity(const RefPtr<DtlsIdentity>& identity) {
+ identity_ = identity;
+ }
+ nsresult SetAlpn(const std::set<std::string>& allowedAlpn,
+ const std::string& alpnDefault);
+ const std::string& GetNegotiatedAlpn() const { return alpn_; }
+
+ nsresult SetVerificationAllowAll();
+
+ nsresult SetVerificationDigest(const DtlsDigest& digest);
+
+ nsresult GetCipherSuite(uint16_t* cipherSuite) const;
+
+ nsresult SetSrtpCiphers(const std::vector<uint16_t>& ciphers);
+ nsresult GetSrtpCipher(uint16_t* cipher) const;
+ static std::vector<uint16_t> GetDefaultSrtpCiphers();
+
+ nsresult ExportKeyingMaterial(const std::string& label, bool use_context,
+ const std::string& context, unsigned char* out,
+ unsigned int outlen);
+
+ // Transport layer overrides.
+ nsresult InitInternal() override;
+ void WasInserted() override;
+ TransportResult SendPacket(MediaPacket& packet) override;
+
+ // Signals
+ void StateChange(TransportLayer* layer, State state);
+ void PacketReceived(TransportLayer* layer, MediaPacket& packet);
+
+ // For testing use only. Returns the fd.
+ PRFileDesc* internal_fd() {
+ CheckThread();
+ return ssl_fd_.get();
+ }
+
+ TRANSPORT_LAYER_ID("dtls")
+
+ protected:
+ void SetState(State state, const char* file, unsigned line) override;
+
+ private:
+ DISALLOW_COPY_ASSIGN(TransportLayerDtls);
+
+ bool Setup();
+ bool SetupCipherSuites(UniquePRFileDesc& ssl_fd);
+ bool SetupAlpn(UniquePRFileDesc& ssl_fd) const;
+ void GetDecryptedPackets();
+ void Handshake();
+
+ bool CheckAlpn();
+
+ static SECStatus GetClientAuthDataHook(void* arg, PRFileDesc* fd,
+ CERTDistNames* caNames,
+ CERTCertificate** pRetCert,
+ SECKEYPrivateKey** pRetKey);
+ static SECStatus AuthCertificateHook(void* arg, PRFileDesc* fd,
+ PRBool checksig, PRBool isServer);
+ SECStatus AuthCertificateHook(PRFileDesc* fd, PRBool checksig,
+ PRBool isServer);
+
+ static void TimerCallback(nsITimer* timer, void* arg);
+
+ SECStatus CheckDigest(const DtlsDigest& digest,
+ UniqueCERTCertificate& cert) const;
+
+ void RecordHandshakeCompletionTelemetry(TransportLayer::State endState);
+ void RecordTlsTelemetry();
+
+ static PRBool WriteSrtpXtn(PRFileDesc* fd, SSLHandshakeType message,
+ uint8_t* data, unsigned int* len,
+ unsigned int max_len, void* arg);
+
+ static SECStatus HandleSrtpXtn(PRFileDesc* fd, SSLHandshakeType message,
+ const uint8_t* data, unsigned int len,
+ SSLAlertDescription* alert, void* arg);
+
+ RefPtr<DtlsIdentity> identity_;
+ // What ALPN identifiers are permitted.
+ std::set<std::string> alpn_allowed_;
+ // What ALPN identifier is used if ALPN is not supported.
+ // The empty string indicates that ALPN is required.
+ std::string alpn_default_;
+ // What ALPN string was negotiated.
+ std::string alpn_;
+ std::vector<uint16_t> enabled_srtp_ciphers_;
+ uint16_t srtp_cipher_ = 0;
+
+ Role role_ = CLIENT;
+ Verification verification_mode_ = VERIFY_UNSET;
+ std::vector<DtlsDigest> digests_;
+
+ Version minVersion_ = Version::DTLS_1_0;
+ Version maxVersion_ = Version::DTLS_1_2;
+
+ // Must delete nspr_io_adapter after ssl_fd_ b/c ssl_fd_ causes an alert
+ // (ssl_fd_ contains an un-owning pointer to nspr_io_adapter_)
+ UniquePtr<TransportLayerNSPRAdapter> nspr_io_adapter_ = nullptr;
+ UniquePRFileDesc ssl_fd_ = nullptr;
+
+ nsCOMPtr<nsITimer> timer_ = nullptr;
+ bool auth_hook_called_ = false;
+ bool cert_ok_ = false;
+};
+
+} // namespace mozilla
+#endif
diff --git a/dom/media/webrtc/transport/transportlayerice.cpp b/dom/media/webrtc/transport/transportlayerice.cpp
new file mode 100644
index 0000000000..03d8131ead
--- /dev/null
+++ b/dom/media/webrtc/transport/transportlayerice.cpp
@@ -0,0 +1,168 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+
+// Some of this code is cut-and-pasted from nICEr. Copyright is:
+
+/*
+Copyright (c) 2007, Adobe Systems, Incorporated
+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 Adobe Systems, Network Resonance 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
+OWNER 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.
+*/
+
+#include <string>
+
+#include "nsComponentManagerUtils.h"
+#include "nsError.h"
+#include "nsNetCID.h"
+
+// nICEr includes
+extern "C" {
+#include "nr_api.h"
+#include "ice_util.h"
+#include "transport_addr.h"
+}
+
+// Local includes
+#include "logging.h"
+#include "nricemediastream.h"
+#include "transportlayerice.h"
+
+namespace mozilla {
+
+#ifdef ERROR
+# undef ERROR
+#endif
+
+MOZ_MTLOG_MODULE("mtransport")
+
+TransportLayerIce::TransportLayerIce() : stream_(nullptr), component_(0) {
+ // setup happens later
+}
+
+TransportLayerIce::~TransportLayerIce() {
+ // No need to do anything here, since we use smart pointers
+}
+
+void TransportLayerIce::SetParameters(RefPtr<NrIceMediaStream> stream,
+ int component) {
+ // Stream could be null in the case of some badly written js that causes
+ // us to be in an ICE restart case, but not have valid streams due to
+ // not calling PeerConnectionImpl::EnsureTransports if
+ // PeerConnectionImpl::SetSignalingState_m thinks the conditions were
+ // not correct. We also solved a case where an incoming answer was
+ // incorrectly beginning an ICE restart when the offer did not indicate one.
+ if (!stream) {
+ MOZ_ASSERT(false);
+ return;
+ }
+
+ stream_ = stream;
+ component_ = component;
+
+ PostSetup();
+}
+
+void TransportLayerIce::PostSetup() {
+ stream_->SignalReady.connect(this, &TransportLayerIce::IceReady);
+ stream_->SignalFailed.connect(this, &TransportLayerIce::IceFailed);
+ stream_->SignalPacketReceived.connect(this,
+ &TransportLayerIce::IcePacketReceived);
+ if (stream_->state() == NrIceMediaStream::ICE_OPEN) {
+ TL_SET_STATE(TS_OPEN);
+ }
+}
+
+TransportResult TransportLayerIce::SendPacket(MediaPacket& packet) {
+ CheckThread();
+ SignalPacketSending(this, packet);
+ nsresult res = stream_->SendPacket(component_, packet.data(), packet.len());
+
+ if (!NS_SUCCEEDED(res)) {
+ return (res == NS_BASE_STREAM_WOULD_BLOCK) ? TE_WOULDBLOCK : TE_ERROR;
+ }
+
+ MOZ_MTLOG(ML_DEBUG,
+ LAYER_INFO << " SendPacket(" << packet.len() << ") succeeded");
+
+ return packet.len();
+}
+
+void TransportLayerIce::IceCandidate(NrIceMediaStream* stream,
+ const std::string&) {
+ // NO-OP for now
+}
+
+void TransportLayerIce::IceReady(NrIceMediaStream* stream) {
+ CheckThread();
+ // only handle the current stream (not the old stream during restart)
+ if (stream != stream_) {
+ return;
+ }
+ MOZ_MTLOG(ML_INFO, LAYER_INFO << "ICE Ready(" << stream->name() << ","
+ << component_ << ")");
+ TL_SET_STATE(TS_OPEN);
+}
+
+void TransportLayerIce::IceFailed(NrIceMediaStream* stream) {
+ CheckThread();
+ // only handle the current stream (not the old stream during restart)
+ if (stream != stream_) {
+ return;
+ }
+ MOZ_MTLOG(ML_INFO, LAYER_INFO << "ICE Failed(" << stream->name() << ","
+ << component_ << ")");
+ TL_SET_STATE(TS_ERROR);
+}
+
+void TransportLayerIce::IcePacketReceived(NrIceMediaStream* stream,
+ int component,
+ const unsigned char* data, int len) {
+ CheckThread();
+ // We get packets for both components, so ignore the ones that aren't
+ // for us.
+ if (component_ != component) return;
+
+ MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "PacketReceived(" << stream->name() << ","
+ << component << "," << len << ")");
+ // Might be useful to allow MediaPacket to borrow a buffer (ie; not take
+ // ownership, but copy it if the MediaPacket is moved). This could be a
+ // footgun though with MediaPackets that end up on the heap.
+ MediaPacket packet;
+ packet.Copy(data, len);
+ packet.Categorize();
+
+ SignalPacketReceived(this, packet);
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/transport/transportlayerice.h b/dom/media/webrtc/transport/transportlayerice.h
new file mode 100644
index 0000000000..1c55b71ce8
--- /dev/null
+++ b/dom/media/webrtc/transport/transportlayerice.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+
+// This is a wrapper around the nICEr ICE stack
+#ifndef transportlayerice_h__
+#define transportlayerice_h__
+
+#include "sigslot.h"
+
+#include "mozilla/RefPtr.h"
+
+#include "m_cpp_utils.h"
+
+#include "nricemediastream.h"
+#include "transportlayer.h"
+
+// An ICE transport layer -- corresponds to a single ICE
+namespace mozilla {
+
+class TransportLayerIce : public TransportLayer {
+ public:
+ TransportLayerIce();
+
+ virtual ~TransportLayerIce();
+
+ void SetParameters(RefPtr<NrIceMediaStream> stream, int component);
+
+ void ResetOldStream(); // called after successful ice restart
+ void RestoreOldStream(); // called after unsuccessful ice restart
+
+ // Transport layer overrides.
+ TransportResult SendPacket(MediaPacket& packet) override;
+
+ // Slots for ICE
+ void IceCandidate(NrIceMediaStream* stream, const std::string&);
+ void IceReady(NrIceMediaStream* stream);
+ void IceFailed(NrIceMediaStream* stream);
+ void IcePacketReceived(NrIceMediaStream* stream, int component,
+ const unsigned char* data, int len);
+
+ // Useful for capturing encrypted packets
+ sigslot::signal2<TransportLayer*, MediaPacket&> SignalPacketSending;
+
+ TRANSPORT_LAYER_ID("ice")
+
+ private:
+ DISALLOW_COPY_ASSIGN(TransportLayerIce);
+ void PostSetup();
+
+ RefPtr<NrIceMediaStream> stream_;
+ int component_;
+};
+
+} // namespace mozilla
+#endif
diff --git a/dom/media/webrtc/transport/transportlayerlog.cpp b/dom/media/webrtc/transport/transportlayerlog.cpp
new file mode 100644
index 0000000000..d78e722dca
--- /dev/null
+++ b/dom/media/webrtc/transport/transportlayerlog.cpp
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+
+#include "logging.h"
+#include "transportlayerlog.h"
+
+namespace mozilla {
+
+MOZ_MTLOG_MODULE("mtransport")
+
+void TransportLayerLogging::WasInserted() {
+ if (downward_) {
+ downward_->SignalStateChange.connect(this,
+ &TransportLayerLogging::StateChange);
+ downward_->SignalPacketReceived.connect(
+ this, &TransportLayerLogging::PacketReceived);
+ TL_SET_STATE(downward_->state());
+ }
+}
+
+TransportResult TransportLayerLogging::SendPacket(MediaPacket& packet) {
+ MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "SendPacket(" << packet.len() << ")");
+
+ if (downward_) {
+ return downward_->SendPacket(packet);
+ }
+ return static_cast<TransportResult>(packet.len());
+}
+
+void TransportLayerLogging::StateChange(TransportLayer* layer, State state) {
+ MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "Received StateChange to " << state);
+
+ TL_SET_STATE(state);
+}
+
+void TransportLayerLogging::PacketReceived(TransportLayer* layer,
+ MediaPacket& packet) {
+ MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "PacketReceived(" << packet.len() << ")");
+
+ SignalPacketReceived(this, packet);
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/transport/transportlayerlog.h b/dom/media/webrtc/transport/transportlayerlog.h
new file mode 100644
index 0000000000..4d0b3fd930
--- /dev/null
+++ b/dom/media/webrtc/transport/transportlayerlog.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+
+#ifndef transportlayerlog_h__
+#define transportlayerlog_h__
+
+#include "m_cpp_utils.h"
+#include "transportlayer.h"
+
+namespace mozilla {
+
+class TransportLayerLogging : public TransportLayer {
+ public:
+ TransportLayerLogging() = default;
+
+ // Overrides for TransportLayer
+ TransportResult SendPacket(MediaPacket& packet) override;
+
+ // Signals (forwarded to upper layer)
+ void StateChange(TransportLayer* layer, State state);
+ void PacketReceived(TransportLayer* layer, MediaPacket& packet);
+
+ TRANSPORT_LAYER_ID("log")
+
+ protected:
+ void WasInserted() override;
+
+ private:
+ DISALLOW_COPY_ASSIGN(TransportLayerLogging);
+};
+
+} // namespace mozilla
+#endif
diff --git a/dom/media/webrtc/transport/transportlayerloopback.cpp b/dom/media/webrtc/transport/transportlayerloopback.cpp
new file mode 100644
index 0000000000..ab2a52c17e
--- /dev/null
+++ b/dom/media/webrtc/transport/transportlayerloopback.cpp
@@ -0,0 +1,119 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+
+#include "logging.h"
+#include "prlock.h"
+
+#include "nsNetCID.h"
+#include "nsServiceManagerUtils.h"
+#include "nsString.h"
+
+#include "transportlayerloopback.h"
+
+namespace mozilla {
+
+MOZ_MTLOG_MODULE("mtransport")
+
+nsresult TransportLayerLoopback::Init() {
+ nsresult rv;
+ target_ = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ if (!NS_SUCCEEDED(rv)) return rv;
+
+ timer_ = NS_NewTimer(target_);
+ MOZ_ASSERT(timer_);
+ if (!timer_) return NS_ERROR_FAILURE;
+
+ packets_lock_ = PR_NewLock();
+ MOZ_ASSERT(packets_lock_);
+ if (!packets_lock_) return NS_ERROR_FAILURE;
+
+ deliverer_ = new Deliverer(this);
+
+ timer_->InitWithCallback(deliverer_, 100, nsITimer::TYPE_REPEATING_SLACK);
+
+ return NS_OK;
+}
+
+// Connect to the other side
+void TransportLayerLoopback::Connect(TransportLayerLoopback* peer) {
+ peer_ = peer;
+
+ TL_SET_STATE(TS_OPEN);
+}
+
+TransportResult TransportLayerLoopback::SendPacket(MediaPacket& packet) {
+ MOZ_MTLOG(ML_DEBUG, LAYER_INFO << "SendPacket(" << packet.len() << ")");
+
+ if (!peer_) {
+ MOZ_MTLOG(ML_ERROR, "Discarding packet because peer not attached");
+ return TE_ERROR;
+ }
+
+ size_t len = packet.len();
+ nsresult res = peer_->QueuePacket(packet);
+ if (!NS_SUCCEEDED(res)) return TE_ERROR;
+
+ return static_cast<TransportResult>(len);
+}
+
+nsresult TransportLayerLoopback::QueuePacket(MediaPacket& packet) {
+ MOZ_ASSERT(packets_lock_);
+
+ PR_Lock(packets_lock_);
+
+ if (combinePackets_ && !packets_.empty()) {
+ MediaPacket* prevPacket = packets_.front();
+
+ MOZ_MTLOG(ML_DEBUG, LAYER_INFO << " Enqueuing combined packets of length "
+ << prevPacket->len() << " and "
+ << packet.len());
+ auto combined = MakeUnique<uint8_t[]>(prevPacket->len() + packet.len());
+ memcpy(combined.get(), prevPacket->data(), prevPacket->len());
+ memcpy(combined.get() + prevPacket->len(), packet.data(), packet.len());
+ prevPacket->Take(std::move(combined), prevPacket->len() + packet.len());
+ } else {
+ MOZ_MTLOG(ML_DEBUG,
+ LAYER_INFO << " Enqueuing packet of length " << packet.len());
+ packets_.push(new MediaPacket(std::move(packet)));
+ }
+
+ PRStatus r = PR_Unlock(packets_lock_);
+ MOZ_ASSERT(r == PR_SUCCESS);
+ if (r != PR_SUCCESS) return NS_ERROR_FAILURE;
+
+ return NS_OK;
+}
+
+void TransportLayerLoopback::DeliverPackets() {
+ while (!packets_.empty()) {
+ UniquePtr<MediaPacket> packet(packets_.front());
+ packets_.pop();
+
+ MOZ_MTLOG(ML_DEBUG,
+ LAYER_INFO << " Delivering packet of length " << packet->len());
+ SignalPacketReceived(this, *packet);
+ }
+}
+
+NS_IMPL_ISUPPORTS(TransportLayerLoopback::Deliverer, nsITimerCallback, nsINamed)
+
+NS_IMETHODIMP TransportLayerLoopback::Deliverer::Notify(nsITimer* timer) {
+ if (!layer_) return NS_OK;
+
+ layer_->DeliverPackets();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP TransportLayerLoopback::Deliverer::GetName(nsACString& aName) {
+ aName.AssignLiteral("TransportLayerLoopback::Deliverer");
+ return NS_OK;
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/transport/transportlayerloopback.h b/dom/media/webrtc/transport/transportlayerloopback.h
new file mode 100644
index 0000000000..6801af8189
--- /dev/null
+++ b/dom/media/webrtc/transport/transportlayerloopback.h
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+
+#ifndef transportlayerloopback_h__
+#define transportlayerloopback_h__
+
+#include "prlock.h"
+
+#include <queue>
+
+#include "nsCOMPtr.h"
+#include "nsINamed.h"
+#include "nsITimer.h"
+
+#include "m_cpp_utils.h"
+#include "transportlayer.h"
+
+// A simple loopback transport layer that is used for testing.
+namespace mozilla {
+
+class TransportLayerLoopback : public TransportLayer {
+ public:
+ TransportLayerLoopback()
+ : peer_(nullptr),
+ timer_(nullptr),
+ packets_(),
+ packets_lock_(nullptr),
+ deliverer_(nullptr),
+ combinePackets_(false) {}
+
+ ~TransportLayerLoopback() {
+ while (!packets_.empty()) {
+ MediaPacket* packet = packets_.front();
+ packets_.pop();
+ delete packet;
+ }
+ if (packets_lock_) {
+ PR_DestroyLock(packets_lock_);
+ }
+ timer_->Cancel();
+ deliverer_->Detach();
+ }
+
+ // Init
+ nsresult Init();
+
+ // Connect to the other side
+ void Connect(TransportLayerLoopback* peer);
+
+ // Disconnect
+ void Disconnect() {
+ TransportLayerLoopback* peer = peer_;
+
+ peer_ = nullptr;
+ if (peer) {
+ peer->Disconnect();
+ }
+ }
+
+ void CombinePackets(bool combine) { combinePackets_ = combine; }
+
+ // Overrides for TransportLayer
+ TransportResult SendPacket(MediaPacket& packet) override;
+
+ // Deliver queued packets
+ void DeliverPackets();
+
+ TRANSPORT_LAYER_ID("loopback")
+
+ private:
+ DISALLOW_COPY_ASSIGN(TransportLayerLoopback);
+
+ // A timer to deliver packets if some are available
+ // Fires every 100 ms
+ class Deliverer : public nsITimerCallback, public nsINamed {
+ public:
+ explicit Deliverer(TransportLayerLoopback* layer) : layer_(layer) {}
+ void Detach() { layer_ = nullptr; }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ private:
+ virtual ~Deliverer() = default;
+
+ DISALLOW_COPY_ASSIGN(Deliverer);
+
+ TransportLayerLoopback* layer_;
+ };
+
+ // Queue a packet for delivery
+ nsresult QueuePacket(MediaPacket& packet);
+
+ TransportLayerLoopback* peer_;
+ nsCOMPtr<nsITimer> timer_;
+ std::queue<MediaPacket*> packets_;
+ PRLock* packets_lock_;
+ RefPtr<Deliverer> deliverer_;
+ bool combinePackets_;
+};
+
+} // namespace mozilla
+#endif
diff --git a/dom/media/webrtc/transport/transportlayersrtp.cpp b/dom/media/webrtc/transport/transportlayersrtp.cpp
new file mode 100644
index 0000000000..25830a2dde
--- /dev/null
+++ b/dom/media/webrtc/transport/transportlayersrtp.cpp
@@ -0,0 +1,222 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+
+#include "transportlayersrtp.h"
+#include "transportlayerdtls.h"
+
+#include "logging.h"
+#include "nsError.h"
+#include "mozilla/Assertions.h"
+
+namespace mozilla {
+
+MOZ_MTLOG_MODULE("mtransport")
+
+static char kDTLSExporterLabel[] = "EXTRACTOR-dtls_srtp";
+
+TransportLayerSrtp::TransportLayerSrtp(TransportLayerDtls& dtls) {
+ // We need to connect to the dtls layer, not the ice layer, because even
+ // though the packets that DTLS decrypts don't flow through us, we do base our
+ // keying information on the keying information established by the DTLS layer.
+ dtls.SignalStateChange.connect(this, &TransportLayerSrtp::StateChange);
+
+ TL_SET_STATE(dtls.state());
+}
+
+void TransportLayerSrtp::WasInserted() {
+ // Connect to the lower layers
+ if (!Setup()) {
+ TL_SET_STATE(TS_ERROR);
+ }
+}
+
+bool TransportLayerSrtp::Setup() {
+ CheckThread();
+ if (!downward_) {
+ MOZ_MTLOG(ML_ERROR, "SRTP layer with nothing below. This is useless");
+ return false;
+ }
+
+ // downward_ is the TransportLayerIce
+ downward_->SignalPacketReceived.connect(this,
+ &TransportLayerSrtp::PacketReceived);
+
+ return true;
+}
+
+TransportResult TransportLayerSrtp::SendPacket(MediaPacket& packet) {
+ if (state() != TS_OPEN) {
+ return TE_ERROR;
+ }
+
+ if (packet.len() < 4) {
+ MOZ_ASSERT(false);
+ return TE_ERROR;
+ }
+
+ MOZ_ASSERT(packet.capacity() - packet.len() >= SRTP_MAX_EXPANSION);
+
+ int out_len;
+ nsresult res;
+ switch (packet.type()) {
+ case MediaPacket::RTP:
+ res = mSendSrtp->ProtectRtp(packet.data(), packet.len(),
+ packet.capacity(), &out_len);
+ packet.SetType(MediaPacket::SRTP);
+ break;
+ case MediaPacket::RTCP:
+ res = mSendSrtp->ProtectRtcp(packet.data(), packet.len(),
+ packet.capacity(), &out_len);
+ packet.SetType(MediaPacket::SRTCP);
+ break;
+ default:
+ MOZ_CRASH("SRTP layer asked to send packet that is neither RTP or RTCP");
+ }
+
+ if (NS_FAILED(res)) {
+ MOZ_MTLOG(ML_ERROR,
+ "Error protecting "
+ << (packet.type() == MediaPacket::RTP ? "RTP" : "RTCP")
+ << " len=" << packet.len() << "[" << std::hex
+ << packet.data()[0] << " " << packet.data()[1] << " "
+ << packet.data()[2] << " " << packet.data()[3] << "]");
+ return TE_ERROR;
+ }
+
+ size_t unencrypted_len = packet.len();
+ packet.SetLength(out_len);
+
+ TransportResult bytes = downward_->SendPacket(packet);
+ if (bytes == out_len) {
+ // Whole packet was written, but the encrypted length might be different.
+ // Don't confuse the caller.
+ return unencrypted_len;
+ }
+
+ if (bytes == TE_WOULDBLOCK) {
+ return TE_WOULDBLOCK;
+ }
+
+ return TE_ERROR;
+}
+
+void TransportLayerSrtp::StateChange(TransportLayer* layer, State state) {
+ if (state == TS_OPEN && !mSendSrtp) {
+ TransportLayerDtls* dtls = static_cast<TransportLayerDtls*>(layer);
+ MOZ_ASSERT(dtls); // DTLS is mandatory
+
+ uint16_t cipher_suite;
+ nsresult res = dtls->GetSrtpCipher(&cipher_suite);
+ if (NS_FAILED(res)) {
+ MOZ_MTLOG(ML_DEBUG, "DTLS-SRTP disabled");
+ TL_SET_STATE(TS_ERROR);
+ return;
+ }
+
+ unsigned int key_size = SrtpFlow::KeySize(cipher_suite);
+ unsigned int salt_size = SrtpFlow::SaltSize(cipher_suite);
+ unsigned int master_key_size = key_size + salt_size;
+ MOZ_ASSERT(master_key_size <= SRTP_MAX_KEY_LENGTH);
+
+ // SRTP Key Exporter as per RFC 5764 S 4.2
+ unsigned char srtp_block[SRTP_MAX_KEY_LENGTH * 2];
+ res = dtls->ExportKeyingMaterial(kDTLSExporterLabel, false, "", srtp_block,
+ sizeof(srtp_block));
+ if (NS_FAILED(res)) {
+ MOZ_MTLOG(ML_ERROR, "Failed to compute DTLS-SRTP keys. This is an error");
+ TL_SET_STATE(TS_ERROR);
+ return;
+ }
+
+ // Slice and dice as per RFC 5764 S 4.2
+ unsigned char client_write_key[SRTP_MAX_KEY_LENGTH];
+ unsigned char server_write_key[SRTP_MAX_KEY_LENGTH];
+ unsigned int offset = 0;
+ memcpy(client_write_key, srtp_block + offset, key_size);
+ offset += key_size;
+ memcpy(server_write_key, srtp_block + offset, key_size);
+ offset += key_size;
+ memcpy(client_write_key + key_size, srtp_block + offset, salt_size);
+ offset += salt_size;
+ memcpy(server_write_key + key_size, srtp_block + offset, salt_size);
+ MOZ_ASSERT((offset + salt_size) == (2 * master_key_size));
+
+ unsigned char* write_key;
+ unsigned char* read_key;
+
+ if (dtls->role() == TransportLayerDtls::CLIENT) {
+ write_key = client_write_key;
+ read_key = server_write_key;
+ } else {
+ write_key = server_write_key;
+ read_key = client_write_key;
+ }
+
+ MOZ_ASSERT(!mSendSrtp && !mRecvSrtp);
+ mSendSrtp =
+ SrtpFlow::Create(cipher_suite, false, write_key, master_key_size);
+ mRecvSrtp = SrtpFlow::Create(cipher_suite, true, read_key, master_key_size);
+ if (!mSendSrtp || !mRecvSrtp) {
+ MOZ_MTLOG(ML_ERROR, "Couldn't create SRTP flow.");
+ TL_SET_STATE(TS_ERROR);
+ return;
+ }
+
+ MOZ_MTLOG(ML_INFO, "Created SRTP flow!");
+ }
+
+ TL_SET_STATE(state);
+}
+
+void TransportLayerSrtp::PacketReceived(TransportLayer* layer,
+ MediaPacket& packet) {
+ if (state() != TS_OPEN) {
+ return;
+ }
+
+ if (!packet.data()) {
+ // Something ate this, probably the DTLS layer
+ return;
+ }
+
+ if (packet.type() != MediaPacket::SRTP &&
+ packet.type() != MediaPacket::SRTCP) {
+ return;
+ }
+
+ // We want to keep the encrypted packet around for packet dumping
+ packet.CopyDataToEncrypted();
+ int outLen;
+ nsresult res;
+
+ if (packet.type() == MediaPacket::SRTP) {
+ packet.SetType(MediaPacket::RTP);
+ res = mRecvSrtp->UnprotectRtp(packet.data(), packet.len(), packet.len(),
+ &outLen);
+ } else {
+ packet.SetType(MediaPacket::RTCP);
+ res = mRecvSrtp->UnprotectRtcp(packet.data(), packet.len(), packet.len(),
+ &outLen);
+ }
+
+ if (NS_SUCCEEDED(res)) {
+ packet.SetLength(outLen);
+ SignalPacketReceived(this, packet);
+ } else {
+ // TODO: What do we do wrt packet dumping here? Maybe signal an empty
+ // packet? Signal the still-encrypted packet?
+ MOZ_MTLOG(ML_ERROR,
+ "Error unprotecting "
+ << (packet.type() == MediaPacket::RTP ? "RTP" : "RTCP")
+ << " len=" << packet.len() << "[" << std::hex
+ << packet.data()[0] << " " << packet.data()[1] << " "
+ << packet.data()[2] << " " << packet.data()[3] << "]");
+ }
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/transport/transportlayersrtp.h b/dom/media/webrtc/transport/transportlayersrtp.h
new file mode 100644
index 0000000000..a5ba035930
--- /dev/null
+++ b/dom/media/webrtc/transport/transportlayersrtp.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef transportlayersrtp_h__
+#define transportlayersrtp_h__
+
+#include <string>
+
+#include "transportlayer.h"
+#include "mozilla/RefPtr.h"
+#include "SrtpFlow.h"
+
+namespace mozilla {
+
+class TransportLayerDtls;
+
+class TransportLayerSrtp final : public TransportLayer {
+ public:
+ explicit TransportLayerSrtp(TransportLayerDtls& dtls);
+ virtual ~TransportLayerSrtp() = default;
+
+ // Transport layer overrides.
+ void WasInserted() override;
+ TransportResult SendPacket(MediaPacket& packet) override;
+
+ // Signals
+ void StateChange(TransportLayer* layer, State state);
+ void PacketReceived(TransportLayer* layer, MediaPacket& packet);
+
+ TRANSPORT_LAYER_ID("srtp")
+
+ private:
+ bool Setup();
+ DISALLOW_COPY_ASSIGN(TransportLayerSrtp);
+ RefPtr<SrtpFlow> mSendSrtp;
+ RefPtr<SrtpFlow> mRecvSrtp;
+};
+
+} // namespace mozilla
+#endif
diff --git a/dom/media/webrtc/transportbridge/MediaPipeline.cpp b/dom/media/webrtc/transportbridge/MediaPipeline.cpp
new file mode 100644
index 0000000000..ec861f69bd
--- /dev/null
+++ b/dom/media/webrtc/transportbridge/MediaPipeline.cpp
@@ -0,0 +1,1655 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+
+#include "MediaPipeline.h"
+
+#include <inttypes.h>
+#include <math.h>
+#include <sstream>
+
+#include "AudioSegment.h"
+#include "AudioConverter.h"
+#include "DOMMediaStream.h"
+#include "ImageContainer.h"
+#include "ImageTypes.h"
+#include "MediaEngine.h"
+#include "MediaSegment.h"
+#include "MediaTrackGraphImpl.h"
+#include "MediaTrackListener.h"
+#include "MediaStreamTrack.h"
+#include "RtpLogger.h"
+#include "VideoFrameConverter.h"
+#include "VideoSegment.h"
+#include "VideoStreamTrack.h"
+#include "VideoUtils.h"
+#include "mozilla/Logging.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/PeerIdentity.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/SharedThreadPool.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/StaticPrefs_media.h"
+#include "mozilla/TaskQueue.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/dom/RTCStatsReportBinding.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/gfx/Types.h"
+#include "nsError.h"
+#include "nsThreadUtils.h"
+#include "transport/runnable_utils.h"
+#include "jsapi/MediaTransportHandler.h"
+#include "jsapi/PeerConnectionImpl.h"
+#include "Tracing.h"
+#include "libwebrtcglue/WebrtcImageBuffer.h"
+#include "libwebrtcglue/MediaConduitInterface.h"
+#include "common_video/include/video_frame_buffer.h"
+#include "modules/rtp_rtcp/include/rtp_rtcp.h"
+#include "modules/rtp_rtcp/include/rtp_header_extension_map.h"
+#include "modules/rtp_rtcp/source/rtp_packet_received.h"
+
+// Max size given stereo is 480*2*2 = 1920 (10ms of 16-bits stereo audio at
+// 48KHz)
+#define AUDIO_SAMPLE_BUFFER_MAX_BYTES (480 * 2 * 2)
+static_assert((WEBRTC_MAX_SAMPLE_RATE / 100) * sizeof(uint16_t) * 2 <=
+ AUDIO_SAMPLE_BUFFER_MAX_BYTES,
+ "AUDIO_SAMPLE_BUFFER_MAX_BYTES is not large enough");
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+
+mozilla::LazyLogModule gMediaPipelineLog("MediaPipeline");
+
+namespace mozilla {
+
+// An async inserter for audio data, to avoid running audio codec encoders
+// on the MTG/input audio thread. Basically just bounces all the audio
+// data to a single audio processing/input queue. We could if we wanted to
+// use multiple threads and a TaskQueue.
+class AudioProxyThread {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AudioProxyThread)
+
+ explicit AudioProxyThread(RefPtr<AudioSessionConduit> aConduit)
+ : mConduit(std::move(aConduit)),
+ mTaskQueue(TaskQueue::Create(
+ GetMediaThreadPool(MediaThreadType::WEBRTC_WORKER), "AudioProxy")),
+ mAudioConverter(nullptr) {
+ MOZ_ASSERT(mConduit);
+ MOZ_COUNT_CTOR(AudioProxyThread);
+ }
+
+ // This function is the identity if aInputRate is supported.
+ // Else, it returns a rate that is supported, that ensure no loss in audio
+ // quality: the sampling rate returned is always greater to the inputed
+ // sampling-rate, if they differ..
+ uint32_t AppropriateSendingRateForInputRate(uint32_t aInputRate) {
+ AudioSessionConduit* conduit =
+ static_cast<AudioSessionConduit*>(mConduit.get());
+ if (conduit->IsSamplingFreqSupported(aInputRate)) {
+ return aInputRate;
+ }
+ if (aInputRate < 16000) {
+ return 16000;
+ }
+ if (aInputRate < 32000) {
+ return 32000;
+ }
+ if (aInputRate < 44100) {
+ return 44100;
+ }
+ return 48000;
+ }
+
+ // From an arbitrary AudioChunk at sampling-rate aRate, process the audio into
+ // something the conduit can work with (or send silence if the track is not
+ // enabled), and send the audio in 10ms chunks to the conduit.
+ void InternalProcessAudioChunk(TrackRate aRate, const AudioChunk& aChunk,
+ bool aEnabled) {
+ MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+
+ // Convert to interleaved 16-bits integer audio, with a maximum of two
+ // channels (since the WebRTC.org code below makes the assumption that the
+ // input audio is either mono or stereo), with a sample-rate rate that is
+ // 16, 32, 44.1, or 48kHz.
+ uint32_t outputChannels = aChunk.ChannelCount() == 1 ? 1 : 2;
+ int32_t transmissionRate = AppropriateSendingRateForInputRate(aRate);
+
+ // We take advantage of the fact that the common case (microphone directly
+ // to PeerConnection, that is, a normal call), the samples are already
+ // 16-bits mono, so the representation in interleaved and planar is the
+ // same, and we can just use that.
+ if (aEnabled && outputChannels == 1 &&
+ aChunk.mBufferFormat == AUDIO_FORMAT_S16 && transmissionRate == aRate) {
+ const int16_t* samples = aChunk.ChannelData<int16_t>().Elements()[0];
+ PacketizeAndSend(samples, transmissionRate, outputChannels,
+ aChunk.mDuration);
+ return;
+ }
+
+ uint32_t sampleCount = aChunk.mDuration * outputChannels;
+ if (mInterleavedAudio.Length() < sampleCount) {
+ mInterleavedAudio.SetLength(sampleCount);
+ }
+
+ if (!aEnabled || aChunk.mBufferFormat == AUDIO_FORMAT_SILENCE) {
+ PodZero(mInterleavedAudio.Elements(), sampleCount);
+ } else if (aChunk.mBufferFormat == AUDIO_FORMAT_FLOAT32) {
+ DownmixAndInterleave(aChunk.ChannelData<float>(), aChunk.mDuration,
+ aChunk.mVolume, outputChannels,
+ mInterleavedAudio.Elements());
+ } else if (aChunk.mBufferFormat == AUDIO_FORMAT_S16) {
+ DownmixAndInterleave(aChunk.ChannelData<int16_t>(), aChunk.mDuration,
+ aChunk.mVolume, outputChannels,
+ mInterleavedAudio.Elements());
+ }
+ int16_t* inputAudio = mInterleavedAudio.Elements();
+ size_t inputAudioFrameCount = aChunk.mDuration;
+
+ AudioConfig inputConfig(AudioConfig::ChannelLayout(outputChannels), aRate,
+ AudioConfig::FORMAT_S16);
+ AudioConfig outputConfig(AudioConfig::ChannelLayout(outputChannels),
+ transmissionRate, AudioConfig::FORMAT_S16);
+ // Resample to an acceptable sample-rate for the sending side
+ if (!mAudioConverter || mAudioConverter->InputConfig() != inputConfig ||
+ mAudioConverter->OutputConfig() != outputConfig) {
+ mAudioConverter = MakeUnique<AudioConverter>(inputConfig, outputConfig);
+ }
+
+ int16_t* processedAudio = nullptr;
+ size_t framesProcessed =
+ mAudioConverter->Process(inputAudio, inputAudioFrameCount);
+
+ if (framesProcessed == 0) {
+ // In place conversion not possible, use a buffer.
+ framesProcessed = mAudioConverter->Process(mOutputAudio, inputAudio,
+ inputAudioFrameCount);
+ processedAudio = mOutputAudio.Data();
+ } else {
+ processedAudio = inputAudio;
+ }
+
+ PacketizeAndSend(processedAudio, transmissionRate, outputChannels,
+ framesProcessed);
+ }
+
+ // This packetizes aAudioData in 10ms chunks and sends it.
+ // aAudioData is interleaved audio data at a rate and with a channel count
+ // that is appropriate to send with the conduit.
+ void PacketizeAndSend(const int16_t* aAudioData, uint32_t aRate,
+ uint32_t aChannels, uint32_t aFrameCount) {
+ MOZ_ASSERT(AppropriateSendingRateForInputRate(aRate) == aRate);
+ MOZ_ASSERT(aChannels == 1 || aChannels == 2);
+ MOZ_ASSERT(aAudioData);
+
+ uint32_t audio_10ms = aRate / 100;
+
+ if (!mPacketizer || mPacketizer->mPacketSize != audio_10ms ||
+ mPacketizer->mChannels != aChannels) {
+ // It's the right thing to drop the bit of audio still in the packetizer:
+ // we don't want to send to the conduit audio that has two different
+ // rates while telling it that it has a constante rate.
+ mPacketizer =
+ MakeUnique<AudioPacketizer<int16_t, int16_t>>(audio_10ms, aChannels);
+ mPacket = MakeUnique<int16_t[]>(audio_10ms * aChannels);
+ }
+
+ mPacketizer->Input(aAudioData, aFrameCount);
+
+ while (mPacketizer->PacketsAvailable()) {
+ mPacketizer->Output(mPacket.get());
+ auto frame = std::make_unique<webrtc::AudioFrame>();
+ // UpdateFrame makes a copy of the audio data.
+ frame->UpdateFrame(frame->timestamp_, mPacket.get(),
+ mPacketizer->mPacketSize, aRate, frame->speech_type_,
+ frame->vad_activity_, mPacketizer->mChannels);
+ mConduit->SendAudioFrame(std::move(frame));
+ }
+ }
+
+ void QueueAudioChunk(TrackRate aRate, const AudioChunk& aChunk,
+ bool aEnabled) {
+ RefPtr<AudioProxyThread> self = this;
+ nsresult rv = mTaskQueue->Dispatch(NS_NewRunnableFunction(
+ "AudioProxyThread::QueueAudioChunk", [self, aRate, aChunk, aEnabled]() {
+ self->InternalProcessAudioChunk(aRate, aChunk, aEnabled);
+ }));
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+ Unused << rv;
+ }
+
+ protected:
+ virtual ~AudioProxyThread() { MOZ_COUNT_DTOR(AudioProxyThread); }
+
+ const RefPtr<AudioSessionConduit> mConduit;
+ const RefPtr<TaskQueue> mTaskQueue;
+ // Only accessed on mTaskQueue
+ UniquePtr<AudioPacketizer<int16_t, int16_t>> mPacketizer;
+ // A buffer to hold a single packet of audio.
+ UniquePtr<int16_t[]> mPacket;
+ nsTArray<int16_t> mInterleavedAudio;
+ AlignedShortBuffer mOutputAudio;
+ UniquePtr<AudioConverter> mAudioConverter;
+};
+
+#define INIT_MIRROR(name, val) \
+ name(AbstractThread::MainThread(), val, "MediaPipeline::" #name " (Mirror)")
+
+MediaPipeline::MediaPipeline(const std::string& aPc,
+ RefPtr<MediaTransportHandler> aTransportHandler,
+ DirectionType aDirection,
+ RefPtr<AbstractThread> aCallThread,
+ RefPtr<nsISerialEventTarget> aStsThread,
+ RefPtr<MediaSessionConduit> aConduit)
+ : mConduit(std::move(aConduit)),
+ mDirection(aDirection),
+ mCallThread(std::move(aCallThread)),
+ mStsThread(std::move(aStsThread)),
+ INIT_MIRROR(mActive, false),
+ mLevel(0),
+ mTransportHandler(std::move(aTransportHandler)),
+ mRtpPacketsSent(0),
+ mRtcpPacketsSent(0),
+ mRtpPacketsReceived(0),
+ mRtcpPacketsReceived(0),
+ mRtpBytesSent(0),
+ mRtpBytesReceived(0),
+ mPc(aPc),
+ mFilter(),
+ mRtpHeaderExtensionMap(new webrtc::RtpHeaderExtensionMap()),
+ mPacketDumper(PacketDumper::GetPacketDumper(mPc)) {}
+
+#undef INIT_MIRROR
+
+MediaPipeline::~MediaPipeline() {
+ MOZ_LOG(gMediaPipelineLog, LogLevel::Info,
+ ("Destroying MediaPipeline: %s", mDescription.c_str()));
+}
+
+void MediaPipeline::Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mActive.DisconnectIfConnected();
+ RUN_ON_THREAD(mStsThread,
+ WrapRunnable(RefPtr<MediaPipeline>(this),
+ &MediaPipeline::DetachTransport_s),
+ NS_DISPATCH_NORMAL);
+}
+
+void MediaPipeline::DetachTransport_s() {
+ ASSERT_ON_THREAD(mStsThread);
+
+ MOZ_LOG(gMediaPipelineLog, LogLevel::Info,
+ ("%s in %s", mDescription.c_str(), __FUNCTION__));
+
+ disconnect_all();
+ mRtpState = TransportLayer::TS_NONE;
+ mRtcpState = TransportLayer::TS_NONE;
+ mTransportId.clear();
+ mConduit->SetTransportActive(false);
+ mRtpSendEventListener.DisconnectIfExists();
+ mSenderRtcpSendEventListener.DisconnectIfExists();
+ mReceiverRtcpSendEventListener.DisconnectIfExists();
+}
+
+void MediaPipeline::UpdateTransport_m(
+ const std::string& aTransportId, UniquePtr<MediaPipelineFilter>&& aFilter) {
+ mStsThread->Dispatch(NS_NewRunnableFunction(
+ __func__, [aTransportId, filter = std::move(aFilter),
+ self = RefPtr<MediaPipeline>(this)]() mutable {
+ self->UpdateTransport_s(aTransportId, std::move(filter));
+ }));
+}
+
+void MediaPipeline::UpdateTransport_s(
+ const std::string& aTransportId, UniquePtr<MediaPipelineFilter>&& aFilter) {
+ ASSERT_ON_THREAD(mStsThread);
+ if (!mSignalsConnected) {
+ mTransportHandler->SignalStateChange.connect(
+ this, &MediaPipeline::RtpStateChange);
+ mTransportHandler->SignalRtcpStateChange.connect(
+ this, &MediaPipeline::RtcpStateChange);
+ mTransportHandler->SignalEncryptedSending.connect(
+ this, &MediaPipeline::EncryptedPacketSending);
+ mTransportHandler->SignalPacketReceived.connect(
+ this, &MediaPipeline::PacketReceived);
+ mTransportHandler->SignalAlpnNegotiated.connect(
+ this, &MediaPipeline::AlpnNegotiated);
+ mSignalsConnected = true;
+ }
+
+ if (aTransportId != mTransportId) {
+ mTransportId = aTransportId;
+ mRtpState = mTransportHandler->GetState(mTransportId, false);
+ mRtcpState = mTransportHandler->GetState(mTransportId, true);
+ CheckTransportStates();
+ }
+
+ if (mFilter) {
+ for (const auto& extension : mFilter->GetExtmap()) {
+ mRtpHeaderExtensionMap->Deregister(extension.uri);
+ }
+ }
+ if (mFilter && aFilter) {
+ // Use the new filter, but don't forget any remote SSRCs that we've learned
+ // by receiving traffic.
+ mFilter->Update(*aFilter);
+ } else {
+ mFilter = std::move(aFilter);
+ }
+ if (mFilter) {
+ for (const auto& extension : mFilter->GetExtmap()) {
+ mRtpHeaderExtensionMap->RegisterByUri(extension.id, extension.uri);
+ }
+ }
+}
+
+void MediaPipeline::GetContributingSourceStats(
+ const nsString& aInboundRtpStreamId,
+ FallibleTArray<dom::RTCRTPContributingSourceStats>& aArr) const {
+ ASSERT_ON_THREAD(mStsThread);
+ // Get the expiry from now
+ DOMHighResTimeStamp expiry =
+ RtpCSRCStats::GetExpiryFromTime(GetTimestampMaker().GetNow().ToDom());
+ for (auto info : mCsrcStats) {
+ if (!info.second.Expired(expiry)) {
+ RTCRTPContributingSourceStats stats;
+ info.second.GetWebidlInstance(stats, aInboundRtpStreamId);
+ if (!aArr.AppendElement(stats, fallible)) {
+ mozalloc_handle_oom(0);
+ }
+ }
+ }
+}
+
+void MediaPipeline::RtpStateChange(const std::string& aTransportId,
+ TransportLayer::State aState) {
+ if (mTransportId != aTransportId) {
+ return;
+ }
+ mRtpState = aState;
+ CheckTransportStates();
+}
+
+void MediaPipeline::RtcpStateChange(const std::string& aTransportId,
+ TransportLayer::State aState) {
+ if (mTransportId != aTransportId) {
+ return;
+ }
+ mRtcpState = aState;
+ CheckTransportStates();
+}
+
+void MediaPipeline::CheckTransportStates() {
+ ASSERT_ON_THREAD(mStsThread);
+
+ if (mRtpState == TransportLayer::TS_CLOSED ||
+ mRtpState == TransportLayer::TS_ERROR ||
+ mRtcpState == TransportLayer::TS_CLOSED ||
+ mRtcpState == TransportLayer::TS_ERROR) {
+ MOZ_LOG(gMediaPipelineLog, LogLevel::Warning,
+ ("RTP Transport failed for pipeline %p flow %s", this,
+ mDescription.c_str()));
+
+ NS_WARNING(
+ "MediaPipeline Transport failed. This is not properly cleaned up yet");
+ // TODO(ekr@rtfm.com): SECURITY: Figure out how to clean up if the
+ // connection was good and now it is bad.
+ // TODO(ekr@rtfm.com): Report up so that the PC knows we
+ // have experienced an error.
+ mConduit->SetTransportActive(false);
+ mRtpSendEventListener.DisconnectIfExists();
+ mSenderRtcpSendEventListener.DisconnectIfExists();
+ mReceiverRtcpSendEventListener.DisconnectIfExists();
+ return;
+ }
+
+ if (mRtpState == TransportLayer::TS_OPEN) {
+ MOZ_LOG(gMediaPipelineLog, LogLevel::Info,
+ ("RTP Transport ready for pipeline %p flow %s", this,
+ mDescription.c_str()));
+ }
+
+ if (mRtcpState == TransportLayer::TS_OPEN) {
+ MOZ_LOG(gMediaPipelineLog, LogLevel::Info,
+ ("RTCP Transport ready for pipeline %p flow %s", this,
+ mDescription.c_str()));
+ }
+
+ if (mRtpState == TransportLayer::TS_OPEN && mRtcpState == mRtpState) {
+ if (mDirection == DirectionType::TRANSMIT) {
+ mConduit->ConnectSenderRtcpEvent(mSenderRtcpReceiveEvent);
+ mRtpSendEventListener = mConduit->SenderRtpSendEvent().Connect(
+ mStsThread, this, &MediaPipeline::SendPacket);
+ mSenderRtcpSendEventListener = mConduit->SenderRtcpSendEvent().Connect(
+ mStsThread, this, &MediaPipeline::SendPacket);
+ } else {
+ mConduit->ConnectReceiverRtcpEvent(mReceiverRtcpReceiveEvent);
+ mConduit->ConnectReceiverRtpEvent(mRtpReceiveEvent);
+ mReceiverRtcpSendEventListener =
+ mConduit->ReceiverRtcpSendEvent().Connect(mStsThread, this,
+ &MediaPipeline::SendPacket);
+ }
+ mConduit->SetTransportActive(true);
+ TransportReady_s();
+ }
+}
+
+void MediaPipeline::SendPacket(MediaPacket&& aPacket) {
+ ASSERT_ON_THREAD(mStsThread);
+
+ const bool isRtp = aPacket.type() == MediaPacket::RTP;
+
+ if (isRtp && mRtpState != TransportLayer::TS_OPEN) {
+ return;
+ }
+
+ if (!isRtp && mRtcpState != TransportLayer::TS_OPEN) {
+ return;
+ }
+
+ aPacket.sdp_level() = Some(Level());
+
+ if (RtpLogger::IsPacketLoggingOn()) {
+ RtpLogger::LogPacket(aPacket, false, mDescription);
+ }
+
+ if (isRtp) {
+ mPacketDumper->Dump(Level(), dom::mozPacketDumpType::Rtp, true,
+ aPacket.data(), aPacket.len());
+ IncrementRtpPacketsSent(aPacket);
+ } else {
+ mPacketDumper->Dump(Level(), dom::mozPacketDumpType::Rtcp, true,
+ aPacket.data(), aPacket.len());
+ IncrementRtcpPacketsSent();
+ }
+
+ MOZ_LOG(
+ gMediaPipelineLog, LogLevel::Debug,
+ ("%s sending %s packet", mDescription.c_str(), (isRtp ? "RTP" : "RTCP")));
+
+ mTransportHandler->SendPacket(mTransportId, std::move(aPacket));
+}
+
+void MediaPipeline::IncrementRtpPacketsSent(const MediaPacket& aPacket) {
+ ASSERT_ON_THREAD(mStsThread);
+ ++mRtpPacketsSent;
+ mRtpBytesSent += aPacket.len();
+
+ if (!(mRtpPacketsSent % 100)) {
+ MOZ_LOG(gMediaPipelineLog, LogLevel::Info,
+ ("RTP sent packet count for %s Pipeline %p: %u (%" PRId64 " bytes)",
+ mDescription.c_str(), this, mRtpPacketsSent, mRtpBytesSent));
+ }
+}
+
+void MediaPipeline::IncrementRtcpPacketsSent() {
+ ASSERT_ON_THREAD(mStsThread);
+ ++mRtcpPacketsSent;
+ if (!(mRtcpPacketsSent % 100)) {
+ MOZ_LOG(gMediaPipelineLog, LogLevel::Info,
+ ("RTCP sent packet count for %s Pipeline %p: %u",
+ mDescription.c_str(), this, mRtcpPacketsSent));
+ }
+}
+
+void MediaPipeline::IncrementRtpPacketsReceived(int32_t aBytes) {
+ ASSERT_ON_THREAD(mStsThread);
+ ++mRtpPacketsReceived;
+ mRtpBytesReceived += aBytes;
+ if (!(mRtpPacketsReceived % 100)) {
+ MOZ_LOG(
+ gMediaPipelineLog, LogLevel::Info,
+ ("RTP received packet count for %s Pipeline %p: %u (%" PRId64 " bytes)",
+ mDescription.c_str(), this, mRtpPacketsReceived, mRtpBytesReceived));
+ }
+}
+
+void MediaPipeline::IncrementRtcpPacketsReceived() {
+ ASSERT_ON_THREAD(mStsThread);
+ ++mRtcpPacketsReceived;
+ if (!(mRtcpPacketsReceived % 100)) {
+ MOZ_LOG(gMediaPipelineLog, LogLevel::Info,
+ ("RTCP received packet count for %s Pipeline %p: %u",
+ mDescription.c_str(), this, mRtcpPacketsReceived));
+ }
+}
+
+void MediaPipeline::RtpPacketReceived(const MediaPacket& packet) {
+ ASSERT_ON_THREAD(mStsThread);
+
+ if (mDirection == DirectionType::TRANSMIT) {
+ return;
+ }
+
+ if (!packet.len()) {
+ return;
+ }
+
+ webrtc::RTPHeader header;
+ rtc::CopyOnWriteBuffer packet_buffer(packet.data(), packet.len());
+ webrtc::RtpPacketReceived parsedPacket(mRtpHeaderExtensionMap.get());
+ if (!parsedPacket.Parse(packet_buffer)) {
+ return;
+ }
+ parsedPacket.GetHeader(&header);
+
+ if (mFilter && !mFilter->Filter(header)) {
+ return;
+ }
+
+ auto now = GetTimestampMaker().GetNow();
+ parsedPacket.set_arrival_time(now.ToRealtime());
+ if (IsVideo()) {
+ parsedPacket.set_payload_type_frequency(webrtc::kVideoPayloadTypeFrequency);
+ }
+
+ // Remove expired RtpCSRCStats
+ if (!mCsrcStats.empty()) {
+ auto expiry = RtpCSRCStats::GetExpiryFromTime(now.ToDom());
+ for (auto p = mCsrcStats.begin(); p != mCsrcStats.end();) {
+ if (p->second.Expired(expiry)) {
+ p = mCsrcStats.erase(p);
+ continue;
+ }
+ p++;
+ }
+ }
+
+ // Add new RtpCSRCStats
+ if (header.numCSRCs) {
+ for (auto i = 0; i < header.numCSRCs; i++) {
+ auto csrcInfo = mCsrcStats.find(header.arrOfCSRCs[i]);
+ if (csrcInfo == mCsrcStats.end()) {
+ mCsrcStats.insert(
+ std::make_pair(header.arrOfCSRCs[i],
+ RtpCSRCStats(header.arrOfCSRCs[i], now.ToDom())));
+ } else {
+ csrcInfo->second.SetTimestamp(now.ToDom());
+ }
+ }
+ }
+
+ MOZ_LOG(gMediaPipelineLog, LogLevel::Debug,
+ ("%s received RTP packet.", mDescription.c_str()));
+ IncrementRtpPacketsReceived(packet.len());
+
+ RtpLogger::LogPacket(packet, true, mDescription);
+
+ // Might be nice to pass ownership of the buffer in this case, but it is a
+ // small optimization in a rare case.
+ mPacketDumper->Dump(mLevel, dom::mozPacketDumpType::Srtp, false,
+ packet.encrypted_data(), packet.encrypted_len());
+
+ mPacketDumper->Dump(mLevel, dom::mozPacketDumpType::Rtp, false, packet.data(),
+ packet.len());
+
+ mRtpReceiveEvent.Notify(std::move(parsedPacket), header);
+}
+
+void MediaPipeline::RtcpPacketReceived(const MediaPacket& packet) {
+ ASSERT_ON_THREAD(mStsThread);
+
+ if (!packet.len()) {
+ return;
+ }
+
+ // We do not filter RTCP. This is because a compound RTCP packet can contain
+ // any collection of RTCP packets, and webrtc.org already knows how to filter
+ // out what it is interested in, and what it is not. Maybe someday we should
+ // have a TransportLayer that breaks up compound RTCP so we can filter them
+ // individually, but I doubt that will matter much.
+
+ MOZ_LOG(gMediaPipelineLog, LogLevel::Debug,
+ ("%s received RTCP packet.", mDescription.c_str()));
+ IncrementRtcpPacketsReceived();
+
+ RtpLogger::LogPacket(packet, true, mDescription);
+
+ // Might be nice to pass ownership of the buffer in this case, but it is a
+ // small optimization in a rare case.
+ mPacketDumper->Dump(mLevel, dom::mozPacketDumpType::Srtcp, false,
+ packet.encrypted_data(), packet.encrypted_len());
+
+ mPacketDumper->Dump(mLevel, dom::mozPacketDumpType::Rtcp, false,
+ packet.data(), packet.len());
+
+ if (StaticPrefs::media_webrtc_net_force_disable_rtcp_reception()) {
+ MOZ_LOG(gMediaPipelineLog, LogLevel::Debug,
+ ("%s RTCP packet forced to be dropped", mDescription.c_str()));
+ return;
+ }
+
+ if (mDirection == DirectionType::TRANSMIT) {
+ mSenderRtcpReceiveEvent.Notify(packet.Clone());
+ } else {
+ mReceiverRtcpReceiveEvent.Notify(packet.Clone());
+ }
+}
+
+void MediaPipeline::PacketReceived(const std::string& aTransportId,
+ const MediaPacket& packet) {
+ ASSERT_ON_THREAD(mStsThread);
+
+ if (mTransportId != aTransportId) {
+ return;
+ }
+
+ MOZ_ASSERT(mRtpState == TransportLayer::TS_OPEN);
+ MOZ_ASSERT(mRtcpState == mRtpState);
+
+ switch (packet.type()) {
+ case MediaPacket::RTP:
+ RtpPacketReceived(packet);
+ break;
+ case MediaPacket::RTCP:
+ RtcpPacketReceived(packet);
+ break;
+ default:;
+ }
+}
+
+void MediaPipeline::AlpnNegotiated(const std::string& aAlpn,
+ bool aPrivacyRequested) {
+ ASSERT_ON_THREAD(mStsThread);
+
+ if (aPrivacyRequested && Direction() == DirectionType::RECEIVE) {
+ // This will force the receive pipelines to drop data until they have
+ // received a private PrincipalHandle from RTCRtpReceiver (which takes a
+ // detour via main thread).
+ static_cast<MediaPipelineReceive*>(this)->OnPrivacyRequested_s();
+ }
+}
+
+void MediaPipeline::EncryptedPacketSending(const std::string& aTransportId,
+ const MediaPacket& aPacket) {
+ ASSERT_ON_THREAD(mStsThread);
+
+ if (mTransportId == aTransportId) {
+ dom::mozPacketDumpType type;
+ if (aPacket.type() == MediaPacket::SRTP) {
+ type = dom::mozPacketDumpType::Srtp;
+ } else if (aPacket.type() == MediaPacket::SRTCP) {
+ type = dom::mozPacketDumpType::Srtcp;
+ } else if (aPacket.type() == MediaPacket::DTLS) {
+ // TODO(bug 1497936): Implement packet dump for DTLS
+ return;
+ } else {
+ MOZ_ASSERT(false);
+ return;
+ }
+ mPacketDumper->Dump(Level(), type, true, aPacket.data(), aPacket.len());
+ }
+}
+
+class MediaPipelineTransmit::PipelineListener
+ : public DirectMediaTrackListener {
+ friend class MediaPipelineTransmit;
+
+ public:
+ explicit PipelineListener(RefPtr<MediaSessionConduit> aConduit)
+ : mConduit(std::move(aConduit)),
+ mActive(false),
+ mEnabled(false),
+ mDirectConnect(false) {}
+
+ ~PipelineListener() {
+ if (mConverter) {
+ mConverter->Shutdown();
+ }
+ }
+
+ void SetActive(bool aActive) {
+ mActive = aActive;
+ if (mConverter) {
+ mConverter->SetActive(aActive);
+ }
+ }
+ void SetEnabled(bool aEnabled) { mEnabled = aEnabled; }
+
+ // These are needed since nested classes don't have access to any particular
+ // instance of the parent
+ void SetAudioProxy(RefPtr<AudioProxyThread> aProxy) {
+ mAudioProcessing = std::move(aProxy);
+ }
+
+ void SetVideoFrameConverter(RefPtr<VideoFrameConverter> aConverter) {
+ mConverter = std::move(aConverter);
+ }
+
+ void OnVideoFrameConverted(webrtc::VideoFrame aVideoFrame) {
+ MOZ_RELEASE_ASSERT(mConduit->type() == MediaSessionConduit::VIDEO);
+ static_cast<VideoSessionConduit*>(mConduit.get())
+ ->SendVideoFrame(std::move(aVideoFrame));
+ }
+
+ // Implement MediaTrackListener
+ void NotifyQueuedChanges(MediaTrackGraph* aGraph, TrackTime aOffset,
+ const MediaSegment& aQueuedMedia) override;
+ void NotifyEnabledStateChanged(MediaTrackGraph* aGraph,
+ bool aEnabled) override;
+
+ // Implement DirectMediaTrackListener
+ void NotifyRealtimeTrackData(MediaTrackGraph* aGraph, TrackTime aOffset,
+ const MediaSegment& aMedia) override;
+ void NotifyDirectListenerInstalled(InstallationResult aResult) override;
+ void NotifyDirectListenerUninstalled() override;
+
+ private:
+ void NewData(const MediaSegment& aMedia, TrackRate aRate = 0);
+
+ const RefPtr<MediaSessionConduit> mConduit;
+ RefPtr<AudioProxyThread> mAudioProcessing;
+ RefPtr<VideoFrameConverter> mConverter;
+
+ // active is true if there is a transport to send on
+ mozilla::Atomic<bool> mActive;
+ // enabled is true if the media access control permits sending
+ // actual content; when false you get black/silence
+ mozilla::Atomic<bool> mEnabled;
+
+ // Written and read on the MediaTrackGraph thread
+ bool mDirectConnect;
+};
+
+MediaPipelineTransmit::MediaPipelineTransmit(
+ const std::string& aPc, RefPtr<MediaTransportHandler> aTransportHandler,
+ RefPtr<AbstractThread> aCallThread, RefPtr<nsISerialEventTarget> aStsThread,
+ bool aIsVideo, RefPtr<MediaSessionConduit> aConduit)
+ : MediaPipeline(aPc, std::move(aTransportHandler), DirectionType::TRANSMIT,
+ std::move(aCallThread), std::move(aStsThread),
+ std::move(aConduit)),
+ mWatchManager(this, AbstractThread::MainThread()),
+ mIsVideo(aIsVideo),
+ mListener(new PipelineListener(mConduit)),
+ mDomTrack(nullptr, "MediaPipelineTransmit::mDomTrack"),
+ mSendTrackOverride(nullptr, "MediaPipelineTransmit::mSendTrackOverride") {
+ if (!IsVideo()) {
+ mAudioProcessing =
+ MakeAndAddRef<AudioProxyThread>(*mConduit->AsAudioSessionConduit());
+ mListener->SetAudioProxy(mAudioProcessing);
+ } else { // Video
+ mConverter = MakeAndAddRef<VideoFrameConverter>(GetTimestampMaker());
+ mFrameListener = mConverter->VideoFrameConvertedEvent().Connect(
+ mConverter->mTaskQueue,
+ [listener = mListener](webrtc::VideoFrame aFrame) {
+ listener->OnVideoFrameConverted(std::move(aFrame));
+ });
+ mListener->SetVideoFrameConverter(mConverter);
+ }
+
+ mWatchManager.Watch(mActive, &MediaPipelineTransmit::UpdateSendState);
+ mWatchManager.Watch(mDomTrack, &MediaPipelineTransmit::UpdateSendState);
+ mWatchManager.Watch(mSendTrackOverride,
+ &MediaPipelineTransmit::UpdateSendState);
+
+ mDescription = GenerateDescription();
+}
+
+MediaPipelineTransmit::~MediaPipelineTransmit() {
+ mFrameListener.DisconnectIfExists();
+
+ MOZ_ASSERT(!mTransmitting);
+ MOZ_ASSERT(!mDomTrack.Ref());
+}
+
+void MediaPipelineTransmit::InitControl(
+ MediaPipelineTransmitControlInterface* aControl) {
+ mActive.Connect(aControl->CanonicalTransmitting());
+}
+
+void MediaPipelineTransmit::Shutdown() {
+ MediaPipeline::Shutdown();
+ mWatchManager.Shutdown();
+ if (mDomTrack.Ref()) {
+ mDomTrack.Ref()->RemovePrincipalChangeObserver(this);
+ mDomTrack = nullptr;
+ }
+ mUnsettingSendTrack = false;
+ UpdateSendState();
+ MOZ_ASSERT(!mTransmitting);
+}
+
+void MediaPipeline::SetDescription_s(const std::string& description) {
+ ASSERT_ON_THREAD(mStsThread);
+ mDescription = description;
+}
+
+std::string MediaPipelineTransmit::GenerateDescription() const {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ std::stringstream description;
+ description << mPc << "| ";
+ description << (mIsVideo ? "Transmit video[" : "Transmit audio[");
+
+ if (mDomTrack.Ref()) {
+ nsString nsTrackId;
+ mDomTrack.Ref()->GetId(nsTrackId);
+ description << NS_ConvertUTF16toUTF8(nsTrackId).get();
+ } else if (mSendTrackOverride.Ref()) {
+ description << "override " << mSendTrackOverride.Ref().get();
+ } else {
+ description << "no track";
+ }
+
+ description << "]";
+
+ return description.str();
+}
+
+void MediaPipelineTransmit::UpdateSendState() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // This runs because either mActive, mDomTrack or mSendTrackOverride changed,
+ // or because mSendTrack was unset async. Based on these inputs this method
+ // is responsible for hooking up mSendTrack to mListener in order to feed data
+ // to the conduit.
+ //
+ // If we are inactive, or if the send track does not match what we want to
+ // send (mDomTrack or mSendTrackOverride), we must stop feeding data to the
+ // conduit. NB that removing the listener from mSendTrack is async, and we
+ // must wait for it to resolve before adding mListener to another track.
+ // mUnsettingSendTrack gates us until the listener has been removed from
+ // mSendTrack.
+ //
+ // If we are active and the send track does match what we want to send, we
+ // make sure mListener is added to the send track. Either now, or if we're
+ // still waiting for another send track to be removed, during a future call to
+ // this method.
+
+ if (mUnsettingSendTrack) {
+ // We must wait for the send track to be unset before we can set it again,
+ // to avoid races. Once unset this function is triggered again.
+ return;
+ }
+
+ const bool wasTransmitting = mTransmitting;
+
+ const bool haveLiveSendTrack = mSendTrack && !mSendTrack->IsDestroyed();
+ const bool haveLiveDomTrack = mDomTrack.Ref() && !mDomTrack.Ref()->Ended();
+ const bool haveLiveOverrideTrack =
+ mSendTrackOverride.Ref() && !mSendTrackOverride.Ref()->IsDestroyed();
+ const bool mustRemoveSendTrack =
+ haveLiveSendTrack && !mSendTrackOverride.Ref() &&
+ (!haveLiveDomTrack || mDomTrack.Ref()->GetTrack() != mSendPortSource);
+
+ mTransmitting = mActive && (haveLiveDomTrack || haveLiveOverrideTrack) &&
+ !mustRemoveSendTrack;
+
+ MOZ_LOG(gMediaPipelineLog, LogLevel::Debug,
+ ("MediaPipeline %p UpdateSendState wasTransmitting=%d, active=%d, "
+ "sendTrack=%p (%s), domTrack=%p (%s), "
+ "sendTrackOverride=%p (%s), mustRemove=%d, mTransmitting=%d",
+ this, wasTransmitting, mActive.Ref(), mSendTrack.get(),
+ haveLiveSendTrack ? "live" : "ended", mDomTrack.Ref().get(),
+ haveLiveDomTrack ? "live" : "ended", mSendTrackOverride.Ref().get(),
+ haveLiveOverrideTrack ? "live" : "ended", mustRemoveSendTrack,
+ mTransmitting));
+
+ if (!wasTransmitting && mTransmitting) {
+ MOZ_LOG(gMediaPipelineLog, LogLevel::Debug,
+ ("Attaching pipeline %p to track %p conduit type=%s", this,
+ mDomTrack.Ref().get(), mIsVideo ? "video" : "audio"));
+ if (mDescriptionInvalidated) {
+ // Only update the description when we attach to a track, as detaching is
+ // always a longer async step than updating the description. Updating on
+ // detach would cause the wrong track id to be attributed in logs.
+ RUN_ON_THREAD(mStsThread,
+ WrapRunnable(RefPtr<MediaPipeline>(this),
+ &MediaPipelineTransmit::SetDescription_s,
+ GenerateDescription()),
+ NS_DISPATCH_NORMAL);
+ mDescriptionInvalidated = false;
+ }
+ if (mSendTrackOverride.Ref()) {
+ // Special path that allows unittests to avoid mDomTrack and the graph by
+ // manually calling SetSendTrack.
+ mSendTrack = mSendTrackOverride.Ref();
+ } else {
+ mSendTrack = mDomTrack.Ref()->Graph()->CreateForwardedInputTrack(
+ mDomTrack.Ref()->GetTrack()->mType);
+ mSendPortSource = mDomTrack.Ref()->GetTrack();
+ mSendPort = mSendTrack->AllocateInputPort(mSendPortSource.get());
+ }
+ if (mIsVideo) {
+ mConverter->SetTrackingId(mDomTrack.Ref()->GetSource().mTrackingId);
+ }
+ mSendTrack->QueueSetAutoend(false);
+ if (mIsVideo) {
+ mSendTrack->AddDirectListener(mListener);
+ }
+ mSendTrack->AddListener(mListener);
+ }
+
+ if (wasTransmitting && !mTransmitting) {
+ MOZ_LOG(gMediaPipelineLog, LogLevel::Debug,
+ ("Detaching pipeline %p from track %p conduit type=%s", this,
+ mDomTrack.Ref().get(), mIsVideo ? "video" : "audio"));
+ mUnsettingSendTrack = true;
+ if (mIsVideo) {
+ mSendTrack->RemoveDirectListener(mListener);
+ }
+ mSendTrack->RemoveListener(mListener)->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [this, self = RefPtr<MediaPipelineTransmit>(this)] {
+ mUnsettingSendTrack = false;
+ mSendTrack = nullptr;
+ if (!mWatchManager.IsShutdown()) {
+ mWatchManager.ManualNotify(&MediaPipelineTransmit::UpdateSendState);
+ }
+ });
+ if (!mSendTrackOverride.Ref()) {
+ // If an override is set it may be re-used.
+ mSendTrack->Destroy();
+ mSendPort->Destroy();
+ mSendPort = nullptr;
+ mSendPortSource = nullptr;
+ }
+ }
+}
+
+bool MediaPipelineTransmit::Transmitting() const {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ return mActive;
+}
+
+bool MediaPipelineTransmit::IsVideo() const { return mIsVideo; }
+
+void MediaPipelineTransmit::PrincipalChanged(dom::MediaStreamTrack* aTrack) {
+ MOZ_ASSERT(aTrack && aTrack == mDomTrack.Ref());
+
+ PeerConnectionWrapper pcw(mPc);
+ if (pcw.impl()) {
+ Document* doc = pcw.impl()->GetParentObject()->GetExtantDoc();
+ if (doc) {
+ UpdateSinkIdentity(doc->NodePrincipal(), pcw.impl()->GetPeerIdentity());
+ } else {
+ MOZ_LOG(gMediaPipelineLog, LogLevel::Info,
+ ("Can't update sink principal; document gone"));
+ }
+ }
+}
+
+void MediaPipelineTransmit::UpdateSinkIdentity(
+ nsIPrincipal* aPrincipal, const PeerIdentity* aSinkIdentity) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mDomTrack.Ref()) {
+ // Nothing to do here
+ return;
+ }
+
+ bool enableTrack = aPrincipal->Subsumes(mDomTrack.Ref()->GetPrincipal());
+ if (!enableTrack) {
+ // first try didn't work, but there's a chance that this is still available
+ // if our track is bound to a peerIdentity, and the peer connection (our
+ // sink) is bound to the same identity, then we can enable the track.
+ const PeerIdentity* trackIdentity = mDomTrack.Ref()->GetPeerIdentity();
+ if (aSinkIdentity && trackIdentity) {
+ enableTrack = (*aSinkIdentity == *trackIdentity);
+ }
+ }
+
+ mListener->SetEnabled(enableTrack);
+}
+
+void MediaPipelineTransmit::TransportReady_s() {
+ ASSERT_ON_THREAD(mStsThread);
+ // Call base ready function.
+ MediaPipeline::TransportReady_s();
+ mListener->SetActive(true);
+}
+
+nsresult MediaPipelineTransmit::SetTrack(
+ const RefPtr<MediaStreamTrack>& aDomTrack) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mDomTrack.Ref()) {
+ mDomTrack.Ref()->RemovePrincipalChangeObserver(this);
+ }
+
+ if (aDomTrack) {
+ nsString nsTrackId;
+ aDomTrack->GetId(nsTrackId);
+ MOZ_LOG(gMediaPipelineLog, LogLevel::Debug,
+ ("Reattaching pipeline to track %p track %s conduit type: %s",
+ aDomTrack.get(), NS_ConvertUTF16toUTF8(nsTrackId).get(),
+ mIsVideo ? "video" : "audio"));
+ }
+
+ mDescriptionInvalidated = true;
+ mDomTrack = aDomTrack;
+ if (mDomTrack.Ref()) {
+ mDomTrack.Ref()->AddPrincipalChangeObserver(this);
+ PrincipalChanged(mDomTrack.Ref());
+ }
+
+ return NS_OK;
+}
+
+RefPtr<dom::MediaStreamTrack> MediaPipelineTransmit::GetTrack() const {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mDomTrack;
+}
+
+void MediaPipelineTransmit::SetSendTrackOverride(
+ const RefPtr<ProcessedMediaTrack>& aSendTrack) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_RELEASE_ASSERT(!mSendTrack);
+ MOZ_RELEASE_ASSERT(!mSendPort);
+ MOZ_RELEASE_ASSERT(!mSendTrackOverride.Ref());
+ mDescriptionInvalidated = true;
+ mSendTrackOverride = aSendTrack;
+}
+
+// Called if we're attached with AddDirectListener()
+void MediaPipelineTransmit::PipelineListener::NotifyRealtimeTrackData(
+ MediaTrackGraph* aGraph, TrackTime aOffset, const MediaSegment& aMedia) {
+ MOZ_LOG(
+ gMediaPipelineLog, LogLevel::Debug,
+ ("MediaPipeline::NotifyRealtimeTrackData() listener=%p, offset=%" PRId64
+ ", duration=%" PRId64,
+ this, aOffset, aMedia.GetDuration()));
+ TRACE_COMMENT(
+ "MediaPipelineTransmit::PipelineListener::NotifyRealtimeTrackData", "%s",
+ aMedia.GetType() == MediaSegment::VIDEO ? "Video" : "Audio");
+ NewData(aMedia, aGraph->GraphRate());
+}
+
+void MediaPipelineTransmit::PipelineListener::NotifyQueuedChanges(
+ MediaTrackGraph* aGraph, TrackTime aOffset,
+ const MediaSegment& aQueuedMedia) {
+ MOZ_LOG(gMediaPipelineLog, LogLevel::Debug,
+ ("MediaPipeline::NotifyQueuedChanges()"));
+
+ if (aQueuedMedia.GetType() == MediaSegment::VIDEO) {
+ // We always get video from the direct listener.
+ return;
+ }
+
+ TRACE("MediaPipelineTransmit::PipelineListener::NotifyQueuedChanges (Audio)");
+
+ if (mDirectConnect) {
+ // ignore non-direct data if we're also getting direct data
+ return;
+ }
+
+ size_t rate;
+ if (aGraph) {
+ rate = aGraph->GraphRate();
+ } else {
+ // When running tests, graph may be null. In that case use a default.
+ rate = 16000;
+ }
+ NewData(aQueuedMedia, rate);
+}
+
+void MediaPipelineTransmit::PipelineListener::NotifyEnabledStateChanged(
+ MediaTrackGraph* aGraph, bool aEnabled) {
+ if (mConduit->type() != MediaSessionConduit::VIDEO) {
+ return;
+ }
+ MOZ_ASSERT(mConverter);
+ mConverter->SetTrackEnabled(aEnabled);
+}
+
+void MediaPipelineTransmit::PipelineListener::NotifyDirectListenerInstalled(
+ InstallationResult aResult) {
+ MOZ_LOG(gMediaPipelineLog, LogLevel::Info,
+ ("MediaPipeline::NotifyDirectListenerInstalled() listener=%p,"
+ " result=%d",
+ this, static_cast<int32_t>(aResult)));
+
+ mDirectConnect = InstallationResult::SUCCESS == aResult;
+}
+
+void MediaPipelineTransmit::PipelineListener::
+ NotifyDirectListenerUninstalled() {
+ MOZ_LOG(
+ gMediaPipelineLog, LogLevel::Info,
+ ("MediaPipeline::NotifyDirectListenerUninstalled() listener=%p", this));
+
+ if (mConduit->type() == MediaSessionConduit::VIDEO) {
+ // Reset the converter's track-enabled state. If re-added to a new track
+ // later and that track is disabled, we will be signaled explicitly.
+ MOZ_ASSERT(mConverter);
+ mConverter->SetTrackEnabled(true);
+ }
+
+ mDirectConnect = false;
+}
+
+void MediaPipelineTransmit::PipelineListener::NewData(
+ const MediaSegment& aMedia, TrackRate aRate /* = 0 */) {
+ if (mConduit->type() != (aMedia.GetType() == MediaSegment::AUDIO
+ ? MediaSessionConduit::AUDIO
+ : MediaSessionConduit::VIDEO)) {
+ MOZ_ASSERT(false,
+ "The media type should always be correct since the "
+ "listener is locked to a specific track");
+ return;
+ }
+
+ // TODO(ekr@rtfm.com): For now assume that we have only one
+ // track type and it's destined for us
+ // See bug 784517
+ if (aMedia.GetType() == MediaSegment::AUDIO) {
+ MOZ_RELEASE_ASSERT(aRate > 0);
+
+ if (!mActive) {
+ MOZ_LOG(gMediaPipelineLog, LogLevel::Debug,
+ ("Discarding audio packets because transport not ready"));
+ return;
+ }
+
+ const AudioSegment* audio = static_cast<const AudioSegment*>(&aMedia);
+ for (AudioSegment::ConstChunkIterator iter(*audio); !iter.IsEnded();
+ iter.Next()) {
+ mAudioProcessing->QueueAudioChunk(aRate, *iter, mEnabled);
+ }
+ } else {
+ const VideoSegment* video = static_cast<const VideoSegment*>(&aMedia);
+
+ for (VideoSegment::ConstChunkIterator iter(*video); !iter.IsEnded();
+ iter.Next()) {
+ mConverter->QueueVideoChunk(*iter, !mEnabled);
+ }
+ }
+}
+
+class GenericReceiveListener : public MediaTrackListener {
+ public:
+ GenericReceiveListener(RefPtr<SourceMediaTrack> aSource,
+ TrackingId aTrackingId)
+ : mSource(std::move(aSource)),
+ mTrackingId(std::move(aTrackingId)),
+ mIsAudio(mSource->mType == MediaSegment::AUDIO),
+ mEnabled(false) {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+ MOZ_DIAGNOSTIC_ASSERT(mSource, "Must be used with a SourceMediaTrack");
+ }
+
+ virtual ~GenericReceiveListener() = default;
+
+ void Init() { mSource->AddListener(this); }
+ void Shutdown() { mSource->RemoveListener(this); }
+
+ void SetEnabled(bool aEnabled) {
+ if (mEnabled == aEnabled) {
+ return;
+ }
+ mEnabled = aEnabled;
+ if (mIsAudio && !mSource->IsDestroyed()) {
+ mSource->SetPullingEnabled(mEnabled);
+ }
+ }
+
+ protected:
+ const RefPtr<SourceMediaTrack> mSource;
+ const TrackingId mTrackingId;
+ const bool mIsAudio;
+ // Main thread only.
+ bool mEnabled;
+};
+
+MediaPipelineReceive::MediaPipelineReceive(
+ const std::string& aPc, RefPtr<MediaTransportHandler> aTransportHandler,
+ RefPtr<AbstractThread> aCallThread, RefPtr<nsISerialEventTarget> aStsThread,
+ RefPtr<MediaSessionConduit> aConduit)
+ : MediaPipeline(aPc, std::move(aTransportHandler), DirectionType::RECEIVE,
+ std::move(aCallThread), std::move(aStsThread),
+ std::move(aConduit)),
+ mWatchManager(this, AbstractThread::MainThread()) {
+ mWatchManager.Watch(mActive, &MediaPipelineReceive::UpdateListener);
+}
+
+MediaPipelineReceive::~MediaPipelineReceive() = default;
+
+void MediaPipelineReceive::InitControl(
+ MediaPipelineReceiveControlInterface* aControl) {
+ mActive.Connect(aControl->CanonicalReceiving());
+}
+
+void MediaPipelineReceive::Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MediaPipeline::Shutdown();
+ mWatchManager.Shutdown();
+}
+
+class MediaPipelineReceiveAudio::PipelineListener
+ : public GenericReceiveListener {
+ public:
+ PipelineListener(RefPtr<SourceMediaTrack> aSource, TrackingId aTrackingId,
+ RefPtr<MediaSessionConduit> aConduit,
+ PrincipalHandle aPrincipalHandle, PrincipalPrivacy aPrivacy)
+ : GenericReceiveListener(std::move(aSource), std::move(aTrackingId)),
+ mConduit(std::move(aConduit)),
+ // AudioSession conduit only supports 16, 32, 44.1 and 48kHz
+ // This is an artificial limitation, it would however require more
+ // changes to support any rates. If the sampling rate is not-supported,
+ // we will use 48kHz instead.
+ mRate(static_cast<AudioSessionConduit*>(mConduit.get())
+ ->IsSamplingFreqSupported(mSource->Graph()->GraphRate())
+ ? mSource->Graph()->GraphRate()
+ : WEBRTC_MAX_SAMPLE_RATE),
+ mTaskQueue(TaskQueue::Create(
+ GetMediaThreadPool(MediaThreadType::WEBRTC_WORKER),
+ "AudioPipelineListener")),
+ mPlayedTicks(0),
+ mAudioFrame(std::make_unique<webrtc::AudioFrame>()),
+ mPrincipalHandle(std::move(aPrincipalHandle)),
+ mPrivacy(aPrivacy),
+ mForceSilence(false) {}
+
+ void Init() {
+ GenericReceiveListener::Init();
+ mSource->SetAppendDataSourceRate(mRate);
+ }
+
+ // Implement MediaTrackListener
+ void NotifyPull(MediaTrackGraph* aGraph, TrackTime aEndOfAppendedData,
+ TrackTime aDesiredTime) override {
+ NotifyPullImpl(aDesiredTime);
+ }
+
+ void OnPrivacyRequested_s() {
+ if (mPrivacy == PrincipalPrivacy::Private) {
+ return;
+ }
+ mForceSilence = true;
+ }
+
+ void SetPrivatePrincipal(PrincipalHandle aHandle) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ class Message : public ControlMessage {
+ public:
+ Message(RefPtr<PipelineListener> aListener,
+ PrincipalHandle aPrivatePrincipal)
+ : ControlMessage(nullptr),
+ mListener(std::move(aListener)),
+ mPrivatePrincipal(std::move(aPrivatePrincipal)) {}
+
+ void Run() override {
+ if (mListener->mPrivacy == PrincipalPrivacy::Private) {
+ return;
+ }
+ mListener->mPrincipalHandle = mPrivatePrincipal;
+ mListener->mPrivacy = PrincipalPrivacy::Private;
+ mListener->mForceSilence = false;
+ }
+
+ const RefPtr<PipelineListener> mListener;
+ PrincipalHandle mPrivatePrincipal;
+ };
+
+ if (mSource->IsDestroyed()) {
+ return;
+ }
+
+ mSource->GraphImpl()->AppendMessage(
+ MakeUnique<Message>(this, std::move(aHandle)));
+ }
+
+ private:
+ ~PipelineListener() = default;
+
+ void NotifyPullImpl(TrackTime aDesiredTime) {
+ TRACE_COMMENT("PiplineListener::NotifyPullImpl", "PipelineListener %p",
+ this);
+ uint32_t samplesPer10ms = mRate / 100;
+
+ // mSource's rate is not necessarily the same as the graph rate, since there
+ // are sample-rate constraints on the inbound audio: only 16, 32, 44.1 and
+ // 48kHz are supported. The audio frames we get here is going to be
+ // resampled when inserted into the graph. aDesiredTime and mPlayedTicks are
+ // in the graph rate.
+
+ while (mPlayedTicks < aDesiredTime) {
+ // This fetches 10ms of data, either mono or stereo
+ MediaConduitErrorCode err =
+ static_cast<AudioSessionConduit*>(mConduit.get())
+ ->GetAudioFrame(mRate, mAudioFrame.get());
+
+ if (err != kMediaConduitNoError) {
+ // Insert silence on conduit/GIPS failure (extremely unlikely)
+ MOZ_LOG(gMediaPipelineLog, LogLevel::Error,
+ ("Audio conduit failed (%d) to return data @ %" PRId64
+ " (desired %" PRId64 " -> %f)",
+ err, mPlayedTicks, aDesiredTime,
+ mSource->TrackTimeToSeconds(aDesiredTime)));
+ constexpr size_t mono = 1;
+ mAudioFrame->UpdateFrame(
+ mAudioFrame->timestamp_, nullptr, samplesPer10ms, mRate,
+ mAudioFrame->speech_type_, mAudioFrame->vad_activity_,
+ std::max(mono, mAudioFrame->num_channels()));
+ }
+
+ MOZ_LOG(
+ gMediaPipelineLog, LogLevel::Debug,
+ ("Audio conduit returned buffer for %zu channels, %zu frames",
+ mAudioFrame->num_channels(), mAudioFrame->samples_per_channel()));
+
+ AudioSegment segment;
+ if (mForceSilence || mAudioFrame->muted()) {
+ segment.AppendNullData(mAudioFrame->samples_per_channel());
+ } else {
+ CheckedInt<size_t> bufferSize(sizeof(uint16_t));
+ bufferSize *= mAudioFrame->samples_per_channel();
+ bufferSize *= mAudioFrame->num_channels();
+ RefPtr<SharedBuffer> samples = SharedBuffer::Create(bufferSize);
+ int16_t* samplesData = static_cast<int16_t*>(samples->Data());
+ AutoTArray<int16_t*, 2> channels;
+ AutoTArray<const int16_t*, 2> outputChannels;
+
+ channels.SetLength(mAudioFrame->num_channels());
+
+ size_t offset = 0;
+ for (size_t i = 0; i < mAudioFrame->num_channels(); i++) {
+ channels[i] = samplesData + offset;
+ offset += mAudioFrame->samples_per_channel();
+ }
+
+ DeinterleaveAndConvertBuffer(
+ mAudioFrame->data(), mAudioFrame->samples_per_channel(),
+ mAudioFrame->num_channels(), channels.Elements());
+
+ outputChannels.AppendElements(channels);
+
+ segment.AppendFrames(samples.forget(), outputChannels,
+ mAudioFrame->samples_per_channel(),
+ mPrincipalHandle);
+ }
+
+ // Handle track not actually added yet or removed/finished
+ if (TrackTime appended = mSource->AppendData(&segment)) {
+ mPlayedTicks += appended;
+ } else {
+ MOZ_LOG(gMediaPipelineLog, LogLevel::Error, ("AppendData failed"));
+ // we can't un-read the data, but that's ok since we don't want to
+ // buffer - but don't i-loop!
+ break;
+ }
+ }
+ }
+
+ const RefPtr<MediaSessionConduit> mConduit;
+ // This conduit's sampling rate. This is either 16, 32, 44.1 or 48kHz, and
+ // tries to be the same as the graph rate. If the graph rate is higher than
+ // 48kHz, mRate is capped to 48kHz. If mRate does not match the graph rate,
+ // audio is resampled to the graph rate.
+ const TrackRate mRate;
+ const RefPtr<TaskQueue> mTaskQueue;
+ // Number of frames of data that has been added to the SourceMediaTrack in
+ // the graph's rate. Graph thread only.
+ TrackTicks mPlayedTicks;
+ // Allocation of an audio frame used as a scratch buffer when reading data out
+ // of libwebrtc for forwarding into the graph. Graph thread only.
+ std::unique_ptr<webrtc::AudioFrame> mAudioFrame;
+ // Principal handle used when appending data to the SourceMediaTrack. Graph
+ // thread only.
+ PrincipalHandle mPrincipalHandle;
+ // Privacy of mPrincipalHandle. Graph thread only.
+ PrincipalPrivacy mPrivacy;
+ // Set to true on the sts thread if privacy is requested when ALPN was
+ // negotiated. Set to false again when mPrincipalHandle is private.
+ Atomic<bool> mForceSilence;
+};
+
+MediaPipelineReceiveAudio::MediaPipelineReceiveAudio(
+ const std::string& aPc, RefPtr<MediaTransportHandler> aTransportHandler,
+ RefPtr<AbstractThread> aCallThread, RefPtr<nsISerialEventTarget> aStsThread,
+ RefPtr<AudioSessionConduit> aConduit, RefPtr<SourceMediaTrack> aSource,
+ TrackingId aTrackingId, PrincipalHandle aPrincipalHandle,
+ PrincipalPrivacy aPrivacy)
+ : MediaPipelineReceive(aPc, std::move(aTransportHandler),
+ std::move(aCallThread), std::move(aStsThread),
+ std::move(aConduit)),
+ mListener(aSource ? new PipelineListener(
+ std::move(aSource), std::move(aTrackingId),
+ mConduit, std::move(aPrincipalHandle), aPrivacy)
+ : nullptr) {
+ mDescription = mPc + "| Receive audio";
+ if (mListener) {
+ mListener->Init();
+ }
+}
+
+void MediaPipelineReceiveAudio::Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MediaPipelineReceive::Shutdown();
+ if (mListener) {
+ mListener->Shutdown();
+ }
+}
+
+void MediaPipelineReceiveAudio::OnPrivacyRequested_s() {
+ ASSERT_ON_THREAD(mStsThread);
+ if (mListener) {
+ mListener->OnPrivacyRequested_s();
+ }
+}
+
+void MediaPipelineReceiveAudio::SetPrivatePrincipal(PrincipalHandle aHandle) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mListener) {
+ mListener->SetPrivatePrincipal(std::move(aHandle));
+ }
+}
+
+void MediaPipelineReceiveAudio::UpdateListener() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mListener) {
+ mListener->SetEnabled(mActive.Ref());
+ }
+}
+
+class MediaPipelineReceiveVideo::PipelineListener
+ : public GenericReceiveListener {
+ public:
+ PipelineListener(RefPtr<SourceMediaTrack> aSource, TrackingId aTrackingId,
+ PrincipalHandle aPrincipalHandle, PrincipalPrivacy aPrivacy)
+ : GenericReceiveListener(std::move(aSource), std::move(aTrackingId)),
+ mImageContainer(
+ MakeAndAddRef<ImageContainer>(ImageContainer::ASYNCHRONOUS)),
+ mMutex("MediaPipelineReceiveVideo::PipelineListener::mMutex"),
+ mPrincipalHandle(std::move(aPrincipalHandle)),
+ mPrivacy(aPrivacy) {}
+ void OnPrivacyRequested_s() {
+ MutexAutoLock lock(mMutex);
+ if (mPrivacy == PrincipalPrivacy::Private) {
+ return;
+ }
+ mForceDropFrames = true;
+ }
+
+ void SetPrivatePrincipal(PrincipalHandle aHandle) {
+ MutexAutoLock lock(mMutex);
+ if (mPrivacy == PrincipalPrivacy::Private) {
+ return;
+ }
+ mPrincipalHandle = std::move(aHandle);
+ mPrivacy = PrincipalPrivacy::Private;
+ mForceDropFrames = false;
+ }
+
+ void RenderVideoFrame(const webrtc::VideoFrameBuffer& aBuffer,
+ uint32_t aTimeStamp, int64_t aRenderTime) {
+ PrincipalHandle principal;
+ {
+ MutexAutoLock lock(mMutex);
+ if (mForceDropFrames) {
+ return;
+ }
+ principal = mPrincipalHandle;
+ }
+ RefPtr<Image> image;
+ if (aBuffer.type() == webrtc::VideoFrameBuffer::Type::kNative) {
+ // We assume that only native handles are used with the
+ // WebrtcMediaDataCodec decoder.
+ const ImageBuffer* imageBuffer =
+ static_cast<const ImageBuffer*>(&aBuffer);
+ image = imageBuffer->GetNativeImage();
+ } else {
+ MOZ_ASSERT(aBuffer.type() == webrtc::VideoFrameBuffer::Type::kI420);
+ rtc::scoped_refptr<const webrtc::I420BufferInterface> i420(
+ aBuffer.GetI420());
+
+ MOZ_ASSERT(i420->DataY());
+ // Create a video frame using |buffer|.
+ PerformanceRecorder<CopyVideoStage> rec(
+ "MediaPipelineReceiveVideo::CopyToImage"_ns, mTrackingId,
+ i420->width(), i420->height());
+
+ RefPtr<PlanarYCbCrImage> yuvImage =
+ mImageContainer->CreatePlanarYCbCrImage();
+
+ PlanarYCbCrData yuvData;
+ yuvData.mYChannel = const_cast<uint8_t*>(i420->DataY());
+ yuvData.mYStride = i420->StrideY();
+ MOZ_ASSERT(i420->StrideU() == i420->StrideV());
+ yuvData.mCbCrStride = i420->StrideU();
+ yuvData.mCbChannel = const_cast<uint8_t*>(i420->DataU());
+ yuvData.mCrChannel = const_cast<uint8_t*>(i420->DataV());
+ yuvData.mPictureRect = IntRect(0, 0, i420->width(), i420->height());
+ yuvData.mStereoMode = StereoMode::MONO;
+ // This isn't the best default.
+ yuvData.mYUVColorSpace = gfx::YUVColorSpace::BT601;
+ yuvData.mChromaSubsampling =
+ gfx::ChromaSubsampling::HALF_WIDTH_AND_HEIGHT;
+
+ if (!yuvImage->CopyData(yuvData)) {
+ MOZ_ASSERT(false);
+ return;
+ }
+ rec.Record();
+
+ image = std::move(yuvImage);
+ }
+
+ VideoSegment segment;
+ auto size = image->GetSize();
+ segment.AppendFrame(image.forget(), size, principal);
+ mSource->AppendData(&segment);
+ }
+
+ private:
+ RefPtr<layers::ImageContainer> mImageContainer;
+ Mutex mMutex;
+ PrincipalHandle mPrincipalHandle MOZ_GUARDED_BY(mMutex);
+ PrincipalPrivacy mPrivacy MOZ_GUARDED_BY(mMutex);
+ // Set to true on the sts thread if privacy is requested when ALPN was
+ // negotiated. Set to false again when mPrincipalHandle is private.
+ bool mForceDropFrames MOZ_GUARDED_BY(mMutex) = false;
+};
+
+class MediaPipelineReceiveVideo::PipelineRenderer
+ : public mozilla::VideoRenderer {
+ public:
+ explicit PipelineRenderer(MediaPipelineReceiveVideo* aPipeline)
+ : mPipeline(aPipeline) {}
+
+ void Detach() { mPipeline = nullptr; }
+
+ // Implement VideoRenderer
+ void FrameSizeChange(unsigned int aWidth, unsigned int aHeight) override {}
+ void RenderVideoFrame(const webrtc::VideoFrameBuffer& aBuffer,
+ uint32_t aTimeStamp, int64_t aRenderTime) override {
+ mPipeline->mListener->RenderVideoFrame(aBuffer, aTimeStamp, aRenderTime);
+ }
+
+ private:
+ MediaPipelineReceiveVideo* mPipeline; // Raw pointer to avoid cycles
+};
+
+MediaPipelineReceiveVideo::MediaPipelineReceiveVideo(
+ const std::string& aPc, RefPtr<MediaTransportHandler> aTransportHandler,
+ RefPtr<AbstractThread> aCallThread, RefPtr<nsISerialEventTarget> aStsThread,
+ RefPtr<VideoSessionConduit> aConduit, RefPtr<SourceMediaTrack> aSource,
+ TrackingId aTrackingId, PrincipalHandle aPrincipalHandle,
+ PrincipalPrivacy aPrivacy)
+ : MediaPipelineReceive(aPc, std::move(aTransportHandler),
+ std::move(aCallThread), std::move(aStsThread),
+ std::move(aConduit)),
+ mRenderer(new PipelineRenderer(this)),
+ mListener(aSource ? new PipelineListener(
+ std::move(aSource), std::move(aTrackingId),
+ std::move(aPrincipalHandle), aPrivacy)
+ : nullptr) {
+ mDescription = mPc + "| Receive video";
+ if (mListener) {
+ mListener->Init();
+ }
+ static_cast<VideoSessionConduit*>(mConduit.get())->AttachRenderer(mRenderer);
+}
+
+void MediaPipelineReceiveVideo::Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MediaPipelineReceive::Shutdown();
+ if (mListener) {
+ mListener->Shutdown();
+ }
+
+ // stop generating video and thus stop invoking the PipelineRenderer
+ // and PipelineListener - the renderer has a raw ptr to the Pipeline to
+ // avoid cycles, and the render callbacks are invoked from a different
+ // thread so simple null-checks would cause TSAN bugs without locks.
+ static_cast<VideoSessionConduit*>(mConduit.get())->DetachRenderer();
+}
+
+void MediaPipelineReceiveVideo::OnPrivacyRequested_s() {
+ ASSERT_ON_THREAD(mStsThread);
+ if (mListener) {
+ mListener->OnPrivacyRequested_s();
+ }
+}
+
+void MediaPipelineReceiveVideo::SetPrivatePrincipal(PrincipalHandle aHandle) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mListener) {
+ mListener->SetPrivatePrincipal(std::move(aHandle));
+ }
+}
+
+void MediaPipelineReceiveVideo::UpdateListener() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mListener) {
+ mListener->SetEnabled(mActive.Ref());
+ }
+}
+
+const dom::RTCStatsTimestampMaker& MediaPipeline::GetTimestampMaker() const {
+ return mConduit->GetTimestampMaker();
+}
+
+DOMHighResTimeStamp MediaPipeline::RtpCSRCStats::GetExpiryFromTime(
+ const DOMHighResTimeStamp aTime) {
+ // DOMHighResTimeStamp is a unit measured in ms
+ return aTime + EXPIRY_TIME_MILLISECONDS;
+}
+
+MediaPipeline::RtpCSRCStats::RtpCSRCStats(const uint32_t aCsrc,
+ const DOMHighResTimeStamp aTime)
+ : mCsrc(aCsrc), mTimestamp(aTime) {}
+
+void MediaPipeline::RtpCSRCStats::GetWebidlInstance(
+ dom::RTCRTPContributingSourceStats& aWebidlObj,
+ const nsString& aInboundRtpStreamId) const {
+ nsString statId = u"csrc_"_ns + aInboundRtpStreamId;
+ statId.AppendLiteral("_");
+ statId.AppendInt(mCsrc);
+ aWebidlObj.mId.Construct(statId);
+ aWebidlObj.mType.Construct(RTCStatsType::Csrc);
+ aWebidlObj.mTimestamp.Construct(mTimestamp);
+ aWebidlObj.mContributorSsrc.Construct(mCsrc);
+ aWebidlObj.mInboundRtpStreamId.Construct(aInboundRtpStreamId);
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/transportbridge/MediaPipeline.h b/dom/media/webrtc/transportbridge/MediaPipeline.h
new file mode 100644
index 0000000000..d58fd12ea3
--- /dev/null
+++ b/dom/media/webrtc/transportbridge/MediaPipeline.h
@@ -0,0 +1,454 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: ekr@rtfm.com
+
+#ifndef mediapipeline_h__
+#define mediapipeline_h__
+
+#include <map>
+
+#include "transport/sigslot.h"
+#include "transport/transportlayer.h" // For TransportLayer::State
+
+#include "libwebrtcglue/MediaConduitControl.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/StateMirroring.h"
+#include "transport/mediapacket.h"
+#include "transport/runnable_utils.h"
+#include "AudioPacketizer.h"
+#include "MediaEventSource.h"
+#include "MediaPipelineFilter.h"
+#include "MediaSegment.h"
+#include "PrincipalChangeObserver.h"
+#include "jsapi/PacketDumper.h"
+#include "PerformanceRecorder.h"
+
+// Should come from MediaEngine.h, but that's a pain to include here
+// because of the MOZILLA_EXTERNAL_LINKAGE stuff.
+#define WEBRTC_MAX_SAMPLE_RATE 48000
+
+class nsIPrincipal;
+
+namespace webrtc {
+struct RTPHeader;
+class RtpHeaderExtensionMap;
+class RtpPacketReceived;
+} // namespace webrtc
+
+namespace mozilla {
+class AudioProxyThread;
+class MediaInputPort;
+class MediaPipelineFilter;
+class MediaTransportHandler;
+class PeerIdentity;
+class ProcessedMediaTrack;
+class SourceMediaTrack;
+class VideoFrameConverter;
+class MediaSessionConduit;
+class AudioSessionConduit;
+class VideoSessionConduit;
+
+namespace dom {
+class MediaStreamTrack;
+struct RTCRTPContributingSourceStats;
+class RTCStatsTimestampMaker;
+} // namespace dom
+
+struct MediaPipelineReceiveControlInterface {
+ virtual AbstractCanonical<bool>* CanonicalReceiving() = 0;
+};
+
+struct MediaPipelineTransmitControlInterface {
+ virtual AbstractCanonical<bool>* CanonicalTransmitting() = 0;
+};
+
+// A class that represents the pipeline of audio and video
+// The dataflow looks like:
+//
+// TRANSMIT
+// CaptureDevice -> stream -> [us] -> conduit -> [us] -> transport -> network
+//
+// RECEIVE
+// network -> transport -> [us] -> conduit -> [us] -> stream -> Playout
+//
+// The boxes labeled [us] are just bridge logic implemented in this class
+//
+// We have to deal with a number of threads:
+//
+// GSM:
+// * Assembles the pipeline
+// SocketTransportService
+// * Receives notification that ICE and DTLS have completed
+// * Processes incoming network data and passes it to the conduit
+// * Processes outgoing RTP and RTCP
+// MediaTrackGraph
+// * Receives outgoing data from the MediaTrackGraph
+// * Receives pull requests for more data from the
+// MediaTrackGraph
+// One or another GIPS threads
+// * Receives RTCP messages to send to the other side
+// * Processes video frames GIPS wants to render
+//
+// For a transmitting conduit, "output" is RTP and "input" is RTCP.
+// For a receiving conduit, "input" is RTP and "output" is RTCP.
+//
+
+class MediaPipeline : public sigslot::has_slots<> {
+ public:
+ enum class DirectionType { TRANSMIT, RECEIVE };
+ MediaPipeline(const std::string& aPc,
+ RefPtr<MediaTransportHandler> aTransportHandler,
+ DirectionType aDirection, RefPtr<AbstractThread> aCallThread,
+ RefPtr<nsISerialEventTarget> aStsThread,
+ RefPtr<MediaSessionConduit> aConduit);
+
+ void SetLevel(size_t aLevel) { mLevel = aLevel; }
+
+ // Main thread shutdown.
+ virtual void Shutdown();
+
+ void UpdateTransport_m(const std::string& aTransportId,
+ UniquePtr<MediaPipelineFilter>&& aFilter);
+
+ void UpdateTransport_s(const std::string& aTransportId,
+ UniquePtr<MediaPipelineFilter>&& aFilter);
+
+ virtual DirectionType Direction() const { return mDirection; }
+ size_t Level() const { return mLevel; }
+ virtual bool IsVideo() const = 0;
+
+ class RtpCSRCStats {
+ public:
+ // Gets an expiration time for CRC info given a reference time,
+ // this reference time would normally be the time of calling.
+ // This value can then be used to check if a RtpCSRCStats
+ // has expired via Expired(...)
+ static DOMHighResTimeStamp GetExpiryFromTime(
+ const DOMHighResTimeStamp aTime);
+
+ RtpCSRCStats(const uint32_t aCsrc, const DOMHighResTimeStamp aTime);
+ ~RtpCSRCStats() = default;
+ // Initialize a webidl representation suitable for adding to a report.
+ // This assumes that the webidl object is empty.
+ // @param aWebidlObj the webidl binding object to popluate
+ // @param aInboundRtpStreamId the associated RTCInboundRTPStreamStats.id
+ void GetWebidlInstance(dom::RTCRTPContributingSourceStats& aWebidlObj,
+ const nsString& aInboundRtpStreamId) const;
+ void SetTimestamp(const DOMHighResTimeStamp aTime) { mTimestamp = aTime; }
+ // Check if the RtpCSRCStats has expired, checks against a
+ // given expiration time.
+ bool Expired(const DOMHighResTimeStamp aExpiry) const {
+ return mTimestamp < aExpiry;
+ }
+
+ private:
+ static const double constexpr EXPIRY_TIME_MILLISECONDS = 10 * 1000;
+ const uint32_t mCsrc;
+ DOMHighResTimeStamp mTimestamp;
+ };
+
+ // Gets the gathered contributing source stats for the last expiration period.
+ // @param aId the stream id to use for populating inboundRtpStreamId field
+ // @param aArr the array to append the stats objects to
+ void GetContributingSourceStats(
+ const nsString& aInboundRtpStreamId,
+ FallibleTArray<dom::RTCRTPContributingSourceStats>& aArr) const;
+
+ int32_t RtpPacketsSent() const { return mRtpPacketsSent; }
+ int64_t RtpBytesSent() const { return mRtpBytesSent; }
+ int32_t RtcpPacketsSent() const { return mRtcpPacketsSent; }
+ int32_t RtpPacketsReceived() const { return mRtpPacketsReceived; }
+ int64_t RtpBytesReceived() const { return mRtpBytesReceived; }
+ int32_t RtcpPacketsReceived() const { return mRtcpPacketsReceived; }
+
+ const dom::RTCStatsTimestampMaker& GetTimestampMaker() const;
+
+ // Thread counting
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaPipeline)
+
+ protected:
+ virtual ~MediaPipeline();
+
+ // The transport is ready
+ virtual void TransportReady_s() {}
+
+ void IncrementRtpPacketsSent(const MediaPacket& aPacket);
+ void IncrementRtcpPacketsSent();
+ void IncrementRtpPacketsReceived(int aBytes);
+ void IncrementRtcpPacketsReceived();
+
+ virtual void SendPacket(MediaPacket&& packet);
+
+ // Process slots on transports
+ void RtpStateChange(const std::string& aTransportId, TransportLayer::State);
+ void RtcpStateChange(const std::string& aTransportId, TransportLayer::State);
+ virtual void CheckTransportStates();
+ void PacketReceived(const std::string& aTransportId,
+ const MediaPacket& packet);
+ void AlpnNegotiated(const std::string& aAlpn, bool aPrivacyRequested);
+
+ void RtpPacketReceived(const MediaPacket& packet);
+ void RtcpPacketReceived(const MediaPacket& packet);
+
+ void EncryptedPacketSending(const std::string& aTransportId,
+ const MediaPacket& aPacket);
+
+ void SetDescription_s(const std::string& description);
+
+ public:
+ const RefPtr<MediaSessionConduit> mConduit;
+ const DirectionType mDirection;
+
+ // Pointers to the threads we need. Initialized at creation
+ // and used all over the place.
+ const RefPtr<AbstractThread> mCallThread;
+ const RefPtr<nsISerialEventTarget> mStsThread;
+
+ protected:
+ // True if we should be actively transmitting or receiving data. Main thread
+ // only.
+ Mirror<bool> mActive;
+ Atomic<size_t> mLevel;
+ std::string mTransportId;
+ const RefPtr<MediaTransportHandler> mTransportHandler;
+
+ TransportLayer::State mRtpState = TransportLayer::TS_NONE;
+ TransportLayer::State mRtcpState = TransportLayer::TS_NONE;
+ bool mSignalsConnected = false;
+
+ // Only safe to access from STS thread.
+ int32_t mRtpPacketsSent;
+ int32_t mRtcpPacketsSent;
+ int32_t mRtpPacketsReceived;
+ int32_t mRtcpPacketsReceived;
+ int64_t mRtpBytesSent;
+ int64_t mRtpBytesReceived;
+
+ // Only safe to access from STS thread.
+ std::map<uint32_t, RtpCSRCStats> mCsrcStats;
+
+ // Written in c'tor. Read on STS and main thread.
+ const std::string mPc;
+
+ // String describing this MediaPipeline for logging purposes. Only safe to
+ // access from STS thread.
+ std::string mDescription;
+
+ // Written in c'tor, all following accesses are on the STS thread.
+ UniquePtr<MediaPipelineFilter> mFilter;
+ const UniquePtr<webrtc::RtpHeaderExtensionMap> mRtpHeaderExtensionMap;
+
+ RefPtr<PacketDumper> mPacketDumper;
+
+ MediaEventProducerExc<webrtc::RtpPacketReceived, webrtc::RTPHeader>
+ mRtpReceiveEvent;
+ MediaEventProducerExc<MediaPacket> mSenderRtcpReceiveEvent;
+ MediaEventProducerExc<MediaPacket> mReceiverRtcpReceiveEvent;
+
+ MediaEventListener mRtpSendEventListener;
+ MediaEventListener mSenderRtcpSendEventListener;
+ MediaEventListener mReceiverRtcpSendEventListener;
+
+ private:
+ bool IsRtp(const unsigned char* aData, size_t aLen) const;
+ // Must be called on the STS thread. Must be called after Shutdown().
+ void DetachTransport_s();
+};
+
+// A specialization of pipeline for reading from an input device
+// and transmitting to the network.
+class MediaPipelineTransmit
+ : public MediaPipeline,
+ public dom::PrincipalChangeObserver<dom::MediaStreamTrack> {
+ public:
+ // Set aRtcpTransport to nullptr to use rtcp-mux
+ MediaPipelineTransmit(const std::string& aPc,
+ RefPtr<MediaTransportHandler> aTransportHandler,
+ RefPtr<AbstractThread> aCallThread,
+ RefPtr<nsISerialEventTarget> aStsThread, bool aIsVideo,
+ RefPtr<MediaSessionConduit> aConduit);
+
+ void InitControl(MediaPipelineTransmitControlInterface* aControl);
+
+ void Shutdown() override;
+
+ bool Transmitting() const;
+
+ // written and used from MainThread
+ bool IsVideo() const override;
+
+ // When the principal of the domtrack changes, it calls through to here
+ // so that we can determine whether to enable track transmission.
+ // In cases where the peer isn't yet identified, we disable the pipeline (not
+ // the stream, that would potentially affect others), so that it sends
+ // black/silence. Once the peer is identified, re-enable those streams.
+ virtual void UpdateSinkIdentity(nsIPrincipal* aPrincipal,
+ const PeerIdentity* aSinkIdentity);
+
+ // for monitoring changes in track ownership
+ void PrincipalChanged(dom::MediaStreamTrack* aTrack) override;
+
+ // Override MediaPipeline::TransportReady_s.
+ void TransportReady_s() override;
+
+ // Replace a track with a different one.
+ nsresult SetTrack(const RefPtr<dom::MediaStreamTrack>& aDomTrack);
+
+ // Used to correlate stats
+ RefPtr<dom::MediaStreamTrack> GetTrack() const;
+
+ // For test use only. This allows a send track to be set without a
+ // corresponding dom track.
+ void SetSendTrackOverride(const RefPtr<ProcessedMediaTrack>& aSendTrack);
+
+ // Separate classes to allow ref counting
+ class PipelineListener;
+ class VideoFrameFeeder;
+
+ protected:
+ ~MediaPipelineTransmit();
+
+ // Updates mDescription (async) with information about the track we are
+ // transmitting.
+ std::string GenerateDescription() const;
+
+ // Sets up mSendPort and mSendTrack to feed mConduit if we are transmitting
+ // and have a dom track but no send track. Main thread only.
+ void UpdateSendState();
+
+ private:
+ WatchManager<MediaPipelineTransmit> mWatchManager;
+ const bool mIsVideo;
+ const RefPtr<PipelineListener> mListener;
+ RefPtr<AudioProxyThread> mAudioProcessing;
+ RefPtr<VideoFrameConverter> mConverter;
+ MediaEventListener mFrameListener;
+ Watchable<RefPtr<dom::MediaStreamTrack>> mDomTrack;
+ // Input port connecting mDomTrack's MediaTrack to mSendTrack.
+ RefPtr<MediaInputPort> mSendPort;
+ // The source track of the mSendTrack. Main thread only.
+ RefPtr<ProcessedMediaTrack> mSendPortSource;
+ // True if a parameter affecting mDescription has changed. To avoid updating
+ // the description unnecessarily. Main thread only.
+ bool mDescriptionInvalidated = true;
+ // Set true once we trigger the async removal of mSendTrack. Set false once
+ // the async removal is done. Main thread only.
+ bool mUnsettingSendTrack = false;
+ // MediaTrack that we send over the network. This allows changing mDomTrack.
+ // Because changing mSendTrack is async and can be racy (when changing from a
+ // track in one graph to a track in another graph), it is set very strictly.
+ // If mSendTrack is null it can be set by UpdateSendState().
+ // If it is non-null it can only be set to null, and only by the
+ // RemoveListener MozPromise handler, as seen in UpdateSendState.
+ RefPtr<ProcessedMediaTrack> mSendTrack;
+ // When this is set and we are active, this track will be used as mSendTrack.
+ // Allows unittests to insert a send track without requiring a dom track or a
+ // graph. Main thread only.
+ Watchable<RefPtr<ProcessedMediaTrack>> mSendTrackOverride;
+ // True when mSendTrack is set, not destroyed and mActive is true. mListener
+ // is attached to mSendTrack when this is true. Main thread only.
+ bool mTransmitting = false;
+};
+
+// A specialization of pipeline for reading from the network and
+// rendering media.
+class MediaPipelineReceive : public MediaPipeline {
+ public:
+ // Set aRtcpTransport to nullptr to use rtcp-mux
+ MediaPipelineReceive(const std::string& aPc,
+ RefPtr<MediaTransportHandler> aTransportHandler,
+ RefPtr<AbstractThread> aCallThread,
+ RefPtr<nsISerialEventTarget> aStsThread,
+ RefPtr<MediaSessionConduit> aConduit);
+
+ void InitControl(MediaPipelineReceiveControlInterface* aControl);
+
+ // Called when ALPN is negotiated and is requesting privacy, so receive
+ // pipelines do not enter data into the graph under a content principal.
+ virtual void OnPrivacyRequested_s() = 0;
+
+ // Called after privacy has been requested, with the updated private
+ // principal.
+ virtual void SetPrivatePrincipal(PrincipalHandle aHandle) = 0;
+
+ void Shutdown() override;
+
+ protected:
+ ~MediaPipelineReceive();
+
+ virtual void UpdateListener() = 0;
+
+ private:
+ WatchManager<MediaPipelineReceive> mWatchManager;
+};
+
+// A specialization of pipeline for reading from the network and
+// rendering audio.
+class MediaPipelineReceiveAudio : public MediaPipelineReceive {
+ public:
+ MediaPipelineReceiveAudio(const std::string& aPc,
+ RefPtr<MediaTransportHandler> aTransportHandler,
+ RefPtr<AbstractThread> aCallThread,
+ RefPtr<nsISerialEventTarget> aStsThread,
+ RefPtr<AudioSessionConduit> aConduit,
+ RefPtr<SourceMediaTrack> aSource,
+ TrackingId aTrackingId,
+ PrincipalHandle aPrincipalHandle,
+ PrincipalPrivacy aPrivacy);
+
+ void Shutdown() override;
+
+ bool IsVideo() const override { return false; }
+
+ void OnPrivacyRequested_s() override;
+ void SetPrivatePrincipal(PrincipalHandle aHandle) override;
+
+ private:
+ void UpdateListener() override;
+
+ // Separate class to allow ref counting
+ class PipelineListener;
+
+ const RefPtr<PipelineListener> mListener;
+};
+
+// A specialization of pipeline for reading from the network and
+// rendering video.
+class MediaPipelineReceiveVideo : public MediaPipelineReceive {
+ public:
+ MediaPipelineReceiveVideo(const std::string& aPc,
+ RefPtr<MediaTransportHandler> aTransportHandler,
+ RefPtr<AbstractThread> aCallThread,
+ RefPtr<nsISerialEventTarget> aStsThread,
+ RefPtr<VideoSessionConduit> aConduit,
+ RefPtr<SourceMediaTrack> aSource,
+ TrackingId aTrackingId,
+ PrincipalHandle aPrincipalHandle,
+ PrincipalPrivacy aPrivacy);
+
+ void Shutdown() override;
+
+ bool IsVideo() const override { return true; }
+
+ void OnPrivacyRequested_s() override;
+ void SetPrivatePrincipal(PrincipalHandle aHandle) override;
+
+ private:
+ void UpdateListener() override;
+
+ class PipelineRenderer;
+ friend class PipelineRenderer;
+
+ // Separate class to allow ref counting
+ class PipelineListener;
+
+ const RefPtr<PipelineRenderer> mRenderer;
+ const RefPtr<PipelineListener> mListener;
+};
+
+} // namespace mozilla
+#endif
diff --git a/dom/media/webrtc/transportbridge/MediaPipelineFilter.cpp b/dom/media/webrtc/transportbridge/MediaPipelineFilter.cpp
new file mode 100644
index 0000000000..1acb73e9f2
--- /dev/null
+++ b/dom/media/webrtc/transportbridge/MediaPipelineFilter.cpp
@@ -0,0 +1,153 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: softtabstop=2:shiftwidth=2:expandtab
+ * */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: bcampen@mozilla.com
+
+#include "MediaPipelineFilter.h"
+
+#include "api/rtp_headers.h"
+#include "api/rtp_parameters.h"
+#include "mozilla/Logging.h"
+
+// defined in MediaPipeline.cpp
+extern mozilla::LazyLogModule gMediaPipelineLog;
+
+#define DEBUG_LOG(x) MOZ_LOG(gMediaPipelineLog, LogLevel::Debug, x)
+
+namespace mozilla {
+MediaPipelineFilter::MediaPipelineFilter(
+ const std::vector<webrtc::RtpExtension>& aExtMap)
+ : mExtMap(aExtMap) {}
+
+void MediaPipelineFilter::SetRemoteMediaStreamId(
+ const Maybe<std::string>& aMid) {
+ if (aMid != mRemoteMid) {
+ DEBUG_LOG(("MediaPipelineFilter added new remote RTP MID: '%s'.",
+ aMid.valueOr("").c_str()));
+ mRemoteMid = aMid;
+ mRemoteMidBindings.clear();
+ }
+}
+
+bool MediaPipelineFilter::Filter(const webrtc::RTPHeader& header) {
+ DEBUG_LOG(("MediaPipelineFilter inspecting seq# %u SSRC: %u",
+ header.sequenceNumber, header.ssrc));
+
+ auto fromStreamId = [](const std::string& aId) {
+ return Maybe<std::string>(aId.empty() ? Nothing() : Some(aId));
+ };
+
+ //
+ // MID Based Filtering
+ //
+
+ const auto mid = fromStreamId(header.extension.mid);
+
+ // Check to see if a bound SSRC is moved to a new MID
+ if (mRemoteMidBindings.count(header.ssrc) == 1 && mid && mRemoteMid != mid) {
+ mRemoteMidBindings.erase(header.ssrc);
+ }
+ // Bind an SSRC if a matching MID is found
+ if (mid && mRemoteMid == mid) {
+ DEBUG_LOG(("MediaPipelineFilter learned SSRC: %u for MID: '%s'",
+ header.ssrc, mRemoteMid.value().c_str()));
+ mRemoteMidBindings.insert(header.ssrc);
+ }
+ // Check for matching MID
+ if (!mRemoteMidBindings.empty()) {
+ MOZ_ASSERT(mRemoteMid != Nothing());
+ if (mRemoteMidBindings.count(header.ssrc) == 1) {
+ DEBUG_LOG(
+ ("MediaPipelineFilter SSRC: %u matched for MID: '%s'."
+ " passing packet",
+ header.ssrc, mRemoteMid.value().c_str()));
+ return true;
+ }
+ DEBUG_LOG(
+ ("MediaPipelineFilter SSRC: %u did not match bound SSRC(s) for"
+ " MID: '%s'. ignoring packet",
+ header.ssrc, mRemoteMid.value().c_str()));
+ for (const uint32_t ssrc : mRemoteMidBindings) {
+ DEBUG_LOG(("MID %s is associated with SSRC: %u",
+ mRemoteMid.value().c_str(), ssrc));
+ }
+ return false;
+ }
+
+ //
+ // RTP-STREAM-ID based filtering (for tests only)
+ //
+
+ //
+ // Remote SSRC based filtering
+ //
+
+ if (remote_ssrc_set_.count(header.ssrc)) {
+ DEBUG_LOG(
+ ("MediaPipelineFilter SSRC: %u matched remote SSRC set."
+ " passing packet",
+ header.ssrc));
+ return true;
+ }
+ DEBUG_LOG(
+ ("MediaPipelineFilter SSRC: %u did not match any of %zu"
+ " remote SSRCS.",
+ header.ssrc, remote_ssrc_set_.size()));
+
+ //
+ // PT, payload type, last ditch effort filtering
+ //
+
+ if (payload_type_set_.count(header.payloadType)) {
+ DEBUG_LOG(
+ ("MediaPipelineFilter payload-type: %u matched %zu"
+ " unique payload type. learning ssrc. passing packet",
+ header.ssrc, remote_ssrc_set_.size()));
+ // Actual match. We need to update the ssrc map so we can route rtcp
+ // sender reports correctly (these use a different payload-type field)
+ AddRemoteSSRC(header.ssrc);
+ return true;
+ }
+ DEBUG_LOG(
+ ("MediaPipelineFilter payload-type: %u did not match any of %zu"
+ " unique payload-types.",
+ header.payloadType, payload_type_set_.size()));
+ DEBUG_LOG(
+ ("MediaPipelineFilter packet failed to match any criteria."
+ " ignoring packet"));
+ return false;
+}
+
+void MediaPipelineFilter::AddRemoteSSRC(uint32_t ssrc) {
+ remote_ssrc_set_.insert(ssrc);
+}
+
+void MediaPipelineFilter::AddUniquePT(uint8_t payload_type) {
+ payload_type_set_.insert(payload_type);
+}
+
+void MediaPipelineFilter::Update(const MediaPipelineFilter& filter_update) {
+ // We will not stomp the remote_ssrc_set_ if the update has no ssrcs,
+ // because we don't want to unlearn any remote ssrcs unless the other end
+ // has explicitly given us a new set.
+ if (!filter_update.remote_ssrc_set_.empty()) {
+ remote_ssrc_set_ = filter_update.remote_ssrc_set_;
+ }
+ // We don't want to overwrite the learned binding unless we have changed MIDs
+ // or the update contains a MID binding.
+ if (!filter_update.mRemoteMidBindings.empty() ||
+ (filter_update.mRemoteMid && filter_update.mRemoteMid != mRemoteMid)) {
+ mRemoteMid = filter_update.mRemoteMid;
+ mRemoteMidBindings = filter_update.mRemoteMidBindings;
+ }
+ payload_type_set_ = filter_update.payload_type_set_;
+
+ // Use extmapping from new filter
+ mExtMap = filter_update.mExtMap;
+}
+
+} // end namespace mozilla
diff --git a/dom/media/webrtc/transportbridge/MediaPipelineFilter.h b/dom/media/webrtc/transportbridge/MediaPipelineFilter.h
new file mode 100644
index 0000000000..9b40bceda8
--- /dev/null
+++ b/dom/media/webrtc/transportbridge/MediaPipelineFilter.h
@@ -0,0 +1,89 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: softtabstop=2:shiftwidth=2:expandtab
+ * */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: bcampen@mozilla.com
+
+#ifndef mediapipelinefilter_h__
+#define mediapipelinefilter_h__
+
+#include <cstddef>
+#include <stdint.h>
+#include <string>
+
+#include <set>
+#include <vector>
+
+#include "mozilla/Maybe.h"
+
+namespace webrtc {
+struct RTPHeader;
+struct RtpExtension;
+} // namespace webrtc
+
+namespace mozilla {
+
+// TODO @@NG update documentation after initial review
+
+// A class that handles the work of filtering RTP packets that arrive at a
+// MediaPipeline. This is primarily important for the use of BUNDLE (ie;
+// multiple m-lines share the same RTP stream). There are three ways that this
+// can work;
+//
+// 1) In our SDP, we include a media-level extmap parameter with a unique
+// integer of our choosing, with the hope that the other side will include
+// this value in a header in the first few RTP packets it sends us. This
+// allows us to perform correlation in cases where the other side has not
+// informed us of the ssrcs it will be sending (either because it did not
+// include them in its SDP, or their SDP has not arrived yet)
+// and also gives us the opportunity to learn SSRCs from packets so adorned.
+//
+// 2) If the remote endpoint includes SSRC media-level attributes in its SDP,
+// we can simply use this information to populate the filter. The only
+// shortcoming here is when RTP packets arrive before the answer does. See
+// above.
+//
+// 3) As a fallback, we can try to use payload type IDs to perform correlation,
+// but only when the type id is unique to this media section.
+// This too allows us to learn about SSRCs (mostly useful for filtering
+// sender reports later).
+class MediaPipelineFilter {
+ public:
+ MediaPipelineFilter() = default;
+ explicit MediaPipelineFilter(
+ const std::vector<webrtc::RtpExtension>& aExtMap);
+
+ // Checks whether this packet passes the filter, possibly updating the filter
+ // in the process (if the MID or payload types are used, they can teach
+ // the filter about ssrcs)
+ bool Filter(const webrtc::RTPHeader& header);
+
+ void AddRemoteSSRC(uint32_t ssrc);
+
+ void SetRemoteMediaStreamId(const Maybe<std::string>& aMid);
+
+ // When a payload type id is unique to our media section, add it here.
+ void AddUniquePT(uint8_t payload_type);
+
+ void Update(const MediaPipelineFilter& filter_update);
+
+ std::vector<webrtc::RtpExtension> GetExtmap() const { return mExtMap; }
+
+ private:
+ // The number of filters we manage here is quite small, so I am optimizing
+ // for readability.
+ std::set<uint32_t> remote_ssrc_set_;
+ std::set<uint8_t> payload_type_set_;
+ Maybe<std::string> mRemoteMid;
+ std::set<uint32_t> mRemoteMidBindings;
+ // RID extension can be set by tests and is sticky, the rest of
+ // the mapping is not.
+ std::vector<webrtc::RtpExtension> mExtMap;
+};
+
+} // end namespace mozilla
+
+#endif // mediapipelinefilter_h__
diff --git a/dom/media/webrtc/transportbridge/RtpLogger.cpp b/dom/media/webrtc/transportbridge/RtpLogger.cpp
new file mode 100644
index 0000000000..aac1fad197
--- /dev/null
+++ b/dom/media/webrtc/transportbridge/RtpLogger.cpp
@@ -0,0 +1,67 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: nohlmeier@mozilla.com
+
+#include "RtpLogger.h"
+#include "mozilla/Logging.h"
+
+#include <ctime>
+#include <iomanip>
+#include <sstream>
+#ifdef _WIN32
+# include <time.h>
+# include <sys/timeb.h>
+#else
+# include <sys/time.h>
+#endif
+
+// Logging context
+using namespace mozilla;
+
+mozilla::LazyLogModule gRtpLoggerLog("RtpLogger");
+
+namespace mozilla {
+
+bool RtpLogger::IsPacketLoggingOn() {
+ return MOZ_LOG_TEST(gRtpLoggerLog, LogLevel::Debug);
+}
+
+void RtpLogger::LogPacket(const MediaPacket& packet, bool input,
+ std::string desc) {
+ if (MOZ_LOG_TEST(gRtpLoggerLog, LogLevel::Debug)) {
+ bool isRtp = (packet.type() == MediaPacket::RTP);
+ std::stringstream ss;
+ /* This creates text2pcap compatible format, e.g.:
+ * RTCP_PACKET O 10:36:26.864934 000000 80 c8 00 06 6d ...
+ */
+ ss << (input ? "I " : "O ");
+ std::time_t t = std::time(nullptr);
+ std::tm tm = *std::localtime(&t);
+ char buf[9];
+ if (0 < strftime(buf, sizeof(buf), "%H:%M:%S", &tm)) {
+ ss << buf;
+ }
+ ss << std::setfill('0');
+#ifdef _WIN32
+ struct timeb tb;
+ ftime(&tb);
+ ss << "." << (tb.millitm) << " ";
+#else
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+ ss << "." << (tv.tv_usec) << " ";
+#endif
+ ss << " 000000";
+ ss << std::hex << std::setfill('0');
+ for (size_t i = 0; i < packet.len(); ++i) {
+ ss << " " << std::setw(2) << (int)packet.data()[i];
+ }
+ MOZ_LOG(gRtpLoggerLog, LogLevel::Debug,
+ ("%s%s%s", desc.c_str(), (isRtp ? " RTP_PACKET " : " RTCP_PACKET "),
+ ss.str().c_str()));
+ }
+}
+
+} // namespace mozilla
diff --git a/dom/media/webrtc/transportbridge/RtpLogger.h b/dom/media/webrtc/transportbridge/RtpLogger.h
new file mode 100644
index 0000000000..fcfaede6e2
--- /dev/null
+++ b/dom/media/webrtc/transportbridge/RtpLogger.h
@@ -0,0 +1,28 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Original author: nohlmeier@mozilla.com
+
+#ifndef rtplogger_h__
+#define rtplogger_h__
+
+#include "transport/mediapacket.h"
+
+namespace mozilla {
+
+/* This class logs RTP and RTCP packets in hex in a format compatible to
+ * text2pcap.
+ * Example to convert the MOZ log file into a PCAP file:
+ * egrep '(RTP_PACKET|RTCP_PACKET)' moz.log | \
+ * text2pcap -D -n -l 1 -i 17 -u 1234,1235 -t '%H:%M:%S.' - rtp.pcap
+ */
+class RtpLogger {
+ public:
+ static bool IsPacketLoggingOn();
+ static void LogPacket(const MediaPacket& packet, bool input,
+ std::string desc);
+};
+
+} // namespace mozilla
+#endif
diff --git a/dom/media/webrtc/transportbridge/moz.build b/dom/media/webrtc/transportbridge/moz.build
new file mode 100644
index 0000000000..290dd26a0a
--- /dev/null
+++ b/dom/media/webrtc/transportbridge/moz.build
@@ -0,0 +1,27 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+include("/dom/media/webrtc/third_party_build/webrtc.mozbuild")
+
+LOCAL_INCLUDES += [
+ "!/ipc/ipdl/_ipdlheaders",
+ "/dom/media",
+ "/dom/media/webrtc",
+ "/ipc/chromium/src",
+ "/media/libyuv/libyuv/include",
+ "/media/webrtc",
+ "/third_party/libsrtp/src/crypto/include",
+ "/third_party/libsrtp/src/include",
+ "/third_party/libwebrtc",
+ "/third_party/libwebrtc/third_party/abseil-cpp",
+]
+
+UNIFIED_SOURCES += [
+ "MediaPipeline.cpp",
+ "MediaPipelineFilter.cpp",
+ "RtpLogger.cpp",
+]
+
+FINAL_LIBRARY = "xul"