This interactive ESP32-based simulation demonstrates NFC reader behavior without requiring physical hardware. You will parse NDEF messages with TLV containers, identify NFC Forum Tag Types 1-5, understand anti-collision protocols for multi-tag environments, and analyze security concepts including relay attacks and authentication mechanisms.
Sensor Squad: Simulating NFC
Sammy the Sensor was curious: “I want to learn about NFC readers, but I do not have the special hardware!” Max the Microcontroller smiled and said, “No problem! We can use a simulation – it is like a video game version of an NFC reader. It shows you exactly how the reader talks to tags, reads their data, and checks if they are real or fake.” Bella the Battery added, “The simulation even shows what happens when two tags are near the reader at the same time. The reader has a clever trick called anti-collision where it asks each tag to identify itself one at a time, like a teacher calling roll.” Lila the LED chimed in, “And the best part? You can see what a hacker attack looks like and how the reader catches it. It is like being a security detective!”
36.1 Learning Objectives
By the end of this chapter, you will be able to:
Deconstruct NDEF Messages: Parse TLV containers, interpret record header flags, and extract payload structures from raw byte sequences
Classify NFC Tag Types: Differentiate NFC Forum Tag Types 1-5 by memory capacity, security features, and appropriate deployment scenarios
Trace Anti-Collision Protocols: Diagram the REQA/ATQA/SELECT sequence that resolves multiple simultaneous tags in the RF field
Critique NFC Security Mechanisms: Evaluate authentication levels, encryption schemes, and relay attack vectors across different tag types
Construct NFC Simulations: Build software models that replicate NFC reader detection, NDEF parsing, and security validation behaviors
For Beginners: Using This Simulation Lab
What is this chapter? An interactive ESP32-based simulation that demonstrates NFC concepts without requiring physical hardware.
Why simulation? Real NFC communication requires specialized RF hardware operating at 13.56 MHz. This simulation models the logical behavior of NFC systems, helping you understand protocols and data structures.
Time: ~30 min | Level: Advanced | Unit: P08.C22.U02
36.3.1 What You Will Learn
This interactive lab simulates an ESP32-based NFC reader system. Since NFC hardware (like the PN532 module) cannot be physically simulated in Wokwi, we demonstrate NFC concepts through software simulation that models:
NFC Tag Detection: How readers detect and identify tags in range
NDEF Message Parsing: Decoding the NFC Data Exchange Format structure
Tag Type Identification: Differentiating between NFC Forum Tag Types 1-5
URL and Text Records: Processing common NDEF record types
Security Concepts: Understanding authentication, encryption, and relay attack prevention
Why Simulation?
Real NFC communication requires specialized RF hardware operating at 13.56 MHz. This simulation models the logical behavior of NFC systems, helping you understand protocols and data structures before working with physical hardware.
36.3.2 Interactive Wokwi Simulation
Setup Instructions
Copy the complete code below into the Wokwi editor
Click the green “Play” button to start the simulation
Open the Serial Monitor (115200 baud) to view output
The simulation will automatically cycle through NFC scenarios
36.3.3 Complete NFC Simulation Code
Copy this code into the Wokwi ESP32 project:
/* * NFC Reader Simulation Lab * * This educational simulation demonstrates NFC concepts including: * - NDEF message structure and parsing * - Tag type identification (Types 1-5) * - URL and Text record handling * - Security concepts (authentication, encryption indicators) * - Anti-collision and tag detection simulation * * Author: IoT Class Educational Materials * License: MIT */#include <Arduino.h>// =============================================================================// SECTION 1: NFC CONSTANTS AND DEFINITIONS// =============================================================================// NFC Forum Tag Typesenum NFCTagType { TAG_TYPE_1 =1,// Topaz, 96-2000 bytes, simple TAG_TYPE_2 =2,// NTAG, MIFARE Ultralight, 48-888 bytes TAG_TYPE_3 =3,// FeliCa, 1-9 KB, Japan transit TAG_TYPE_4 =4,// DESFire, JCOP, up to 32KB, ISO 14443-4 TAG_TYPE_5 =5// ICODE SLIX, ISO 15693};// NDEF Record Type Name Format (TNF)enum NDEFTypeName { TNF_EMPTY =0x00, TNF_WELL_KNOWN =0x01, TNF_MIME_MEDIA =0x02, TNF_ABSOLUTE_URI =0x03, TNF_EXTERNAL =0x04, TNF_UNKNOWN =0x05, TNF_UNCHANGED =0x06, TNF_RESERVED =0x07};// Well-Known Record Types#define RTD_TEXT 'T'#define RTD_URI 'U'#define RTD_SMART_POSTER 'S'// URI Identifier Codes (prefix compression)constchar* URI_PREFIXES[]={"",// 0x00 - No prefix"http://www.",// 0x01"https://www.",// 0x02"http://",// 0x03"https://",// 0x04"tel:",// 0x05"mailto:",// 0x06"ftp://anonymous:anonymous@",// 0x07"ftp://ftp.",// 0x08"ftps://",// 0x09"sftp://",// 0x0A"smb://",// 0x0B"nfs://",// 0x0C"ftp://",// 0x0D"dav://",// 0x0E"news:",// 0x0F"telnet://",// 0x10"imap:",// 0x11"rtsp://",// 0x12"urn:",// 0x13"pop:",// 0x14"sip:",// 0x15"sips:",// 0x16"tftp:",// 0x17"btspp://",// 0x18"btl2cap://",// 0x19"btgoep://",// 0x1A"tcpobex://",// 0x1B"irdaobex://",// 0x1C"file://",// 0x1D"urn:epc:id:",// 0x1E"urn:epc:tag:",// 0x1F"urn:epc:pat:",// 0x20"urn:epc:raw:",// 0x21"urn:epc:",// 0x22"urn:nfc:"// 0x23};#define URI_PREFIX_COUNT 36// =============================================================================// SECTION 2: DATA STRUCTURES// =============================================================================// NDEF Record Structurestruct NDEFRecord {uint8_t tnf;// Type Name Format (3 bits)bool mb;// Message Begin flagbool me;// Message End flagbool cf;// Chunk Flagbool sr;// Short Record (payload < 256 bytes)bool il;// ID Length presentuint8_t typeLength;uint32_t payloadLength;uint8_t idLength;char type[64];uint8_t payload[256];char id[64];};// NFC Tag Structurestruct NFCTag {uint8_t uid[7];// Unique Identifier (4 or 7 bytes)uint8_t uidLength; NFCTagType tagType;uint16_t memorySize;// Total memory in bytesuint16_t usedMemory;bool isLocked;bool hasPassword;uint8_t ndefData[512];uint16_t ndefLength;char tagName[32];// Security featuresbool hasAuthentication;bool isEncrypted;uint8_t securityLevel;// 0=none, 1=password, 2=AES, 3=3DES};// NFC Reader Stateenum ReaderState { STATE_IDLE, STATE_POLLING, STATE_DETECTED, STATE_AUTHENTICATING, STATE_READING, STATE_WRITING, STATE_ERROR};// =============================================================================// SECTION 3: SIMULATED NFC TAG DATABASE// =============================================================================// Pre-configured simulated tagsNFCTag simulatedTags[5];int numSimulatedTags =5;void initializeSimulatedTags(){// Tag 1: Simple URL Tag (NTAG213) simulatedTags[0]={.uid ={0x04,0xA3,0xB2,0xC1,0xD4,0x5E,0x80},.uidLength =7,.tagType = TAG_TYPE_2,.memorySize =144,.usedMemory =45,.isLocked =false,.hasPassword =false,.ndefData ={0},.ndefLength =0,.tagName ="URL Tag (NTAG213)",.hasAuthentication =false,.isEncrypted =false,.securityLevel =0};// Create NDEF message for URL tag// NDEF TLV: Type=03, Length, NDEF Messageuint8_t urlNdef[]={0x03,0x14,// NDEF TLV, length 200xD1,// MB=1, ME=1, CF=0, SR=1, IL=0, TNF=010x01,// Type Length = 10x10,// Payload Length = 160x55,// Type = 'U' (URI)0x04,// URI prefix: https://'i','o','t','c','l','a','s','s',// Payload'.','e','x','a','m','p','l','e',0xFE// Terminator TLV}; memcpy(simulatedTags[0].ndefData, urlNdef,sizeof(urlNdef)); simulatedTags[0].ndefLength =sizeof(urlNdef);// Tag 2: Text Record Tag (NTAG216) simulatedTags[1]={.uid ={0x04,0xE7,0xF8,0x92,0x1A,0x3C,0x70},.uidLength =7,.tagType = TAG_TYPE_2,.memorySize =888,.usedMemory =128,.isLocked =false,.hasPassword =true,.ndefData ={0},.ndefLength =0,.tagName ="Text Tag (NTAG216)",.hasAuthentication =false,.isEncrypted =false,.securityLevel =1};// NDEF message with text recorduint8_t textNdef[]={0x03,0x21,// NDEF TLV, length 330xD1,// MB=1, ME=1, CF=0, SR=1, IL=0, TNF=010x01,// Type Length = 10x1D,// Payload Length = 290x54,// Type = 'T' (Text)0x02,// Status byte: UTF-8, lang len=2'e','n',// Language code: English'W','e','l','c','o','m','e',' ','t','o',' ','I','o','T',' ','C','l','a','s','s',' ','L','a','b','!',0xFE// Terminator TLV}; memcpy(simulatedTags[1].ndefData, textNdef,sizeof(textNdef)); simulatedTags[1].ndefLength =sizeof(textNdef);// Tag 3: Smart Poster (NTAG215) simulatedTags[2]={.uid ={0x04,0xB5,0xC6,0xD7,0xE8,0xF9,0x0A},.uidLength =7,.tagType = TAG_TYPE_2,.memorySize =504,.usedMemory =256,.isLocked =true,.hasPassword =false,.ndefData ={0},.ndefLength =0,.tagName ="Smart Poster (NTAG215)",.hasAuthentication =false,.isEncrypted =false,.securityLevel =0};// Smart poster with URL and titleuint8_t posterNdef[]={0x03,0x35,// NDEF TLV, length 53// Record 1: Smart Poster (container)0x91,// MB=1, ME=0, CF=0, SR=1, IL=0, TNF=010x02,// Type Length = 20x2F,// Payload Length = 47'S','p',// Type = "Sp" (Smart Poster)// Nested Record 1: URI0x91,// MB=1, ME=00x01,0x10,0x55,0x04,// URI record'g','i','t','h','u','b','.','c','o','m','/','i','o','t',// Nested Record 2: Title0x51,// MB=0, ME=10x01,0x0F,0x54,0x02,// Text record'e','n','I','o','T',' ','P','r','o','j','e','c','t',0xFE// Terminator}; memcpy(simulatedTags[2].ndefData, posterNdef,sizeof(posterNdef)); simulatedTags[2].ndefLength =sizeof(posterNdef);// Tag 4: Secure Access Card (MIFARE DESFire) simulatedTags[3]={.uid ={0x04,0x12,0x34,0x56,0x78,0x9A,0xBC},.uidLength =7,.tagType = TAG_TYPE_4,.memorySize =8192,.usedMemory =512,.isLocked =false,.hasPassword =true,.ndefData ={0},.ndefLength =0,.tagName ="Access Card (DESFire)",.hasAuthentication =true,.isEncrypted =true,.securityLevel =3};// Secure tag with encrypted payload indicatoruint8_t secureNdef[]={0x03,0x28,// NDEF TLV0xD2,// TNF=02 (MIME type)0x18,// Type length = 240x0C,// Payload length = 12'a','p','p','l','i','c','a','t','i','o','n','/','v','n','d','.','a','c','c','e','s','s','-','c',0xAE,0x7B,0xC2,0x8F,0x51,0xD4,0x33,0x19,// Encrypted data0x8A,0xE2,0x1C,0x44,0xFE}; memcpy(simulatedTags[3].ndefData, secureNdef,sizeof(secureNdef)); simulatedTags[3].ndefLength =sizeof(secureNdef);// Tag 5: vCard Contact (NTAG424) simulatedTags[4]={.uid ={0x04,0xFF,0xEE,0xDD,0xCC,0xBB,0xAA},.uidLength =7,.tagType = TAG_TYPE_4,.memorySize =416,.usedMemory =280,.isLocked =false,.hasPassword =true,.ndefData ={0},.ndefLength =0,.tagName ="Contact Card (NTAG424)",.hasAuthentication =true,.isEncrypted =false,.securityLevel =2};// vCard MIME type recorduint8_t vcardNdef[]={0x03,0x42,// NDEF TLV0xD2,// TNF=02 (MIME type)0x0A,// Type length = 100x34,// Payload length = 52't','e','x','t','/','v','c','a','r','d','B','E','G','I','N',':','V','C','A','R','D','\n','F','N',':','I','o','T',' ','L','a','b','\n','T','E','L',':','+','1','5','5','5','1','2','3','4','\n','E','N','D',':','V','C','A','R','D',0xFE}; memcpy(simulatedTags[4].ndefData, vcardNdef,sizeof(vcardNdef)); simulatedTags[4].ndefLength =sizeof(vcardNdef);}// =============================================================================// SECTION 4: NFC READER SIMULATION CLASS// =============================================================================class NFCReaderSimulator {private: ReaderState state;int currentTagIndex;unsignedlong lastPollTime;unsignedlong pollInterval;bool verboseMode;public: NFCReaderSimulator(){ state = STATE_IDLE; currentTagIndex =-1; lastPollTime =0; pollInterval =3000;// 3 seconds between tag reads verboseMode =true;}void begin(){ Serial.println("\n"+ String('=',60)); Serial.println(" NFC READER SIMULATION - IoT Class Lab"); Serial.println(String('=',60)); Serial.println("\nInitializing simulated PN532 NFC reader..."); delay(500);// Simulate hardware initialization printProgress("Checking I2C connection",300); printProgress("Loading firmware version 1.6",200); printProgress("Configuring RF field",300); printProgress("Setting ISO14443A mode",200); Serial.println("\n[OK] NFC Reader initialized successfully!"); Serial.println(" Hardware: Simulated PN532 via I2C"); Serial.println(" Firmware: v1.6 (simulation)"); Serial.println(" Frequency: 13.56 MHz"); Serial.println(" Protocols: ISO14443A/B, ISO15693, FeliCa"); Serial.println("\n[INFO] Starting tag polling...\n"); state = STATE_POLLING;}void printProgress(constchar* message,int delayMs){ Serial.print(" "); Serial.print(message); Serial.print("..."); delay(delayMs); Serial.println(" OK");}void poll(){if(millis()- lastPollTime < pollInterval){return;} lastPollTime = millis();// Cycle through simulated tags currentTagIndex =(currentTagIndex +1)% numSimulatedTags; Serial.println("\n"+ String('-',60)); Serial.println("POLLING: Searching for NFC tags..."); delay(200);// Simulate RF field activation and detection simulateTagDetection(&simulatedTags[currentTagIndex]);}void simulateTagDetection(NFCTag* tag){ state = STATE_DETECTED; Serial.println("\n[DETECTED] NFC Tag Found!"); Serial.println(String('-',40));// Print UID Serial.print("UID: ");for(int i =0; i < tag->uidLength; i++){if(i >0) Serial.print(":");if(tag->uid[i]<0x10) Serial.print("0"); Serial.print(tag->uid[i], HEX);} Serial.println();// Print tag info Serial.print("Tag Name: "); Serial.println(tag->tagName); Serial.print("Tag Type: NFC Forum Type "); Serial.println(tag->tagType); Serial.print("Memory: "); Serial.print(tag->usedMemory); Serial.print("/"); Serial.print(tag->memorySize); Serial.println(" bytes");// Security info printSecurityInfo(tag);// Handle authentication if neededif(tag->hasAuthentication){ simulateAuthentication(tag);}// Read NDEF data state = STATE_READING; readNDEFMessage(tag); state = STATE_POLLING;}void printSecurityInfo(NFCTag* tag){ Serial.println("\n[SECURITY]"); Serial.print(" Locked: "); Serial.println(tag->isLocked ?"Yes (Read-Only)":"No (Read/Write)"); Serial.print(" Password Protected: "); Serial.println(tag->hasPassword ?"Yes":"No"); Serial.print(" Authentication: "); Serial.println(tag->hasAuthentication ?"Required":"Not Required"); Serial.print(" Encryption: "); Serial.println(tag->isEncrypted ?"Yes":"No"); Serial.print(" Security Level: ");switch(tag->securityLevel){case0: Serial.println("0 - None");break;case1: Serial.println("1 - Password (32-bit)");break;case2: Serial.println("2 - AES-128");break;case3: Serial.println("3 - 3DES/AES-256");break;}}void simulateAuthentication(NFCTag* tag){ Serial.println("\n[AUTHENTICATION]"); state = STATE_AUTHENTICATING;if(tag->securityLevel >=2){ Serial.println(" Initiating mutual authentication..."); delay(100); Serial.print(" Generating random challenge: ");for(int i =0; i <8; i++){ Serial.print(random(256), HEX);} Serial.println(); delay(150); Serial.println(" Verifying cryptographic response..."); delay(100); Serial.println(" [OK] Authentication successful!");}elseif(tag->hasPassword){ Serial.println(" Using default password for simulation..."); delay(100); Serial.println(" [OK] Password accepted!");}}void readNDEFMessage(NFCTag* tag){ Serial.println("\n[NDEF MESSAGE]");if(tag->ndefLength ==0){ Serial.println(" No NDEF data found on tag.");return;}// Parse TLV structureint pos =0;while(pos < tag->ndefLength){uint8_t tlvType = tag->ndefData[pos++];if(tlvType ==0x00){// NULL TLV - skipcontinue;}elseif(tlvType ==0xFE){// Terminator TLV Serial.println(" [END] NDEF Terminator TLV");break;}elseif(tlvType ==0x03){// NDEF Message TLVuint8_t length = tag->ndefData[pos++]; Serial.print(" NDEF TLV: Length = "); Serial.print(length); Serial.println(" bytes");// Parse NDEF records parseNDEFRecords(&tag->ndefData[pos], length); pos += length;}}}void parseNDEFRecords(uint8_t* data,int length){int pos =0;int recordNum =1;while(pos < length){// Parse record headeruint8_t header = data[pos++];bool mb =(header &0x80)!=0;// Message Beginbool me =(header &0x40)!=0;// Message Endbool cf =(header &0x20)!=0;// Chunk Flagbool sr =(header &0x10)!=0;// Short Recordbool il =(header &0x08)!=0;// ID Length presentuint8_t tnf = header &0x07;// Type Name Formatuint8_t typeLength = data[pos++];uint32_t payloadLength = sr ? data[pos++]:((uint32_t)data[pos]<<24)|((uint32_t)data[pos+1]<<16)|((uint32_t)data[pos+2]<<8)| data[pos+3];if(!sr) pos +=4;uint8_t idLength = il ? data[pos++]:0; Serial.println(); Serial.print(" Record #"); Serial.print(recordNum++); Serial.println(":"); Serial.print(" Flags: MB="); Serial.print(mb); Serial.print(" ME="); Serial.print(me); Serial.print(" SR="); Serial.print(sr); Serial.print(" TNF="); Serial.println(tnf);// Get typechar type[65]={0};for(int i =0; i < typeLength && i <64; i++){ type[i]= data[pos++];}// Skip ID if present pos += idLength; Serial.print(" Type: "); printTNFDescription(tnf); Serial.print(" - \""); Serial.print(type); Serial.println("\""); Serial.print(" Payload Length: "); Serial.print(payloadLength); Serial.println(" bytes");// Parse payload based on typeif(tnf == TNF_WELL_KNOWN){if(type[0]== RTD_URI){ parseURIPayload(&data[pos], payloadLength);}elseif(type[0]== RTD_TEXT){ parseTextPayload(&data[pos], payloadLength);}elseif(type[0]=='S'&& type[1]=='p'){ Serial.println(" [SMART POSTER - Contains nested records]"); parseNDEFRecords(&data[pos], payloadLength);}}elseif(tnf == TNF_MIME_MEDIA){ parseMIMEPayload(type,&data[pos], payloadLength);} pos += payloadLength;if(me)break;// Last record}}void printTNFDescription(uint8_t tnf){switch(tnf){case TNF_EMPTY: Serial.print("Empty");break;case TNF_WELL_KNOWN: Serial.print("Well-Known");break;case TNF_MIME_MEDIA: Serial.print("MIME-Type");break;case TNF_ABSOLUTE_URI: Serial.print("Absolute-URI");break;case TNF_EXTERNAL: Serial.print("External");break;case TNF_UNKNOWN: Serial.print("Unknown");break;case TNF_UNCHANGED: Serial.print("Unchanged");break;default: Serial.print("Reserved");break;}}void parseURIPayload(uint8_t* payload,int length){uint8_t prefixCode = payload[0]; Serial.print(" URI Prefix Code: 0x");if(prefixCode <0x10) Serial.print("0"); Serial.println(prefixCode, HEX); String uri ="";if(prefixCode < URI_PREFIX_COUNT){ uri = URI_PREFIXES[prefixCode];}for(int i =1; i < length; i++){ uri +=(char)payload[i];} Serial.print(" Full URI: "); Serial.println(uri);// Analyze URI type Serial.print(" URI Type: ");if(uri.startsWith("https://")){ Serial.println("Secure Web Link (HTTPS)");}elseif(uri.startsWith("http://")){ Serial.println("Web Link (HTTP) - Not secure!");}elseif(uri.startsWith("tel:")){ Serial.println("Phone Number");}elseif(uri.startsWith("mailto:")){ Serial.println("Email Address");}else{ Serial.println("Other URI Scheme");}}void parseTextPayload(uint8_t* payload,int length){uint8_t statusByte = payload[0];bool utf16 =(statusByte &0x80)!=0;uint8_t langLength = statusByte &0x3F; Serial.print(" Encoding: "); Serial.println(utf16 ?"UTF-16":"UTF-8"); String lang ="";for(int i =0; i < langLength; i++){ lang +=(char)payload[1+ i];} Serial.print(" Language: "); Serial.println(lang); String text ="";for(int i =1+ langLength; i < length; i++){ text +=(char)payload[i];} Serial.print(" Text: \""); Serial.print(text); Serial.println("\"");}void parseMIMEPayload(constchar* mimeType,uint8_t* payload,int length){ Serial.print(" MIME Type: "); Serial.println(mimeType);if(strstr(mimeType,"vcard")!= NULL){ Serial.println(" [vCard Contact Data]"); Serial.print(" Content: ");for(int i =0; i < length && i <80; i++){if(payload[i]>=32&& payload[i]<127){ Serial.print((char)payload[i]);}else{ Serial.print(".");}} Serial.println();}elseif(strstr(mimeType,"access")!= NULL){ Serial.println(" [Access Control Data - Encrypted]"); Serial.print(" Encrypted Bytes: ");for(int i =0; i < length && i <16; i++){if(payload[i]<0x10) Serial.print("0"); Serial.print(payload[i], HEX); Serial.print(" ");} Serial.println();}else{ Serial.print(" Raw Data ("); Serial.print(length); Serial.println(" bytes)");}}void demonstrateAntiCollision(){ Serial.println("\n"+ String('=',60)); Serial.println("DEMONSTRATION: NFC Anti-Collision"); Serial.println(String('=',60)); Serial.println("\nWhen multiple tags are in RF field simultaneously:"); Serial.println("1. Reader sends REQA (Request Type A) command"); Serial.println("2. All tags respond with ATQA (Answer To Request)"); Serial.println("3. Reader initiates anti-collision loop:"); Serial.println(" - Sends SELECT command with partial UID"); Serial.println(" - Tags with matching bits respond"); Serial.println(" - Collisions detected via Manchester encoding"); Serial.println(" - Reader narrows down bit-by-bit"); Serial.println("4. Single tag selected, others remain quiet"); Serial.println("5. Process repeats for remaining tags"); delay(500); Serial.println("\nSimulating multi-tag detection..."); delay(300); Serial.println("\n[RF FIELD] Activated at 13.56 MHz"); delay(200); Serial.println("[REQA] Sent: 0x26"); delay(100); Serial.println("[ATQA] Received: 0x0004 (NTAG family)"); delay(100); Serial.println("[COLLISION] Detected at bit position 24"); delay(150); Serial.println("[SELECT] NVB=40, UID prefix: 04:A3:B2:C1"); delay(100); Serial.println("[SAK] Received: 0x00 (UID complete)"); delay(100); Serial.println("[SELECTED] Tag 1 active, others halted"); Serial.println("\n[OK] Anti-collision complete - 1 tag selected");}void demonstrateRelayAttack(){ Serial.println("\n"+ String('=',60)); Serial.println("SECURITY: Relay Attack Demonstration"); Serial.println(String('=',60)); Serial.println("\nRelay Attack Concept:"); Serial.println("Attacker uses two devices to extend NFC range:"); Serial.println(""); Serial.println(" [Victim Card] <--NFC--> [Attacker A]"); Serial.println(" |"); Serial.println(" (relay link)"); Serial.println(" |"); Serial.println(" [Payment Terminal] <--NFC--> [Attacker B]"); Serial.println(""); Serial.println("Timeline of attack:"); Serial.println(" T+0ms: Terminal activates, requests payment"); Serial.println(" T+5ms: Attacker B relays request to Attacker A"); Serial.println(" T+50ms: Attacker A presents request to victim"); Serial.println(" T+55ms: Victim's card responds (auto-response)"); Serial.println(" T+60ms: Response relayed back to terminal"); Serial.println(" T+65ms: Terminal accepts payment"); Serial.println(""); Serial.println("Detection/Prevention Methods:"); Serial.println(" 1. Distance bounding: Measure round-trip time"); Serial.println(" - Light travels ~30cm in 1ns"); Serial.println(" - NFC range: ~10cm = ~0.3ns"); Serial.println(" - Relay adds >100ns delay = DETECTED!"); Serial.println(""); Serial.println(" 2. Require user interaction:"); Serial.println(" - Biometric (fingerprint/face)"); Serial.println(" - PIN entry"); Serial.println(" - Button press"); Serial.println(""); Serial.println(" 3. Payment limits for contactless:"); Serial.println(" - UK: GBP 100 limit without PIN"); Serial.println(" - US: $100-200 typical limit"); Serial.println(""); Serial.println("[!] Always require authentication for high-value NFC operations!");}void printTagTypeInfo(){ Serial.println("\n"+ String('=',60)); Serial.println("NFC FORUM TAG TYPES REFERENCE"); Serial.println(String('=',60)); Serial.println("\nType 1 (TOPAZ, Broadcom):"); Serial.println(" Memory: 96 - 2000 bytes"); Serial.println(" Read/Write: Yes (lockable)"); Serial.println(" Speed: 106 kbps"); Serial.println(" Use: Low-cost tags, one-time URLs"); Serial.println("\nType 2 (NTAG, MIFARE Ultralight):"); Serial.println(" Memory: 48 - 888 bytes"); Serial.println(" Read/Write: Yes (optional password)"); Serial.println(" Speed: 106 kbps"); Serial.println(" Use: Smart posters, IoT triggers"); Serial.println(" Examples: NTAG213 (144B), NTAG215 (504B), NTAG216 (888B)"); Serial.println("\nType 3 (FeliCa, Sony):"); Serial.println(" Memory: 1 - 9 KB"); Serial.println(" Read/Write: Yes"); Serial.println(" Speed: 212/424 kbps"); Serial.println(" Use: Japan transit (Suica), e-wallets"); Serial.println("\nType 4 (DESFire, JCOP, ISO 14443-4):"); Serial.println(" Memory: Up to 32 KB"); Serial.println(" Read/Write: Yes"); Serial.println(" Speed: 106-848 kbps"); Serial.println(" Security: AES-128, 3DES encryption"); Serial.println(" Use: Access control, payments, secure ID"); Serial.println(" Examples: MIFARE DESFire, NTAG424"); Serial.println("\nType 5 (ICODE SLIX, ISO 15693):"); Serial.println(" Memory: Up to 8 KB"); Serial.println(" Read/Write: Yes"); Serial.println(" Speed: 6.6 - 26 kbps (slower)"); Serial.println(" Range: Up to 1 meter (extended)"); Serial.println(" Use: Library books, industrial RFID");}};// =============================================================================// SECTION 5: GLOBAL OBJECTS AND SETUP// =============================================================================NFCReaderSimulator nfcReader;unsignedlong demoStartTime;int demoPhase =0;void setup(){ Serial.begin(115200);while(!Serial) delay(10); delay(1000);// Initialize simulated tag database initializeSimulatedTags();// Initialize reader nfcReader.begin(); demoStartTime = millis();}void loop(){unsignedlong elapsed = millis()- demoStartTime;// Run through demonstration phasesswitch(demoPhase){case0:// Phase 0: Poll through all tags (0-18 seconds)if(elapsed <18000){ nfcReader.poll();}else{ demoPhase =1;}break;case1:// Phase 1: Show tag type reference nfcReader.printTagTypeInfo(); delay(3000); demoPhase =2;break;case2:// Phase 2: Anti-collision demo nfcReader.demonstrateAntiCollision(); delay(2000); demoPhase =3;break;case3:// Phase 3: Security demo nfcReader.demonstrateRelayAttack(); delay(2000); demoPhase =4;break;case4:// Phase 4: Summary and restart Serial.println("\n"+ String('=',60)); Serial.println("LAB COMPLETE - SUMMARY"); Serial.println(String('=',60)); Serial.println("\nKey concepts demonstrated:"); Serial.println(" [x] NDEF message structure and TLV parsing"); Serial.println(" [x] URI and Text record decoding"); Serial.println(" [x] Tag type identification (Types 1-5)"); Serial.println(" [x] Security levels and authentication"); Serial.println(" [x] Anti-collision protocol"); Serial.println(" [x] Relay attack awareness"); Serial.println("\nRestarting demonstration in 10 seconds...\n"); delay(10000); demoPhase =0; demoStartTime = millis();break;} delay(100);// Small delay for loop}// =============================================================================// SECTION 6: UTILITY FUNCTIONS// =============================================================================// Calculate NDEF message checksum (for validation)uint8_t calculateNDEFChecksum(uint8_t* data,int length){uint8_t checksum =0;for(int i =0; i < length; i++){ checksum ^= data[i];}return checksum;}// Format UID for displayString formatUID(uint8_t* uid,int length){ String result ="";for(int i =0; i < length; i++){if(i >0) result +=":";if(uid[i]<0x10) result +="0"; result += String(uid[i], HEX);} result.toUpperCase();return result;}// Convert byte array to hex stringString bytesToHex(uint8_t* bytes,int length){ String result ="";for(int i =0; i < length; i++){if(bytes[i]<0x10) result +="0"; result += String(bytes[i], HEX);} result.toUpperCase();return result;}
36.4 Understanding the Code
The simulation demonstrates these key NFC concepts:
1. NDEF Message Structure
The NFC Data Exchange Format (NDEF) is the standard for encoding data on NFC tags:
The NDEF header byte packs 8 flags into a single byte: \(\text{Header} = (\text{MB} \ll 7) | (\text{ME} \ll 6) | (\text{CF} \ll 5) | (\text{SR} \ll 4) | (\text{IL} \ll 3) | \text{TNF}\). Worked example: For a single URI record, MB=1, ME=1, CF=0, SR=1, IL=0, TNF=0x01 gives \(\text{0xD1} = (1 \ll 7) | (1 \ll 6) | (0 \ll 5) | (1 \ll 4) | (0 \ll 3) | 0x01 = 0b11010001 = 0xD1\).
Try It: NDEF Header Byte Builder
Toggle the NDEF record header flags and TNF value to see how they combine into a single header byte. This demonstrates the bit-packing used in every NDEF record.
Enter a URL to see how NFC compresses it using prefix codes. The widget shows the original size versus the compressed NDEF payload size, demonstrating why prefix compression matters for small-memory tags.
Show code
viewof user_url = Inputs.text({label:"Enter a URL",placeholder:"https://www.example.com/iot",value:"https://www.example.com/iot",width:"100%"})
The simulation demonstrates all five NFC Forum tag types:
Type
Example IC
Memory
Security
Use Case
1
Topaz
96-2KB
None
Low-cost labels
2
NTAG213/215/216
48-888B
Password
Smart posters
3
FeliCa
1-9KB
Crypto
Transit cards
4
DESFire
32KB
AES/3DES
Payments, access
5
ICODE
8KB
Password
Industrial RFID
Try It: NFC Tag Type Selector
Select a use case to see which NFC Forum Tag Type best fits your requirements. Adjust the memory and security needs to compare tag options side by side.
Modify the code to add a sixth simulated tag with these specifications: - Tag Type: 5 (ICODE SLIX) - Memory: 256 bytes - Contains: A custom external type record with an application identifier
Hints:
Add a new entry to simulatedTags[] array
Use TNF_EXTERNAL (0x04) for the record type
Update numSimulatedTags to 6
Challenge 2: Implement Write Simulation
Add a function to simulate writing NDEF data to a tag:
void simulateWriteNDEF(NFCTag* tag,constchar* url){// Your implementation:// 1. Check if tag is locked (reject if true)// 2. Check if tag has password (simulate auth)// 3. Build NDEF TLV with URI record// 4. Check if data fits in tag memory// 5. Update tag->ndefData and tag->ndefLength}
Challenge 3: Add Distance-Based Detection
Modify simulateTagDetection() to include simulated RSSI/distance:
// Add to tag detection:float distance = random(1,100)/10.0;// 0.1 - 10.0 cmif(distance >4.0){ Serial.println(" [WARNING] Tag at edge of range"); Serial.println(" Signal may be unstable");}
Then add logic to occasionally “fail” detection when distance > 8cm.
Challenge 4: Implement Multi-Record Messages
Create a function that builds Smart Poster NDEF messages with: - A URI record (the link) - A Title record (display text) - An Action record (recommended action)
Reference the existing posterNdef[] array structure.
Match: NFC Concept to Its Purpose
Order: NFC Reader Tag Detection Sequence
36.6 Expected Outcomes
After completing this lab, you should be able to:
Explain NDEF structure: Describe the TLV container format, record headers, and payload organization
Parse NFC data: Read and interpret raw NDEF bytes including:
Type Name Format (TNF) classification
Record type identification (U, T, Sp, MIME)
Payload decoding for URI and Text records
Identify tag types: Recognize NFC Forum Tag Types 1-5 and their characteristics:
Design NFC applications: Select appropriate tag types and record formats for:
Smart posters and marketing
Access control and authentication
Device pairing and IoT triggers
Payment and secure transactions
Try It: Relay Attack Timing Simulator
Adjust the relay distance to see how it affects round-trip timing. Distance bounding protocols detect relay attacks by measuring whether the response arrives faster than the speed of light would allow at the claimed distance.
After mastering this simulation, practice with real hardware:
Arduino + PN532: Use the Adafruit PN532 library with actual NFC tags
Mobile development: Build Android NFC apps using the NfcAdapter API
Security testing: Explore tools like Proxmark3 for NFC security research
36.7 Knowledge Check
Quiz: NFC Simulation Concepts
Question 1: In the NDEF TLV structure, what does the terminator byte 0xFE indicate?
Question 2: Why does the simulation’s anti-collision protocol use bit-by-bit UID resolution?
Question 3: In the simulation, the secure access card (MIFARE DESFire) has security level 3. What does this mean?
Worked Example: Debugging Failed NFC Tag Reads
Problem: A smart building deploys 200 NFC tags at conference room doors for room booking. After 2 weeks, facilities reports that “30% of tags don’t work.” Users report inconsistent reads - sometimes tags work, sometimes they don’t.
Initial Investigation:
Test 1: Physical inspection
All 60 “failed” tags are still physically attached
No visible damage to stickers
Tags are NTAG213 (144 bytes, Type 2)
Test 2: Try with multiple phones
iPhone 13: Reads 40 of 60 tags
Samsung Galaxy S21: Reads 50 of 60 tags
Pixel 6: Reads 55 of 60 tags
Observation: Same tag works with some phones but not others. This rules out “dead tag” theory.
Step 1: Check Tag Placement
Use simulation code to understand NFC antenna locations:
// Add to simulation to show antenna positionsvoid printAntennaLocations(){ Serial.println("NFC Antenna Locations by Phone:"); Serial.println(" iPhone 13: Top center (near camera)"); Serial.println(" Galaxy S21: Center back (upper)"); Serial.println(" Pixel 6: Center back (middle)"); Serial.println(); Serial.println("Common user error: Tapping bottom of phone"); Serial.println(" → Phone's NFC antenna is NOT near charging port!"); Serial.println(" → Must tap with TOP or CENTER BACK of phone");}
Discovery 1: Site visit reveals users are tapping the BOTTOM of their phones near charging port, not the NFC antenna area.
Step 2: Check Tag Placement Surfaces
Inspect the 10 tags that don’t work with ANY phone:
// Add surface material detection to simulationstruct TagEnvironment {constchar* surface;float metalContent;float expectedRSSI;bool willWork;};TagEnvironment surfaces[]={{"Plastic door sign",0.0,-50,true},{"Wooden door",0.0,-45,true},{"Glass",0.0,-48,true},{"Metal door frame",1.0,-95,false},// PROBLEM!{"Metal light switch plate",0.8,-88,false}// PROBLEM!};
Discovery 2: 10 problematic tags are mounted on metal door frames or near metal light switches. Metal detunes the NFC antenna, reducing read range from 4 cm to <1 cm or completely blocking reads.
Discovery 3: 5 tags have truncated NDEF data. Users tapped too quickly during initial programming, causing incomplete writes.
Step 4: Measure Read Distance
Add distance simulation:
float calculateReadDistance(float tagRSSI,constchar* surface){float baseDistance =4.0;// cm// Adjust for surfaceif(strstr(surface,"metal")){ baseDistance *=0.1;// 90% reduction on metal}elseif(strstr(surface,"glass")){ baseDistance *=0.9;// 10% reduction on glass}// Adjust for signal strengthif(tagRSSI <-80){ baseDistance *=0.3;// Weak signal}return baseDistance;}
Discovery 4: Tags on plastic surfaces have 4 cm read range. Tags on glass have 3.6 cm range. Tags on metal have 0.4 cm range (essentially non-functional).
Root Causes Identified:
Issue
Affected Tags
Cause
User tapping wrong phone area
50 tags
User error - tapping bottom not top
Metal surface detuning
10 tags
Installation error - mounted on metal
Corrupted NDEF data
5 tags
Incomplete write during programming
Intermittent reader
3 tags
Dirty/damaged tag surface
Solutions Implemented:
1. User education (50 tags fixed):
Created visual guides showing NFC antenna locations
Added “tap here” dots to conference room signs
Sent email with diagrams to all staff
2. Tag relocation (10 tags fixed):
Moved tags from metal door frames to plastic door signs
Added ferrite shielding sheets ($0.50 each) between tag and metal for tags that can’t be relocated
3. Reprogram corrupted tags (5 tags fixed):
// Proper NDEF write procedurevoid writeNDEFSafely(Tag tag,constchar* url){// 1. Check tag capacityif(strlen(url)+10> tag.capacity){ Serial.println("ERROR: URL too long for tag");return;}// 2. Write NDEF message NdefMessage message = createUriMessage(url);bool success = tag.write(message);// 3. CRITICAL: Verify write delay(100);// Wait for write to complete NdefMessage readBack = tag.read();if(!readBack.equals(message)){ Serial.println("ERROR: Write verification failed");// Retry up to 3 times}else{ Serial.println("SUCCESS: Tag written and verified");}// 4. Lock tag to prevent accidental overwrites tag.lockPermanently();}
4. Replace damaged tags (3 tags fixed):
Physical damage to antenna coil
Replaced with new NTAG213 tags
Total cost: 3 × $0.20 = $0.60
Results After Fixes:
Success rate: 30% → 98%
Average read time: 1.5 seconds → 0.3 seconds
User satisfaction: “frustrating” → “works great”
Lessons Learned:
NFC antenna location varies by phone - Always provide visual guides
Metal surfaces kill NFC - Test all installation locations before bulk deployment
Verify every write operation - 2.5% of NFC writes fail if tag is removed too soon
Site survey matters - Don’t assume “4 cm range” works everywhere
User training is critical - Even the best technology fails without user education
NFC simulations typically assume perfect coupling and no environmental interference. Real-world factors (tag orientation, metal proximity, reader sensitivity variation) are not modelled. Fix: always validate simulation results with physical NFC hardware before deployment.
2. Not Testing With Multiple Reader and Tag Combinations
An NFC application tested with one reader-tag pair may fail with a different vendor’s hardware due to RF parameter variations. Fix: test with at least three different NFC reader chipsets and tag manufacturers to verify interoperability.
3. Skipping Error Handling in Simulated Tag Operations
Simulations rarely inject errors (CRC failures, anti-collision conflicts). Fix: manually inject error conditions (corrupt frames, timeout events) in the simulation to verify that the application handles them gracefully.
🏷️ Label the Diagram
💻 Code Challenge
36.8 Summary
This lab demonstrated NFC concepts through interactive simulation:
NDEF Message Parsing: TLV container structure, record headers with MB/ME/CF/SR/IL/TNF flags, and payload processing for URI/Text/MIME types
URI Prefix Compression: How NFC saves space using 36 standardized prefix codes (0x01-0x23) to compress common URL schemes
Tag Type Identification: Characteristics of NFC Forum Types 1-5 including memory capacity, security levels, and appropriate use cases
Anti-Collision Protocol: REQA/ATQA/SELECT sequence for handling multiple tags using bit-by-bit UID resolution