NdefMessage.fromBytes constructor
NdefMessage.fromBytes(
- Uint8List bytes
Decodes a message from the NFC Forum wire format.
Chunked records are reassembled into one logical record, so a caller never sees NdefTypeNameFormat.unchanged.
Throws a FormatException on truncated or malformed input.
Implementation
factory NdefMessage.fromBytes(Uint8List bytes) {
final records = <NdefRecord>[];
var offset = 0;
var sawMessageBegin = false;
var sawMessageEnd = false;
// State for a chunked record in progress.
NdefTypeNameFormat? chunkFormat;
Uint8List? chunkType;
Uint8List? chunkIdentifier;
BytesBuilder? chunkPayload;
int readByte() {
if (offset >= bytes.length) throw const FormatException('truncated NDEF message');
return bytes[offset++];
}
Uint8List readBytes(int length) {
if (offset + length > bytes.length) throw const FormatException('truncated NDEF message');
final slice = Uint8List.sublistView(bytes, offset, offset + length);
offset += length;
// Copy: sublistView aliases the caller's buffer, and records are documented immutable.
return Uint8List.fromList(slice);
}
while (offset < bytes.length) {
final header = readByte();
final isChunked = (header & _flagChunked) != 0;
final isShort = (header & _flagShortRecord) != 0;
final hasId = (header & _flagIdLengthPresent) != 0;
// 0x07 is reserved by the specification and has no enum value; indexing on it blind
// would raise a RangeError out of what is otherwise a well-defined parse failure.
final formatIndex = header & _maskTypeNameFormat;
if (formatIndex >= NdefTypeNameFormat.values.length) {
throw const FormatException('reserved NDEF type name format 0x07');
}
final format = NdefTypeNameFormat.values[formatIndex];
if ((header & _flagMessageBegin) != 0) {
if (sawMessageBegin) throw const FormatException('second MB flag in one NDEF message');
sawMessageBegin = true;
} else if (records.isEmpty && chunkPayload == null) {
throw const FormatException('NDEF message does not start with an MB record');
}
if (sawMessageEnd) throw const FormatException('record after the ME flag');
final typeLength = readByte();
final int payloadLength;
if (isShort) {
payloadLength = readByte();
} else {
final b0 = readByte(), b1 = readByte(), b2 = readByte(), b3 = readByte();
// Four bytes cannot exceed 2^32-1, which Dart's int holds exactly, so there is
// nothing to guard against here; a length longer than the buffer is caught by
// readBytes below.
payloadLength = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
}
final idLength = hasId ? readByte() : 0;
final type = readBytes(typeLength);
final identifier = readBytes(idLength);
final payload = readBytes(payloadLength);
if ((header & _flagMessageEnd) != 0) sawMessageEnd = true;
if (chunkPayload != null) {
// Middle or final chunk of a record already in progress.
if (format != NdefTypeNameFormat.unchanged) {
throw const FormatException('chunk continuation must use the UNCHANGED type name format');
}
if (typeLength != 0) throw const FormatException('chunk continuation must not carry a type');
chunkPayload.add(payload);
if (!isChunked) {
records.add(
NdefRecord.fromParts(
typeNameFormat: chunkFormat!,
type: chunkType!,
identifier: chunkIdentifier!,
payload: chunkPayload.takeBytes(),
),
);
chunkFormat = null;
chunkType = null;
chunkIdentifier = null;
chunkPayload = null;
}
continue;
}
if (isChunked) {
// First chunk: its type and identifier describe the whole logical record.
if (format == NdefTypeNameFormat.unchanged) {
throw const FormatException('unexpected UNCHANGED in first chunk or logical record');
}
chunkFormat = format;
chunkType = type;
chunkIdentifier = identifier;
chunkPayload = BytesBuilder(copy: false)..add(payload);
continue;
}
// UNCHANGED only ever means "the previous record's payload continues here". Reaching
// this line means there was no chunk to continue, and letting it through would hand
// the caller a record this library's own writer refuses to encode.
if (format == NdefTypeNameFormat.unchanged) {
throw const FormatException('UNCHANGED record outside a chunked sequence');
}
records.add(
NdefRecord.fromParts(typeNameFormat: format, type: type, identifier: identifier, payload: payload),
);
}
if (chunkPayload != null) throw const FormatException('NDEF message ends inside a chunked record');
if (records.isNotEmpty && !sawMessageEnd) throw const FormatException('NDEF message has no ME record');
return NdefMessage(records);
}