tor-browser

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

RTCPeerConnection-restartIce.https.html (20243B)


      1 <!doctype html>
      2 <meta charset=utf-8>
      3 <title></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 function getLines(sdp, startsWith) {
     11  const lines = sdp.split("\r\n").filter(l => l.startsWith(startsWith));
     12  assert_true(lines.length > 0, `One or more ${startsWith} in sdp`);
     13  return lines;
     14 }
     15 
     16 const getUfrags = ({sdp}) => getLines(sdp, "a=ice-ufrag:");
     17 const getPwds = ({sdp}) => getLines(sdp, "a=ice-pwd:");
     18 
     19 const negotiators = [
     20  {
     21    tag: "",
     22    async setOffer(pc) {
     23      await pc.setLocalDescription(await pc.createOffer());
     24    },
     25    async setAnswer(pc) {
     26      await pc.setLocalDescription(await pc.createAnswer());
     27    },
     28  },
     29  {
     30    tag: " (perfect negotiation)",
     31    async setOffer(pc) {
     32      await pc.setLocalDescription();
     33    },
     34    async setAnswer(pc) {
     35      await pc.setLocalDescription();
     36    },
     37  },
     38 ];
     39 
     40 async function exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator) {
     41  await negotiator.setOffer(pc1);
     42  await pc2.setRemoteDescription(pc1.localDescription);
     43  await negotiator.setAnswer(pc2);
     44  await pc1.setRemoteDescription(pc2.localDescription); // End on pc1. No race
     45 }
     46 
     47 async function exchangeOfferAnswerEndOnSecond(pc1, pc2, negotiator) {
     48  await negotiator.setOffer(pc1);
     49  await pc2.setRemoteDescription(pc1.localDescription);
     50  await pc1.setRemoteDescription(await pc2.createAnswer());
     51  await pc2.setLocalDescription(pc1.remoteDescription); // End on pc2. No race
     52 }
     53 
     54 async function assertNoNegotiationNeeded(t, pc, state = "stable") {
     55  assert_equals(pc.signalingState, state, `In ${state} state`);
     56  const event = await Promise.race([
     57    new Promise(r => pc.onnegotiationneeded = r),
     58    new Promise(r => t.step_timeout(r, 10))
     59  ]);
     60  assert_equals(event, undefined, "No negotiationneeded event");
     61 }
     62 
     63 // In Chromium, assert_equals() produces test expectations with the values
     64 // compared. Because ufrags are different on each run, this would make Chromium
     65 // test expectations different on each run on tests that failed when comparing
     66 // ufrags. To work around this problem, assert_ufrags_equals() and
     67 // assert_ufrags_not_equals() should be preferred over assert_equals() and
     68 // assert_not_equals().
     69 function assert_ufrags_equals(x, y, description) {
     70  assert_true(x === y, description);
     71 }
     72 function assert_ufrags_not_equals(x, y, description) {
     73  assert_false(x === y, description);
     74 }
     75 
     76 promise_test(async t => {
     77  const pc = new RTCPeerConnection();
     78  pc.close();
     79  pc.restartIce();
     80  await assertNoNegotiationNeeded(t, pc, "closed");
     81 }, "restartIce() has no effect on a closed peer connection");
     82 
     83 promise_test(async t => {
     84  const pc1 = new RTCPeerConnection();
     85  const pc2 = new RTCPeerConnection();
     86  t.add_cleanup(() => pc1.close());
     87  t.add_cleanup(() => pc2.close());
     88 
     89  pc1.restartIce();
     90  await assertNoNegotiationNeeded(t, pc1);
     91  pc1.addTransceiver("audio");
     92  await new Promise(r => pc1.onnegotiationneeded = r);
     93  await assertNoNegotiationNeeded(t, pc1);
     94 }, "restartIce() does not trigger negotiation ahead of initial negotiation");
     95 
     96 // Run remaining tests twice: once for each negotiator
     97 
     98 for (const negotiator of negotiators) {
     99  const {tag} = negotiator;
    100 
    101  promise_test(async t => {
    102    const pc1 = new RTCPeerConnection();
    103    const pc2 = new RTCPeerConnection();
    104    t.add_cleanup(() => pc1.close());
    105    t.add_cleanup(() => pc2.close());
    106 
    107    pc1.addTransceiver("audio");
    108    await new Promise(r => pc1.onnegotiationneeded = r);
    109    pc1.restartIce();
    110    await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator);
    111    await assertNoNegotiationNeeded(t, pc1);
    112  }, `restartIce() has no effect on initial negotiation${tag}`);
    113 
    114  promise_test(async t => {
    115    const pc1 = new RTCPeerConnection();
    116    const pc2 = new RTCPeerConnection();
    117    t.add_cleanup(() => pc1.close());
    118    t.add_cleanup(() => pc2.close());
    119 
    120    pc1.addTransceiver("audio");
    121    await new Promise(r => pc1.onnegotiationneeded = r);
    122    await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator);
    123    pc1.restartIce();
    124    await new Promise(r => pc1.onnegotiationneeded = r);
    125  }, `restartIce() fires negotiationneeded after initial negotiation${tag}`);
    126 
    127  promise_test(async t => {
    128    const pc1 = new RTCPeerConnection();
    129    const pc2 = new RTCPeerConnection();
    130    t.add_cleanup(() => pc1.close());
    131    t.add_cleanup(() => pc2.close());
    132 
    133    pc1.addTransceiver("audio");
    134    await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator);
    135 
    136    const [oldUfrag1] = getUfrags(pc1.localDescription);
    137    const [oldUfrag2] = getUfrags(pc2.localDescription);
    138    await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator);
    139    assert_ufrags_equals(getUfrags(pc1.localDescription)[0], oldUfrag1, "control 1");
    140    assert_ufrags_equals(getUfrags(pc2.localDescription)[0], oldUfrag2, "control 2");
    141 
    142    pc1.restartIce();
    143    await new Promise(r => pc1.onnegotiationneeded = r);
    144    await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator);
    145    const [newUfrag1] = getUfrags(pc1.localDescription);
    146    const [newUfrag2] = getUfrags(pc2.localDescription);
    147    assert_ufrags_not_equals(newUfrag1, oldUfrag1, "ufrag 1 changed");
    148    assert_ufrags_not_equals(newUfrag1, oldUfrag2, "ufrag 2 changed");
    149    await assertNoNegotiationNeeded(t, pc1);
    150 
    151    await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator);
    152    assert_ufrags_equals(getUfrags(pc1.localDescription)[0], newUfrag1, "Unchanged 1");
    153    assert_ufrags_equals(getUfrags(pc2.localDescription)[0], newUfrag2, "Unchanged 2");
    154  }, `restartIce() causes fresh ufrags${tag}`);
    155 
    156  promise_test(async t => {
    157    const config = {bundlePolicy: "max-bundle"};
    158    const pc1 = new RTCPeerConnection(config);
    159    const pc2 = new RTCPeerConnection(config);
    160    t.add_cleanup(() => pc1.close());
    161    t.add_cleanup(() => pc2.close());
    162 
    163    pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate);
    164    pc2.onicecandidate = e => pc1.addIceCandidate(e.candidate);
    165 
    166    // See the explanation below about Chrome's onnegotiationneeded firing
    167    // too early.
    168    const negotiationNeededPromise1 =
    169        new Promise(r => pc1.onnegotiationneeded = r);
    170    pc1.addTransceiver("video");
    171    pc1.addTransceiver("audio");
    172    await negotiationNeededPromise1;
    173    await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator);
    174 
    175    const [videoTc, audioTc] = pc1.getTransceivers();
    176    const [videoTp, audioTp] =
    177        pc1.getTransceivers().map(tc => tc.sender.transport);
    178    assert_equals(pc1.getTransceivers().length, 2, 'transceiver count');
    179 
    180    // On Chrome, it is possible (likely, even) that videoTc.sender.transport.state
    181    // will be 'connected' by the time we get here.  We'll race 2 promises here:
    182    // 1. Resolve after onstatechange is called with connected state.
    183    // 2. If already connected, resolve immediately.
    184    await Promise.race([
    185      new Promise(r => videoTc.sender.transport.onstatechange =
    186        () => videoTc.sender.transport.state == "connected" && r()),
    187      new Promise(r => videoTc.sender.transport.state == "connected" && r())
    188    ]);
    189    assert_equals(videoTc.sender.transport.state, "connected");
    190 
    191    await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator);
    192    assert_equals(videoTp, pc1.getTransceivers()[0].sender.transport,
    193                  'offer/answer retains dtls transport');
    194    assert_equals(audioTp, pc1.getTransceivers()[1].sender.transport,
    195                  'offer/answer retains dtls transport');
    196 
    197    const negotiationNeededPromise2 =
    198        new Promise(r => pc1.onnegotiationneeded = r);
    199    pc1.restartIce();
    200    await negotiationNeededPromise2;
    201    await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator);
    202 
    203    const [newVideoTp, newAudioTp] =
    204        pc1.getTransceivers().map(tc => tc.sender.transport);
    205    assert_equals(videoTp, newVideoTp, 'ice restart retains dtls transport');
    206    assert_equals(audioTp, newAudioTp, 'ice restart retains dtls transport');
    207  }, `restartIce() retains dtls transports${tag}`);
    208 
    209  promise_test(async t => {
    210    const pc1 = new RTCPeerConnection();
    211    const pc2 = new RTCPeerConnection();
    212    t.add_cleanup(() => pc1.close());
    213    t.add_cleanup(() => pc2.close());
    214 
    215    pc1.addTransceiver("audio");
    216    await new Promise(r => pc1.onnegotiationneeded = r);
    217    await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator);
    218 
    219    const [oldUfrag1] = getUfrags(pc1.localDescription);
    220    const [oldUfrag2] = getUfrags(pc2.localDescription);
    221 
    222    await negotiator.setOffer(pc1);
    223    pc1.restartIce();
    224    await pc2.setRemoteDescription(pc1.localDescription);
    225    await negotiator.setAnswer(pc2);
    226    // Several tests in this file initializes the onnegotiationneeded listener
    227    // before the setLocalDescription() or setRemoteDescription() that we expect
    228    // to trigger negotiation needed. This allows Chrome to exercise these tests
    229    // without timing out due to a bug that causes onnegotiationneeded to fire too
    230    // early.
    231    // TODO(https://crbug.com/985797): Once Chrome does not fire ONN too early,
    232    // simply do "await new Promise(...)" instead of
    233    // "await negotiationNeededPromise" here and in other tests in this file.
    234    const negotiationNeededPromise =
    235        new Promise(r => pc1.onnegotiationneeded = r);
    236    await pc1.setRemoteDescription(pc2.localDescription);
    237    assert_ufrags_equals(getUfrags(pc1.localDescription)[0], oldUfrag1, "Unchanged 1");
    238    assert_ufrags_equals(getUfrags(pc2.localDescription)[0], oldUfrag2, "Unchanged 2");
    239    await negotiationNeededPromise;
    240    await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator);
    241    const [newUfrag1] = getUfrags(pc1.localDescription);
    242    const [newUfrag2] = getUfrags(pc2.localDescription);
    243    assert_ufrags_not_equals(newUfrag1, oldUfrag1, "ufrag 1 changed");
    244    assert_ufrags_not_equals(newUfrag1, oldUfrag2, "ufrag 2 changed");
    245    await assertNoNegotiationNeeded(t, pc1);
    246  }, `restartIce() works in have-local-offer${tag}`);
    247 
    248  promise_test(async t => {
    249    const pc1 = new RTCPeerConnection();
    250    const pc2 = new RTCPeerConnection();
    251    t.add_cleanup(() => pc1.close());
    252    t.add_cleanup(() => pc2.close());
    253 
    254    pc1.addTransceiver("audio");
    255    await new Promise(r => pc1.onnegotiationneeded = r);
    256    await negotiator.setOffer(pc1);
    257    pc1.restartIce();
    258    await pc2.setRemoteDescription(pc1.localDescription);
    259    await negotiator.setAnswer(pc2);
    260    const negotiationNeededPromise =
    261        new Promise(r => pc1.onnegotiationneeded = r);
    262    await pc1.setRemoteDescription(pc2.localDescription);
    263    const [oldUfrag1] = getUfrags(pc1.localDescription);
    264    const [oldUfrag2] = getUfrags(pc2.localDescription);
    265    await negotiationNeededPromise;
    266    await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator);
    267    const [newUfrag1] = getUfrags(pc1.localDescription);
    268    const [newUfrag2] = getUfrags(pc2.localDescription);
    269    assert_ufrags_not_equals(newUfrag1, oldUfrag1, "ufrag 1 changed");
    270    assert_ufrags_not_equals(newUfrag1, oldUfrag2, "ufrag 2 changed");
    271    await assertNoNegotiationNeeded(t, pc1);
    272  }, `restartIce() works in initial have-local-offer${tag}`);
    273 
    274  promise_test(async t => {
    275    const pc1 = new RTCPeerConnection();
    276    const pc2 = new RTCPeerConnection();
    277    t.add_cleanup(() => pc1.close());
    278    t.add_cleanup(() => pc2.close());
    279 
    280    pc1.addTransceiver("audio");
    281    await new Promise(r => pc1.onnegotiationneeded = r);
    282    await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator);
    283 
    284    const [oldUfrag1] = getUfrags(pc1.localDescription);
    285    const [oldUfrag2] = getUfrags(pc2.localDescription);
    286 
    287    await negotiator.setOffer(pc2);
    288    await pc1.setRemoteDescription(pc2.localDescription);
    289    pc1.restartIce();
    290    await pc2.setRemoteDescription(await pc1.createAnswer());
    291    const negotiationNeededPromise =
    292        new Promise(r => pc1.onnegotiationneeded = r);
    293    await pc1.setLocalDescription(pc2.remoteDescription); // End on pc1. No race
    294    assert_ufrags_equals(getUfrags(pc1.localDescription)[0], oldUfrag1, "Unchanged 1");
    295    assert_ufrags_equals(getUfrags(pc2.localDescription)[0], oldUfrag2, "Unchanged 2");
    296    await negotiationNeededPromise;
    297    await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator);
    298    const [newUfrag1] = getUfrags(pc1.localDescription);
    299    const [newUfrag2] = getUfrags(pc2.localDescription);
    300    assert_ufrags_not_equals(newUfrag1, oldUfrag1, "ufrag 1 changed");
    301    assert_ufrags_not_equals(newUfrag1, oldUfrag2, "ufrag 2 changed");
    302    await assertNoNegotiationNeeded(t, pc1);
    303  }, `restartIce() works in have-remote-offer${tag}`);
    304 
    305  promise_test(async t => {
    306    const pc1 = new RTCPeerConnection();
    307    const pc2 = new RTCPeerConnection();
    308    t.add_cleanup(() => pc1.close());
    309    t.add_cleanup(() => pc2.close());
    310 
    311    pc2.addTransceiver("audio");
    312    await negotiator.setOffer(pc2);
    313    await pc1.setRemoteDescription(pc2.localDescription);
    314    pc1.restartIce();
    315    await pc2.setRemoteDescription(await pc1.createAnswer());
    316    await pc1.setLocalDescription(pc2.remoteDescription); // End on pc1. No race
    317    await assertNoNegotiationNeeded(t, pc1);
    318  }, `restartIce() does nothing in initial have-remote-offer${tag}`);
    319 
    320  promise_test(async t => {
    321    const pc1 = new RTCPeerConnection();
    322    const pc2 = new RTCPeerConnection();
    323    t.add_cleanup(() => pc1.close());
    324    t.add_cleanup(() => pc2.close());
    325 
    326    pc1.addTransceiver("audio");
    327    await new Promise(r => pc1.onnegotiationneeded = r);
    328    await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator);
    329 
    330    const [oldUfrag1] = getUfrags(pc1.localDescription);
    331    const [oldUfrag2] = getUfrags(pc2.localDescription);
    332 
    333    pc1.restartIce();
    334    await new Promise(r => pc1.onnegotiationneeded = r);
    335    const negotiationNeededPromise =
    336        new Promise(r => pc1.onnegotiationneeded = r);
    337    await exchangeOfferAnswerEndOnSecond(pc2, pc1, negotiator);
    338    assert_ufrags_equals(getUfrags(pc1.localDescription)[0], oldUfrag1, "nothing yet 1");
    339    assert_ufrags_equals(getUfrags(pc2.localDescription)[0], oldUfrag2, "nothing yet 2");
    340    await negotiationNeededPromise;
    341    await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator);
    342    const [newUfrag1] = getUfrags(pc1.localDescription);
    343    const [newUfrag2] = getUfrags(pc2.localDescription);
    344    assert_ufrags_not_equals(newUfrag1, oldUfrag1, "ufrag 1 changed");
    345    assert_ufrags_not_equals(newUfrag2, oldUfrag2, "ufrag 2 changed");
    346    await assertNoNegotiationNeeded(t, pc1);
    347  }, `restartIce() survives remote offer${tag}`);
    348 
    349  promise_test(async t => {
    350    const pc1 = new RTCPeerConnection();
    351    const pc2 = new RTCPeerConnection();
    352    t.add_cleanup(() => pc1.close());
    353    t.add_cleanup(() => pc2.close());
    354 
    355    pc1.addTransceiver("audio");
    356    await new Promise(r => pc1.onnegotiationneeded = r);
    357    await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator);
    358 
    359    const [oldUfrag1] = getUfrags(pc1.localDescription);
    360    const [oldUfrag2] = getUfrags(pc2.localDescription);
    361 
    362    pc1.restartIce();
    363    pc2.restartIce();
    364    await new Promise(r => pc1.onnegotiationneeded = r);
    365    await exchangeOfferAnswerEndOnSecond(pc2, pc1, negotiator);
    366    const [newUfrag1] = getUfrags(pc1.localDescription);
    367    const [newUfrag2] = getUfrags(pc2.localDescription);
    368    assert_ufrags_not_equals(newUfrag1, oldUfrag1, "ufrag 1 changed");
    369    assert_ufrags_not_equals(newUfrag1, oldUfrag2, "ufrag 2 changed");
    370    await assertNoNegotiationNeeded(t, pc1);
    371 
    372    await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator);
    373    assert_ufrags_equals(getUfrags(pc1.localDescription)[0], newUfrag1, "Unchanged 1");
    374    assert_ufrags_equals(getUfrags(pc2.localDescription)[0], newUfrag2, "Unchanged 2");
    375    await assertNoNegotiationNeeded(t, pc1);
    376  }, `restartIce() is satisfied by remote ICE restart${tag}`);
    377 
    378  promise_test(async t => {
    379    const pc1 = new RTCPeerConnection();
    380    const pc2 = new RTCPeerConnection();
    381    t.add_cleanup(() => pc1.close());
    382    t.add_cleanup(() => pc2.close());
    383 
    384    pc1.addTransceiver("audio");
    385    await new Promise(r => pc1.onnegotiationneeded = r);
    386    await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator);
    387 
    388    const [oldUfrag1] = getUfrags(pc1.localDescription);
    389    const [oldUfrag2] = getUfrags(pc2.localDescription);
    390 
    391    pc1.restartIce();
    392    await new Promise(r => pc1.onnegotiationneeded = r);
    393    await pc1.setLocalDescription(await pc1.createOffer({iceRestart: false}));
    394    await pc2.setRemoteDescription(pc1.localDescription);
    395    await negotiator.setAnswer(pc2);
    396    await pc1.setRemoteDescription(pc2.localDescription);
    397    const [newUfrag1] = getUfrags(pc1.localDescription);
    398    const [newUfrag2] = getUfrags(pc2.localDescription);
    399    assert_ufrags_not_equals(newUfrag1, oldUfrag1, "ufrag 1 changed");
    400    assert_ufrags_not_equals(newUfrag1, oldUfrag2, "ufrag 2 changed");
    401    await assertNoNegotiationNeeded(t, pc1);
    402  }, `restartIce() trumps {iceRestart: false}${tag}`);
    403 
    404  promise_test(async t => {
    405    const pc1 = new RTCPeerConnection();
    406    const pc2 = new RTCPeerConnection();
    407    t.add_cleanup(() => pc1.close());
    408    t.add_cleanup(() => pc2.close());
    409 
    410    pc1.addTransceiver("audio");
    411    await new Promise(r => pc1.onnegotiationneeded = r);
    412    await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator);
    413 
    414    const [oldUfrag1] = getUfrags(pc1.localDescription);
    415    const [oldUfrag2] = getUfrags(pc2.localDescription);
    416 
    417    pc1.restartIce();
    418    await new Promise(r => pc1.onnegotiationneeded = r);
    419    await negotiator.setOffer(pc1);
    420    const negotiationNeededPromise =
    421        new Promise(r => pc1.onnegotiationneeded = r);
    422    await pc1.setLocalDescription({type: "rollback"});
    423    await negotiationNeededPromise;
    424    await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator);
    425    const [newUfrag1] = getUfrags(pc1.localDescription);
    426    const [newUfrag2] = getUfrags(pc2.localDescription);
    427    assert_ufrags_not_equals(newUfrag1, oldUfrag1, "ufrag 1 changed");
    428    assert_ufrags_not_equals(newUfrag1, oldUfrag2, "ufrag 2 changed");
    429    await assertNoNegotiationNeeded(t, pc1);
    430  }, `restartIce() survives rollback${tag}`);
    431 
    432  promise_test(async t => {
    433    const pc1 = new RTCPeerConnection({bundlePolicy: "max-compat"});
    434    const pc2 = new RTCPeerConnection({bundlePolicy: "max-compat"});
    435    t.add_cleanup(() => pc1.close());
    436    t.add_cleanup(() => pc2.close());
    437 
    438    pc1.addTransceiver("audio");
    439    pc1.addTransceiver("video");
    440    await new Promise(r => pc1.onnegotiationneeded = r);
    441    await exchangeOfferAnswerEndOnFirst(pc1, pc2, negotiator);
    442 
    443    const oldUfrags1 = getUfrags(pc1.localDescription);
    444    const oldUfrags2 = getUfrags(pc2.localDescription);
    445    const oldPwds2 = getPwds(pc2.localDescription);
    446 
    447    pc1.restartIce();
    448    await new Promise(r => pc1.onnegotiationneeded = r);
    449 
    450    // Engineer a partial ICE restart from pc2
    451    pc2.restartIce();
    452    await negotiator.setOffer(pc2);
    453    {
    454      let {type, sdp} = pc2.localDescription;
    455      // Restore both old ice-ufrag and old ice-pwd to trigger a partial restart
    456      sdp = sdp.replace(getUfrags({sdp})[0], oldUfrags2[0]);
    457      sdp = sdp.replace(getPwds({sdp})[0], oldPwds2[0]);
    458      const newUfrags2 = getUfrags({sdp});
    459      const newPwds2 = getPwds({sdp});
    460      assert_ufrags_equals(newUfrags2[0], oldUfrags2[0], "control ufrag match");
    461      assert_ufrags_equals(newPwds2[0], oldPwds2[0], "control pwd match");
    462      assert_ufrags_not_equals(newUfrags2[1], oldUfrags2[1], "control ufrag non-match");
    463      assert_ufrags_not_equals(newPwds2[1], oldPwds2[1], "control pwd non-match");
    464      await pc1.setRemoteDescription({type, sdp});
    465    }
    466    const negotiationNeededPromise =
    467        new Promise(r => pc1.onnegotiationneeded = r);
    468    await negotiator.setAnswer(pc1);
    469    const newUfrags1 = getUfrags(pc1.localDescription);
    470    assert_ufrags_equals(newUfrags1[0], oldUfrags1[0], "Unchanged 1");
    471    assert_ufrags_not_equals(newUfrags1[1], oldUfrags1[1], "Restarted 2");
    472    await negotiationNeededPromise;
    473    await negotiator.setOffer(pc1);
    474    const newestUfrags1 = getUfrags(pc1.localDescription);
    475    assert_ufrags_not_equals(newestUfrags1[0], oldUfrags1[0], "Restarted 1");
    476    assert_ufrags_not_equals(newestUfrags1[1], oldUfrags1[1], "Restarted 2");
    477    await assertNoNegotiationNeeded(t, pc1);
    478  }, `restartIce() survives remote offer containing partial restart${tag}`);
    479 
    480 }
    481 
    482 </script>