tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

RTCRtpCorruptionDetection-headerExtensionControl.html (6777B)


      1 <!doctype html>
      2 <meta charset=utf-8>
      3 <title>Coruption Detection Header Extension</title>
      4 <script src=/resources/testharness.js></script>
      5 <script src=/resources/testharnessreport.js></script>
      6 <script src="../webrtc/RTCPeerConnection-helper.js"></script>
      7 <script>
      8 'use strict';
      9 
     10 // If the `corruption-detection` header does not exists among the header
     11 // extensions, it does not do anything.
     12 function enableCorruptionDetectionIfExists(transceiver) {
     13  const extensions = transceiver.getHeaderExtensionsToNegotiate();
     14  for (let i = 0; i < extensions.length; ++i) {
     15    if (extensions[i].uri.includes('corruption-detection')) {
     16      extensions[i].direction = 'sendrecv';
     17    }
     18  }
     19  transceiver.setHeaderExtensionsToNegotiate(extensions);
     20 }
     21 
     22 // Adds corruption detection RTP header extension to both peers' video section.
     23 async function doSdpNegotiationWithCorruptionDetection(pc1, pc2) {
     24  // Create offer with corruption-detection.
     25  pc1.getTransceivers().forEach((transceiver) => {
     26    enableCorruptionDetectionIfExists(transceiver);
     27  });
     28  await pc1.setLocalDescription();
     29  await pc2.setRemoteDescription(pc1.localDescription);
     30 
     31  // Create answer with corruption-detection.
     32  pc2.getTransceivers().forEach((transceiver) => {
     33    enableCorruptionDetectionIfExists(transceiver);
     34  });
     35  await pc2.setLocalDescription();
     36  await pc1.setRemoteDescription(pc2.localDescription);
     37 }
     38 
     39 // Returns the inbound stats based on the kind.
     40 // @param {string} [kind] - Either 'video' or 'audio'.
     41 async function getInboundRtpStats(t, pc, kind) {
     42  while (true) {
     43    const stats = await pc.getStats();
     44    const values = [...stats.values()];
     45    const inboundRtp = values.find(s => s.type == 'inbound-rtp' && s.kind == kind);
     46    // If video is transmitted, expect the corruption metrics to be populated.
     47    if (inboundRtp && kind == 'video' &&
     48        (inboundRtp.corruptionMeasurements ??0 > 0)) {
     49      return inboundRtp;
     50    }
     51 
     52    // If video is not transmitted, expect anything in the stream to be populated,
     53    // to indicated that something is flowing in the pipeline.
     54    if (inboundRtp && kind == 'audio' &&
     55        (inboundRtp.audioLevel ??0 > 0)) {
     56      return inboundRtp;
     57    }
     58 
     59    await new Promise(r => t.step_timeout(r, 1000));
     60  }
     61 }
     62 
     63 async function createAudioVideoTracksWithCleanUp(t) {
     64  const stream = await getNoiseStream({video: true, audio: true});
     65  const audioTrack = stream.getAudioTracks()[0];
     66  const videoTrack = stream.getVideoTracks()[0];
     67  t.add_cleanup(() => audioTrack.stop());
     68  t.add_cleanup(() => videoTrack.stop());
     69  return [audioTrack, videoTrack, stream];
     70 }
     71 
     72 promise_test(async t => {
     73  const pc1 = createPeerConnectionWithCleanup(t);
     74  const pc2 = createPeerConnectionWithCleanup(t);
     75 
     76  pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate);
     77  pc2.onicecandidate = e => pc1.addIceCandidate(e.candidate);
     78 
     79  // Only add a video track to pc1.
     80  const [audioTrack, videoTrack, stream] =
     81    await createAudioVideoTracksWithCleanUp(t);
     82  pc1.addTrack(videoTrack, stream);
     83 
     84  doSdpNegotiationWithCorruptionDetection(pc1, pc2);
     85 
     86  // Corruption score is calculated on receive side (`pc2`).
     87  const inboundRtp = await getInboundRtpStats(t, pc2, 'video');
     88  assert_not_equals(inboundRtp.totalCorruptionProbability, undefined);
     89  assert_not_equals(inboundRtp.totalSquaredCorruptionProbability, undefined);
     90  assert_not_equals(inboundRtp.corruptionMeasurements, undefined);
     91 }, 'If the corruption-detection header extension is present in the RTP packets,' +
     92   'corruption metrics must be present.');
     93 
     94 promise_test(async t => {
     95  const pc1 = createPeerConnectionWithCleanup(t);
     96  const pc2 = createPeerConnectionWithCleanup(t);
     97 
     98  pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate);
     99  pc2.onicecandidate = e => pc1.addIceCandidate(e.candidate);
    100 
    101  // Add audio and video tracks to both pc1 and pc2.
    102  const [audioTrack, videoTrack, stream] =
    103    await createAudioVideoTracksWithCleanUp(t);
    104  pc1.addTrack(audioTrack, stream);
    105  pc1.addTrack(videoTrack, stream);
    106  pc2.addTrack(audioTrack, stream);
    107  pc2.addTrack(videoTrack, stream);
    108 
    109  doSdpNegotiationWithCorruptionDetection(pc1, pc2);
    110 
    111  function checkInboundRtpStats(inboundRtp) {
    112    assert_not_equals(inboundRtp.totalCorruptionProbability, undefined);
    113    assert_not_equals(inboundRtp.totalSquaredCorruptionProbability, undefined);
    114    assert_not_equals(inboundRtp.corruptionMeasurements, undefined);
    115  }
    116 
    117  const inboundRtpPc1 = await getInboundRtpStats(t, pc1, 'video');
    118  const inboundRtpPc2 = await getInboundRtpStats(t, pc2, 'video');
    119  checkInboundRtpStats(inboundRtpPc1);
    120  checkInboundRtpStats(inboundRtpPc2);
    121 }, 'If the corruption-detection header extension is present in the RTP packets,' +
    122   'corruption metrics must be present, both ways.');
    123 
    124 promise_test(async t => {
    125  const pc1 = createPeerConnectionWithCleanup(t);
    126  const pc2 = createPeerConnectionWithCleanup(t);
    127 
    128  pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate);
    129  pc2.onicecandidate = e => pc1.addIceCandidate(e.candidate);
    130 
    131  // Only add a video track to pc1.
    132  const [audioTrack, videoTrack, stream] =
    133    await createAudioVideoTracksWithCleanUp(t);
    134  pc1.addTrack(videoTrack, stream);
    135 
    136  doSdpNegotiationWithCorruptionDetection(pc1, pc2);
    137 
    138  const inboundRtp = await getInboundRtpStats(t, pc2, 'video');
    139 
    140  // This check does not guarantee that each measurement is in the range [0, 1].
    141  // But it is the best we can do.
    142  const mean = inboundRtp.totalCorruptionProbability / inboundRtp.corruptionMeasurements;
    143  assert_less_than_equal(mean, 1);
    144  assert_greater_than_equal(mean, 0);
    145 }, 'Each measurement added to totalCorruptionProbability MUST be in the range [0.0, 1.0].');
    146 
    147 promise_test(async t => {
    148  const pc1 = createPeerConnectionWithCleanup(t);
    149  const pc2 = createPeerConnectionWithCleanup(t);
    150 
    151  pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate);
    152  pc2.onicecandidate = e => pc1.addIceCandidate(e.candidate);
    153 
    154  // Only add an audio track to pc1.
    155  const [audioTrack, videoTrack, stream] =
    156    await createAudioVideoTracksWithCleanUp(t);
    157  pc1.addTrack(audioTrack, stream);
    158 
    159  // Some browsers need an audio element attached to the DOM.
    160  pc2.ontrack = (e) => {
    161    const element = document.createElement('audio');
    162    element.autoplay = true;
    163    element.srcObject = e.streams[0];
    164    document.body.appendChild(element);
    165    t.add_cleanup(() => { document.body.removeChild(element) });
    166  };
    167 
    168  doSdpNegotiationWithCorruptionDetection(pc1, pc2);
    169 
    170  const inboundRtp = await getInboundRtpStats (t, pc2, 'audio');
    171  assert_equals(inboundRtp.totalCorruptionProbability, undefined);
    172  assert_equals(inboundRtp.totalSquaredCorruptionProbability, undefined);
    173  assert_equals(inboundRtp.corruptionMeasurements, undefined);
    174 }, 'Corruption metrics must not exists for audio.');
    175 
    176 </script>