Skip to content

Multicast UDPSocket support in wpt #55304

@vkrot-cell

Description

@vkrot-cell

There is a new web feature that allows to subscribe to UDP multicast groups to receive UDP packets there.
See the explainer https://github.com/WICG/direct-sockets/blob/main/docs/multicast-explainer.md

Multicast tests work in Chrome browser tests except on MacOS (known chromium issue) content/browser/direct_sockets/direct_sockets_udp_browsertest.cc

WPT the environment is too restrictive, and packets are not arrived at multicast group silently. The path to WPT tests in chromium: third_party/blink/web_tests/external/wpt/direct-sockets/udp_socket.https.html
Run tests: autoninja -C out/Default blink_tests && third_party/blink/tools/run_web_tests.py --target Default virtual/direct-sockets --verbose --gtest_filter multicast.https.html,multicast.https.html.headers --driver-logging --timeout-ms 5000 --no-retry-failures

When it is fixed, the tests can be added as shown in the code

<!DOCTYPE html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/test-only-api.js"></script>
<script>
'use strict';

// The test creates:
// 2 bound sockets that subscribe to the same group for reading
// 1 bound socket that sends data to the group.
// Test is finished when all messages are received by all subscribers.

// parameters:
// multicastAllowAddressSharing = true. to have 2 subscribers on the same machine.
// multicastTimeToLive = 0. To not try to send data outside of this machine.
// multicastLoopback = true. To get the packet back.

// It is fine to reuse the same address, because
// the port will be generated unique on this machine.
// multicast addresses are in range 224.0.0.0 to 239.255.255.255.
const multicastGroupAddress = '237.132.100.17';
const kRequiredDatagrams = 150;
const kRequiredBytes =
  kRequiredDatagrams * (kRequiredDatagrams + 1) / 2;

class MulticastReader {
  constructor(multicastPort) {
    this.socket = new UDPSocket({
      localAddress: '0.0.0.0',
      localPort: multicastPort,
      multicastAllowAddressSharing: true
    });
  }

  async create() {
    const { readable, multicastController, localAddress, localPort } = await this.socket.opened;
    await multicastController.joinGroup(multicastGroupAddress);

    this.readable = readable;
    this.multicastController = multicastController;
    this.localPort = localPort;

    console.log('reading socket is opened and joinedGroup at ' + localAddress + ':' + localPort);
  }

  async read() {
    const reader = this.readable.getReader();

    let bytesRead = 0;
    while (bytesRead < kRequiredBytes) {
      const { value: { data }, done } = await reader.read();
      assert_false(done);
      console.log('first piece of data was received! ' + data.length);
      for (let index = 0; index < data.length; index++) {
        assert_equals(data[index], bytesRead % 256);
        bytesRead++;
      }
    }
    assert_equals(bytesRead, kRequiredBytes);

    reader.releaseLock();
    await this.socket.close();

    console.log('all data has been read');
  }
}

promise_test(async () => {
  const senderSocket = new UDPSocket({ localAddress: '127.0.0.1',
     multicastTimeToLive: 0,
     multicastLoopback: true });
  const { writable } = await senderSocket.opened;
  console.log('sender udp socket is opened ');

  // Pass undefined as multicastPort, so it will be allocated.
  const multicastReader1 = new MulticastReader(undefined);
  await multicastReader1.create();
  const multicastPort = multicastReader1.localPort;

  const multicastReader2 = new MulticastReader(multicastPort);
  await multicastReader2.create();

  const readLoop1 = (async () => {
    console.log('read loop for the first socket is started');
    await multicastReader1.read();
  })();

  const readLoop2 = (async () => {
    console.log('read loop for the second socket is started');
    await multicastReader2.read();
  })();

  const sendLoop = (async () => {
    let bytesWritten = 0;
    let chunkLength = 0;
    const writer = writable.getWriter();

    while (bytesWritten < kRequiredBytes) {
      chunkLength = Math.min(chunkLength + 1, kRequiredBytes - bytesWritten);
      let chunk = new Uint8Array(chunkLength);
      for (let index = 0; index < chunkLength; index++) {
        chunk[index] = bytesWritten % 256;
        bytesWritten++;
      }
      await writer.ready;
      await writer.write({
        data: chunk,
        remoteAddress: multicastGroupAddress,
        remotePort: multicastPort
      });

    }
    assert_equals(bytesWritten, kRequiredBytes);
    writer.releaseLock();
    await senderSocket.close();
    console.log('all data has been sent');
  })();

  await sendLoop;
  await readLoop1;
  await readLoop2;

}, "UDPSocket sends messages to a multicast group that has 2 subscribers");
</script>

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions