From 94bd830da541c357100a0a9865bcefd364441b34 Mon Sep 17 00:00:00 2001 From: Shengqi Chen Date: Sat, 14 Mar 2026 00:09:33 +0800 Subject: [PATCH 1/3] Replace assert with proper exceptions for release-mode safety MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit assert() is stripped in release builds, leaving invalid states undetected. Replace all remaining assert() calls in lib/ with appropriate exception types: - RangeError.range for numeric bound checks - FormatException for malformed data during decoding - ArgumentError.value for invalid argument values Also fix misuse of ArgumentError.notNull for a non-null validation, and correct "adress" โ†’ "address" typo in error message. Made-with: Cursor --- lib/records/media/bluetooth.dart | 49 +++++++++++++++++++--------- lib/records/well_known/handover.dart | 38 +++++++++++++-------- lib/records/well_known/text.dart | 4 ++- 3 files changed, 61 insertions(+), 30 deletions(-) diff --git a/lib/records/media/bluetooth.dart b/lib/records/media/bluetooth.dart index 63fb82a..91ae377 100644 --- a/lib/records/media/bluetooth.dart +++ b/lib/records/media/bluetooth.dart @@ -17,7 +17,13 @@ class _Address { _Address.fromBytes(Uint8List? bytes) { if (bytes != null) { - assert(bytes.length == 6, "Bytes length of address data must be 6 bytes"); + if (bytes.length != 6) { + throw ArgumentError.value( + bytes.length, + "bytes.length", + "Bluetooth address must be 6 bytes", + ); + } addr = bytes; } } @@ -36,13 +42,21 @@ class _Address { if (exp.hasMatch(address)) { var nums = address.split(RegExp("[-:]")); var bts = []; - assert(nums.length == 6); + if (nums.length != 6) { + throw FormatException( + "Bluetooth address must have 6 octets, got ${nums.length}", + address, + ); + } for (var n in nums) { bts.add(int.parse(n, radix: 16)); } addr = Uint8List.fromList(bts); } else { - throw ArgumentError("Pattern of adress string is wrong, got $address"); + throw FormatException( + "Invalid Bluetooth address format", + address, + ); } } } @@ -337,10 +351,9 @@ class DeviceClass { } static String getServiceClassName(int index) { - assert( - index >= 13 && index < 24, - "Index of Service Class Name must be in [13,24)", - ); + if (index < 13 || index >= 24) { + throw RangeError.range(index, 13, 23, "index"); + } return serviceClassNameList[index - 13]; } @@ -1020,10 +1033,12 @@ class BluetoothLowEnergyRecord extends BluetoothRecord { String? get roleCapabilities { if (attributes.containsKey(EIRType.LERole)) { - assert( - attributes[EIRType.LERole]!.length == 1, - "Bytes length of LE Role must be 1", - ); + if (attributes[EIRType.LERole]!.length != 1) { + throw FormatException( + "LE Role attribute must be 1 byte, " + "got ${attributes[EIRType.LERole]!.length}", + ); + } var index = attributes[EIRType.LERole]![0]; if (index < leRoleList.length) { return leRoleList[index]; @@ -1100,10 +1115,12 @@ class BluetoothLowEnergyRecord extends BluetoothRecord { String? get appearance { if (attributes.containsKey(EIRType.Appearance)) { - assert( - attributes[EIRType.Appearance]!.length == 4, - "Bytes length of appearance must be 4", - ); + if (attributes[EIRType.Appearance]!.length != 4) { + throw FormatException( + "Appearance attribute must be 4 bytes, " + "got ${attributes[EIRType.Appearance]!.length}", + ); + } int? value = ByteUtils.bytesToInt( attributes[EIRType.Appearance], endianness: Endianness.Little, @@ -1127,7 +1144,7 @@ class BluetoothLowEnergyRecord extends BluetoothRecord { } } if (index == null) { - throw ArgumentError.notNull("Appearance $appearance is not correct"); + throw ArgumentError.value(appearance, "appearance", "Unknown appearance"); } attributes[EIRType.Appearance] = ByteUtils.intToBytes( index, diff --git a/lib/records/well_known/handover.dart b/lib/records/well_known/handover.dart index b69347a..ac9ddd3 100644 --- a/lib/records/well_known/handover.dart +++ b/lib/records/well_known/handover.dart @@ -81,10 +81,15 @@ class AlternativeCarrierRecord extends WellKnownRecord { /// Sets the carrier power state from an index. set carrierPowerStateIndex(int carrierPowerStateIndex) { - assert( - carrierPowerStateIndex >= 0 && - carrierPowerStateIndex < CarrierPowerState.values.length, - ); + if (carrierPowerStateIndex < 0 || + carrierPowerStateIndex >= CarrierPowerState.values.length) { + throw RangeError.range( + carrierPowerStateIndex, + 0, + CarrierPowerState.values.length - 1, + "carrierPowerStateIndex", + ); + } carrierPowerState = CarrierPowerState.values[carrierPowerStateIndex]; } @@ -97,10 +102,14 @@ class AlternativeCarrierRecord extends WellKnownRecord { payload.add(carrierDataReference.length); payload.addAll(carrierDataReference); - assert( - auxDataReferenceList.length < 255, - "Number of auxDataReference must be in [0,256)", - ); + if (auxDataReferenceList.length >= 255) { + throw RangeError.range( + auxDataReferenceList.length, + 0, + 254, + "auxDataReferenceList.length", + ); + } payload.add(auxDataReferenceList.length); for (int i = 0; i < auxDataReferenceList.length; i++) { @@ -125,10 +134,11 @@ class AlternativeCarrierRecord extends WellKnownRecord { auxDataReferenceList.add(stream.readBytes(auxDataReferenceLength)); } - assert( - stream.isEnd() == true, - "payload has ${stream.unreadLength} bytes after decode", - ); + if (!stream.isEnd()) { + throw FormatException( + "Payload has ${stream.unreadLength} unexpected trailing bytes", + ); + } } } @@ -195,7 +205,9 @@ class CollisionResolutionRecord extends WellKnownRecord { "RandomNumber expects int or Uint8List, got ${randomNumber.runtimeType}", ); } - assert(value >= 0 && value <= 0xffff); + if (value < 0 || value > 0xffff) { + throw RangeError.range(value, 0, 0xffff, "randomNumber"); + } _randomNumber = value; } diff --git a/lib/records/well_known/text.dart b/lib/records/well_known/text.dart index 1587aa8..37583c2 100644 --- a/lib/records/well_known/text.dart +++ b/lib/records/well_known/text.dart @@ -106,7 +106,9 @@ class TextRecord extends WellKnownRecord { int flag = stream.readByte(); int languagePayloadLength = flag & 0x3F; - assert(languagePayloadLength != 0, "language code length can not be zero"); + if (languagePayloadLength == 0) { + throw FormatException("Language code length must not be zero"); + } if (flag >> 7 == 1) { encoding = TextEncoding.UTF16; From 3a7ed31fe6273caaf669e13659d03d49f12c8f6d Mon Sep 17 00:00:00 2001 From: Shengqi Chen Date: Sat, 14 Mar 2026 00:21:49 +0800 Subject: [PATCH 2/3] Split monolithic test file into per-record-type test files Extract common helpers (testParse, testGenerate, testRoundTrip) into test/helpers.dart. Split ndef_test.dart into focused test files mirroring the lib/records/ directory structure: test/ helpers.dart record_test.dart utilities_test.dart records/ well_known/ uri_test.dart text_test.dart signature_test.dart device_info_test.dart smart_poster_test.dart handover_test.dart media/ bluetooth_test.dart wifi_test.dart Also replace assert() with expect() across all test files for better failure diagnostics, and reorganize wifi_test.dart with group() for logical structure. Made-with: Cursor --- test/helpers.dart | 43 +++ test/ndef_test.dart | 331 ------------------ test/record_test.dart | 60 ++++ test/records/media/bluetooth_test.dart | 29 ++ test/records/media/wifi_test.dart | 226 ++++++++++++ test/records/well_known/device_info_test.dart | 31 ++ test/records/well_known/handover_test.dart | 104 ++++++ test/records/well_known/signature_test.dart | 40 +++ .../records/well_known/smart_poster_test.dart | 43 +++ test/records/well_known/text_test.dart | 55 +++ test/records/well_known/uri_test.dart | 46 +++ test/utilities_test.dart | 43 +++ test/wifi_test.dart | 213 ----------- 13 files changed, 720 insertions(+), 544 deletions(-) create mode 100644 test/helpers.dart delete mode 100644 test/ndef_test.dart create mode 100644 test/record_test.dart create mode 100644 test/records/media/bluetooth_test.dart create mode 100644 test/records/media/wifi_test.dart create mode 100644 test/records/well_known/device_info_test.dart create mode 100644 test/records/well_known/handover_test.dart create mode 100644 test/records/well_known/signature_test.dart create mode 100644 test/records/well_known/smart_poster_test.dart create mode 100644 test/records/well_known/text_test.dart create mode 100644 test/records/well_known/uri_test.dart create mode 100644 test/utilities_test.dart delete mode 100644 test/wifi_test.dart diff --git a/test/helpers.dart b/test/helpers.dart new file mode 100644 index 0000000..9226ec5 --- /dev/null +++ b/test/helpers.dart @@ -0,0 +1,43 @@ +import 'dart:typed_data'; + +import 'package:test/test.dart'; + +import 'package:ndef/ndef.dart'; +import 'package:ndef/utilities.dart'; + +/// Decode hex strings and verify they match the expected records. +void testParse(List hexStrings, List> messages) { + for (int i = 0; i < hexStrings.length; i++) { + var decoded = decodeRawNdefMessage(hexStrings[i].toBytes()); + expect(decoded.length, messages[i].length, + reason: 'Message $i record count mismatch'); + for (int j = 0; j < decoded.length; j++) { + expect(decoded[j].isEqual(messages[i][j]), isTrue, + reason: 'Message $i record $j mismatch'); + } + } +} + +/// Encode records and verify they produce the expected hex strings. +void testGenerate(List hexStrings, List> messages) { + for (int i = 0; i < hexStrings.length; i++) { + expect(encodeNdefMessage(messages[i]).toHexString(), hexStrings[i], + reason: 'Message $i encoding mismatch'); + } +} + +/// Encode then decode records, verifying round-trip fidelity. +void testRoundTrip(List> messages) { + for (int i = 0; i < messages.length; i++) { + var decoded = decodeRawNdefMessage(encodeNdefMessage(messages[i])); + expect(decoded.length, messages[i].length, + reason: 'Round-trip message $i record count mismatch'); + for (int j = 0; j < decoded.length; j++) { + expect(decoded[j].isEqual(messages[i][j]), isTrue, + reason: 'Round-trip message $i record $j mismatch'); + } + } +} + +/// Shorthand to convert a hex string to [Uint8List]. +Uint8List hexToBytes(String hex) => ByteUtils.hexStringToBytes(hex); diff --git a/test/ndef_test.dart b/test/ndef_test.dart deleted file mode 100644 index a08ed7b..0000000 --- a/test/ndef_test.dart +++ /dev/null @@ -1,331 +0,0 @@ -import 'dart:convert'; -import 'dart:typed_data'; - -import 'package:test/test.dart'; - -import 'package:ndef/ndef.dart'; -import 'package:ndef/utilities.dart'; - -void testParse(List hexStrings, List> messages) { - for (int i = 0; i < hexStrings.length; i++) { - var decoded = decodeRawNdefMessage(hexStrings[i].toBytes()); - assert(decoded.length == messages[i].length); - for (int j = 0; j < decoded.length; j++) { - assert(decoded[j].isEqual(messages[i][j])); - } - } -} - -void testGenerate(List hexStrings, List> messages) { - for (int i = 0; i < hexStrings.length; i++) { - assert(encodeNdefMessage(messages[i]).toHexString() == hexStrings[i]); - } -} - -void main() { - test('ndef message with uri type', () { - List hexStrings = [ - "91011655046769746875622e636f6d2f6e6663696d2f6e64656651010b55046769746875622e636f6d", - ]; - - List> messages = [ - [ - UriRecord.fromString("https://github.com/nfcim/ndef"), - UriRecord.fromString("https://github.com") - ], - ]; - - testParse(hexStrings, messages); - testGenerate(hexStrings, messages); - }); - - test('ndef message with text type', () { - List hexStrings = [ - "d1010f5402656e48656c6c6f20576f726c6421", - "d101145485656d6f6a69fffe3dd801de3dd802de3ed828dd" - ]; - - List> messages = [ - [TextRecord(language: 'en', text: 'Hello World!')], - [ - TextRecord( - language: 'emoji', text: '๐Ÿ˜๐Ÿ˜‚๐Ÿคจ', encoding: TextEncoding.UTF16) - ], - ]; - - testParse(hexStrings, messages); - testGenerate(hexStrings, messages); - }); - - test('ndef message with text type, payload length >255 bytes', () { - List hexStrings = [ - "c1010000013b5402656e4142434445464748494a4b4c4d4e4f505152535455565758595a4142434445464748494a4b4c4d4e4f505152535455565758595a4142434445464748494a4b4c4d4e4f505152535455565758595a4142434445464748494a4b4c4d4e4f505152535455565758595a4142434445464748494a4b4c4d4e4f505152535455565758595a4142434445464748494a4b4c4d4e4f505152535455565758595a4142434445464748494a4b4c4d4e4f505152535455565758595a4142434445464748494a4b4c4d4e4f505152535455565758595a4142434445464748494a4b4c4d4e4f505152535455565758595a4142434445464748494a4b4c4d4e4f505152535455565758595a4142434445464748494a4b4c4d4e4f505152535455565758595a4142434445464748494a4b4c4d4e4f505152535455565758595a", - ]; - - // text of 312 characters - List> messages = [ - [ - TextRecord( - language: 'en', - text: - 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ') - ], - ]; - - testParse(hexStrings, messages); - testGenerate(hexStrings, messages); - }); - - test('ndef message with signature type', () { - List hexStrings = [ - "d10306536967200002000000", - "d1034d536967200b0200473045022100a410c28fd9437fd24f6656f121e62bcc5f65e36257f5faadf68e3e83d40d481a0220335b1dff8d6fe722fcf7018be9684d2de5670b256fdfc02aa25bdae16f624b8000", - ]; - - List> messages = [ - [SignatureRecord()], - [ - SignatureRecord( - signatureType: 'ECDSA-P256', - signature: ByteUtils.hexStringToBytes( - "3045022100a410c28fd9437fd24f6656f121e62bcc5f65e36257f5faadf68e3e83d40d481a0220335b1dff8d6fe722fcf7018be9684d2de5670b256fdfc02aa25bdae16f624b80")) - ], - ]; - - testParse(hexStrings, messages); - testGenerate(hexStrings, messages); - }); - - test('ndef message with device information type', () { - List hexStrings = [ - "d1023b446900056e6663696d01096e666344657669636502076e66634e616d6503106361ae18d5b011ea9d0840a3ccfd09570405312e302e30ff054e4643494d", - ]; - - List> messages = [ - [ - DeviceInformationRecord( - vendorName: "nfcim", - modelName: "nfcDevice", - uniqueName: "nfcName", - uuid: "6361ae18-d5b0-11ea-9d08-40a3ccfd0957", - versionString: "1.0.0", - undefinedData: [ - DataElement.fromString(255, "NFCIM"), - ]) - ], - ]; - - testParse(hexStrings, messages); - testGenerate(hexStrings, messages); - }); - - test('ndef message with smart poster type', () { - List hexStrings = [ - "d10249537091011655046769746875622e636f6d2f6e6663696d2f6e6465661101075402656e6e64656611030161637400120909696d6167652f706e676120706963747572655101047300002710", - "d1020f5370d1010b55046769746875622e636f6d", - ]; - - List> messages = [ - [ - SmartPosterRecord( - title: "ndef", - uri: "https://github.com/nfcim/ndef", - action: Action.exec, - icon: {"image/png": Uint8List.fromList(utf8.encode("a picture"))}, - size: 10000), - // typeInfo: null), - ], - [ - SmartPosterRecord(uri: "https://github.com"), - ] - ]; - - testParse(hexStrings, messages); - testGenerate(hexStrings, messages); - }); - - test('ndef message with handover type', () { - List hexStrings = [ - 'd10201487211', - '910211487212910202637212345102046163010131005a030201612f62310001', - '91021248731391020461630101310051030265727201ff5a030201612f62310001', - '91020a486d13d102046163010161005a0a0301746578742f706c61696e61000102', - '91021148721291020263721234510204616301013100590205014863310203612f62' - ]; - - List> messages = [ - [ - HandoverRequestRecord(versionString: "1.1"), - ], - [ - HandoverRequestRecord( - versionString: "1.2", - collisionResolutionNumber: 0x1234, - alternativeCarrierRecordList: [ - AlternativeCarrierRecord( - carrierPowerState: CarrierPowerState.active, - carrierDataReference: latin1.encode('1')) - ]), - MimeRecord( - decodedType: 'a/b', - id: latin1.encode('1'), - payload: ByteUtils.hexStringToBytes('0001')) - ], - [ - HandoverSelectRecord( - error: ErrorRecord( - errorNum: 1, errorData: ByteUtils.intToBytes(255, 1)), - alternativeCarrierRecordList: [ - AlternativeCarrierRecord( - carrierPowerState: CarrierPowerState.active, - carrierDataReference: latin1.encode('1')) - ]), - MimeRecord( - decodedType: 'a/b', - id: latin1.encode('1'), - payload: ByteUtils.hexStringToBytes('0001')) - ], - [ - HandoverMediationRecord( - versionString: '1.3', - alternativeCarrierRecordList: [ - AlternativeCarrierRecord( - carrierPowerState: CarrierPowerState.active, - carrierDataReference: latin1.encode('a')) - ]), - MimeRecord( - decodedType: 'text/plain', - id: latin1.encode('a'), - payload: ByteUtils.hexStringToBytes('000102')) - ], - [ - HandoverRequestRecord( - versionString: "1.2", - collisionResolutionNumber: 0x1234, - alternativeCarrierRecordList: [ - AlternativeCarrierRecord( - carrierPowerState: CarrierPowerState.active, - carrierDataReference: latin1.encode('1')) - ]), - HandoverCarrierRecord( - carrierTnf: TypeNameFormat.media, - carrierType: 'a/b', - carrierData: Uint8List(0), - id: latin1.encode('1')) - ] - ]; - - for (int i = 0; i < hexStrings.length; i++) { - var decoded = - decodeRawNdefMessage(ByteUtils.hexStringToBytes(hexStrings[i])); - assert(decoded.length == messages[i].length); - for (int j = 0; j < decoded.length; j++) { - assert(decoded[j].isEqual(messages[i][j])); - } - } - for (int i = 0; i < hexStrings.length; i++) { - var decoded = decodeRawNdefMessage(encodeNdefMessage(messages[i])); - assert(decoded.length == messages[i].length); - for (int j = 0; j < decoded.length; j++) { - assert(decoded[j].isEqual(messages[i][j])); - } - } - }); - - test('ndef message with bluetooth type', () { - List hexStrings = [ - "d2200b6170706c69636174696f6e2f766e642e626c7565746f6f74682e65702e6f6f620b0006050403020102ff61", - ]; - - List> messages = [ - [ - BluetoothEasyPairingRecord( - address: EPAddress(address: "06:05:04:03:02:01"), - attributes: { - EIRType.ManufacturerSpecificData: Uint8List.fromList([97]) - }) - ], - ]; - - testParse(hexStrings, messages); - testGenerate(hexStrings, messages); - }); - - test('ndef message with absolute uri', () { - List hexStrings = [ - '931d0068747470733a2f2f6769746875622e636f6d2f6e6663696d2f6e64656653120068747470733a2f2f6769746875622e636f6d', - ]; - - List> messages = [ - [ - AbsoluteUriRecord(uri: "https://github.com/nfcim/ndef"), - AbsoluteUriRecord(uri: "https://github.com") - ], - ]; - testParse(hexStrings, messages); - testGenerate(hexStrings, messages); - }); - - test('utilities test', () { - assert(ByteUtils.bytesEqual(null, null) == true); - assert(ByteUtils.bytesEqual( - Uint8List.fromList([1, 2, 3]), Uint8List.fromList([1, 2])) == - false); - assert(ByteUtils.bytesEqual( - Uint8List.fromList([1, 2, 3]), Uint8List.fromList([1, 2, 4])) == - false); - assert(ByteUtils.bytesEqual( - Uint8List.fromList([1, 2, 3]), Uint8List.fromList([1, 2, 3])) == - true); - assert(ByteUtils.bytesEqual(Uint8List.fromList([1, 2, 3]), null) == false); - - var bytes = Uint8List(0); - assert(bytes.toHexString() == ""); - assert(Uint8List.fromList([]).toHexString() == ""); - }); - - test('blank record construction', () { - var record = NDEFRecord(); - assert(record.tnf == TypeNameFormat.empty); - assert(record.decodedType == null); - assert(record.type == null); - assert(record.fullType == null); - assert(record.minPayloadLength == 0); - assert(record.maxPayloadLength == null); - assert(record.payload == null); - - var uriRecord = UriRecord(); - assert(uriRecord.tnf == TypeNameFormat.nfcWellKnown); - assert(uriRecord.decodedType == 'U'); - assert(ByteUtils.bytesEqual(uriRecord.type, Uint8List.fromList([85]))); - assert(uriRecord.fullType == "urn:nfc:wkt:U"); - assert(uriRecord.minPayloadLength == 1); - assert(uriRecord.maxPayloadLength == null); - assert(uriRecord.prefix == null); - assert(uriRecord.iriString == null); - assert(uriRecord.uriString == null); - assert(uriRecord.uri == null); - assert(uriRecord.payload == null); - - var textRecord = TextRecord(); - assert(textRecord.encoding == TextEncoding.UTF8); - assert(textRecord.encodingString == "UTF-8"); - assert(textRecord.language == null); - assert(textRecord.text == null); - - var wellKnownRecord = WellKnownRecord(); - assert(wellKnownRecord.tnf == TypeNameFormat.nfcWellKnown); - assert(wellKnownRecord.payload == null); - assert(wellKnownRecord.id == null); - }); - - // exception test - test( - 'exception test', - () => expect(() { - UriRecord record = UriRecord(); - record.prefix = "test"; - }, throwsArgumentError)); - - // TODO: more tests -} diff --git a/test/record_test.dart b/test/record_test.dart new file mode 100644 index 0000000..c4aa049 --- /dev/null +++ b/test/record_test.dart @@ -0,0 +1,60 @@ +import 'dart:typed_data'; + +import 'package:test/test.dart'; + +import 'package:ndef/ndef.dart'; +import 'package:ndef/utilities.dart'; + +void main() { + group('NDEFRecord blank construction', () { + test('empty record defaults', () { + var record = NDEFRecord(); + expect(record.tnf, TypeNameFormat.empty); + expect(record.decodedType, isNull); + expect(record.type, isNull); + expect(record.fullType, isNull); + expect(record.minPayloadLength, 0); + expect(record.maxPayloadLength, isNull); + expect(record.payload, isNull); + }); + + test('UriRecord defaults', () { + var record = UriRecord(); + expect(record.tnf, TypeNameFormat.nfcWellKnown); + expect(record.decodedType, 'U'); + expect(ByteUtils.bytesEqual(record.type, Uint8List.fromList([85])), + isTrue); + expect(record.fullType, "urn:nfc:wkt:U"); + expect(record.minPayloadLength, 1); + expect(record.maxPayloadLength, isNull); + expect(record.prefix, isNull); + expect(record.iriString, isNull); + expect(record.uriString, isNull); + expect(record.uri, isNull); + expect(record.payload, isNull); + }); + + test('TextRecord defaults', () { + var record = TextRecord(); + expect(record.encoding, TextEncoding.UTF8); + expect(record.encodingString, "UTF-8"); + expect(record.language, isNull); + expect(record.text, isNull); + }); + + test('WellKnownRecord defaults', () { + var record = WellKnownRecord(); + expect(record.tnf, TypeNameFormat.nfcWellKnown); + expect(record.payload, isNull); + expect(record.id, isNull); + }); + }); + + group('NDEFRecord exceptions', () { + test('invalid URI prefix throws ArgumentError', () { + expect(() { + UriRecord().prefix = "test"; + }, throwsArgumentError); + }); + }); +} diff --git a/test/records/media/bluetooth_test.dart b/test/records/media/bluetooth_test.dart new file mode 100644 index 0000000..b295039 --- /dev/null +++ b/test/records/media/bluetooth_test.dart @@ -0,0 +1,29 @@ +import 'dart:typed_data'; + +import 'package:test/test.dart'; + +import 'package:ndef/ndef.dart'; +import '../../helpers.dart'; + +void main() { + group('BluetoothEasyPairingRecord', () { + test('encode and decode', () { + var hexStrings = [ + "d2200b6170706c69636174696f6e2f766e642e626c7565746f6f74682e65702e6f6f620b0006050403020102ff61", + ]; + var messages = [ + [ + BluetoothEasyPairingRecord( + address: EPAddress(address: "06:05:04:03:02:01"), + attributes: { + EIRType.ManufacturerSpecificData: Uint8List.fromList([97]), + }, + ), + ], + ]; + + testParse(hexStrings, messages); + testGenerate(hexStrings, messages); + }); + }); +} diff --git a/test/records/media/wifi_test.dart b/test/records/media/wifi_test.dart new file mode 100644 index 0000000..18cdadd --- /dev/null +++ b/test/records/media/wifi_test.dart @@ -0,0 +1,226 @@ +import 'dart:typed_data'; + +import 'package:test/test.dart'; + +import 'package:ndef/ndef.dart'; + +void main() { + group('WifiRecord', () { + group('authentication and encryption types', () { + test('WSC values', () { + expect(WifiAuthenticationType.open.wscValue, 0x0001); + expect(WifiAuthenticationType.wpa2Personal.wscValue, 0x0020); + expect(WifiAuthenticationType.wpaWpa2Personal.wscValue, 0x0022); + + expect(WifiEncryptionType.aes.wscValue, 0x0008); + expect(WifiEncryptionType.aesTkip.wscValue, 0x000C); + }); + + test('fromWscValue lookup', () { + expect(WifiAuthenticationType.fromWscValue(0x0020), + WifiAuthenticationType.wpa2Personal); + expect(WifiAuthenticationType.fromWscValue(0x9999), + WifiAuthenticationType.open); + expect( + WifiEncryptionType.fromWscValue(0x0008), WifiEncryptionType.aes); + }); + }); + + group('construction and validation', () { + test('basic properties', () { + var record = WifiRecord( + ssid: 'TestNetwork', + networkKey: 'password123', + authenticationType: WifiAuthenticationType.wpa2Personal, + encryptionType: WifiEncryptionType.aes, + macAddress: 'AA:BB:CC:DD:EE:FF', + ); + + expect(record.ssid, 'TestNetwork'); + expect(record.networkKey, 'password123'); + expect(record.decodedType, 'application/vnd.wfa.wsc'); + }); + + test('invalid MAC address throws', () { + expect( + () => WifiRecord( + ssid: 'Test', networkKey: 'pass', macAddress: 'invalid'), + throwsArgumentError, + ); + }); + + test('missing SSID throws on payload access', () { + expect( + () => WifiRecord(networkKey: 'pass').payload, throwsArgumentError); + }); + + test('missing password for secured network throws', () { + expect( + () => WifiRecord( + ssid: 'Test', + authenticationType: WifiAuthenticationType.wpa2Personal, + ).payload, + throwsArgumentError, + ); + }); + + test('empty payload throws', () { + expect( + () => WifiRecord().payload = Uint8List(0), throwsArgumentError); + }); + }); + + group('round-trip encoding', () { + test('WPA2 with MAC address', () { + var original = WifiRecord( + ssid: 'MyNetwork', + networkKey: 'mypassword123', + authenticationType: WifiAuthenticationType.wpa2Personal, + encryptionType: WifiEncryptionType.aes, + macAddress: '11:22:33:44:55:66', + ); + + var encoded = encodeNdefMessage([original]); + var decoded = decodeRawNdefMessage(encoded); + + expect(decoded.length, 1); + expect(decoded[0], isA()); + + var record = decoded[0] as WifiRecord; + expect(record.ssid, 'MyNetwork'); + expect(record.networkKey, 'mypassword123'); + expect( + record.authenticationType, WifiAuthenticationType.wpa2Personal); + expect(record.encryptionType, WifiEncryptionType.aes); + expect(record.macAddress, '11:22:33:44:55:66'); + }); + }); + + group('decode real payloads', () { + test('js-nfc-wifi-parser payload', () { + final payload = Uint8List.fromList([ + 16, 14, 0, 62, + 16, 38, 0, 1, 1, + 16, 69, 0, 11, 87, 76, 65, 78, 45, 56, 50, 67, 81, 90, 54, + 16, 3, 0, 2, 0, 34, + 16, 15, 0, 2, 0, 12, + 16, 39, 0, 16, 52, 57, 53, 54, 52, 52, 53, 54, 56, 48, 51, 57, 48, + 50, 54, 51, + 16, 32, 0, 6, 255, 255, 255, 255, 255, 255, + ]); + + var record = WifiRecord()..payload = payload; + + expect(record.ssid, 'WLAN-82CQZ6'); + expect(record.networkKey, '4956445680390263'); + expect(record.authenticationType, + WifiAuthenticationType.wpaWpa2Personal); + expect(record.encryptionType, WifiEncryptionType.aesTkip); + expect(record.networkIndex, 1); + expect(record.macAddress, 'FF:FF:FF:FF:FF:FF'); + expect(payload[0] << 8 | payload[1], 0x100E); + }); + + test('open network payload', () { + final payload = Uint8List.fromList([ + 0x10, 0x0E, 0x00, 0x25, + 0x10, 0x26, 0x00, 0x01, 0x01, + 0x10, 0x45, 0x00, 0x07, 0x6d, 0x79, 0x2d, 0x73, 0x73, 0x69, 0x64, + 0x10, 0x03, 0x00, 0x02, 0x00, 0x01, + 0x10, 0x0F, 0x00, 0x02, 0x00, 0x01, + ]); + + var record = WifiRecord()..payload = payload; + + expect(record.ssid, 'my-ssid'); + expect(record.authenticationType, WifiAuthenticationType.open); + expect(record.encryptionType, WifiEncryptionType.none); + expect(record.networkIndex, 1); + }); + + test('WPA2 payload with MAC address', () { + final payload = Uint8List.fromList([ + 0x10, 0x0E, 0x00, 0x45, + 0x10, 0x26, 0x00, 0x01, 0x01, + 0x10, 0x45, 0x00, 0x0a, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, + 0x10, 0x03, 0x00, 0x02, 0x00, 0x20, + 0x10, 0x0F, 0x00, 0x02, 0x00, 0x08, + 0x10, 0x27, 0x00, 0x0a, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x30, + 0x10, 0x20, 0x00, 0x06, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + ]); + + var record = WifiRecord()..payload = payload; + + expect(record.ssid, 'abcdefghij'); + expect(record.networkKey, '1234567890'); + expect( + record.authenticationType, WifiAuthenticationType.wpa2Personal); + expect(record.encryptionType, WifiEncryptionType.aes); + expect(record.macAddress, '01:02:03:04:05:06'); + expect(record.networkIndex, 1); + }); + }); + + group('special cases', () { + test('open network round-trip', () { + var record = WifiRecord( + ssid: 'OpenNet', + authenticationType: WifiAuthenticationType.open, + encryptionType: WifiEncryptionType.none, + ); + var decoded = + decodeRawNdefMessage(encodeNdefMessage([record]))[0] as WifiRecord; + expect(decoded.ssid, 'OpenNet'); + expect(decoded.authenticationType, WifiAuthenticationType.open); + }); + + test('WPA3 round-trip', () { + var record = WifiRecord( + ssid: 'WPA3Net', + networkKey: 'securepass', + authenticationType: WifiAuthenticationType.wpa3Personal, + encryptionType: WifiEncryptionType.aes, + ); + var decoded = + decodeRawNdefMessage(encodeNdefMessage([record]))[0] as WifiRecord; + expect( + decoded.authenticationType, WifiAuthenticationType.wpa3Personal); + }); + + test('mixed WPA/WPA2 round-trip', () { + var record = WifiRecord( + ssid: 'MixedNet', + networkKey: 'mixedpass', + authenticationType: WifiAuthenticationType.wpaWpa2Personal, + encryptionType: WifiEncryptionType.aesTkip, + ); + var decoded = + decodeRawNdefMessage(encodeNdefMessage([record]))[0] as WifiRecord; + expect(decoded.authenticationType, + WifiAuthenticationType.wpaWpa2Personal); + expect(decoded.encryptionType, WifiEncryptionType.aesTkip); + }); + + test('MAC address normalization', () { + var record = WifiRecord( + ssid: 'Test', + networkKey: 'pass', + macAddress: 'aa:bb:cc:dd:ee:ff', + ); + var decoded = + decodeRawNdefMessage(encodeNdefMessage([record]))[0] as WifiRecord; + expect(decoded.macAddress, 'AA:BB:CC:DD:EE:FF'); + }); + + test('max length SSID', () { + var longSsid = 'A' * 32; + var record = WifiRecord(ssid: longSsid, networkKey: 'pass'); + var decoded = + decodeRawNdefMessage(encodeNdefMessage([record]))[0] as WifiRecord; + expect(decoded.ssid, longSsid); + }); + }); + }); +} diff --git a/test/records/well_known/device_info_test.dart b/test/records/well_known/device_info_test.dart new file mode 100644 index 0000000..fb3825f --- /dev/null +++ b/test/records/well_known/device_info_test.dart @@ -0,0 +1,31 @@ +import 'package:test/test.dart'; + +import 'package:ndef/ndef.dart'; +import '../../helpers.dart'; + +void main() { + group('DeviceInformationRecord', () { + test('encode and decode', () { + var hexStrings = [ + "d1023b446900056e6663696d01096e666344657669636502076e66634e616d6503106361ae18d5b011ea9d0840a3ccfd09570405312e302e30ff054e4643494d", + ]; + var messages = [ + [ + DeviceInformationRecord( + vendorName: "nfcim", + modelName: "nfcDevice", + uniqueName: "nfcName", + uuid: "6361ae18-d5b0-11ea-9d08-40a3ccfd0957", + versionString: "1.0.0", + undefinedData: [ + DataElement.fromString(255, "NFCIM"), + ], + ), + ], + ]; + + testParse(hexStrings, messages); + testGenerate(hexStrings, messages); + }); + }); +} diff --git a/test/records/well_known/handover_test.dart b/test/records/well_known/handover_test.dart new file mode 100644 index 0000000..7f7329d --- /dev/null +++ b/test/records/well_known/handover_test.dart @@ -0,0 +1,104 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:test/test.dart'; + +import 'package:ndef/ndef.dart'; +import 'package:ndef/utilities.dart'; +import '../../helpers.dart'; + +void main() { + group('HandoverRecord', () { + final hexStrings = [ + 'd10201487211', + '910211487212910202637212345102046163010131005a030201612f62310001', + '91021248731391020461630101310051030265727201ff5a030201612f62310001', + '91020a486d13d102046163010161005a0a0301746578742f706c61696e61000102', + '91021148721291020263721234510204616301013100590205014863310203612f62', + ]; + + final messages = >[ + [ + HandoverRequestRecord(versionString: "1.1"), + ], + [ + HandoverRequestRecord( + versionString: "1.2", + collisionResolutionNumber: 0x1234, + alternativeCarrierRecordList: [ + AlternativeCarrierRecord( + carrierPowerState: CarrierPowerState.active, + carrierDataReference: latin1.encode('1'), + ), + ], + ), + MimeRecord( + decodedType: 'a/b', + id: latin1.encode('1'), + payload: hexToBytes('0001'), + ), + ], + [ + HandoverSelectRecord( + error: ErrorRecord( + errorNum: 1, + errorData: ByteUtils.intToBytes(255, 1), + ), + alternativeCarrierRecordList: [ + AlternativeCarrierRecord( + carrierPowerState: CarrierPowerState.active, + carrierDataReference: latin1.encode('1'), + ), + ], + ), + MimeRecord( + decodedType: 'a/b', + id: latin1.encode('1'), + payload: hexToBytes('0001'), + ), + ], + [ + HandoverMediationRecord( + versionString: '1.3', + alternativeCarrierRecordList: [ + AlternativeCarrierRecord( + carrierPowerState: CarrierPowerState.active, + carrierDataReference: latin1.encode('a'), + ), + ], + ), + MimeRecord( + decodedType: 'text/plain', + id: latin1.encode('a'), + payload: hexToBytes('000102'), + ), + ], + [ + HandoverRequestRecord( + versionString: "1.2", + collisionResolutionNumber: 0x1234, + alternativeCarrierRecordList: [ + AlternativeCarrierRecord( + carrierPowerState: CarrierPowerState.active, + carrierDataReference: latin1.encode('1'), + ), + ], + ), + HandoverCarrierRecord( + carrierTnf: TypeNameFormat.media, + carrierType: 'a/b', + carrierData: Uint8List(0), + id: latin1.encode('1'), + ), + ], + ]; + + test('parse from hex', () { + testParse(hexStrings, messages); + }); + + test('round-trip encode/decode', () { + testRoundTrip(messages); + }); + }); +} diff --git a/test/records/well_known/signature_test.dart b/test/records/well_known/signature_test.dart new file mode 100644 index 0000000..eb89609 --- /dev/null +++ b/test/records/well_known/signature_test.dart @@ -0,0 +1,40 @@ +import 'package:test/test.dart'; + +import 'package:ndef/ndef.dart'; +import 'package:ndef/utilities.dart'; +import '../../helpers.dart'; + +void main() { + group('SignatureRecord', () { + test('encode and decode empty signature', () { + var hexStrings = [ + "d10306536967200002000000", + ]; + var messages = [ + [SignatureRecord()], + ]; + + testParse(hexStrings, messages); + testGenerate(hexStrings, messages); + }); + + test('encode and decode ECDSA-P256 signature', () { + var hexStrings = [ + "d1034d536967200b0200473045022100a410c28fd9437fd24f6656f121e62bcc5f65e36257f5faadf68e3e83d40d481a0220335b1dff8d6fe722fcf7018be9684d2de5670b256fdfc02aa25bdae16f624b8000", + ]; + var messages = [ + [ + SignatureRecord( + signatureType: 'ECDSA-P256', + signature: ByteUtils.hexStringToBytes( + "3045022100a410c28fd9437fd24f6656f121e62bcc5f65e36257f5faadf68e3e83d40d481a0220335b1dff8d6fe722fcf7018be9684d2de5670b256fdfc02aa25bdae16f624b80", + ), + ), + ], + ]; + + testParse(hexStrings, messages); + testGenerate(hexStrings, messages); + }); + }); +} diff --git a/test/records/well_known/smart_poster_test.dart b/test/records/well_known/smart_poster_test.dart new file mode 100644 index 0000000..cd8bedc --- /dev/null +++ b/test/records/well_known/smart_poster_test.dart @@ -0,0 +1,43 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:test/test.dart'; + +import 'package:ndef/ndef.dart'; +import '../../helpers.dart'; + +void main() { + group('SmartPosterRecord', () { + test('encode and decode full smart poster', () { + var hexStrings = [ + "d10249537091011655046769746875622e636f6d2f6e6663696d2f6e6465661101075402656e6e64656611030161637400120909696d6167652f706e676120706963747572655101047300002710", + ]; + var messages = [ + [ + SmartPosterRecord( + title: "ndef", + uri: "https://github.com/nfcim/ndef", + action: Action.exec, + icon: {"image/png": Uint8List.fromList(utf8.encode("a picture"))}, + size: 10000, + ), + ], + ]; + + testParse(hexStrings, messages); + testGenerate(hexStrings, messages); + }); + + test('encode and decode uri-only smart poster', () { + var hexStrings = [ + "d1020f5370d1010b55046769746875622e636f6d", + ]; + var messages = [ + [SmartPosterRecord(uri: "https://github.com")], + ]; + + testParse(hexStrings, messages); + testGenerate(hexStrings, messages); + }); + }); +} diff --git a/test/records/well_known/text_test.dart b/test/records/well_known/text_test.dart new file mode 100644 index 0000000..e7f97d4 --- /dev/null +++ b/test/records/well_known/text_test.dart @@ -0,0 +1,55 @@ +import 'package:test/test.dart'; + +import 'package:ndef/ndef.dart'; +import '../../helpers.dart'; + +void main() { + group('TextRecord', () { + test('encode and decode UTF-8', () { + var hexStrings = [ + "d1010f5402656e48656c6c6f20576f726c6421", + ]; + var messages = [ + [TextRecord(language: 'en', text: 'Hello World!')], + ]; + + testParse(hexStrings, messages); + testGenerate(hexStrings, messages); + }); + + test('encode and decode UTF-16', () { + var hexStrings = [ + "d101145485656d6f6a69fffe3dd801de3dd802de3ed828dd", + ]; + var messages = [ + [ + TextRecord( + language: 'emoji', + text: '๐Ÿ˜๐Ÿ˜‚๐Ÿคจ', + encoding: TextEncoding.UTF16, + ), + ], + ]; + + testParse(hexStrings, messages); + testGenerate(hexStrings, messages); + }); + + test('payload length > 255 bytes', () { + var hexStrings = [ + "c1010000013b5402656e4142434445464748494a4b4c4d4e4f505152535455565758595a4142434445464748494a4b4c4d4e4f505152535455565758595a4142434445464748494a4b4c4d4e4f505152535455565758595a4142434445464748494a4b4c4d4e4f505152535455565758595a4142434445464748494a4b4c4d4e4f505152535455565758595a4142434445464748494a4b4c4d4e4f505152535455565758595a4142434445464748494a4b4c4d4e4f505152535455565758595a4142434445464748494a4b4c4d4e4f505152535455565758595a4142434445464748494a4b4c4d4e4f505152535455565758595a4142434445464748494a4b4c4d4e4f505152535455565758595a4142434445464748494a4b4c4d4e4f505152535455565758595a4142434445464748494a4b4c4d4e4f505152535455565758595a", + ]; + var messages = [ + [ + TextRecord( + language: 'en', + text: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' * 12, + ), + ], + ]; + + testParse(hexStrings, messages); + testGenerate(hexStrings, messages); + }); + }); +} diff --git a/test/records/well_known/uri_test.dart b/test/records/well_known/uri_test.dart new file mode 100644 index 0000000..e052195 --- /dev/null +++ b/test/records/well_known/uri_test.dart @@ -0,0 +1,46 @@ +import 'package:test/test.dart'; + +import 'package:ndef/ndef.dart'; +import '../../helpers.dart'; + +void main() { + group('UriRecord', () { + test('encode and decode', () { + var hexStrings = [ + "91011655046769746875622e636f6d2f6e6663696d2f6e64656651010b55046769746875622e636f6d", + ]; + var messages = [ + [ + UriRecord.fromString("https://github.com/nfcim/ndef"), + UriRecord.fromString("https://github.com"), + ], + ]; + + testParse(hexStrings, messages); + testGenerate(hexStrings, messages); + }); + + test('invalid prefix throws', () { + expect(() { + UriRecord().prefix = "test"; + }, throwsArgumentError); + }); + }); + + group('AbsoluteUriRecord', () { + test('encode and decode', () { + var hexStrings = [ + '931d0068747470733a2f2f6769746875622e636f6d2f6e6663696d2f6e64656653120068747470733a2f2f6769746875622e636f6d', + ]; + var messages = [ + [ + AbsoluteUriRecord(uri: "https://github.com/nfcim/ndef"), + AbsoluteUriRecord(uri: "https://github.com"), + ], + ]; + + testParse(hexStrings, messages); + testGenerate(hexStrings, messages); + }); + }); +} diff --git a/test/utilities_test.dart b/test/utilities_test.dart new file mode 100644 index 0000000..d324a4a --- /dev/null +++ b/test/utilities_test.dart @@ -0,0 +1,43 @@ +import 'dart:typed_data'; + +import 'package:test/test.dart'; + +import 'package:ndef/utilities.dart'; + +void main() { + group('ByteUtils', () { + test('bytesEqual', () { + expect(ByteUtils.bytesEqual(null, null), isTrue); + expect( + ByteUtils.bytesEqual( + Uint8List.fromList([1, 2, 3]), + Uint8List.fromList([1, 2]), + ), + isFalse, + ); + expect( + ByteUtils.bytesEqual( + Uint8List.fromList([1, 2, 3]), + Uint8List.fromList([1, 2, 4]), + ), + isFalse, + ); + expect( + ByteUtils.bytesEqual( + Uint8List.fromList([1, 2, 3]), + Uint8List.fromList([1, 2, 3]), + ), + isTrue, + ); + expect( + ByteUtils.bytesEqual(Uint8List.fromList([1, 2, 3]), null), + isFalse, + ); + }); + + test('toHexString on empty bytes', () { + expect(Uint8List(0).toHexString(), ""); + expect(Uint8List.fromList([]).toHexString(), ""); + }); + }); +} diff --git a/test/wifi_test.dart b/test/wifi_test.dart deleted file mode 100644 index 7b1019a..0000000 --- a/test/wifi_test.dart +++ /dev/null @@ -1,213 +0,0 @@ -import 'dart:typed_data'; -import 'package:test/test.dart'; -import 'package:ndef/ndef.dart'; - -void main() { - test('wifi authentication and encryption types', () { - assert(WifiAuthenticationType.open.wscValue == 0x0001); - assert(WifiAuthenticationType.wpa2Personal.wscValue == 0x0020); - assert(WifiAuthenticationType.wpaWpa2Personal.wscValue == 0x0022); - - assert(WifiAuthenticationType.fromWscValue(0x0020) == - WifiAuthenticationType.wpa2Personal); - assert(WifiAuthenticationType.fromWscValue(0x9999) == - WifiAuthenticationType.open); - - assert(WifiEncryptionType.aes.wscValue == 0x0008); - assert(WifiEncryptionType.aesTkip.wscValue == 0x000C); - assert(WifiEncryptionType.fromWscValue(0x0008) == WifiEncryptionType.aes); - }); - - test('wifi record construction and validation', () { - var record = WifiRecord( - ssid: 'TestNetwork', - networkKey: 'password123', - authenticationType: WifiAuthenticationType.wpa2Personal, - encryptionType: WifiEncryptionType.aes, - macAddress: 'AA:BB:CC:DD:EE:FF', - ); - - assert(record.ssid == 'TestNetwork'); - assert(record.networkKey == 'password123'); - assert(record.decodedType == 'application/vnd.wfa.wsc'); - - // Invalid MAC address - expect( - () => - WifiRecord(ssid: 'Test', networkKey: 'pass', macAddress: 'invalid'), - throwsArgumentError); - - // Missing SSID - expect(() => WifiRecord(networkKey: 'pass').payload, throwsArgumentError); - - // Missing password for secured network - expect( - () => WifiRecord( - ssid: 'Test', - authenticationType: WifiAuthenticationType.wpa2Personal) - .payload, - throwsArgumentError); - - // Invalid payload - expect(() => WifiRecord().payload = Uint8List(0), throwsArgumentError); - }); - - test('wifi record round-trip encoding', () { - var original = WifiRecord( - ssid: 'MyNetwork', - networkKey: 'mypassword123', - authenticationType: WifiAuthenticationType.wpa2Personal, - encryptionType: WifiEncryptionType.aes, - macAddress: '11:22:33:44:55:66', - ); - - var encoded = encodeNdefMessage([original]); - var decoded = decodeRawNdefMessage(encoded); - - assert(decoded.length == 1); - assert(decoded[0] is WifiRecord); - - var wifiRecord = decoded[0] as WifiRecord; - assert(wifiRecord.ssid == 'MyNetwork'); - assert(wifiRecord.networkKey == 'mypassword123'); - assert( - wifiRecord.authenticationType == WifiAuthenticationType.wpa2Personal); - assert(wifiRecord.encryptionType == WifiEncryptionType.aes); - assert(wifiRecord.macAddress == '11:22:33:44:55:66'); - }); - - test('wifi record decode real NFC payload from js-nfc-wifi-parser', () { - // Real NFC WiFi payload from https://github.com/gfnork/js-nfc-wifi-parser/blob/master/test/mocks/nfc-payload.json - final payload = Uint8List.fromList([ - 16, 14, 0, 62, // Credential container (0x100E), length 62 - 16, 38, 0, 1, 1, // Network Index (0x1026) = 1 - 16, 69, 0, 11, 87, 76, 65, 78, 45, 56, 50, 67, 81, 90, - 54, // SSID = "WLAN-82CQZ6" - 16, 3, 0, 2, 0, 34, // Auth Type (0x1003) = 0x0022 (WPA/WPA2-Personal) - 16, 15, 0, 2, 0, 12, // Encryption Type (0x100F) = 0x000C (AES/TKIP) - 16, 39, 0, 16, 52, 57, 53, 54, 52, 52, 53, 54, 56, 48, 51, 57, 48, 50, 54, - 51, // Network Key = "4956445680390263" - 16, 32, 0, 6, 255, 255, 255, 255, 255, - 255, // MAC Address = FF:FF:FF:FF:FF:FF - ]); - - var record = WifiRecord(); - record.payload = payload; - - assert(record.ssid == 'WLAN-82CQZ6'); - assert(record.networkKey == '4956445680390263'); - assert(record.authenticationType == WifiAuthenticationType.wpaWpa2Personal); - assert(record.encryptionType == WifiEncryptionType.aesTkip); - assert(record.networkIndex == 1); - assert(record.macAddress == 'FF:FF:FF:FF:FF:FF'); - - // Verify credential container starts with 0x100E - assert((payload[0] << 8 | payload[1]) == 0x100E); - }); - - test('wifi record decode open network payload', () { - // Real NFC WiFi payload from https://github.com/nfcpy/ndeflib/blob/master/tests/test_wifi.py - final payload = Uint8List.fromList([ - 0x10, 0x0E, 0x00, 0x25, // Credential container (0x100E), length 37 - 0x10, 0x26, 0x00, 0x01, 0x01, // Network Index = 1 - 0x10, 0x45, 0x00, 0x07, 0x6d, 0x79, 0x2d, 0x73, 0x73, 0x69, - 0x64, // SSID = "my-ssid" - 0x10, 0x03, 0x00, 0x02, 0x00, 0x01, // Auth Type = Open (0x0001) - 0x10, 0x0F, 0x00, 0x02, 0x00, 0x01, // Encryption Type = None (0x0001) - ]); - - var record = WifiRecord(); - record.payload = payload; - - assert(record.ssid == 'my-ssid'); - assert(record.authenticationType == WifiAuthenticationType.open); - assert(record.encryptionType == WifiEncryptionType.none); - assert(record.networkIndex == 1); - }); - - test('wifi record decode WPA2 payload with MAC address', () { - // Real NFC WiFi payload from https://github.com/nfcpy/ndeflib/blob/master/tests/test_wifi.py - final payload = Uint8List.fromList([ - 0x10, 0x0E, 0x00, 0x45, // Credential container, length 69 - 0x10, 0x26, 0x00, 0x01, 0x01, // Network Index = 1 - 0x10, 0x45, 0x00, 0x0a, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, - 0x69, 0x6a, // SSID = "abcdefghij" - 0x10, 0x03, 0x00, 0x02, 0x00, 0x20, // Auth Type = WPA2-Personal (0x0020) - 0x10, 0x0F, 0x00, 0x02, 0x00, 0x08, // Encryption Type = AES (0x0008) - 0x10, 0x27, 0x00, 0x0a, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, - 0x39, 0x30, // Network Key = "1234567890" - 0x10, 0x20, 0x00, 0x06, 0x01, 0x02, 0x03, 0x04, 0x05, - 0x06, // MAC Address = 01:02:03:04:05:06 - ]); - - var record = WifiRecord(); - record.payload = payload; - - assert(record.ssid == 'abcdefghij'); - assert(record.networkKey == '1234567890'); - assert(record.authenticationType == WifiAuthenticationType.wpa2Personal); - assert(record.encryptionType == WifiEncryptionType.aes); - assert(record.macAddress == '01:02:03:04:05:06'); - assert(record.networkIndex == 1); - }); - - test('wifi record special cases', () { - // Open network - var openRecord = WifiRecord( - ssid: 'OpenNet', - authenticationType: WifiAuthenticationType.open, - encryptionType: WifiEncryptionType.none, - ); - var encoded = encodeNdefMessage([openRecord]); - var decoded = decodeRawNdefMessage(encoded); - var decodedWifi = decoded[0] as WifiRecord; - assert(decodedWifi.ssid == 'OpenNet'); - assert(decodedWifi.authenticationType == WifiAuthenticationType.open); - - // WPA3 Personal - var wpa3Record = WifiRecord( - ssid: 'WPA3Net', - networkKey: 'securepass', - authenticationType: WifiAuthenticationType.wpa3Personal, - encryptionType: WifiEncryptionType.aes, - ); - encoded = encodeNdefMessage([wpa3Record]); - decoded = decodeRawNdefMessage(encoded); - decodedWifi = decoded[0] as WifiRecord; - assert( - decodedWifi.authenticationType == WifiAuthenticationType.wpa3Personal); - - // Mixed WPA/WPA2 - var mixedRecord = WifiRecord( - ssid: 'MixedNet', - networkKey: 'mixedpass', - authenticationType: WifiAuthenticationType.wpaWpa2Personal, - encryptionType: WifiEncryptionType.aesTkip, - ); - encoded = encodeNdefMessage([mixedRecord]); - decoded = decodeRawNdefMessage(encoded); - decodedWifi = decoded[0] as WifiRecord; - assert(decodedWifi.authenticationType == - WifiAuthenticationType.wpaWpa2Personal); - assert(decodedWifi.encryptionType == WifiEncryptionType.aesTkip); - - // MAC address normalization (lowercase to uppercase) - var macRecord = WifiRecord( - ssid: 'Test', - networkKey: 'pass', - macAddress: 'aa:bb:cc:dd:ee:ff', - ); - encoded = encodeNdefMessage([macRecord]); - decoded = decodeRawNdefMessage(encoded); - decodedWifi = decoded[0] as WifiRecord; - assert(decodedWifi.macAddress == 'AA:BB:CC:DD:EE:FF'); - - // Long SSID (32 chars max) - var longSsid = 'A' * 32; - var longSsidRecord = WifiRecord(ssid: longSsid, networkKey: 'pass'); - encoded = encodeNdefMessage([longSsidRecord]); - decoded = decodeRawNdefMessage(encoded); - decodedWifi = decoded[0] as WifiRecord; - assert(decodedWifi.ssid == longSsid); - }); -} From de115ee1e8036f3369abe7f5815efc3e9f415f42 Mon Sep 17 00:00:00 2001 From: Shengqi Chen Date: Sat, 14 Mar 2026 00:30:12 +0800 Subject: [PATCH 3/3] chore: run dart fmt Signed-off-by: Shengqi Chen --- test/record_test.dart | 4 +- test/records/media/wifi_test.dart | 202 ++++++++++++++++++++++++------ 2 files changed, 168 insertions(+), 38 deletions(-) diff --git a/test/record_test.dart b/test/record_test.dart index c4aa049..58543d5 100644 --- a/test/record_test.dart +++ b/test/record_test.dart @@ -22,8 +22,8 @@ void main() { var record = UriRecord(); expect(record.tnf, TypeNameFormat.nfcWellKnown); expect(record.decodedType, 'U'); - expect(ByteUtils.bytesEqual(record.type, Uint8List.fromList([85])), - isTrue); + expect( + ByteUtils.bytesEqual(record.type, Uint8List.fromList([85])), isTrue); expect(record.fullType, "urn:nfc:wkt:U"); expect(record.minPayloadLength, 1); expect(record.maxPayloadLength, isNull); diff --git a/test/records/media/wifi_test.dart b/test/records/media/wifi_test.dart index 18cdadd..1ac4896 100644 --- a/test/records/media/wifi_test.dart +++ b/test/records/media/wifi_test.dart @@ -21,8 +21,7 @@ void main() { WifiAuthenticationType.wpa2Personal); expect(WifiAuthenticationType.fromWscValue(0x9999), WifiAuthenticationType.open); - expect( - WifiEncryptionType.fromWscValue(0x0008), WifiEncryptionType.aes); + expect(WifiEncryptionType.fromWscValue(0x0008), WifiEncryptionType.aes); }); }); @@ -65,8 +64,7 @@ void main() { }); test('empty payload throws', () { - expect( - () => WifiRecord().payload = Uint8List(0), throwsArgumentError); + expect(() => WifiRecord().payload = Uint8List(0), throwsArgumentError); }); }); @@ -89,8 +87,7 @@ void main() { var record = decoded[0] as WifiRecord; expect(record.ssid, 'MyNetwork'); expect(record.networkKey, 'mypassword123'); - expect( - record.authenticationType, WifiAuthenticationType.wpa2Personal); + expect(record.authenticationType, WifiAuthenticationType.wpa2Personal); expect(record.encryptionType, WifiEncryptionType.aes); expect(record.macAddress, '11:22:33:44:55:66'); }); @@ -99,22 +96,80 @@ void main() { group('decode real payloads', () { test('js-nfc-wifi-parser payload', () { final payload = Uint8List.fromList([ - 16, 14, 0, 62, - 16, 38, 0, 1, 1, - 16, 69, 0, 11, 87, 76, 65, 78, 45, 56, 50, 67, 81, 90, 54, - 16, 3, 0, 2, 0, 34, - 16, 15, 0, 2, 0, 12, - 16, 39, 0, 16, 52, 57, 53, 54, 52, 52, 53, 54, 56, 48, 51, 57, 48, - 50, 54, 51, - 16, 32, 0, 6, 255, 255, 255, 255, 255, 255, + 16, + 14, + 0, + 62, + 16, + 38, + 0, + 1, + 1, + 16, + 69, + 0, + 11, + 87, + 76, + 65, + 78, + 45, + 56, + 50, + 67, + 81, + 90, + 54, + 16, + 3, + 0, + 2, + 0, + 34, + 16, + 15, + 0, + 2, + 0, + 12, + 16, + 39, + 0, + 16, + 52, + 57, + 53, + 54, + 52, + 52, + 53, + 54, + 56, + 48, + 51, + 57, + 48, + 50, + 54, + 51, + 16, + 32, + 0, + 6, + 255, + 255, + 255, + 255, + 255, + 255, ]); var record = WifiRecord()..payload = payload; expect(record.ssid, 'WLAN-82CQZ6'); expect(record.networkKey, '4956445680390263'); - expect(record.authenticationType, - WifiAuthenticationType.wpaWpa2Personal); + expect( + record.authenticationType, WifiAuthenticationType.wpaWpa2Personal); expect(record.encryptionType, WifiEncryptionType.aesTkip); expect(record.networkIndex, 1); expect(record.macAddress, 'FF:FF:FF:FF:FF:FF'); @@ -123,11 +178,38 @@ void main() { test('open network payload', () { final payload = Uint8List.fromList([ - 0x10, 0x0E, 0x00, 0x25, - 0x10, 0x26, 0x00, 0x01, 0x01, - 0x10, 0x45, 0x00, 0x07, 0x6d, 0x79, 0x2d, 0x73, 0x73, 0x69, 0x64, - 0x10, 0x03, 0x00, 0x02, 0x00, 0x01, - 0x10, 0x0F, 0x00, 0x02, 0x00, 0x01, + 0x10, + 0x0E, + 0x00, + 0x25, + 0x10, + 0x26, + 0x00, + 0x01, + 0x01, + 0x10, + 0x45, + 0x00, + 0x07, + 0x6d, + 0x79, + 0x2d, + 0x73, + 0x73, + 0x69, + 0x64, + 0x10, + 0x03, + 0x00, + 0x02, + 0x00, + 0x01, + 0x10, + 0x0F, + 0x00, + 0x02, + 0x00, + 0x01, ]); var record = WifiRecord()..payload = payload; @@ -140,23 +222,72 @@ void main() { test('WPA2 payload with MAC address', () { final payload = Uint8List.fromList([ - 0x10, 0x0E, 0x00, 0x45, - 0x10, 0x26, 0x00, 0x01, 0x01, - 0x10, 0x45, 0x00, 0x0a, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, - 0x68, 0x69, 0x6a, - 0x10, 0x03, 0x00, 0x02, 0x00, 0x20, - 0x10, 0x0F, 0x00, 0x02, 0x00, 0x08, - 0x10, 0x27, 0x00, 0x0a, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, - 0x38, 0x39, 0x30, - 0x10, 0x20, 0x00, 0x06, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x10, + 0x0E, + 0x00, + 0x45, + 0x10, + 0x26, + 0x00, + 0x01, + 0x01, + 0x10, + 0x45, + 0x00, + 0x0a, + 0x61, + 0x62, + 0x63, + 0x64, + 0x65, + 0x66, + 0x67, + 0x68, + 0x69, + 0x6a, + 0x10, + 0x03, + 0x00, + 0x02, + 0x00, + 0x20, + 0x10, + 0x0F, + 0x00, + 0x02, + 0x00, + 0x08, + 0x10, + 0x27, + 0x00, + 0x0a, + 0x31, + 0x32, + 0x33, + 0x34, + 0x35, + 0x36, + 0x37, + 0x38, + 0x39, + 0x30, + 0x10, + 0x20, + 0x00, + 0x06, + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, ]); var record = WifiRecord()..payload = payload; expect(record.ssid, 'abcdefghij'); expect(record.networkKey, '1234567890'); - expect( - record.authenticationType, WifiAuthenticationType.wpa2Personal); + expect(record.authenticationType, WifiAuthenticationType.wpa2Personal); expect(record.encryptionType, WifiEncryptionType.aes); expect(record.macAddress, '01:02:03:04:05:06'); expect(record.networkIndex, 1); @@ -185,8 +316,7 @@ void main() { ); var decoded = decodeRawNdefMessage(encodeNdefMessage([record]))[0] as WifiRecord; - expect( - decoded.authenticationType, WifiAuthenticationType.wpa3Personal); + expect(decoded.authenticationType, WifiAuthenticationType.wpa3Personal); }); test('mixed WPA/WPA2 round-trip', () { @@ -198,8 +328,8 @@ void main() { ); var decoded = decodeRawNdefMessage(encodeNdefMessage([record]))[0] as WifiRecord; - expect(decoded.authenticationType, - WifiAuthenticationType.wpaWpa2Personal); + expect( + decoded.authenticationType, WifiAuthenticationType.wpaWpa2Personal); expect(decoded.encryptionType, WifiEncryptionType.aesTkip); });