NdefMessage.decode constructor

NdefMessage.decode(
  1. Uint8List bytes
)

Decodes an NdefMessage from its NDEF binary wire format.

bytes must contain exactly one complete NDEF message, from the first physical record's MB bit through the last physical record's ME bit, with no leading or trailing bytes. Chunked records (CF = 1, followed by TNF = UNCHANGED continuations) are reassembled into a single logical NdefRecord.

Throws FormatException if bytes is malformed or truncated, if the MB/ME bits are inconsistent, or if a chunk sequence is malformed or left unterminated.

Implementation

factory NdefMessage.decode(Uint8List bytes) {
  var offset = 0;

  int readByte() {
    if (offset >= bytes.length) {
      throw FormatException('Truncated NDEF message.');
    }
    return bytes[offset++];
  }

  Uint8List readBytes(int length) {
    if (offset + length > bytes.length) {
      throw FormatException('Truncated NDEF message.');
    }
    final result = bytes.sublist(offset, offset + length);
    offset += length;
    return result;
  }

  final records = <NdefRecord>[];
  var isFirstPhysicalRecord = true;
  var messageEnded = false;

  // The chunk sequence currently being reassembled, if any.
  TypeNameFormat? chunkTnf;
  Uint8List? chunkType;
  Uint8List? chunkId;
  BytesBuilder? chunkPayload;

  while (offset < bytes.length) {
    if (messageEnded) {
      throw FormatException('Unexpected data after the ME record.');
    }

    final header = readByte();
    final messageBegin = header & 0x80 != 0;
    final messageEnd = header & 0x40 != 0;
    final chunkFlag = header & 0x20 != 0;
    final shortRecord = header & 0x10 != 0;
    final hasId = header & 0x08 != 0;
    final tnf = header & 0x07;

    if (isFirstPhysicalRecord != messageBegin) {
      throw FormatException('MB bit must be set on the first record only.');
    }
    isFirstPhysicalRecord = false;
    if (chunkFlag && messageEnd) {
      throw FormatException('ME bit must not be set on a chunked record.');
    }
    if (tnf == 0x07) {
      throw FormatException('Reserved TNF value 0x07 is not allowed.');
    }

    final typeLength = readByte();
    final payloadLength = shortRecord
        ? readByte()
        : (readByte() << 24) | (readByte() << 16) | (readByte() << 8) | readByte();
    final idLength = hasId ? readByte() : 0;

    final type = readBytes(typeLength);
    final identifier = readBytes(idLength);
    final payload = readBytes(payloadLength);

    final inChunk = chunkPayload != null;

    if (!inChunk && tnf == TypeNameFormat.unchanged.index) {
      throw FormatException('Unexpected UNCHANGED record outside of a chunk sequence.');
    }

    if (inChunk) {
      if (tnf != TypeNameFormat.unchanged.index || typeLength != 0 || hasId) {
        throw FormatException(
          'A chunk continuation must have TNF = UNCHANGED and no type or id.',
        );
      }
      chunkPayload.add(payload);
      if (!chunkFlag) {
        records.add(
          NdefRecord(
            typeNameFormat: chunkTnf!,
            type: chunkType!,
            identifier: chunkId!,
            payload: chunkPayload.toBytes(),
          ),
        );
        chunkTnf = null;
        chunkType = null;
        chunkId = null;
        chunkPayload = null;
      }
    } else if (chunkFlag) {
      if (typeLength == 0 && tnf != TypeNameFormat.unknown.index) {
        throw FormatException('The first chunk of a chunked record must have a type field.');
      }
      chunkTnf = TypeNameFormat.values[tnf];
      chunkType = type;
      chunkId = identifier;
      chunkPayload = BytesBuilder()..add(payload);
    } else {
      records.add(
        NdefRecord(
          typeNameFormat: TypeNameFormat.values[tnf],
          type: type,
          identifier: identifier,
          payload: payload,
        ),
      );
    }

    messageEnded = messageEnd;
  }

  if (chunkPayload != null) {
    throw FormatException('Unterminated chunked record.');
  }
  if (!messageEnded) {
    throw FormatException('Incomplete NDEF message: missing ME record.');
  }

  return NdefMessage(records: records);
}