async function* makeNetstringIterator(
  input,
  { name = '<unknown>', maxMessageLength = 999999999 } = {},
) {
  // eslint-disable-next-line no-bitwise
  const maxPrefixLength = `${maxMessageLength | 0}:`.length;

  // byte offset of data consumed so far in the input stream
  let offset = 0;

  // The iterator can be in 2 states: waiting for the length, or waiting for the data
  // - When waiting for the length, the lengthBuffer is an array containing
  //   digits charCodes for the length prefix
  // - When waiting for the data, the dataBuffer is either:
  //   - null to indicate no data has been received yet.
  //   - A newly allocated buffer large enough to accommodate the whole expected data.
  //   In either case, remainingDataLength contains the length of the data to read.
  //   If the whole data is received in one chunk, no copy is made.
  /** @type {number[] | null} */
  let lengthBuffer = [];
  /** @type {Uint8Array | null} */
  let dataBuffer = null;
  let remainingDataLength = -1;

  for await (const chunk of input) {
    let buffer = chunk;

    while (buffer.length) {
      // Waiting for full length prefix
      if (lengthBuffer) {
        let i = 0;
        while (i < buffer.length) {
          const c = buffer[i];
          i += 1;
          if (c >= ZERO && c <= NINE) {
            lengthBuffer.push(c);
            if (lengthBuffer.length === maxPrefixLength) {
              throw Error(
                `Too long netstring length prefix ${JSON.stringify(
                  String.fromCharCode(...lengthBuffer),
                )}... at offset ${offset} of ${name}`,
              );
            }
          } else if (c === COLON && lengthBuffer.length) {
            lengthBuffer.push(c);
            break;
          } else {
            throw Error(
              `Invalid netstring length prefix ${JSON.stringify(
                String.fromCharCode(...lengthBuffer, c),
              )} at offset ${offset} of ${name}`,
            );
          }
        }

        buffer = buffer.subarray(i);

        if (lengthBuffer[lengthBuffer.length - 1] === COLON) {
          lengthBuffer.pop();
          const prefix = String.fromCharCode(...lengthBuffer);
          remainingDataLength = +prefix;
          if (Number.isNaN(remainingDataLength)) {
            throw Error(
              `Invalid netstring prefix length ${prefix} at offset ${offset} of ${name}`,
            );
          } else if (remainingDataLength > maxMessageLength) {
            throw Error(
              `Netstring message too big (length ${remainingDataLength}) at offset ${offset} of ${name}`,
            );
          }
          offset += lengthBuffer.length + 1;
          lengthBuffer = null;
        }
      }

      // Waiting for data
      if (!lengthBuffer) {
        if (buffer.length > remainingDataLength) {
          const remainingData = buffer.subarray(0, remainingDataLength);
          const data = dataBuffer
            ? (dataBuffer.set(
                remainingData,
                dataBuffer.length - remainingDataLength,
              ),
              dataBuffer)
            : remainingData;
          dataBuffer = null;
          offset += data.length;
          if (buffer[remainingDataLength] !== COMMA) {
            throw Error(
              `Invalid netstring separator "${String.fromCharCode(
                buffer[remainingDataLength],
              )} at offset ${offset} of ${name}`,
            );
          }
          offset += 1;
          buffer = buffer.subarray(remainingDataLength + 1);
          remainingDataLength = -1;
          lengthBuffer = [];
          yield data;
        } else if (buffer.length) {
          if (!dataBuffer && buffer.length === remainingDataLength) {
            dataBuffer = buffer;
          } else {
            dataBuffer = dataBuffer || new Uint8Array(remainingDataLength);
            dataBuffer.set(buffer, dataBuffer.length - remainingDataLength);
          }
          remainingDataLength -= buffer.length;
          buffer = buffer.subarray(buffer.length);
        }
      }
    }
  }

  if (!lengthBuffer) {
    throw Error(`Unexpected dangling message at offset ${offset} of ${name}`);
  }

  return undefined;
}
@param {Iterable<Uint8Array> | AsyncIterable<Uint8Array>} input
@param {object} opts
@param {string} [opts.name]
@param {number} [opts.maxMessageLength]