diff --git a/client/src/cmdhfmfu.c b/client/src/cmdhfmfu.c
index a2eee5ba4..2c1a443cd 100644
--- a/client/src/cmdhfmfu.c
+++ b/client/src/cmdhfmfu.c
@@ -7829,16 +7829,9 @@ int CmdHF14MfuNDEFWrite(const char *Cmd) {
return PM3_SUCCESS;
}
-// ---------------------------------------------------------------------------
-// NDEF formatting
-//
-// Page 03h is the OTP Capability Container (CC): once written, it cannot be
-// changed. Refuse unknown types to avoid a permanent wrong CC.
-// Restore NXP's delivery content for pages 03h-05h. NTAG212/213/213F/213TT
-// include a Lock Control TLV before the NDEF TLV; other types do not, so
-// content is copied per type.
-// Only NTAG21x ships with a CC. MF0ICU1/2, MF0ULx1 and NTAG203 leave 03h
-// blank; derive their CC from the user memory range and use an empty NDEF.
+// NDEF formatting - restores the NXP factory delivery content (Capability
+// Container + empty NDEF message) for the detected tag type. Block 3 is OTP.
+// Per-type sources and rationale: doc/mfu_ndef_format_notes.md
typedef struct {
uint64_t tagtype;
const char *name;
@@ -7846,167 +7839,21 @@ typedef struct {
} mfu_ndef_format_t;
static const mfu_ndef_format_t mfu_ndef_format_table[] = {
-
- // MIFARE Ultralight MF0ICU1 MLEN 06h = 48 bytes, user memory pages 04h-0Fh
- // https://www.nxp.com/docs/en/data-sheet/MF0ICU1.pdf rev 3.9 - 23 July 2014
- // memory organization ........... section 7.5, Table 5, page 10 of 31
- // data pages .................... section 7.5, Table 5, page 10 of 31
- // page 03h is OTP, no CC at delivery - CC derived from the page range
- {
- MFU_TT_UL, "MIFARE Ultralight",
- {{0xE1, 0x10, 0x06, 0x00}, {0x03, 0x00, 0xFE, 0x00}, {0x00, 0x00, 0x00, 0x00}}
- },
-
- // MIFARE Ultralight C MF0ICU2 MLEN 12h = 144 bytes, user memory pages 04h-27h
- // https://www.nxp.com/docs/en/data-sheet/MF0ICU2.pdf rev 3.5 - 30 January 2026
- // memory organization ........... section 7.5, Table 5, page 8 of 35
- // data pages .................... section 7.5, Table 5, page 8 of 35
- // OTP preset to all 0 ........... section 7.5.4, page 11 of 35
- // CC derived from the page range. The data area ends at page 27h, right before
- // the lock bytes at page 28h, so no Lock Control TLV is needed.
- {
- MFU_TT_UL_C, "MIFARE Ultralight C",
- {{0xE1, 0x10, 0x12, 0x00}, {0x03, 0x00, 0xFE, 0x00}, {0x00, 0x00, 0x00, 0x00}}
- },
-
- // MIFARE Ultralight EV1 48 MF0UL11 MLEN 06h = 48 bytes, user memory pages 04h-0Fh
- // https://www.nxp.com/docs/en/data-sheet/MF0ULX1.pdf rev 3.3 - 9 April 2019
- // memory organization ........... section 8.5, Fig 5, page 10 of 45
- // data pages .................... section 8.5.5, page 14 of 45
- // OTP default 00 00 00 00h ...... section 8.5.4, page 13-14 of 45
- // CC derived from the page range.
- {
- MFU_TT_UL_EV1_48, "MIFARE Ultralight EV1 48",
- {{0xE1, 0x10, 0x06, 0x00}, {0x03, 0x00, 0xFE, 0x00}, {0x00, 0x00, 0x00, 0x00}}
- },
-
- // MIFARE Ultralight EV1 128 MF0UL21 MLEN 10h = 128 bytes, user memory pages 04h-23h
- // https://www.nxp.com/docs/en/data-sheet/MF0ULX1.pdf rev 3.3 - 9 April 2019
- // memory organization ........... section 8.5, Fig 6, page 11 of 45
- // data pages .................... section 8.5.5, page 14 of 45
- // OTP default 00 00 00 00h ...... section 8.5.4, page 13-14 of 45
- // CC derived from the page range.
- {
- MFU_TT_UL_EV1_128, "MIFARE Ultralight EV1 128",
- {{0xE1, 0x10, 0x10, 0x00}, {0x03, 0x00, 0xFE, 0x00}, {0x00, 0x00, 0x00, 0x00}}
- },
-
- // NTAG203 NT2H0301 MLEN 12h = 144 bytes, user memory pages 04h-27h
- // https://www.nxp.com/docs/en/data-sheet/NTAG203.pdf rev 3.0 - 17 October 2011
- // memory organization ........... section 8.5, Table 5, page 10 of 30
- // data pages .................... section 8.5.4, page 13 of 30
- // OTP preset to all 0 ........... section 8.5.3, page 13 of 30
- // CC derived from the page range: page 03h is a plain OTP page here, not a
- // Capability Container, so there is no delivery value to copy. Its dynamic
- // lock bytes at page 28h use 4 page granularity (section 8.5.2, Fig 7, page
- // 12 of 30) against 2 for NTAG213, so that Lock Control TLV would misdescribe
- // this part - the plain empty NDEF message is written instead.
- {
- MFU_TT_NTAG_203, "NTAG203",
- {{0xE1, 0x10, 0x12, 0x00}, {0x03, 0x00, 0xFE, 0x00}, {0x00, 0x00, 0x00, 0x00}}
- },
-
- // NTAG210 NT2H1011 MLEN 06h = 48 bytes, user memory pages 04h-0Fh
- // https://www.nxp.com/docs/en/data-sheet/NTAG210_212.pdf rev 3.0 - 14 March 2013
- // memory organization ........... section 8.5, Fig 4, page 10 of 46
- // data pages .................... section 8.5.5, page 13 of 46
- // content at delivery ........... section 8.5.6, Table 4, page 14 of 46
- {
- MFU_TT_NTAG_210, "NTAG210",
- {{0xE1, 0x10, 0x06, 0x00}, {0x03, 0x00, 0xFE, 0x00}, {0x00, 0x00, 0x00, 0x00}}
- },
-
- // NTAG210u NT2L1001 / NT2H1001 MLEN 06h = 48 bytes, user memory blocks 04h-0Fh
- // https://www.nxp.com/docs/en/data-sheet/NT2L1001_NT2H1001.pdf rev 3.0 - 7 September 2016
- // data blocks ................... section 9.5.4, page 11 of 32
- // content at delivery ........... section 9.5.5, Table 4, page 11 of 32
- {
- MFU_TT_NTAG_210u, "NTAG210u",
- {{0xE1, 0x10, 0x06, 0x00}, {0x03, 0x00, 0xFE, 0x00}, {0x00, 0x00, 0x00, 0x00}}
- },
-
- // NTAG212 NT2L1211 MLEN 10h = 128 bytes, user memory pages 04h-23h
- // https://www.nxp.com/docs/en/data-sheet/NTAG210_212.pdf rev 3.0 - 14 March 2013
- // memory organization ........... section 8.5, Fig 5, page 10 of 46
- // data pages .................... section 8.5.5, page 13 of 46
- // content at delivery ........... section 8.5.6, Table 5, page 14 of 46
- {
- MFU_TT_NTAG_212, "NTAG212",
- {{0xE1, 0x10, 0x10, 0x00}, {0x01, 0x03, 0x90, 0x0A}, {0x34, 0x03, 0x00, 0xFE}}
- },
-
- // NTAG213 NT2H1311 MLEN 12h = 144 bytes, user memory pages 04h-27h
- // https://www.nxp.com/docs/en/data-sheet/NTAG213_215_216.pdf rev 3.2 - 2 June 2015
- // memory organization ........... section 8.5, Fig 5, page 11 of 60
- // data pages .................... section 8.5.5, page 16 of 60
- // content at delivery ........... section 8.5.6, Table 5, page 17 of 60
- {
- MFU_TT_NTAG_213, "NTAG213",
- {{0xE1, 0x10, 0x12, 0x00}, {0x01, 0x03, 0xA0, 0x0C}, {0x34, 0x03, 0x00, 0xFE}}
- },
-
- // NTAG213F NT2H1311F MLEN 12h = 144 bytes, user memory pages 04h-27h
- // https://www.nxp.com/docs/en/data-sheet/NTAG213F_216F.pdf rev 3.6 - 28 September 2015
- // memory organization ........... section 8.5, Fig 6, page 12 of 55
- // data pages .................... section 8.5.5, page 16 of 55
- // content at delivery ........... section 8.5.6, Table 5, page 17 of 55
- {
- MFU_TT_NTAG_213_F, "NTAG213F",
- {{0xE1, 0x10, 0x12, 0x00}, {0x01, 0x03, 0xA0, 0x0C}, {0x34, 0x03, 0x00, 0xFE}}
- },
-
- // NTAG213TT NT2H1311TT MLEN 12h = 144 bytes, user memory pages 04h-27h
- // https://www.nxp.com/docs/en/data-sheet/NT2H1311TT.pdf rev 1.1 - 28 March 2017
- // memory organization ........... section 8.5, Fig 4, page 11 of 57
- // data pages .................... section 8.5.5, page 14 of 57
- // content at delivery ........... section 8.5.6, Table 5, page 15 of 57
- {
- MFU_TT_NTAG_213_TT, "NTAG213TT",
- {{0xE1, 0x10, 0x12, 0x00}, {0x01, 0x03, 0xA0, 0x0C}, {0x34, 0x03, 0x00, 0xFE}}
- },
-
- // NTAG213C NT2H1311C1DTL MLEN 12h = 144 bytes, user memory pages 04h-27h
- // NO DATA SHEET. NXP publishes nothing for this part number and no other
- // public documentation could be found.
- // Most data come from commit ad19f8384 (2020-09-26, "add accurate detection for
- // NT2H1311C1DTL")
- {
- MFU_TT_NTAG_213_C, "NTAG213C",
- {{0xE1, 0x10, 0x12, 0x00}, {0x01, 0x03, 0xA0, 0x0C}, {0x34, 0x03, 0x00, 0xFE}}
- },
-
- // NTAG215 NT2H1511 MLEN 3Eh = 496 bytes, user memory pages 04h-81h
- // https://www.nxp.com/docs/en/data-sheet/NTAG213_215_216.pdf rev 3.2 - 2 June 2015
- // memory organization ........... section 8.5, Fig 6, page 11 of 60
- // data pages .................... section 8.5.5, page 16 of 60
- // content at delivery ........... section 8.5.6, Table 6, page 17 of 60
- // The factory MLEN announces 496 bytes while the user memory holds 504. The
- // data sheet gives no reason for the 8 byte difference
- {
- MFU_TT_NTAG_215, "NTAG215",
- {{0xE1, 0x10, 0x3E, 0x00}, {0x03, 0x00, 0xFE, 0x00}, {0x00, 0x00, 0x00, 0x00}}
- },
-
- // NTAG216 NT2H1611 MLEN 6Dh = 872 bytes, user memory pages 04h-E1h
- // https://www.nxp.com/docs/en/data-sheet/NTAG213_215_216.pdf rev 3.2 - 2 June 2015
- // memory organization ........... section 8.5, Fig 7, page 12 of 60
- // data pages .................... section 8.5.5, page 16 of 60
- // content at delivery ........... section 8.5.6, Table 7, page 17 of 60
- // Announces 872 bytes against 888 of user memory.
- {
- MFU_TT_NTAG_216, "NTAG216",
- {{0xE1, 0x10, 0x6D, 0x00}, {0x03, 0x00, 0xFE, 0x00}, {0x00, 0x00, 0x00, 0x00}}
- },
-
- // NTAG216F NT2H1611F MLEN 6Dh = 872 bytes, user memory pages 04h-E1h
- // https://www.nxp.com/docs/en/data-sheet/NTAG213F_216F.pdf rev 3.6 - 28 September 2015
- // memory organization ........... section 8.5, Fig 7, page 12 of 55
- // data pages .................... section 8.5.5, page 16 of 55
- // content at delivery ........... section 8.5.6, Table 6, page 17 of 55
- {
- MFU_TT_NTAG_216_F, "NTAG216F",
- {{0xE1, 0x10, 0x6D, 0x00}, {0x03, 0x00, 0xFE, 0x00}, {0x00, 0x00, 0x00, 0x00}}
- },
+ { MFU_TT_UL, "MIFARE Ultralight", {{0xE1, 0x10, 0x06, 0x00}, {0x03, 0x00, 0xFE, 0x00}, {0x00, 0x00, 0x00, 0x00}} },
+ { MFU_TT_UL_C, "MIFARE Ultralight C", {{0xE1, 0x10, 0x12, 0x00}, {0x03, 0x00, 0xFE, 0x00}, {0x00, 0x00, 0x00, 0x00}} },
+ { MFU_TT_UL_EV1_48, "MIFARE Ultralight EV1 48", {{0xE1, 0x10, 0x06, 0x00}, {0x03, 0x00, 0xFE, 0x00}, {0x00, 0x00, 0x00, 0x00}} },
+ { MFU_TT_UL_EV1_128, "MIFARE Ultralight EV1 128", {{0xE1, 0x10, 0x10, 0x00}, {0x03, 0x00, 0xFE, 0x00}, {0x00, 0x00, 0x00, 0x00}} },
+ { MFU_TT_NTAG_203, "NTAG203", {{0xE1, 0x10, 0x12, 0x00}, {0x03, 0x00, 0xFE, 0x00}, {0x00, 0x00, 0x00, 0x00}} },
+ { MFU_TT_NTAG_210, "NTAG210", {{0xE1, 0x10, 0x06, 0x00}, {0x03, 0x00, 0xFE, 0x00}, {0x00, 0x00, 0x00, 0x00}} },
+ { MFU_TT_NTAG_210u, "NTAG210u", {{0xE1, 0x10, 0x06, 0x00}, {0x03, 0x00, 0xFE, 0x00}, {0x00, 0x00, 0x00, 0x00}} },
+ { MFU_TT_NTAG_212, "NTAG212", {{0xE1, 0x10, 0x10, 0x00}, {0x01, 0x03, 0x90, 0x0A}, {0x34, 0x03, 0x00, 0xFE}} },
+ { MFU_TT_NTAG_213, "NTAG213", {{0xE1, 0x10, 0x12, 0x00}, {0x01, 0x03, 0xA0, 0x0C}, {0x34, 0x03, 0x00, 0xFE}} },
+ { MFU_TT_NTAG_213_F, "NTAG213F", {{0xE1, 0x10, 0x12, 0x00}, {0x01, 0x03, 0xA0, 0x0C}, {0x34, 0x03, 0x00, 0xFE}} },
+ { MFU_TT_NTAG_213_TT, "NTAG213TT", {{0xE1, 0x10, 0x12, 0x00}, {0x01, 0x03, 0xA0, 0x0C}, {0x34, 0x03, 0x00, 0xFE}} },
+ { MFU_TT_NTAG_213_C, "NTAG213C", {{0xE1, 0x10, 0x12, 0x00}, {0x01, 0x03, 0xA0, 0x0C}, {0x34, 0x03, 0x00, 0xFE}} },
+ { MFU_TT_NTAG_215, "NTAG215", {{0xE1, 0x10, 0x3E, 0x00}, {0x03, 0x00, 0xFE, 0x00}, {0x00, 0x00, 0x00, 0x00}} },
+ { MFU_TT_NTAG_216, "NTAG216", {{0xE1, 0x10, 0x6D, 0x00}, {0x03, 0x00, 0xFE, 0x00}, {0x00, 0x00, 0x00, 0x00}} },
+ { MFU_TT_NTAG_216_F, "NTAG216F", {{0xE1, 0x10, 0x6D, 0x00}, {0x03, 0x00, 0xFE, 0x00}, {0x00, 0x00, 0x00, 0x00}} },
};
static const mfu_ndef_format_t *mfu_get_ndef_format(uint64_t tagtype) {
@@ -8036,7 +7883,7 @@ int CmdHF14AMfUFormat(const char *Cmd) {
"hf mfu format -k FFFFFFFF\n"
"hf mfu format -k 49454D4B41455242214E4143554F5946\n"
"hf mfu format -d E1101200 --force"
- );
+ );
void *argtable[] = {
arg_param_begin,
@@ -8134,10 +7981,7 @@ int CmdHF14AMfUFormat(const char *Cmd) {
if (cc_len == MFU_BLOCK_SIZE) {
- // A hand written Capability Container still must not announce more NDEF
- // area than the tag physically holds. Block 3 is OTP, so an over reporting
- // MLEN can never be taken back, and ndefwrite would then clamp against the
- // whole chip - lock bytes, configuration and key pages included.
+ // -d must not announce more memory than this tag type actually has
if ((fmt != NULL) && (cc_override[2] > fmt->page[0][2]) && (force == false)) {
PrintAndLogEx(FAILED, "Capability Container announces more memory than this tag has");
PrintAndLogEx(INFO, " requested... %d bytes ( MLEN %02X )", cc_override[2] * 8, cc_override[2]);
@@ -8194,9 +8038,7 @@ int CmdHF14AMfUFormat(const char *Cmd) {
return PM3_ESOFT;
}
- // Block 3 is OTP: a WRITE is OR'ed with what is already there and a bit that
- // is set can never be cleared. Anything the OR can not produce is refused
- // before a single byte goes to the tag.
+ // block 3 is OTP: refuse a target the current content can't reach via OR
uint8_t *cur = data + (MFU_NDEF_CC_BLOCK * MFU_BLOCK_SIZE);
bool blank = true;
bool reachable = true;
@@ -8239,20 +8081,13 @@ int CmdHF14AMfUFormat(const char *Cmd) {
}
}
- // The erase range is derived from the MLEN in the table, never from -d, so a
- // hand written Capability Container can not push it past the user memory.
- // Every table entry announces at most the user memory of that type, which
- // keeps the last block below the lock bytes and configuration pages.
+ // erase range from the table MLEN, never from -d - can't run past user memory
uint16_t last_block = MFU_NDEF_CC_BLOCK + 2;
if (erase && (fmt != NULL)) {
last_block = (uint16_t)(MFU_NDEF_CC_BLOCK + (fmt->page[0][2] * 2));
}
- // WRITE addresses blocks with a single byte, so block 255 is the last one
- // reachable - the same limit MFU_NDEF_MAX_BYTES encodes for ndefread/ndefwrite.
- // No current table entry comes close, but mfu_write_block takes a uint8_t and
- // the cast below would silently wrap a larger block number back onto the UID
- // and lock pages, so clamp here rather than rely on the table staying small.
+ // block 255 is the last one WRITE can reach with its single address byte
if (last_block > 0xFF) {
PrintAndLogEx(INFO, "Data area runs past block 255, stopping at the last addressable block");
last_block = 0xFF;
@@ -8314,10 +8149,7 @@ int CmdHF14AMfUFormat(const char *Cmd) {
DropField();
PrintAndLogEx(NORMAL, "");
- // Block 3 is one time programmable, so there is exactly one chance to get it
- // right. A tag can ACK a WRITE and still not commit the page - a weak field, a
- // tear, or a block locking bit that was already set - and the write status
- // alone would not show it. Read the formatted blocks back rather than trust it.
+ // read the formatted blocks back rather than trust the write status alone
if (ul_auth_select(&card, tagtype, (has_auth_key || has_pwd), auth_key_ptr, pack, sizeof(pack), use_schann) == PM3_ESOFT) {
PrintAndLogEx(WARNING, "Wrote the tag but could not re-select it to verify");
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf mfu ndefread") "` to check it yourself");
@@ -8345,9 +8177,7 @@ int CmdHF14AMfUFormat(const char *Cmd) {
if (memcmp(verify + MFU_BLOCK_SIZE, pages[1], 2 * MFU_BLOCK_SIZE) != 0) {
PrintAndLogEx(FAILED, "Capability Container is correct but the empty NDEF message is not");
- // pages[1] and pages[2] are contiguous, and so are the two blocks in
- // verify, so each side prints in a single call - sprint_hex_inrow hands
- // back one shared static buffer, so two calls per line would alias.
+ // one sprint_hex_inrow() call per line: it returns a shared static buffer
PrintAndLogEx(INFO, " wanted...... " _GREEN_("%s"), sprint_hex_inrow(pages[1], 2 * MFU_BLOCK_SIZE));
PrintAndLogEx(INFO, " on tag now.. " _RED_("%s"), sprint_hex_inrow(verify + MFU_BLOCK_SIZE, 2 * MFU_BLOCK_SIZE));
PrintAndLogEx(HINT, "Hint: these blocks are ordinary user memory, check the lock bytes");
diff --git a/doc/mfu_ndef_format_notes.md b/doc/mfu_ndef_format_notes.md
new file mode 100644
index 000000000..d4a79a86e
--- /dev/null
+++ b/doc/mfu_ndef_format_notes.md
@@ -0,0 +1,102 @@
+# Notes on `hf mfu format`
+
+
+# Table of Contents
+- [Notes on hf mfu format](#notes-on-hf-mfu-format)
+- [Table of Contents](#table-of-contents)
+ - [Why the command exists](#why-the-command-exists)
+ - [Capability Container basics](#capability-container-basics)
+ - [Per-type delivery content](#per-type-delivery-content)
+ - [Lock Control TLV](#lock-control-tlv)
+ - [NTAG215 / NTAG216 under-reporting](#ntag215--ntag216-under-reporting)
+ - [NTAG213C](#ntag213c)
+
+## Why the command exists
+^[Top](#top)
+
+`hf mfu ndefwrite` refuses a tag with no Capability Container (CC). Before this command the only
+way to add one was a hand computed `hf mfu wrbl -b 3 -d ` — block 3 is One Time Programmable
+(OTP), so a wrong value is permanent.
+
+`hf mfu format` writes the NXP factory delivery content (CC + an empty NDEF message) for the
+detected tag type, restoring what the tag looked like before anything was written to it.
+
+## Capability Container basics
+^[Top](#top)
+
+Page 3 (`E1 10 00`) is OTP on every type below: a WRITE is bit-wise OR'ed with the current
+content, so a bit already set to 1 can never be cleared again. `MLEN * 8` is the size of the NDEF
+data area in bytes.
+
+Consequences for the implementation, in `mfu_get_ndef_format()` / `CmdHF14AMfUFormat()` in
+`client/src/cmdhfmfu.c`:
+
+- an unknown tag type is refused rather than guessed at
+- a target CC that the current OTP content cannot reach (checked with the same bit-wise OR) is
+ refused before anything is written
+- `-d` on a *known* type is capped at that type's own MLEN unless `--force` is given, so a typo
+ cannot silently announce more memory than the tag holds
+- `--erase`'s end block is derived from the table's MLEN, never from `-d`, so it cannot run past
+ the user memory into the lock bytes or configuration pages
+- after writing, the command re-selects and reads blocks 3-5 back to confirm the OTP write
+ actually took — a tag can ACK a WRITE and still not commit the page (weak field, tearing, a lock
+ bit already set)
+
+## Per-type delivery content
+^[Top](#top)
+
+Only the NTAG21x family ships with a CC at all. UL / UL-C / UL EV1 and NTAG203 leave page 3 blank
+at delivery, so their CC is derived from the user memory range instead of copied from a data
+sheet.
+
+| Type | Part | MLEN | User memory | Data sheet |
+|---|---|---|---|---|
+| MIFARE Ultralight | MF0ICU1 | 06h (48B) | pages 04h-0Fh | [MF0ICU1.pdf](https://www.nxp.com/docs/en/data-sheet/MF0ICU1.pdf) rev 3.9, §7.5 Table 5 (p.10/31, OTP blank) |
+| MIFARE Ultralight C | MF0ICU2 | 12h (144B) | pages 04h-27h | [MF0ICU2.pdf](https://www.nxp.com/docs/en/data-sheet/MF0ICU2.pdf) rev 3.5, §7.5 Table 5 (p.8/35), §7.5.4 (p.11/35, OTP blank) |
+| MIFARE Ultralight EV1 48 | MF0UL11 | 06h (48B) | pages 04h-0Fh | [MF0ULX1.pdf](https://www.nxp.com/docs/en/data-sheet/MF0ULX1.pdf) rev 3.3, §8.5 Fig 5 (p.10/45), §8.5.4 (p.13-14/45, OTP blank) |
+| MIFARE Ultralight EV1 128 | MF0UL21 | 10h (128B) | pages 04h-23h | [MF0ULX1.pdf](https://www.nxp.com/docs/en/data-sheet/MF0ULX1.pdf) rev 3.3, §8.5 Fig 6 (p.11/45), §8.5.4 (p.13-14/45, OTP blank) |
+| NTAG203 | NT2H0301 | 12h (144B) | pages 04h-27h | [NTAG203.pdf](https://www.nxp.com/docs/en/data-sheet/NTAG203.pdf) rev 3.0, §8.5 Table 5 (p.10/30), §8.5.3 (p.13/30, OTP blank) |
+| NTAG210 | NT2H1011 | 06h (48B) | pages 04h-0Fh | [NTAG210_212.pdf](https://www.nxp.com/docs/en/data-sheet/NTAG210_212.pdf) rev 3.0, §8.5.6 Table 4 (p.14/46) |
+| NTAG210u | NT2L1001 / NT2H1001 | 06h (48B) | pages 04h-0Fh | [NT2L1001_NT2H1001.pdf](https://www.nxp.com/docs/en/data-sheet/NT2L1001_NT2H1001.pdf) rev 3.0, §9.5.5 Table 4 (p.11/32) |
+| NTAG212 | NT2L1211 | 10h (128B) | pages 04h-23h | [NTAG210_212.pdf](https://www.nxp.com/docs/en/data-sheet/NTAG210_212.pdf) rev 3.0, §8.5.6 Table 5 (p.14/46) |
+| NTAG213 | NT2H1311 | 12h (144B) | pages 04h-27h | [NTAG213_215_216.pdf](https://www.nxp.com/docs/en/data-sheet/NTAG213_215_216.pdf) rev 3.2, §8.5.6 Table 5 (p.17/60) |
+| NTAG213F | NT2H1311F | 12h (144B) | pages 04h-27h | [NTAG213F_216F.pdf](https://www.nxp.com/docs/en/data-sheet/NTAG213F_216F.pdf) rev 3.6, §8.5.6 Table 5 (p.17/55) |
+| NTAG213TT | NT2H1311TT | 12h (144B) | pages 04h-27h | [NT2H1311TT.pdf](https://www.nxp.com/docs/en/data-sheet/NT2H1311TT.pdf) rev 1.1, §8.5.6 Table 5 (p.15/57) |
+| NTAG213C | NT2H1311C1DTL | 12h (144B) | pages 04h-27h | none — see [NTAG213C](#ntag213c) |
+| NTAG215 | NT2H1511 | 3Eh (496B) | pages 04h-81h | [NTAG213_215_216.pdf](https://www.nxp.com/docs/en/data-sheet/NTAG213_215_216.pdf) rev 3.2, §8.5.6 Table 6 (p.17/60) |
+| NTAG216 | NT2H1611 | 6Dh (872B) | pages 04h-E1h | [NTAG213_215_216.pdf](https://www.nxp.com/docs/en/data-sheet/NTAG213_215_216.pdf) rev 3.2, §8.5.6 Table 7 (p.17/60) |
+| NTAG216F | NT2H1611F | 6Dh (872B) | pages 04h-E1h | [NTAG213F_216F.pdf](https://www.nxp.com/docs/en/data-sheet/NTAG213F_216F.pdf) rev 3.6, §8.5.6 Table 6 (p.17/55) |
+
+## Lock Control TLV
+^[Top](#top)
+
+Standards ref: NFC Forum Type 2 Tag Operation, and the Capability Container layout in the data
+sheets cited above.
+
+NTAG212, NTAG213, NTAG213F and NTAG213TT are delivered with a 5 byte Lock Control TLV ahead of the
+NDEF TLV (`01 03 ...`). The other types in the table
+are not. This TLV tells an NFC device where the *dynamic* lock bytes live so it can lock the tag
+read-only — on these parts the dynamic lock bytes sit just past the user memory (e.g. NTAG213 at
+page 40h, right after the page 04h-27h data area), so the TLV exists purely for that use case, not
+because a writer needs to avoid overwriting anything.
+
+`hf mfu format` copies this TLV verbatim from the factory content; it does not construct one.
+`hf mfu ndefwrite` preserves any control TLV (type `01` or `02`) it finds ahead of the NDEF TLV
+before overwriting the data area — see the code for the exact scan.
+
+## NTAG215 / NTAG216 under-reporting
+^[Top](#top)
+
+The factory MLEN announces less than the physical user memory: NTAG215 announces 496 bytes of a
+504 byte area, NTAG216 announces 872 of 888. The data sheet does not explain the 8/16 byte gap.
+The NXP value is used as-is rather than corrected upward, since under-reporting can never let a
+write run past the user memory while a larger value could.
+
+## NTAG213C
+^[Top](#top)
+
+NXP publishes no data sheet for this part number (NT2H1311C1DTL) and none could be found anywhere
+else. It was added to the client in commit `ad19f8384` (2020-09-26, "add accurate detection for
+NT2H1311C1DTL") from an observed tag's `GET_VERSION` response, which differs from a plain NTAG213
+only in the minor product version byte (`01h` vs `00h`); the storage size byte — the one that
+encodes the 144 byte user memory — is identical. The NTAG213 content is used on that basis.