// Copyright 2018-2026 the Deno authors. MIT license.
import {
  assert,
  assertEquals,
  assertGreaterOrEqual,
  assertRejects,
  assertStringIncludes,
  assertThrows,
  delay,
  execCode,
  execCode2,
  execCode3,
  tmpUnixSocketPath,
} from "./test_util.ts";

// Since these tests may run in parallel, ensure this port is unique to this file
const listenPort = 4503;
const listenPort2 = 4504;

let isCI: boolean;
try {
  isCI = Deno.env.get("CI") !== undefined;
} catch {
  isCI = true;
}

Deno.test({ permissions: { net: true } }, function netTcpListenClose() {
  const listener = Deno.listen({ hostname: "127.0.0.1", port: listenPort });
  assert(listener.addr.transport === "tcp");
  assertEquals(listener.addr.hostname, "127.0.0.1");
  assertEquals(listener.addr.port, listenPort);
  listener.close();
});

Deno.test(
  {
    permissions: { net: true },
  },
  function netTcpListenEphemeralClose() {
    const listener = Deno.listen({ hostname: "127.0.0.1" });
    assert(listener.addr.transport === "tcp");
    assertEquals(listener.addr.hostname, "127.0.0.1");
    // Ephemeral port range starts at 49152 according to RFC 6335 / IANA, but
    // many OSes use the lower number below (old OSes also started at 1024)
    assertGreaterOrEqual(listener.addr.port, 32768);
    listener.close();
  },
);

Deno.test(
  {
    permissions: { net: true },
  },
  function netUdpListenClose() {
    const socket = Deno.listenDatagram({
      hostname: "127.0.0.1",
      port: listenPort,
      transport: "udp",
    });
    assert(socket.addr.transport === "udp");
    assertEquals(socket.addr.hostname, "127.0.0.1");
    assertEquals(socket.addr.port, listenPort);
    socket.close();
  },
);

Deno.test(
  {
    permissions: { net: true },
  },
  function netUdpListenEphemeralClose() {
    const socket = Deno.listenDatagram({
      hostname: "127.0.0.1",
      transport: "udp",
    });
    assert(socket.addr.transport === "udp");
    assertEquals(socket.addr.hostname, "127.0.0.1");
    // Ephemeral port range starts at 49152 according to RFC 6335 / IANA, but
    // many OSes use the lower number below (old OSes also started at 1024)
    assertGreaterOrEqual(socket.addr.port, 32768);
    socket.close();
  },
);

Deno.test(
  {
    ignore: Deno.build.os === "windows",
    permissions: { read: true, write: true, net: true },
  },
  function netUnixListenClose() {
    const filePath = tmpUnixSocketPath();
    const socket = Deno.listen({
      path: filePath,
      transport: "unix",
    });
    assert(socket.addr.transport === "unix");
    assertEquals(socket.addr.path, filePath);
    socket.close();
  },
);

Deno.test(
  {
    ignore: Deno.build.os === "windows",
    permissions: { read: true, write: true, net: true },
  },
  function netUnixPacketListenClose() {
    const filePath = tmpUnixSocketPath();
    const socket = Deno.listenDatagram({
      path: filePath,
      transport: "unixpacket",
    });
    assert(socket.addr.transport === "unixpacket");
    assertEquals(socket.addr.path, filePath);
    socket.close();
  },
);

Deno.test(
  {
    ignore: Deno.build.os === "windows",
    permissions: { read: true, write: true },
  },
  function netUnixPacketAnonListenClose() {
    const socket = Deno.listenDatagram({
      transport: "unixpacket",
    });
    assert(socket.addr.transport === "unixpacket");
    assertEquals(socket.addr.path, null);
    socket.close();
  },
);

Deno.test(
  {
    ignore: Deno.build.os === "windows",
    permissions: { read: true, write: false },
  },
  function netUnixListenWritePermission() {
    assertThrows(() => {
      const filePath = tmpUnixSocketPath();
      const socket = Deno.listen({
        path: filePath,
        transport: "unix",
      });
      assert(socket.addr.transport === "unix");
      assertEquals(socket.addr.path, filePath);
      socket.close();
    }, Deno.errors.NotCapable);
  },
);

Deno.test(
  {
    ignore: Deno.build.os === "windows",
    permissions: { read: true, write: false },
  },
  function netUnixPacketListenWritePermission() {
    assertThrows(() => {
      const filePath = tmpUnixSocketPath();
      const socket = Deno.listenDatagram({
        path: filePath,
        transport: "unixpacket",
      });
      assert(socket.addr.transport === "unixpacket");
      assertEquals(socket.addr.path, filePath);
      socket.close();
    }, Deno.errors.NotCapable);
  },
);

Deno.test(
  {
    ignore: Deno.build.os === "windows",
    permissions: { read: true, write: false },
  },
  function netUnixPacketAnonListenWritePermission() {
    const socket = Deno.listenDatagram({
      transport: "unixpacket",
    });
    assert(socket.addr.transport === "unixpacket");
    assertEquals(socket.addr.path, null);
    socket.close();
  },
);

Deno.test(
  {
    permissions: { net: true },
  },
  async function netTcpCloseWhileAccept() {
    const listener = Deno.listen({ port: listenPort });
    const p = listener.accept();
    listener.close();
    await assertRejects(
      () => p,
      Deno.errors.BadResource,
      "Listener has been closed",
    );
  },
);

Deno.test(
  {
    permissions: { net: true },
  },
  async function netTcpListenAfterCloseWhileAcceptSettles() {
    const listener = Deno.listen({ hostname: "127.0.0.1", port: 0 });
    const { hostname, port } = listener.addr as Deno.NetAddr;
    const acceptPromise = listener.accept();
    listener.close();
    await assertRejects(
      () => acceptPromise,
      Deno.errors.BadResource,
      "Listener has been closed",
    );

    const listener2 = Deno.listen({ hostname, port });
    listener2.close();
  },
);

Deno.test(
  {
    permissions: { net: true },
  },
  async function netTcpListenAfterCloseWhileAsyncIteratorSettles() {
    const listener = Deno.listen({ hostname: "127.0.0.1", port: 0 });
    const { hostname, port } = listener.addr as Deno.NetAddr;
    const nextPromise = listener[Symbol.asyncIterator]().next();
    listener.close();
    assertEquals(await nextPromise, { value: undefined, done: true });

    const listener2 = Deno.listen({ hostname, port });
    listener2.close();
  },
);

Deno.test(
  {
    ignore: Deno.build.os === "windows",
    permissions: { read: true, write: true, net: true },
  },
  async function netUnixCloseWhileAccept() {
    const filePath = tmpUnixSocketPath();
    const listener = Deno.listen({
      path: filePath,
      transport: "unix",
    });
    const p = listener.accept();
    listener.close();
    await assertRejects(
      () => p,
      Deno.errors.BadResource,
      "Listener has been closed",
    );
  },
);

Deno.test(
  { permissions: { net: true } },
  async function netTcpConcurrentAccept() {
    const listener = Deno.listen({ port: 4510 });
    let acceptErrCount = 0;
    const checkErr = (e: Error) => {
      if (e.message === "Listener has been closed") {
        assertEquals(acceptErrCount, 1);
      } else if (e.message === "Another accept task is ongoing") {
        acceptErrCount++;
      } else {
        throw e;
      }
    };
    const p = listener.accept().catch(checkErr);
    const p1 = listener.accept().catch(checkErr);
    await Promise.race([p, p1]);
    listener.close();
    await Promise.all([p, p1]);
    assertEquals(acceptErrCount, 1);
  },
);

Deno.test(
  {
    ignore: Deno.build.os === "windows",
    permissions: { read: true, write: true, net: true },
  },
  async function netUnixConcurrentAccept() {
    const filePath = tmpUnixSocketPath();
    const listener = Deno.listen({ transport: "unix", path: filePath });
    let acceptErrCount = 0;
    const checkErr = (e: Error) => {
      if (e.message === "Listener has been closed") {
        assertEquals(acceptErrCount, 1);
      } else if (e instanceof Deno.errors.Busy) { // "Listener already in use"
        acceptErrCount++;
      } else {
        throw e;
      }
    };
    const p = listener.accept().catch(checkErr);
    const p1 = listener.accept().catch(checkErr);
    await Promise.race([p, p1]);
    listener.close();
    await Promise.all([p, p1]);
    assertEquals(acceptErrCount, 1);
  },
);

Deno.test({ permissions: { net: true } }, async function netTcpDialListen() {
  const listener = Deno.listen({ port: listenPort });
  listener.accept().then(
    async (conn) => {
      assert(conn.remoteAddr != null);
      assert(conn.localAddr.transport === "tcp");
      assertEquals(conn.localAddr.hostname, "127.0.0.1");
      assertEquals(conn.localAddr.port, listenPort);
      await conn.write(new Uint8Array([1, 2, 3]));
      conn.close();
    },
  );

  const conn = await Deno.connect({ hostname: "127.0.0.1", port: listenPort });
  assert(conn.remoteAddr.transport === "tcp");
  assertEquals(conn.remoteAddr.hostname, "127.0.0.1");
  assertEquals(conn.remoteAddr.port, listenPort);
  assert(conn.localAddr != null);
  const buf = new Uint8Array(1024);
  const readResult = await conn.read(buf);
  assertEquals(3, readResult);
  assertEquals(1, buf[0]);
  assertEquals(2, buf[1]);
  assertEquals(3, buf[2]);

  assert(readResult !== null);

  const readResult2 = await conn.read(buf);
  assertEquals(readResult2, null);

  listener.close();
  conn.close();
});

Deno.test({ permissions: { net: true } }, async function netTcpSetNoDelay() {
  const listener = Deno.listen({ port: listenPort });
  listener.accept().then(
    async (conn) => {
      assert(conn.remoteAddr != null);
      assert(conn.localAddr.transport === "tcp");
      assertEquals(conn.localAddr.hostname, "127.0.0.1");
      assertEquals(conn.localAddr.port, listenPort);
      await conn.write(new Uint8Array([1, 2, 3]));
      conn.close();
    },
  );

  const conn = await Deno.connect({ hostname: "127.0.0.1", port: listenPort });
  conn.setNoDelay(true);
  assert(conn.remoteAddr.transport === "tcp");
  assertEquals(conn.remoteAddr.hostname, "127.0.0.1");
  assertEquals(conn.remoteAddr.port, listenPort);
  assert(conn.localAddr != null);
  const buf = new Uint8Array(1024);
  const readResult = await conn.read(buf);
  assertEquals(3, readResult);
  assertEquals(1, buf[0]);
  assertEquals(2, buf[1]);
  assertEquals(3, buf[2]);

  assert(readResult !== null);

  const readResult2 = await conn.read(buf);
  assertEquals(readResult2, null);

  listener.close();
  conn.close();
});

Deno.test({ permissions: { net: true } }, async function netTcpSetKeepAlive() {
  const listener = Deno.listen({ port: listenPort });
  listener.accept().then(
    async (conn) => {
      assert(conn.remoteAddr != null);
      assert(conn.localAddr.transport === "tcp");
      assertEquals(conn.localAddr.hostname, "127.0.0.1");
      assertEquals(conn.localAddr.port, listenPort);
      await conn.write(new Uint8Array([1, 2, 3]));
      conn.close();
    },
  );

  const conn = await Deno.connect({ hostname: "127.0.0.1", port: listenPort });
  conn.setKeepAlive(true);
  assert(conn.remoteAddr.transport === "tcp");
  assertEquals(conn.remoteAddr.hostname, "127.0.0.1");
  assertEquals(conn.remoteAddr.port, listenPort);
  assert(conn.localAddr != null);
  const buf = new Uint8Array(1024);
  const readResult = await conn.read(buf);
  assertEquals(3, readResult);
  assertEquals(1, buf[0]);
  assertEquals(2, buf[1]);
  assertEquals(3, buf[2]);

  assert(readResult !== null);

  const readResult2 = await conn.read(buf);
  assertEquals(readResult2, null);

  listener.close();
  conn.close();
});

Deno.test(
  {
    ignore: Deno.build.os === "windows",
    permissions: { read: true, write: true, net: true },
  },
  async function netUnixDialListen() {
    const filePath = tmpUnixSocketPath();
    const listener = Deno.listen({ path: filePath, transport: "unix" });
    listener.accept().then(
      async (conn) => {
        assert(conn.remoteAddr != null);
        assert(conn.localAddr.transport === "unix");
        assertEquals(conn.localAddr.path, filePath);
        await conn.write(new Uint8Array([1, 2, 3]));
        conn.close();
      },
    );
    const conn = await Deno.connect({ path: filePath, transport: "unix" });
    assert(conn.remoteAddr.transport === "unix");
    assertEquals(conn.remoteAddr.path, filePath);
    assert(conn.remoteAddr != null);
    const buf = new Uint8Array(1024);
    const readResult = await conn.read(buf);
    assertEquals(3, readResult);
    assertEquals(1, buf[0]);
    assertEquals(2, buf[1]);
    assertEquals(3, buf[2]);

    assert(readResult !== null);

    const readResult2 = await conn.read(buf);
    assertEquals(readResult2, null);

    listener.close();
    conn.close();
  },
);

Deno.test(
  { permissions: { net: true } },
  async function netUdpSendReceive() {
    const alice = Deno.listenDatagram({
      port: listenPort,
      transport: "udp",
      hostname: "127.0.0.1",
    });
    assert(alice.addr.transport === "udp");
    assertEquals(alice.addr.port, listenPort);
    assertEquals(alice.addr.hostname, "127.0.0.1");

    const bob = Deno.listenDatagram({
      port: listenPort2,
      transport: "udp",
      hostname: "127.0.0.1",
    });
    assert(bob.addr.transport === "udp");
    assertEquals(bob.addr.port, listenPort2);
    assertEquals(bob.addr.hostname, "127.0.0.1");

    const sent = new Uint8Array([1, 2, 3]);
    const byteLength = await alice.send(sent, bob.addr);

    assertEquals(byteLength, 3);

    const [recvd, remote] = await bob.receive();
    assert(remote.transport === "udp");
    assertEquals(remote.port, listenPort);
    assertEquals(recvd.length, 3);
    assertEquals(1, recvd[0]);
    assertEquals(2, recvd[1]);
    assertEquals(3, recvd[2]);
    alice.close();
    bob.close();
  },
);

// Regression test for https://github.com/denoland/deno/issues/25581
// `Deno.listenDatagram` with no `hostname` should bind to `0.0.0.0` (matching
// the documented default and `Deno.listen`/`Deno.serve`), not `127.0.0.1`.
// Binding to `127.0.0.1` made it impossible to send to a network broadcast
// address such as `10.x.x.255` (the OS rejected the send with EINVAL/EFAULT).
Deno.test(
  { permissions: { net: true } },
  function netUdpDefaultHostnameIsAnyAddress() {
    const socket = Deno.listenDatagram({ port: 0, transport: "udp" });
    assert(socket.addr.transport === "udp");
    assertEquals(socket.addr.hostname, "0.0.0.0");
    socket.close();
  },
);

Deno.test(
  { permissions: { net: true } },
  async function netUdpSendReceiveTestSizeLimits() {
    // Ensure payload being sent is within UDP limit, which seems to be 65507
    // bytes
    const alice = Deno.listenDatagram({
      port: listenPort,
      transport: "udp",
      hostname: "127.0.0.1",
    });
    // wrap this in a try/catch so other tests can continue if we fail
    // if we don't close here then listening on future tests fails
    try {
      assert(alice.addr.transport === "udp");
      assertEquals(alice.addr.port, listenPort);
      assertEquals(alice.addr.hostname, "127.0.0.1");
    } catch (err) {
      alice.close();
      throw err;
    }

    const bob = Deno.listenDatagram({
      port: listenPort2,
      transport: "udp",
      hostname: "127.0.0.1",
    });
    try {
      assert(bob.addr.transport === "udp");
      assertEquals(bob.addr.port, listenPort2);
      assertEquals(bob.addr.hostname, "127.0.0.1");
    } catch (err) {
      bob.close();
      throw err;
    }

    const sizes = [0, 1, 2, 256, 1024, 4096, 16384, 65506, 65507, 65508, 65536];
    const rx = /.+ \(os error \d+\)/;

    for (const size of sizes) {
      const tosend = new Uint8Array(size);
      let byteLength = 0;
      try {
        byteLength = await alice.send(tosend, bob.addr);
      } catch (err) {
        // Note: we have to do the test this way as different OS's have
        // different UDP size limits enabled, so we will just ensure if
        // an error is thrown it is the one we are expecting.
        assert((err as Error).message.match(rx));
        alice.close();
        bob.close();
        return;
      }
      assertEquals(byteLength, size);
      const [recvd, remote] = await bob.receive();
      assert(remote.transport === "udp");
      assertEquals(remote.port, listenPort);
      assertEquals(recvd.length, size);
    }

    alice.close();
    bob.close();
  },
);

Deno.test(
  { permissions: { net: true }, ignore: true },
  async function netUdpSendReceiveBroadcast() {
    // Must bind sender to an address that can send to the broadcast address on MacOS.
    // Macos will give us error 49 when sending the broadcast packet if we omit hostname here.
    const alice = Deno.listenDatagram({
      port: listenPort,
      transport: "udp",
      hostname: "0.0.0.0",
    });

    const bob = Deno.listenDatagram({
      port: listenPort,
      transport: "udp",
      hostname: "0.0.0.0",
    });
    assert(bob.addr.transport === "udp");
    assertEquals(bob.addr.port, listenPort);
    assertEquals(bob.addr.hostname, "0.0.0.0");

    const broadcastAddr = { ...bob.addr, hostname: "255.255.255.255" };

    const sent = new Uint8Array([1, 2, 3]);
    const byteLength = await alice.send(sent, broadcastAddr);

    assertEquals(byteLength, 3);
    const [recvd, remote] = await bob.receive();
    assert(remote.transport === "udp");
    assertEquals(remote.port, listenPort);
    assertEquals(recvd.length, 3);
    assertEquals(1, recvd[0]);
    assertEquals(2, recvd[1]);
    assertEquals(3, recvd[2]);
    alice.close();
    bob.close();
  },
);

Deno.test(
  { permissions: { net: true }, ignore: true },
  async function netUdpMulticastV4() {
    const listener = Deno.listenDatagram({
      hostname: "0.0.0.0",
      port: 5353,
      transport: "udp",
      reuseAddress: true,
    });

    const membership = await listener.joinMulticastV4(
      "224.0.0.251",
      "127.0.0.1",
    );

    membership.setLoopback(true);
    membership.setLoopback(false);
    membership.setTTL(50);
    membership.leave();
    listener.close();
  },
);

Deno.test(
  { permissions: { net: true }, ignore: true },
  async function netUdpMulticastV6() {
    const listener = Deno.listenDatagram({
      hostname: "::",
      port: 5353,
      transport: "udp",
      reuseAddress: true,
    });

    const membership = await listener.joinMulticastV6(
      "ff02::fb",
      1,
    );

    membership.setLoopback(true);
    membership.setLoopback(false);
    membership.leave();
    listener.close();
  },
);

Deno.test(
  { permissions: { net: true }, ignore: true },
  async function netUdpSendReceiveMulticastv4() {
    const alice = Deno.listenDatagram({
      hostname: "0.0.0.0",
      port: 5353,
      transport: "udp",
      reuseAddress: true,
      loopback: true,
    });

    const bob = Deno.listenDatagram({
      hostname: "0.0.0.0",
      port: 5353,
      transport: "udp",
      reuseAddress: true,
    });

    const aliceMembership = await alice.joinMulticastV4(
      "224.0.0.1",
      "0.0.0.0",
    );

    const bobMembership = await bob.joinMulticastV4("224.0.0.1", "0.0.0.0");

    const sent = new Uint8Array([1, 2, 3]);

    await alice.send(sent, {
      hostname: "224.0.0.1",
      port: 5353,
      transport: "udp",
    });

    const [recvd, remote] = await bob.receive();

    assert(remote.transport === "udp");
    assertEquals(remote.port, 5353);
    assertEquals(recvd.length, 3);
    assertEquals(1, recvd[0]);
    assertEquals(2, recvd[1]);
    assertEquals(3, recvd[2]);

    aliceMembership.leave();
    bobMembership.leave();

    alice.close();
    bob.close();
  },
);

Deno.test(
  { permissions: { net: true }, ignore: true },
  async function netUdpMulticastLoopbackOption() {
    // Must bind sender to an address that can send to the broadcast address on MacOS.
    // Macos will give us error 49 when sending the broadcast packet if we omit hostname here.
    const listener = Deno.listenDatagram({
      port: 5353,
      transport: "udp",
      hostname: "0.0.0.0",
      loopback: true,
      reuseAddress: true,
    });

    const membership = await listener.joinMulticastV4(
      "224.0.0.1",
      "0.0.0.0",
    );

    // await membership.setLoopback(true);

    const sent = new Uint8Array([1, 2, 3]);
    const byteLength = await listener.send(sent, {
      hostname: "224.0.0.1",
      port: 5353,
      transport: "udp",
    });

    assertEquals(byteLength, 3);
    const [recvd, remote] = await listener.receive();
    assert(remote.transport === "udp");
    assertEquals(remote.port, 5353);
    assertEquals(recvd.length, 3);
    assertEquals(1, recvd[0]);
    assertEquals(2, recvd[1]);
    assertEquals(3, recvd[2]);
    membership.leave();
    listener.close();
  },
);

Deno.test(
  { permissions: { net: true } },
  async function netUdpConcurrentSendReceive() {
    const socket = Deno.listenDatagram({
      port: listenPort,
      transport: "udp",
      hostname: "127.0.0.1",
    });
    assert(socket.addr.transport === "udp");
    assertEquals(socket.addr.port, listenPort);
    assertEquals(socket.addr.hostname, "127.0.0.1");

    const recvPromise = socket.receive();

    const sendBuf = new Uint8Array([1, 2, 3]);
    const sendLen = await socket.send(sendBuf, socket.addr);
    assertEquals(sendLen, 3);

    const [recvBuf, _recvAddr] = await recvPromise;
    assertEquals(recvBuf.length, 3);
    assertEquals(1, recvBuf[0]);
    assertEquals(2, recvBuf[1]);
    assertEquals(3, recvBuf[2]);

    socket.close();
  },
);

Deno.test(
  { permissions: { net: true } },
  async function netUdpBorrowMutError() {
    const socket = Deno.listenDatagram({
      port: listenPort,
      transport: "udp",
      hostname: "127.0.0.1",
    });
    // Panic happened on second send: BorrowMutError
    const a = socket.send(new Uint8Array(), socket.addr);
    const b = socket.send(new Uint8Array(), socket.addr);
    await Promise.all([a, b]);
    socket.close();
  },
);

Deno.test(
  {
    ignore: Deno.build.os === "windows",
    permissions: { read: true, write: true, net: true },
  },
  async function netUnixPacketSendReceive() {
    const aliceFilePath = tmpUnixSocketPath();
    const alice = Deno.listenDatagram({
      path: aliceFilePath,
      transport: "unixpacket",
    });
    assert(alice.addr.transport === "unixpacket");
    assertEquals(alice.addr.path, aliceFilePath);

    const bobFilePath = tmpUnixSocketPath();
    const bob = Deno.listenDatagram({
      path: bobFilePath,
      transport: "unixpacket",
    });
    assert(bob.addr.transport === "unixpacket");
    assertEquals(bob.addr.path, bobFilePath);

    const sent = new Uint8Array([1, 2, 3]);
    const byteLength = await alice.send(sent, bob.addr);
    assertEquals(byteLength, 3);

    const [recvd, remote] = await bob.receive();
    assert(remote.transport === "unixpacket");
    assertEquals(remote.path, aliceFilePath);
    assertEquals(recvd.length, 3);
    assertEquals(1, recvd[0]);
    assertEquals(2, recvd[1]);
    assertEquals(3, recvd[2]);
    alice.close();
    bob.close();
  },
);

// TODO(lucacasonato): support concurrent reads and writes on unixpacket sockets
Deno.test(
  { ignore: true, permissions: { read: true, write: true, net: true } },
  async function netUnixPacketConcurrentSendReceive() {
    const filePath = tmpUnixSocketPath();
    const socket = Deno.listenDatagram({
      path: filePath,
      transport: "unixpacket",
    });
    assert(socket.addr.transport === "unixpacket");
    assertEquals(socket.addr.path, filePath);

    const recvPromise = socket.receive();

    const sendBuf = new Uint8Array([1, 2, 3]);
    const sendLen = await socket.send(sendBuf, socket.addr);
    assertEquals(sendLen, 3);

    const [recvBuf, _recvAddr] = await recvPromise;
    assertEquals(recvBuf.length, 3);
    assertEquals(1, recvBuf[0]);
    assertEquals(2, recvBuf[1]);
    assertEquals(3, recvBuf[2]);

    socket.close();
  },
);

Deno.test(
  { permissions: { net: true } },
  async function netTcpListenIteratorBreakClosesResource() {
    async function iterate(listener: Deno.Listener) {
      let i = 0;

      for await (const conn of listener) {
        conn.close();
        i++;

        if (i > 1) {
          break;
        }
      }
    }

    const addr = { hostname: "127.0.0.1", port: 8888 };
    const listener = Deno.listen(addr);
    const iteratePromise = iterate(listener);

    await delay(100);
    const conn1 = await Deno.connect(addr);
    conn1.close();
    const conn2 = await Deno.connect(addr);
    conn2.close();

    await iteratePromise;
  },
);

Deno.test(
  { permissions: { net: true } },
  async function netTcpListenCloseWhileIterating() {
    const listener = Deno.listen({ port: 8001 });
    const nextWhileClosing = listener[Symbol.asyncIterator]().next();
    listener.close();
    assertEquals(await nextWhileClosing, { value: undefined, done: true });

    const nextAfterClosing = listener[Symbol.asyncIterator]().next();
    assertEquals(await nextAfterClosing, { value: undefined, done: true });
  },
);

Deno.test(
  { permissions: { net: true } },
  async function netUdpListenCloseWhileIterating() {
    const socket = Deno.listenDatagram({ port: 8000, transport: "udp" });
    const nextWhileClosing = socket[Symbol.asyncIterator]().next();
    socket.close();
    assertEquals(await nextWhileClosing, { value: undefined, done: true });

    const nextAfterClosing = socket[Symbol.asyncIterator]().next();
    assertEquals(await nextAfterClosing, { value: undefined, done: true });
  },
);

Deno.test(
  {
    ignore: Deno.build.os === "windows",
    permissions: { read: true, write: true, net: true },
  },
  async function netUnixListenCloseWhileIterating() {
    const filePath = tmpUnixSocketPath();
    const socket = Deno.listen({ path: filePath, transport: "unix" });
    const nextWhileClosing = socket[Symbol.asyncIterator]().next();
    socket.close();
    assertEquals(await nextWhileClosing, { value: undefined, done: true });

    const nextAfterClosing = socket[Symbol.asyncIterator]().next();
    assertEquals(await nextAfterClosing, { value: undefined, done: true });
  },
);

Deno.test(
  {
    ignore: Deno.build.os === "windows",
    permissions: { read: true, write: true, net: true },
  },
  async function netUnixPacketListenCloseWhileIterating() {
    const filePath = tmpUnixSocketPath();
    const socket = Deno.listenDatagram({
      path: filePath,
      transport: "unixpacket",
    });
    const nextWhileClosing = socket[Symbol.asyncIterator]().next();
    socket.close();
    assertEquals(await nextWhileClosing, { value: undefined, done: true });

    const nextAfterClosing = socket[Symbol.asyncIterator]().next();
    assertEquals(await nextAfterClosing, { value: undefined, done: true });
  },
);

Deno.test(
  { permissions: { net: true } },
  async function netListenAsyncIterator() {
    const addr = { hostname: "127.0.0.1", port: listenPort };
    const listener = Deno.listen(addr);
    const runAsyncIterator = async () => {
      for await (const conn of listener) {
        await conn.write(new Uint8Array([1, 2, 3]));
        conn.close();
      }
    };
    runAsyncIterator();
    const conn = await Deno.connect(addr);
    const buf = new Uint8Array(1024);
    const readResult = await conn.read(buf);
    assertEquals(3, readResult);
    assertEquals(1, buf[0]);
    assertEquals(2, buf[1]);
    assertEquals(3, buf[2]);

    assert(readResult !== null);

    const readResult2 = await conn.read(buf);
    assertEquals(readResult2, null);

    listener.close();
    conn.close();
  },
);

Deno.test(
  {
    permissions: { net: true },
  },
  async function netCloseWriteSuccess() {
    const addr = { hostname: "127.0.0.1", port: listenPort };
    const listener = Deno.listen(addr);
    const { promise: closePromise, resolve } = Promise.withResolvers<void>();
    listener.accept().then(async (conn) => {
      await conn.write(new Uint8Array([1, 2, 3]));
      await closePromise;
      conn.close();
    });
    const conn = await Deno.connect(addr);
    conn.closeWrite(); // closing write
    const buf = new Uint8Array(1024);
    // Check read not impacted
    const readResult = await conn.read(buf);
    assertEquals(3, readResult);
    assertEquals(1, buf[0]);
    assertEquals(2, buf[1]);
    assertEquals(3, buf[2]);
    // Verify that the write end of the socket is closed.
    // TODO(piscisaureus): assert that thrown error is of a specific type.
    await assertRejects(async () => {
      await conn.write(new Uint8Array([1, 2, 3]));
    });
    resolve();
    listener.close();
    conn.close();
  },
);

Deno.test(
  {
    // https://github.com/denoland/deno/issues/11580
    ignore: Deno.build.os === "darwin" && isCI,
    permissions: { net: true },
  },
  async function netHangsOnClose() {
    let acceptedConn: Deno.Conn;

    async function iteratorReq(listener: Deno.Listener) {
      const p = new Uint8Array(10);
      const conn = await listener.accept();
      acceptedConn = conn;

      try {
        while (true) {
          const nread = await conn.read(p);
          if (nread === null) {
            break;
          }
          await conn.write(new Uint8Array([1, 2, 3]));
        }
      } catch (err) {
        assert(err);
        assert(err instanceof Deno.errors.Interrupted);
      }
    }

    const addr = { hostname: "127.0.0.1", port: listenPort };
    const listener = Deno.listen(addr);
    const listenerPromise = iteratorReq(listener);
    const connectionPromise = (async () => {
      const conn = await Deno.connect(addr);
      await conn.write(new Uint8Array([1, 2, 3, 4]));
      const buf = new Uint8Array(10);
      await conn.read(buf);
      conn!.close();
      acceptedConn!.close();
      listener.close();
    })();

    await Promise.all([
      listenerPromise,
      connectionPromise,
    ]);
  },
);

Deno.test(
  {
    permissions: { net: true },
  },
  function netExplicitUndefinedHostname() {
    const listener = Deno.listen({ hostname: undefined, port: 8080 });
    assertEquals((listener.addr as Deno.NetAddr).hostname, "0.0.0.0");
    listener.close();
  },
);

Deno.test(
  {
    ignore: Deno.build.os !== "linux",
    permissions: { read: true, write: true, net: true },
  },
  async function netUnixAbstractPathAddr() {
    const path = `\0deno-net-test-${crypto.randomUUID()}`;
    const listener = Deno.listen({
      path,
      transport: "unix",
    });
    assertEquals(listener.addr.path, path);

    const acceptPromise = listener.accept();
    const conn = await Deno.connect({ path, transport: "unix" });
    assertEquals(conn.remoteAddr.path, path);

    const acceptedConn = await acceptPromise;
    assertEquals(acceptedConn.localAddr.path, path);

    conn.close();
    acceptedConn.close();
    listener.close();
  },
);

Deno.test(
  {
    ignore: Deno.build.os !== "linux",
    permissions: { read: true, write: true, net: true },
  },
  async function netUnixPacketAbstractPathAddr() {
    const alicePath = `\0deno-net-test-${crypto.randomUUID()}`;
    const alice = Deno.listenDatagram({
      path: alicePath,
      transport: "unixpacket",
    });
    assert(alice.addr.transport === "unixpacket");
    assertEquals(alice.addr.path, alicePath);

    const bobPath = `\0deno-net-test-${crypto.randomUUID()}`;
    const bob = Deno.listenDatagram({
      path: bobPath,
      transport: "unixpacket",
    });
    assert(bob.addr.transport === "unixpacket");
    assertEquals(bob.addr.path, bobPath);

    const sent = new Uint8Array([1, 2, 3]);
    assertEquals(await alice.send(sent, bob.addr), sent.byteLength);

    const [received, remoteAddr] = await bob.receive();
    assert(remoteAddr.transport === "unixpacket");
    assertEquals(remoteAddr.path, alicePath);
    assertEquals(received, sent);

    alice.close();
    bob.close();
  },
);

Deno.test({ permissions: { net: true } }, async function whatwgStreams() {
  const server = (async () => {
    const listener = Deno.listen({ hostname: "127.0.0.1", port: listenPort });
    const conn = await listener.accept();
    await conn.readable.pipeTo(conn.writable);
    listener.close();
  })();

  const conn = await Deno.connect({ hostname: "127.0.0.1", port: listenPort });
  const reader = conn.readable.getReader();
  const writer = conn.writable.getWriter();
  const encoder = new TextEncoder();
  const decoder = new TextDecoder();
  const data = encoder.encode("Hello World");

  await writer.write(data);
  const { value, done } = await reader.read();
  assert(!done);
  assertEquals(decoder.decode(value), "Hello World");
  await reader.cancel();
  await server;
});

Deno.test(
  { permissions: { read: true } },
  async function readableStreamTextEncoderPipe() {
    const filename = "tests/testdata/assets/hello.txt";
    const file = await Deno.open(filename);
    const readable = file.readable.pipeThrough(new TextDecoderStream());
    const chunks = [];
    for await (const chunk of readable) {
      chunks.push(chunk);
    }
    assertEquals(chunks.length, 1);
    assertEquals(chunks[0].length, 12);
  },
);

Deno.test(
  { permissions: { read: true, write: true } },
  async function writableStream() {
    const path = await Deno.makeTempFile();
    const file = await Deno.open(path, { write: true });
    assert(file.writable instanceof WritableStream);
    const readable = new ReadableStream({
      start(controller) {
        controller.enqueue(new TextEncoder().encode("hello "));
        controller.enqueue(new TextEncoder().encode("world!"));
        controller.close();
      },
    });
    await readable.pipeTo(file.writable);
    const res = await Deno.readTextFile(path);
    assertEquals(res, "hello world!");
  },
);

Deno.test(
  { permissions: { read: true, run: true } },
  async function netListenUnref() {
    const [statusCode, _output] = await execCode(`
      async function main() {
        const listener = Deno.listen({ port: ${listenPort} });
        listener.unref();
        await listener.accept(); // This doesn't block the program from exiting
      }
      main();
    `);
    assertEquals(statusCode, 0);
  },
);

Deno.test(
  { permissions: { read: true, run: true } },
  async function netListenUnref2() {
    const [statusCode, _output] = await execCode(`
      async function main() {
        const listener = Deno.listen({ port: ${listenPort} });
        await listener.accept();
        listener.unref();
        await listener.accept(); // The program exits here
        throw new Error(); // The program doesn't reach here
      }
      main();
      const conn = await Deno.connect({ port: ${listenPort} });
      conn.close();
    `);
    assertEquals(statusCode, 0);
  },
);

Deno.test(
  { permissions: { read: true, run: true, net: true } },
  async function netListenUnrefAndRef() {
    const p = execCode2(`
      async function main() {
        const listener = Deno.listen({ port: ${listenPort} });
        listener.unref();
        listener.ref(); // This restores 'ref' state of listener
        console.log("started");
        await listener.accept();
        console.log("accepted")
      }
      main();
    `);
    await p.waitStdoutText("started");
    const conn = await Deno.connect({ port: listenPort });
    conn.close();
    const [statusCode, output] = await p.finished();
    assertEquals(statusCode, 0);
    assertEquals(output.trim(), "started\naccepted");
  },
);

Deno.test(
  { permissions: { net: true } },
  async function netListenUnrefConcurrentAccept() {
    const timer = setTimeout(() => {}, 1000);
    const listener = Deno.listen({ port: listenPort });
    listener.accept().catch(() => {});
    listener.unref();
    // Unref'd listener still causes Busy error
    // on concurrent accept calls.
    await assertRejects(async () => {
      await listener.accept(); // The program exits here
    }, Deno.errors.Busy);
    listener.close();
    clearTimeout(timer);
  },
);

Deno.test({
  ignore: Deno.build.os === "windows",
  permissions: { read: true, write: true, net: true },
}, function netUnixListenAddrAlreadyInUse() {
  const filePath = tmpUnixSocketPath();
  const listener = Deno.listen({ path: filePath, transport: "unix" });
  assertThrows(
    () => {
      Deno.listen({ path: filePath, transport: "unix" });
    },
    Deno.errors.AddrInUse,
  );
  listener.close();
});

Deno.test(
  { permissions: { net: true, read: true, run: true } },
  async function netConnUnref() {
    const listener = Deno.listen({ port: listenPort });
    const intervalId = setInterval(() => {}); // This keeps event loop alive.

    const program = execCode(`
      async function main() {
        const conn = await Deno.connect({ port: ${listenPort} });
        conn.unref();
        await conn.read(new Uint8Array(10)); // The program exits here
        throw new Error(); // The program doesn't reach here
      }
      main();
    `);
    const conn = await listener.accept();
    const [statusCode, _output] = await program;
    conn.close();
    listener.close();
    clearInterval(intervalId);
    assertEquals(statusCode, 0);
  },
);

Deno.test(
  { permissions: { net: true, read: true, run: true } },
  async function netConnUnrefReadable() {
    const listener = Deno.listen({ port: listenPort });
    const intervalId = setInterval(() => {}); // This keeps event loop alive.

    const program = execCode(`
      async function main() {
        const conn = await Deno.connect({ port: ${listenPort} });
        conn.unref();
        const reader = conn.readable.getReader();
        await reader.read(); // The program exits here
        throw new Error(); // The program doesn't reach here
      }
      main();
    `);
    const conn = await listener.accept();
    const [statusCode, _output] = await program;
    conn.close();
    listener.close();
    clearInterval(intervalId);
    assertEquals(statusCode, 0);
  },
);

Deno.test({ permissions: { net: true } }, async function netTcpReuseAddr() {
  const listener1 = Deno.listen({
    hostname: "127.0.0.1",
    port: listenPort,
  });
  listener1.accept().then(
    (conn) => {
      conn.close();
    },
  );

  const conn1 = await Deno.connect({ hostname: "127.0.0.1", port: listenPort });
  const buf1 = new Uint8Array(1024);
  await conn1.read(buf1);
  listener1.close();
  conn1.close();

  const listener2 = Deno.listen({
    hostname: "127.0.0.1",
    port: listenPort,
  });

  listener2.accept().then(
    (conn) => {
      conn.close();
    },
  );

  const conn2 = await Deno.connect({ hostname: "127.0.0.1", port: listenPort });
  const buf2 = new Uint8Array(1024);
  await conn2.read(buf2);

  listener2.close();
  conn2.close();
});

Deno.test(
  { permissions: { net: true } },
  async function netUdpReuseAddr() {
    const sender = Deno.listenDatagram({
      port: 4002,
      transport: "udp",
      hostname: "127.0.0.1",
    });
    const listener1 = Deno.listenDatagram({
      port: 4000,
      transport: "udp",
      reuseAddress: true,
      hostname: "127.0.0.1",
    });
    const listener2 = Deno.listenDatagram({
      port: 4000,
      transport: "udp",
      reuseAddress: true,
      hostname: "127.0.0.1",
    });

    const sent = new Uint8Array([1, 2, 3]);
    await sender.send(sent, listener1.addr);
    await Promise.any([listener1.receive(), listener2.receive()]).then(
      ([recvd, remote]) => {
        assert(remote.transport === "udp");
        assertEquals(recvd.length, 3);
        assertEquals(1, recvd[0]);
        assertEquals(2, recvd[1]);
        assertEquals(3, recvd[2]);
      },
    );
    sender.close();
    listener1.close();
    listener2.close();
  },
);

Deno.test(
  { permissions: { net: true } },
  function netUdpNoReuseAddr() {
    let listener1;
    try {
      listener1 = Deno.listenDatagram({
        port: 4001,
        transport: "udp",
        reuseAddress: false,
      });
    } catch (err) {
      assert(err);
      assert(err instanceof Deno.errors.AddrInUse); // AddrInUse from previous test
    }

    assertThrows(() => {
      Deno.listenDatagram({
        port: 4001,
        transport: "udp",
        reuseAddress: false,
      });
    }, Deno.errors.AddrInUse);
    if (typeof listener1 !== "undefined") {
      listener1.close();
    }
  },
);

Deno.test({
  ignore: Deno.build.os !== "linux",
  permissions: { net: true },
}, async function netTcpListenReusePort() {
  const port = 4003;
  const listener1 = Deno.listen({ port, reusePort: true });
  const listener2 = Deno.listen({ port, reusePort: true });
  let p1;
  let p2;
  let listener1Recv = false;
  let listener2Recv = false;
  while (!listener1Recv || !listener2Recv) {
    if (!p1) {
      p1 = listener1.accept().then((conn) => {
        conn.close();
        listener1Recv = true;
        p1 = undefined;
        listener1.close();
      }).catch(() => {});
    }
    if (!p2) {
      p2 = listener2.accept().then((conn) => {
        conn.close();
        listener2Recv = true;
        p2 = undefined;
        listener2.close();
      }).catch(() => {});
    }
    const conn = await Deno.connect({ port });
    conn.close();
    await Promise.race([p1, p2]);
  }
});

Deno.test(
  { permissions: { net: true }, ignore: true },
  async function netTcpWithAbortSignal() {
    const controller = new AbortController();
    setTimeout(() => controller.abort(), 1_000);
    const error = await assertRejects(
      async () => {
        await Deno.connect({
          hostname: "deno.com",
          port: 50000,
          transport: "tcp",
          signal: controller.signal,
        });
      },
    );
    assert(error instanceof DOMException);
    assertEquals(error.name, "AbortError");
  },
);

Deno.test(
  { permissions: { net: true } },
  async function netTcpInvalidPortWithAbortSignalDoesNotLeak() {
    const controller = new AbortController();
    await assertRejects(
      () => Deno.connect({ port: -1, signal: controller.signal }),
      RangeError,
    );
  },
);

Deno.test(
  { permissions: { net: false } },
  async function netTcpPermissionErrorWithAbortSignalDoesNotLeak() {
    const controller = new AbortController();
    await assertRejects(
      () =>
        Deno.connect({
          hostname: "127.0.0.1",
          port: 80,
          signal: controller.signal,
        }),
      Deno.errors.NotCapable,
    );
  },
);

Deno.test(
  { permissions: { net: false } },
  async function resolveDnsPermissionErrorWithAbortSignalDoesNotLeak() {
    const controller = new AbortController();
    await assertRejects(
      () =>
        Deno.resolveDns("example.com", "A", {
          nameServer: { ipAddr: "127.0.0.1" },
          signal: controller.signal,
        }),
      Deno.errors.NotCapable,
    );
  },
);

Deno.test({
  ignore: Deno.build.os === "linux",
  permissions: { net: true },
}, function netTcpListenReusePortDoesNothing() {
  const listener1 = Deno.listen({ port: 4003, reusePort: true });
  assertThrows(() => {
    Deno.listen({ port: 4003, reusePort: true });
  }, Deno.errors.AddrInUse);
  listener1.close();
});

Deno.test({
  permissions: { net: true },
}, function netTcpListenDoesNotThrowOnStringPort() {
  // @ts-ignore String port is not allowed by typing, but it shouldn't throw
  // for backwards compatibility.
  const listener = Deno.listen({ hostname: "localhost", port: "0" });
  listener.close();
});

Deno.test(
  { permissions: { net: true } },
  async function listenerExplicitResourceManagement() {
    let done: Promise<Deno.errors.BadResource>;

    {
      using listener = Deno.listen({ port: listenPort });

      done = assertRejects(
        () => listener.accept(),
        Deno.errors.BadResource,
      );
    }

    await done;
  },
);

Deno.test(
  { permissions: { net: true } },
  async function listenerExplicitResourceManagementManualClose() {
    using listener = Deno.listen({ port: listenPort });
    listener.close();
    await assertRejects( // definitely closed
      () => listener.accept(),
      Deno.errors.BadResource,
    );
    // calling [Symbol.dispose] after manual close is a no-op
  },
);

// Regression test for GHSA-rjhc-mq2r-fp9w: Unix socket connect/listen used
// to check only filesystem permissions, letting scripts with
// `--allow-read=/var/run/docker.sock` reach Docker without any `--allow-net`
// grant. They now also require an `--allow-net=unix:<path>` rule (or
// unscoped `--allow-net`).
Deno.test(
  {
    ignore: Deno.build.os === "windows",
    permissions: { read: true, write: true },
  },
  function netUnixListenRequiresAllowNet() {
    const filePath = tmpUnixSocketPath();
    assertThrows(
      () => Deno.listen({ path: filePath, transport: "unix" }),
      Deno.errors.NotCapable,
    );
  },
);

Deno.test(
  {
    ignore: Deno.build.os === "windows",
    permissions: { read: true, write: true },
  },
  async function netUnixConnectRequiresAllowNet() {
    const filePath = tmpUnixSocketPath();
    await assertRejects(
      () => Deno.connect({ path: filePath, transport: "unix" }),
      Deno.errors.NotCapable,
    );
  },
);

// Scoped form must match the exact path — a different unix path does not
// grant access to this one.
Deno.test(
  {
    ignore: Deno.build.os === "windows",
    permissions: {
      read: true,
      write: true,
      net: ["unix:/some/other/path.sock"],
    },
  },
  function netUnixScopedAllowNetMatchesExactly() {
    const filePath = tmpUnixSocketPath();
    assertThrows(
      () => Deno.listen({ path: filePath, transport: "unix" }),
      Deno.errors.NotCapable,
    );
  },
);

// The datagram (unixpacket) half of the boundary must be guarded too:
// `Deno.listenDatagram({ transport: "unixpacket" })` is denied without an
// `--allow-net=unix:<path>` grant.
Deno.test(
  {
    ignore: Deno.build.os === "windows",
    permissions: { read: true, write: true },
  },
  function netUnixPacketListenRequiresAllowNet() {
    const filePath = tmpUnixSocketPath();
    assertThrows(
      () => Deno.listenDatagram({ path: filePath, transport: "unixpacket" }),
      Deno.errors.NotCapable,
    );
  },
);

// `DatagramConn.send()` checks the destination path independently of the
// listen path. Here the socket is listened with `--allow-net` scoped to its
// own path, but sending to a different (ungranted) path is denied. Run in a
// subprocess (`deno run`, not `deno eval` which has implicit full permissions)
// so the dynamic socket paths can be passed in the allow-net rule.
Deno.test(
  {
    ignore: Deno.build.os === "windows",
    permissions: { read: true, write: true, run: true },
  },
  async function netUnixPacketSendRequiresAllowNet() {
    const alicePath = tmpUnixSocketPath();
    const bobPath = tmpUnixSocketPath();
    const scriptPath = Deno.makeTempFileSync({ suffix: ".js" });
    Deno.writeTextFileSync(
      scriptPath,
      `
      const alice = Deno.listenDatagram({
        path: ${JSON.stringify(alicePath)},
        transport: "unixpacket",
      });
      try {
        await alice.send(new Uint8Array([1, 2, 3]), {
          path: ${JSON.stringify(bobPath)},
          transport: "unixpacket",
        });
        console.log("SENT");
      } catch (e) {
        console.log(
          e instanceof Deno.errors.NotCapable ? "NOT_CAPABLE" : "OTHER:" + e.name,
        );
      } finally {
        alice.close();
      }
      `,
    );
    const [status, output] = await execCode3(Deno.execPath(), [
      "run",
      "--unstable-net",
      "--allow-read",
      "--allow-write",
      `--allow-net=unix:${alicePath}`,
      scriptPath,
    ]).finished();
    assertEquals(status, 0);
    assertStringIncludes(output, "NOT_CAPABLE");
  },
);

// A `--allow-net=unix:<path>` rule scoped to the exact socket path grants both
// listen and connect. Run in a subprocess so the dynamic socket path can be
// passed in the allow-net rule.
Deno.test(
  {
    ignore: Deno.build.os === "windows",
    permissions: { read: true, write: true, run: true },
  },
  async function netUnixScopedAllowNetGrantsAccess() {
    const socketPath = tmpUnixSocketPath();
    const scriptPath = Deno.makeTempFileSync({ suffix: ".js" });
    Deno.writeTextFileSync(
      scriptPath,
      `
      const listener = Deno.listen({
        path: ${JSON.stringify(socketPath)},
        transport: "unix",
      });
      const conn = await Deno.connect({
        path: ${JSON.stringify(socketPath)},
        transport: "unix",
      });
      conn.close();
      listener.close();
      console.log("OK");
      `,
    );
    const [status, output] = await execCode3(Deno.execPath(), [
      "run",
      "--allow-read",
      "--allow-write",
      `--allow-net=unix:${socketPath}`,
      scriptPath,
    ]).finished();
    assertEquals(status, 0);
    assertStringIncludes(output, "OK");
  },
);

// A `unix:` rule must be an absolute path: a relative path is rejected at flag
// parse time before the script runs.
Deno.test(
  {
    ignore: Deno.build.os === "windows",
    permissions: { read: true, write: true, run: true },
  },
  async function netUnixAllowNetRejectsNonAbsolutePath() {
    const scriptPath = Deno.makeTempFileSync({ suffix: ".js" });
    Deno.writeTextFileSync(scriptPath, "console.log('UNREACHABLE');");
    const { code, stdout, stderr } = await new Deno.Command(Deno.execPath(), {
      args: ["run", "--allow-net=unix:relative.sock", scriptPath],
      stdout: "piped",
      stderr: "piped",
    }).output();
    const decoder = new TextDecoder();
    assert(code !== 0);
    assertEquals(decoder.decode(stdout), "");
    assertStringIncludes(decoder.decode(stderr), "invalid unix socket");
  },
);

// A `unix:` rule is lexically normalized at parse time, so a rule spelled
// with `..`/`.` components still matches a connect to the normalized path.
Deno.test(
  {
    ignore: Deno.build.os === "windows",
    permissions: { read: true, write: true, run: true },
  },
  async function netUnixScopedAllowNetNormalizesRulePath() {
    const socketPath = tmpUnixSocketPath();
    const parts = socketPath.split("/");
    const base = parts.pop()!;
    const dir = parts.pop()!;
    const denormalizedPath = [...parts, dir, "..", dir, ".", base].join("/");
    const scriptPath = Deno.makeTempFileSync({ suffix: ".js" });
    Deno.writeTextFileSync(
      scriptPath,
      `
      const listener = Deno.listen({
        path: ${JSON.stringify(socketPath)},
        transport: "unix",
      });
      listener.close();
      console.log("OK");
      `,
    );
    const [status, output] = await execCode3(Deno.execPath(), [
      "run",
      "--allow-read",
      "--allow-write",
      `--allow-net=unix:${denormalizedPath}`,
      scriptPath,
    ]).finished();
    assertEquals(status, 0);
    assertStringIncludes(output, "OK");
  },
);

Deno.test(
  { permissions: { net: true } },
  async function netTcpCloseCancelsPendingReadAndSendsFin() {
    // Regression test for #35292: closing a TCP conn while a read is in flight
    // must cancel that read and send a FIN, so the peer observes EOF. The
    // Resource trait's default `cancel_read_ops` is a no-op; net resources must
    // override it, otherwise `close()` leaves the socket open until the process
    // exits.
    const listener = Deno.listen({ hostname: "127.0.0.1", port: 0 });
    const port = (listener.addr as Deno.NetAddr).port;

    const serverSawEof = (async () => {
      const conn = await listener.accept();
      const buf = new Uint8Array(64);
      const n = await conn.read(buf);
      conn.close();
      return n === null;
    })();

    const conn = await Deno.connect({ hostname: "127.0.0.1", port });
    // Put a read in flight, then close while it is pending.
    const reader = conn.readable.getReader();
    const pendingRead = reader.read().catch(() => {});
    await delay(100);
    conn.close();

    // Abort the timeout once the race settles so the test sanitizer does not
    // report a leaked timer.
    const ac = new AbortController();
    const sawEof = await Promise.race([
      serverSawEof,
      delay(2000, { signal: ac.signal }).then(() => false, () => false),
    ]);
    ac.abort();
    assert(
      sawEof,
      "peer did not observe FIN after conn.close() with a pending read",
    );

    await pendingRead;
    try {
      reader.releaseLock();
    } catch {
      // already released
    }
    listener.close();
  },
);

Deno.test(
  {
    ignore: Deno.build.os === "windows",
    permissions: { read: true, write: true, net: true },
  },
  async function netUnixCloseCancelsPendingReadAndSendsEof() {
    // Same as the TCP regression test above, for Unix domain sockets
    // (UnixStreamResource shares the same fix).
    const filePath = tmpUnixSocketPath();
    const listener = Deno.listen({ path: filePath, transport: "unix" });

    const serverSawEof = (async () => {
      const conn = await listener.accept();
      const buf = new Uint8Array(64);
      const n = await conn.read(buf);
      conn.close();
      return n === null;
    })();

    const conn = await Deno.connect({ path: filePath, transport: "unix" });
    const reader = conn.readable.getReader();
    const pendingRead = reader.read().catch(() => {});
    await delay(100);
    conn.close();

    const ac = new AbortController();
    const sawEof = await Promise.race([
      serverSawEof,
      delay(2000, { signal: ac.signal }).then(() => false, () => false),
    ]);
    ac.abort();
    assert(
      sawEof,
      "peer did not observe EOF after conn.close() with a pending read",
    );

    await pendingRead;
    try {
      reader.releaseLock();
    } catch {
      // already released
    }
    listener.close();
  },
);
