<!doctype html>
<html>
    <head>
        <meta charset="utf-8">
        <script src="/resources/testharness.js"></script>
        <script src="/resources/testharnessreport.js"></script>
    </head>
    <body>
        <script>

promise_test(async (test) => {
    const pc = new RTCPeerConnection();
    const senderTransform = new SFrameTransform();
    const receiverTransform = new SFrameTransform();
    const sender1 = pc.addTransceiver('audio').sender;
    const sender2 = pc.addTransceiver('video').sender;
    const receiver1 = pc.getReceivers()[0];
    const receiver2 = pc.getReceivers()[1];

    sender1.transform = senderTransform;
    receiver1.transform = receiverTransform;
    assert_throws_dom("InvalidStateError", () => sender2.transform = senderTransform);
    assert_throws_dom("InvalidStateError", () => receiver2.transform = receiverTransform);

    sender1.transform = senderTransform;
    receiver1.transform = receiverTransform;

    sender1.transform = null;
    receiver1.transform = null;
}, "Cannot reuse attached transforms");

test(() => {
    const senderTransform = new SFrameTransform();

    assert_true(senderTransform.readable instanceof ReadableStream);
    assert_true(senderTransform.writable instanceof WritableStream);
}, "SFrameTransform exposes readable and writable");

promise_test(async (test) => {
    const pc = new RTCPeerConnection();
    const senderTransform = new SFrameTransform();
    const receiverTransform = new SFrameTransform();
    const sender1 = pc.addTransceiver('audio').sender;
    const sender2 = pc.addTransceiver('video').sender;
    const receiver1 = pc.getReceivers()[0];
    const receiver2 = pc.getReceivers()[1];

    assert_false(senderTransform.readable.locked, "sender readable before");
    assert_false(senderTransform.writable.locked, "sender writable before");
    assert_false(receiverTransform.readable.locked, "receiver readable before");
    assert_false(receiverTransform.writable.locked, "receiver writable before");

    sender1.transform = senderTransform;
    receiver1.transform = receiverTransform;

    assert_true(senderTransform.readable.locked, "sender readable during");
    assert_true(senderTransform.writable.locked, "sender writable during");
    assert_true(receiverTransform.readable.locked, "receiver readable during");
    assert_true(receiverTransform.writable.locked, "receiver writable during");

    sender1.transform = null;
    receiver1.transform = null;

    assert_true(senderTransform.readable.locked, "sender readable after");
    assert_true(senderTransform.writable.locked, "sender writable after");
    assert_true(receiverTransform.readable.locked, "receiver readable after");
    assert_true(receiverTransform.writable.locked, "receiver writable after");
}, "readable/writable are locked when attached and after being attached");

promise_test(async (test) => {
    const key = await crypto.subtle.importKey("raw", new Uint8Array([143, 77, 43, 10, 72, 19, 37, 67, 236, 219, 24, 93, 26, 165, 91, 178]), "HKDF", false, ["deriveBits", "deriveKey"]);

    const senderTransform = new SFrameTransform({ role : 'encrypt', authenticationSize: 10 });
    senderTransform.setEncryptionKey(key);

    const receiverTransform = new SFrameTransform({ role : 'decrypt', authenticationSize: 10 });
    receiverTransform.setEncryptionKey(key);

    const writer = senderTransform.writable.getWriter();
    const reader = receiverTransform.readable.getReader();

    senderTransform.readable.pipeTo(receiverTransform.writable);

    const sent = new ArrayBuffer(8);
    const view = new Int8Array(sent);
    for (let cptr = 0; cptr < sent.byteLength; ++cptr)
        view[cptr] = cptr;

    writer.write(sent);
    const received = await reader.read();

    assert_equals(received.value.byteLength, 8);
    const view2 = new Int8Array(received.value);
    for (let cptr = 0; cptr < sent.byteLength; ++cptr)
        assert_equals(view2[cptr], view[cptr]);
}, "SFrame with array buffer - authentication size 10");

promise_test(async (test) => {
    const key = await crypto.subtle.importKey("raw", new Uint8Array([143, 77, 43, 10, 72, 19, 37, 67, 236, 219, 24, 93, 26, 165, 91, 178]), "HKDF", false, ["deriveBits", "deriveKey"]);

    const senderTransform = new SFrameTransform({ role : 'encrypt', authenticationSize: 10 });
    const senderWriter = senderTransform.writable.getWriter();
    const senderReader = senderTransform.readable.getReader();

    const receiverTransform = new SFrameTransform({ role : 'decrypt', authenticationSize: 10 });
    const receiverWriter = receiverTransform.writable.getWriter();
    const receiverReader = receiverTransform.readable.getReader();

    senderTransform.setEncryptionKey(key);
    receiverTransform.setEncryptionKey(key);

    const chunk = new ArrayBuffer(8);

    // decryption should fail, leading to an empty array buffer.
    await receiverWriter.write(chunk);
    let received = await receiverReader.read();
    assert_equals(received.value.byteLength, 0);

    // We write again but this time with a chunk we can decrypt.
    await senderWriter.write(chunk);
    const encrypted = await senderReader.read();
    await receiverWriter.write(encrypted.value);
    received = await receiverReader.read();
    assert_equals(received.value.byteLength, 8);
}, "SFrame decryption with array buffer that is too small");

promise_test(async (test) => {
    const key = await crypto.subtle.importKey("raw", new Uint8Array([143, 77, 43, 10, 72, 19, 37, 67, 236, 219, 24, 93, 26, 165, 91, 178]), "HKDF", false, ["deriveBits", "deriveKey"]);

    const receiverTransform = new SFrameTransform({ role : 'decrypt', authenticationSize: 10 });
    const receiverWriter = receiverTransform.writable.getWriter();
    receiverTransform.setEncryptionKey(key);

    // decryption should fail, leading to erroring the transform.
    await promise_rejects_js(test, TypeError, receiverWriter.write({ }));
    await promise_rejects_js(test, TypeError, receiverWriter.closed);
}, "SFrame transform gets errored if trying to process unexpected value types");

        </script>
    </body>
</html>
