Card commands
Lower-level command objects are useful when an app needs explicit control after scan() returns a connected transport. They are open classes; see Subclassing.
let commands = UltralightCommands(transport: transport)
let pages = try await commands.readPages(startPage: 4)
try await commands.writePage(4, data: Data([0x01, 0x02, 0x03, 0x04]),
memoryMap: UltralightMemoryMap.forType(info.type))
let desfire = DESFireCommands(transport: transport)
let version = try await desfire.getVersion()
let aids = try await desfire.getApplicationIDs()
let ntag424 = NTAG424Commands(transport: transport)
let settings = try await ntag424.getFileSettings(fileID: 0x02)
if let sdm = settings.sdm { print(sdm.macOffset as Any) }
try await ntag424.writeDataPlain(fileID: 0x02, offset: 0, data: ndefFile)
let select = CommandAPDU.select(aid: passportAID)
let response = try await iso7816Transport.sendAPDU(select)
if response.isApplicationNotFound { /* the card does not carry this AID */ }
let security = ISO15693SecurityManager(transport: iso15693Transport)
try await security.configureAFI(0x01, permanentlyLock: false)
Ultralight / NTAG write guard
UltralightCommands.writePage(_:data:memoryMap:allowIrreversible:) and compatibilityWrite(_:data:memoryMap:allowIrreversible:), and the top-level CoreExtendedNFC.writeUltralightPage and compatibilityWriteUltralightPage, refuse writes that may be irreversible unless allowIrreversible is true. Pages 0-2 (UID and static lock bytes) are always checked and are refused unless allowIrreversible is true. Page 3 (OTP / Capability Container) bits can only be set, never cleared, so the library reads page 3 first and allows the write only when it would change nothing; writing a new CC needs allowIrreversible: true (or formatNDEF, which opts in). With a memoryMap, every page outside the product's user data area (dynamic lock, CFG, PWD/PACK, Ultralight C key pages) is refused too. Without one, UltralightCommands checks pages 0-3, and the top-level writeUltralightPage / compatibilityWriteUltralightPage also refuse every page above 15. The 1.x names CoreExtendedNFC.writePage / compatibilityWrite are deprecated forwarders with the same guard. A refused write throws NFCError.irreversibleWriteRefused(page:) before anything is sent; malformed arguments throw invalidParameter. WRITE and COMPATIBILITY_WRITE require the 0x0A ACK; an empty response is a failure. UltralightDump.restore validates every page (4 bytes, matching card type) before writing. allowIrreversible: true can permanently lock or brick the tag.
ISO 15693 security
ISO15693SecurityManager.configureAFI(_:permanentlyLock:) and configureDSFID(_:permanentlyLock:) write the value, and send LOCK AFI / LOCK DSFID only when permanentlyLock is true. That lock is irreversible. The older lock: overloads are deprecated. The facade range functions readISO15693Blocks(range:transport:) and getISO15693BlockSecurityStatus(range:transport:) take a ClosedRange<Int> such as 0...3 and throw NFCError.invalidParameter for bounds outside 0...255 before anything is sent; the NSRange overloads are deprecated. ISO15693Reader.readBlock(at:) and writeBlock(at:data:) use the extended commands for blocks from 256 on, and readAllBlocks(info:) replaces the deprecated readAllBlocks(transport:). ISO15693SystemInfo.dsfid, afi and icReference are optional and are nil when the tag does not report them. Block security status bytes are read through ISO15693BlockSecurityStatus, which takes only Lock_flag (bit b1, ISO/IEC 15693-3); the other bits are RFU, and ST tags that flag password protection there are no longer reported as locked. GET SYSTEM INFORMATION (2Bh) holds the block count in one byte, so it cannot describe more than 256 blocks: ISO15693Reader.getSystemInfo(), used by getISO15693SystemInfo, the dump and the Type 5 NDEF paths, asks EXTENDED GET SYSTEM INFORMATION (3Bh, ISO/IEC 15693-3:2019, sent through ISO15693TagTransporting.extendedGetSystemInfo(infoFlags:)) for the memory size when 2Bh reports 256 blocks or no size. ISO15693ExtendedSystemInfo.parse(_:) reads its 2-byte block count, and the result is used only when its block size matches and it reports at least as many blocks; a tag that rejects 3Bh keeps the 2Bh answer. ISO15693TagTransporting.getSystemInfo() itself still sends only 2Bh. Tag error codes 0x12 / 0x14 (block locked) are thrown as NFCError.tagLocked; other ISO/IEC 15693-3 error codes are thrown as NFCError.iso15693Error(code:).
DESFire
getVersion() maps the hardware major version to EV1 (0x01), EV2 (0x12, 0x22, and 0x42 on DESFire/EMV combo chips) or EV3 (0x33, and 0x43 on hardware type 0x81). Other major versions, such as DESFire Light (0x30) and DUOX (0xA0), are reported as generic .mifareDesfire; read hardwareType and hardwareMajorVersion to tell them apart. File settings include Transaction MAC files (type 0x05). The library has no DESFire write API. The automatic dump reads every free-access file and keys each one by AID (3 bytes) ‖ file ID (1 byte), so equal file IDs in different applications stay distinct. Applications and files that need authentication are skipped and counted. Transaction MAC files are counted but not read.
Authentication covers AuthenticateISO (0x1A, 2K3DES; a key with equal halves is treated as single DES) and AuthenticateEV2First (0x71, AES-128). EV2First Part 1 sends KeyNo ‖ LenCap ‖ PCDcap2, with LenCap taken from pcdCapabilities (0 to 6 bytes, empty by default). After AuthenticateISO the card appends an 8-byte CMAC to plain responses; the authenticated read helpers strip it but do not verify it. Not implemented: AuthenticateAES (0xAA), 3K3DES keys (AuthenticateISO takes 16-byte keys only), AuthenticateEV2NonFirst, EV1 and EV2 CMAC secure messaging for commands (MACed and enciphered communication), the EV2 command counter, and Transaction MAC (TMAC) computation. Authenticated reads therefore work only on files whose communication mode is plain.
NTAG 424 DNA
NTAG424Commands sends NTAG 424 DNA native commands in the ISO 7816 wrapped form shared with DESFire (90 CMD 00 00 Lc data 00), in CommMode.Plain only, to the NDEF application D2760000850101, which must already be selected (iOS selects it when it is the first AID in select-identifiers, and a Type 4 NDEF read selects it too). getFileSettings(fileID:) (F5h) returns NTAG424FileSettings: fileType, fileOption, communicationMode (.plain, .mac, .full), accessRights (read, write, readWrite, change nibbles, each also as an AccessCondition: .key(n), .free, .never) and the 24-bit fileSize. When FileOption bit 6 is set, sdm holds SDMOptions, SDMAccessRights (metaRead bits 15-12, fileRead bits 11-8, counterRetrieval bits 3-0, sent least significant byte first, as in the AN12196 examples) and the offsets the datasheet makes present: uidOffset and readCounterOffset for plain mirroring (SDMMetaRead Eh), piccDataOffset for encrypted PICC data (SDMMetaRead 0h-4h), tagTamperStatusOffset when SDMOptions bit 3 is set (NTAG 424 DNA TT only), macInputOffset and macOffset when SDMFileRead is not Fh, encryptedDataOffset / encryptedDataLength with SDMENCFileData, and readCounterLimit with SDMReadCtrLimit. The parser throws invalidResponse for truncated responses, trailing bytes (including the response MAC an authenticated session adds), a file type other than StandardData, and a reserved SDMMetaRead value.
writeDataPlain(fileID:offset:data:chunkSize:) uses the NTAG 424 WriteData command 8Dh, not the DESFire 3Dh. Each command carries FileNo ‖ Offset (3, LE) ‖ Length (3, LE) ‖ data; data longer than chunkSize (default 111 bytes, so each command fits one 128-byte ISO/IEC 14443-4 frame, the only unit the datasheet makes tearing-protected; at most 248, the datasheet limit per WriteData, in which case the reader chains frames and an interrupted chain can leave that chunk partially written) is sent as consecutive WriteData commands at advancing offsets. Each must be answered 9100; the first other status stops the write (desfireError for 91xx, unexpectedStatusWord otherwise) and chunks already acknowledged stay written. A file number above 1Fh, empty data, an offset or end beyond 24 bits, or a chunk size outside 1...248 throws invalidParameter before anything is sent. MAC / Full writes, ChangeFileSettings (SDM provisioning) and EV2 command counters are not implemented.
NTAG 424 DNA has DESFire's ATQA and SAK, so iOS can report it as a DESFire tag. Its GetApplicationIDs answers 91 1C; dumpCard then checks GetVersion, and when the hardware type is 04h (DESFireVersionInfo.ntagHardwareType) it dumps the tag as a Type 4 tag (CC and NDEF file) instead of failing.
APDUs
CommandAPDU encodes short APDUs, and switches to extended length automatically when the command data exceeds 255 bytes or more than 256 response bytes are expected (Nc up to 65535, Ne up to 65536). APDUExchange.transmit(_:maxGetResponseFrames:retryOnWrongLength:using:) resolves 6Cxx (re-issue with the corrected Le) and 61xx (GET RESPONSE chaining). It throws NFCError.responseChainTooLong(maxFrames:) after defaultMaxGetResponseFrames (256) follow-ups.
Type 4
Type4Reader is the Type 4 entry point; Type4Tag is deprecated and forwards to it. Type4CC parses both the NDEF File Control TLV (T4T 2.0, 2-byte NLEN) and the Extended NDEF File Control TLV (T4T 3.0, T = 06h, 4-byte ENLEN). maxNDEFFileSize carries the full size, and ndefLengthFieldSize is 2 or 4. Writes split the length header by MLc and refuse NDEF files larger than 32 KB. UPDATE BINARY answered with 6982 / 6985, or a CC that denies write access, throws NFCError.tagLocked.
Identification
| Type | Use |
|---|---|
CardIdentifier | ATQA and SAK card-family classification. |
ISO14443 | CRC_A, CRC_B, and ATS parsing. parseATS accepts the ATS with or without its TL byte, returns empty fields when T0 b8 is set, and maps FSCI 9-C to 512-4096 bytes (ISO/IEC 14443-4:2016; D-F reserved, treated as 256). |
ASN1Parser | BER-TLV parsing for eMRTD and card payloads. |
ATQA 0344 (7-byte UID) or 0304 (4-byte random ID) with SAK 20 is classified as DESFire. Any other ATQA with SAK 20, and SAK 28 / 38 (ISO 14443-4 with Classic emulation), is reported as .smartMX, a generic ISO 7816 card that refineCardInfo probes for DESFire, Type 4 and eMRTD. Refinement also detects Ultralight C (a 16-byte answer to READ 2B, no authentication attempted), NTAG210/212 from GET_VERSION, and FeliCa Lite vs Lite-S on system code 88B4 (only Lite-S answers a read of block 0x90).