List? decodeMDnsResponse()

in packages/multicast_dns/lib/src/packet.dart [214:382]


List<ResourceRecord>? decodeMDnsResponse(List<int> packet) {
  final int length = packet.length;
  if (length < _kHeaderSize) {
    return null;
  }

  final Uint8List data =
      packet is Uint8List ? packet : Uint8List.fromList(packet);
  final ByteData packetBytes = ByteData.view(data.buffer);

  final int answerCount = packetBytes.getUint16(_kAncountOffset);
  final int authorityCount = packetBytes.getUint16(_kNscountOffset);
  final int additionalCount = packetBytes.getUint16(_kArcountOffset);
  final int remainingCount = answerCount + authorityCount + additionalCount;

  if (remainingCount == 0) {
    return null;
  }

  final int questionCount = packetBytes.getUint16(_kQdcountOffset);
  int offset = _kHeaderSize;

  void checkLength(int required) {
    if (length < required) {
      throw MDnsDecodeException(required);
    }
  }

  ResourceRecord? readResourceRecord() {
    // First read the FQDN.
    final _FQDNReadResult result = _readFQDN(data, packetBytes, offset, length);
    final String fqdn = result.fqdn;
    offset += result.bytesRead;
    checkLength(offset + 2);
    final int type = packetBytes.getUint16(offset);
    offset += 2;
    // The first bit of the rrclass field is set to indicate that the answer is
    // unique and the querier should flush the cached answer for this name
    // (RFC 6762, Sec. 10.2). We ignore it for now since we don't cache answers.
    checkLength(offset + 2);
    final int resourceRecordClass = packetBytes.getUint16(offset) & 0x7fff;

    if (resourceRecordClass != ResourceRecordClass.internet) {
      // We do not support other classes.
      return null;
    }

    offset += 2;
    checkLength(offset + 4);
    final int ttl = packetBytes.getInt32(offset);
    offset += 4;

    checkLength(offset + 2);
    final int readDataLength = packetBytes.getUint16(offset);
    offset += 2;
    final int validUntil = DateTime.now().millisecondsSinceEpoch + ttl * 1000;
    switch (type) {
      case ResourceRecordType.addressIPv4:
        checkLength(offset + readDataLength);
        final StringBuffer addr = StringBuffer();
        final int stop = offset + readDataLength;
        addr.write(packetBytes.getUint8(offset));
        offset++;
        for (; offset < stop; offset++) {
          addr.write('.');
          addr.write(packetBytes.getUint8(offset));
        }
        return IPAddressResourceRecord(fqdn, validUntil,
            address: InternetAddress(addr.toString()));
      case ResourceRecordType.addressIPv6:
        checkLength(offset + readDataLength);
        final StringBuffer addr = StringBuffer();
        final int stop = offset + readDataLength;
        addr.write(packetBytes.getUint16(offset).toRadixString(16));
        offset += 2;
        for (; offset < stop; offset += 2) {
          addr.write(':');
          addr.write(packetBytes.getUint16(offset).toRadixString(16));
        }
        return IPAddressResourceRecord(
          fqdn,
          validUntil,
          address: InternetAddress(addr.toString()),
        );
      case ResourceRecordType.service:
        checkLength(offset + 2);
        final int priority = packetBytes.getUint16(offset);
        offset += 2;
        checkLength(offset + 2);
        final int weight = packetBytes.getUint16(offset);
        offset += 2;
        checkLength(offset + 2);
        final int port = packetBytes.getUint16(offset);
        offset += 2;
        final _FQDNReadResult result =
            _readFQDN(data, packetBytes, offset, length);
        offset += result.bytesRead;
        return SrvResourceRecord(
          fqdn,
          validUntil,
          target: result.fqdn,
          port: port,
          priority: priority,
          weight: weight,
        );
      case ResourceRecordType.serverPointer:
        checkLength(offset + readDataLength);
        final _FQDNReadResult result =
            _readFQDN(data, packetBytes, offset, length);
        offset += readDataLength;
        return PtrResourceRecord(
          fqdn,
          validUntil,
          domainName: result.fqdn,
        );
      case ResourceRecordType.text:
        checkLength(offset + readDataLength);
        // The first byte of the buffer is the length of the first string of
        // the TXT record. Further length-prefixed strings may follow. We
        // concatenate them with newlines.
        final StringBuffer strings = StringBuffer();
        int index = 0;
        while (index < readDataLength) {
          final int txtLength = data[offset + index];
          index++;
          if (txtLength == 0) {
            continue;
          }
          final String text = utf8.decode(
            Uint8List.view(data.buffer, offset + index, txtLength),
            allowMalformed: true,
          );
          strings.writeln(text);
          index += txtLength;
        }
        offset += readDataLength;
        return TxtResourceRecord(fqdn, validUntil, text: strings.toString());
      default:
        checkLength(offset + readDataLength);
        offset += readDataLength;
        return null;
    }
  }

  // This list can't be fixed length right now because we might get
  // resource record types we don't support, and consumers expect this list
  // to not have null entries.
  final List<ResourceRecord> result = <ResourceRecord>[];

  try {
    for (int i = 0; i < questionCount; i++) {
      final _FQDNReadResult result =
          _readFQDN(data, packetBytes, offset, length);
      offset += result.bytesRead;
      checkLength(offset + 4);
      offset += 4;
    }
    for (int i = 0; i < remainingCount; i++) {
      final ResourceRecord? record = readResourceRecord();
      if (record != null) {
        result.add(record);
      }
    }
  } on MDnsDecodeException {
    // If decoding fails return null.
    return null;
  }
  return result;
}