tor-browser

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

h265-level-id.https.html (7297B)


      1 <!doctype html>
      2 <meta charset=utf-8>
      3 <title>RTX codec integrity checks</title>
      4 <script src="/resources/testharness.js"></script>
      5 <script src="/resources/testharnessreport.js"></script>
      6 <script src="../RTCPeerConnection-helper.js"></script>
      7 <script>
      8 'use strict';
      9 
     10 const kProfileIdKey = 'profile-id';
     11 const kLevelIdKey = 'level-id';
     12 
     13 // The level-id value for Level X.Y is calculated as (X * 10 + Y) * 3.
     14 // The lowest Level, 1.0, is thus (1 * 10 + 0) * 3 = 30.
     15 const kH265Level1dot0 = '30';
     16 
     17 function parseFmtpMap(sdpFmtpLine) {
     18  const map = new Map();
     19  // For each entry (semi-colon separated key=value).
     20  for (let i = 0; i < sdpFmtpLine.length; ++i) {
     21    let entryEnd = sdpFmtpLine.indexOf(';', i);
     22    if (entryEnd == -1) {
     23      entryEnd = sdpFmtpLine.length;
     24    }
     25    const entryStr = sdpFmtpLine.substring(i, entryEnd);
     26    const keyValue = entryStr.split('=');
     27    if (keyValue.length != 2) {
     28      throw 'Failed to parse sdpFmtpLine';
     29    }
     30    map.set(keyValue[0], keyValue[1]);
     31    i = entryEnd;
     32  }
     33  return map;
     34 }
     35 
     36 function findCodecWithProfileId(codecs, mimeType, profileId) {
     37  return codecs.find(codec => {
     38    if (codec.mimeType != mimeType) {
     39      return false;
     40    }
     41    return parseFmtpMap(codec.sdpFmtpLine).get(kProfileIdKey) == profileId;
     42  });
     43 }
     44 
     45 // Returns `[h265SendCodec, h265RecvCodec]` or aborts the calling test with
     46 // [PRECONDITION_FAILED].
     47 function getH265CodecsOrFailPrecondition() {
     48  const h265SendCodec = RTCRtpSender.getCapabilities('video').codecs.find(
     49      c => c.mimeType == 'video/H265');
     50  assert_implements_optional(
     51      h265SendCodec !== undefined,
     52      `H265 is not available for sending.`);
     53 
     54  const h265SendCodecFmtpMap = parseFmtpMap(h265SendCodec.sdpFmtpLine);
     55  const profileId = h265SendCodecFmtpMap.get(kProfileIdKey);
     56  assert_not_equals(profileId, undefined,
     57                    `profile-id is missing from sdpFmtpLine`);
     58 
     59  const h265RecvCodec = findCodecWithProfileId(
     60      RTCRtpReceiver.getCapabilities('video').codecs, 'video/H265', profileId);
     61  assert_implements_optional(
     62      h265RecvCodec !== undefined,
     63      `H265 profile-id=${profileId} is not available for receiving.`);
     64 
     65  return [h265SendCodec, h265RecvCodec];
     66 }
     67 
     68 function sdpModifyFmtpLevelId(sdp, newLevelId) {
     69  const lines = sdp.split('\r\n');
     70  for (let i = 0; i < lines.length; ++i) {
     71    if (!lines[i].startsWith('a=fmtp:')) {
     72      continue;
     73    }
     74    const spaceIndex = lines[i].indexOf(' ');
     75    if (spaceIndex == -1) {
     76      continue;
     77    }
     78    const fmtpMap = parseFmtpMap(lines[i].substring(spaceIndex + 1));
     79    if (!fmtpMap.has(kLevelIdKey)) {
     80      continue;
     81    }
     82    fmtpMap.set(kLevelIdKey, newLevelId);
     83    const sdpFmtpLine =
     84        Array.from(fmtpMap, ([key,value]) => `${key}=${value}`).join(';');
     85    lines[i] = lines[i].substring(0, spaceIndex) + ' ' + sdpFmtpLine;
     86  }
     87  return lines.join('\r\n');
     88 }
     89 
     90 promise_test(async t => {
     91  const [h265SendCodec, h265RecvCodec] = getH265CodecsOrFailPrecondition();
     92 
     93  const pc1 = new RTCPeerConnection();
     94  t.add_cleanup(() => pc1.close());
     95  const pc2 = new RTCPeerConnection();
     96  t.add_cleanup(() => pc2.close());
     97 
     98  const pc1Transceiver = pc1.addTransceiver('video', {direction: 'sendonly'});
     99  pc1Transceiver.setCodecPreferences([h265SendCodec]);
    100 
    101  await pc1.setLocalDescription();
    102  await pc2.setRemoteDescription(pc1.localDescription);
    103  await pc2.setLocalDescription();
    104  // Modify SDP to tell `pc1` that `pc2` can only receive level-id=30.
    105  await pc1.setRemoteDescription({
    106    type: 'answer',
    107    sdp: sdpModifyFmtpLevelId(pc2.localDescription.sdp, kH265Level1dot0)
    108  });
    109 
    110  // Confirm level-id=30 was negotiated regardless of sender capabilities.
    111  const sender = pc1Transceiver.sender;
    112  const params = sender.getParameters();
    113  assert_equals(params.codecs.length, 1);
    114  const negotiatedFmtpMap = parseFmtpMap(params.codecs[0].sdpFmtpLine);
    115  assert_equals(negotiatedFmtpMap.get(kLevelIdKey), kH265Level1dot0);
    116 }, `Offer to send H265, answer to receive level-id=30 results in level-id=30`);
    117 
    118 promise_test(async t => {
    119  const [h265SendCodec, h265RecvCodec] = getH265CodecsOrFailPrecondition();
    120 
    121  const pc1 = new RTCPeerConnection();
    122  t.add_cleanup(() => pc1.close());
    123  const pc2 = new RTCPeerConnection();
    124  t.add_cleanup(() => pc2.close());
    125 
    126  const pc1Transceiver = pc1.addTransceiver('video', {direction: 'recvonly'});
    127  pc1Transceiver.setCodecPreferences([h265RecvCodec]);
    128 
    129  await pc1.setLocalDescription();
    130  // Modify SDP to tell `pc2` that `pc1` can only receive level-id=30.
    131  await pc2.setRemoteDescription({
    132    type: 'offer',
    133    sdp: sdpModifyFmtpLevelId(pc1.localDescription.sdp, kH265Level1dot0)
    134  });
    135  const [pc2Transceiver] = pc2.getTransceivers();
    136  pc2Transceiver.direction = 'sendonly';
    137  await pc2.setLocalDescription();
    138  await pc1.setRemoteDescription(pc2.localDescription);
    139 
    140  // Confirm level-id=30 was negotiated regardless of sender capabilities.
    141  const sender = pc2Transceiver.sender;
    142  const params = sender.getParameters();
    143  assert_equals(params.codecs.length, 1);
    144  const negotiatedFmtpMap = parseFmtpMap(params.codecs[0].sdpFmtpLine);
    145  assert_equals(negotiatedFmtpMap.get(kLevelIdKey), kH265Level1dot0);
    146  // Setting a codec that was negotiated should always work, regardless of the
    147  // level-id in sender capabilities.
    148  params.encodings[0].codec = params.codecs[0];
    149  await sender.setParameters(params);
    150  assert_equals(sender.getParameters().encodings[0].codec.mimeType,
    151                params.codecs[0].mimeType);
    152  assert_equals(sender.getParameters().encodings[0].codec.sdpFmtpLine,
    153                params.codecs[0].sdpFmtpLine);
    154 }, `Offer to receive level-id=30 and set codec from getParameters`);
    155 
    156 promise_test(async t => {
    157  const [h265SendCodec, h265RecvCodec] = getH265CodecsOrFailPrecondition();
    158 
    159  const pc1 = new RTCPeerConnection();
    160  t.add_cleanup(() => pc1.close());
    161  const pc2 = new RTCPeerConnection();
    162  t.add_cleanup(() => pc2.close());
    163 
    164  const pc1Transceiver = pc1.addTransceiver('video', {direction: 'recvonly'});
    165  pc1Transceiver.setCodecPreferences([h265RecvCodec]);
    166 
    167  await pc1.setLocalDescription();
    168  // Modify SDP to tell `pc2` that `pc1` can only receive level-id=30.
    169  await pc2.setRemoteDescription({
    170    type: 'offer',
    171    sdp: sdpModifyFmtpLevelId(pc1.localDescription.sdp, kH265Level1dot0)
    172  });
    173  const [pc2Transceiver] = pc2.getTransceivers();
    174  pc2Transceiver.direction = 'sendonly';
    175  await pc2.setLocalDescription();
    176  await pc1.setRemoteDescription(pc2.localDescription);
    177 
    178  // Confirm level-id=30 was negotiated regardless of sender capabilities.
    179  const sender = pc2Transceiver.sender;
    180  const params = sender.getParameters();
    181  assert_equals(params.codecs.length, 1);
    182  const negotiatedFmtpMap = parseFmtpMap(params.codecs[0].sdpFmtpLine);
    183  assert_equals(negotiatedFmtpMap.get(kLevelIdKey), kH265Level1dot0);
    184  // Setting a codec from getCapabilities should work, even if a lower level-id
    185  // was negotiated.
    186  params.encodings[0].codec = h265SendCodec;
    187  await sender.setParameters(params);
    188  assert_equals(sender.getParameters().encodings[0].codec.mimeType,
    189                h265SendCodec.mimeType);
    190  assert_equals(sender.getParameters().encodings[0].codec.sdpFmtpLine,
    191                h265SendCodec.sdpFmtpLine);
    192 }, `Offer to receive level-id=30 and set codec from getCapabilities`);
    193 </script>