889  NFC Reader Simulation Lab

889.1 Learning Objectives

By the end of this chapter, you will be able to:

  • Parse NDEF Messages: Decode TLV containers, record headers, and payload structures
  • Identify Tag Types: Recognize NFC Forum Tag Types 1-5 and their characteristics
  • Understand Anti-Collision: Explain how NFC readers handle multiple tags in the RF field
  • Analyze Security Features: Evaluate authentication, encryption levels, and relay attack vectors
  • Implement NFC Simulations: Build software simulations that model NFC reader behavior

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.

Prerequisites: - NFC Fundamentals - NDEF format understanding - NFC Tag Programming - Basic programming concepts

What you’ll learn: - NDEF message structure and parsing - URI prefix compression - Tag type characteristics - Security and anti-collision protocols

889.2 Prerequisites

Before diving into this lab, you should be familiar with:

  • NFC Fundamentals: Understanding NDEF message structure and tag types
  • NFC Tag Programming: Basic NFC programming concepts
  • C++ Basics: Understanding of Arduino/ESP32 programming for the simulation code

Prerequisites: - NFC Fundamentals - NDEF format basics - NFC Tag Programming - Programming examples

Next Steps: - NFC Real-World Applications - Payments, smart home, authentication - NFC Security and Comparisons - Security deep dive

Practice: - Simulations Hub - More interactive simulations

889.3 NFC Reader Lab: Interactive Simulation

Time: ~30 min | Level: Advanced | Unit: P08.C22.U02

889.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
TipWhy 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.

889.3.2 Interactive Wokwi Simulation

NoteSetup Instructions
  1. Copy the complete code below into the Wokwi editor
  2. Click the green β€œPlay” button to start the simulation
  3. Open the Serial Monitor (115200 baud) to view output
  4. The simulation will automatically cycle through NFC scenarios

889.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 Types
enum 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)
const char* 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 Structure
struct NDEFRecord {
    uint8_t tnf;           // Type Name Format (3 bits)
    bool mb;               // Message Begin flag
    bool me;               // Message End flag
    bool cf;               // Chunk Flag
    bool sr;               // Short Record (payload < 256 bytes)
    bool il;               // ID Length present
    uint8_t typeLength;
    uint32_t payloadLength;
    uint8_t idLength;
    char type[64];
    uint8_t payload[256];
    char id[64];
};

// NFC Tag Structure
struct NFCTag {
    uint8_t uid[7];        // Unique Identifier (4 or 7 bytes)
    uint8_t uidLength;
    NFCTagType tagType;
    uint16_t memorySize;   // Total memory in bytes
    uint16_t usedMemory;
    bool isLocked;
    bool hasPassword;
    uint8_t ndefData[512];
    uint16_t ndefLength;
    char tagName[32];

    // Security features
    bool hasAuthentication;
    bool isEncrypted;
    uint8_t securityLevel; // 0=none, 1=password, 2=AES, 3=3DES
};

// NFC Reader State
enum ReaderState {
    STATE_IDLE,
    STATE_POLLING,
    STATE_DETECTED,
    STATE_AUTHENTICATING,
    STATE_READING,
    STATE_WRITING,
    STATE_ERROR
};

// =============================================================================
// SECTION 3: SIMULATED NFC TAG DATABASE
// =============================================================================

// Pre-configured simulated tags
NFCTag 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 Message
    uint8_t urlNdef[] = {
        0x03, 0x14,                    // NDEF TLV, length 20
        0xD1,                          // MB=1, ME=1, CF=0, SR=1, IL=0, TNF=01
        0x01,                          // Type Length = 1
        0x10,                          // Payload Length = 16
        0x55,                          // 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 record
    uint8_t textNdef[] = {
        0x03, 0x21,                    // NDEF TLV, length 33
        0xD1,                          // MB=1, ME=1, CF=0, SR=1, IL=0, TNF=01
        0x01,                          // Type Length = 1
        0x1D,                          // Payload Length = 29
        0x54,                          // 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 title
    uint8_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=01
        0x02,                          // Type Length = 2
        0x2F,                          // Payload Length = 47
        'S', 'p',                      // Type = "Sp" (Smart Poster)
        // Nested Record 1: URI
        0x91,                          // MB=1, ME=0
        0x01, 0x10, 0x55, 0x04,       // URI record
        'g', 'i', 't', 'h', 'u', 'b', '.', 'c', 'o', 'm',
        '/', 'i', 'o', 't',
        // Nested Record 2: Title
        0x51,                          // MB=0, ME=1
        0x01, 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 indicator
    uint8_t secureNdef[] = {
        0x03, 0x28,                    // NDEF TLV
        0xD2,                          // TNF=02 (MIME type)
        0x18,                          // Type length = 24
        0x0C,                          // 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 data
        0x8A, 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 record
    uint8_t vcardNdef[] = {
        0x03, 0x42,                    // NDEF TLV
        0xD2,                          // TNF=02 (MIME type)
        0x0A,                          // Type length = 10
        0x34,                          // 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;
    unsigned long lastPollTime;
    unsigned long 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(const char* 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 needed
        if (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) {
            case 0: Serial.println("0 - None"); break;
            case 1: Serial.println("1 - Password (32-bit)"); break;
            case 2: Serial.println("2 - AES-128"); break;
            case 3: 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!");
        } else if (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 structure
        int pos = 0;
        while (pos < tag->ndefLength) {
            uint8_t tlvType = tag->ndefData[pos++];

            if (tlvType == 0x00) {
                // NULL TLV - skip
                continue;
            } else if (tlvType == 0xFE) {
                // Terminator TLV
                Serial.println("  [END] NDEF Terminator TLV");
                break;
            } else if (tlvType == 0x03) {
                // NDEF Message TLV
                uint8_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 header
            uint8_t header = data[pos++];

            bool mb = (header & 0x80) != 0;  // Message Begin
            bool me = (header & 0x40) != 0;  // Message End
            bool cf = (header & 0x20) != 0;  // Chunk Flag
            bool sr = (header & 0x10) != 0;  // Short Record
            bool il = (header & 0x08) != 0;  // ID Length present
            uint8_t tnf = header & 0x07;     // Type Name Format

            uint8_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 type
            char 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 type
            if (tnf == TNF_WELL_KNOWN) {
                if (type[0] == RTD_URI) {
                    parseURIPayload(&data[pos], payloadLength);
                } else if (type[0] == RTD_TEXT) {
                    parseTextPayload(&data[pos], payloadLength);
                } else if (type[0] == 'S' && type[1] == 'p') {
                    Serial.println("    [SMART POSTER - Contains nested records]");
                    parseNDEFRecords(&data[pos], payloadLength);
                }
            } else if (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)");
        } else if (uri.startsWith("http://")) {
            Serial.println("Web Link (HTTP) - Not secure!");
        } else if (uri.startsWith("tel:")) {
            Serial.println("Phone Number");
        } else if (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(const char* 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();
        } else if (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;
unsigned long 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() {
    unsigned long elapsed = millis() - demoStartTime;

    // Run through demonstration phases
    switch(demoPhase) {
        case 0:
            // Phase 0: Poll through all tags (0-18 seconds)
            if (elapsed < 18000) {
                nfcReader.poll();
            } else {
                demoPhase = 1;
            }
            break;

        case 1:
            // Phase 1: Show tag type reference
            nfcReader.printTagTypeInfo();
            delay(3000);
            demoPhase = 2;
            break;

        case 2:
            // Phase 2: Anti-collision demo
            nfcReader.demonstrateAntiCollision();
            delay(2000);
            demoPhase = 3;
            break;

        case 3:
            // Phase 3: Security demo
            nfcReader.demonstrateRelayAttack();
            delay(2000);
            demoPhase = 4;
            break;

        case 4:
            // 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 display
String 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 string
String 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;
}

889.4 Understanding the Code

The simulation demonstrates these key NFC concepts:

Note1. NDEF Message Structure

The NFC Data Exchange Format (NDEF) is the standard for encoding data on NFC tags:

+---------------------+
| TLV Container       |
| Type: 0x03 (NDEF)   |
| Length: varies      |
+---------------------+
| NDEF Record 1       |
| - Header flags      |
| - Type (U/T/Sp)     |
| - Payload           |
+---------------------+
| NDEF Record N...    |
+---------------------+
| Terminator TLV      |
| Type: 0xFE          |
+---------------------+

Header Flags: - MB (Message Begin): First record in message - ME (Message End): Last record in message - CF (Chunk Flag): Record is chunked - SR (Short Record): Payload < 256 bytes - IL (ID Length): ID field present - TNF (Type Name Format): 3-bit type classification

Note2. URI Prefix Compression

NFC uses prefix codes to save space on small tags:

Code Prefix Savings
0x01 http://www. 11 bytes
0x02 https://www. 12 bytes
0x03 http:// 7 bytes
0x04 https:// 8 bytes
0x05 tel: 4 bytes
0x06 mailto: 7 bytes

Example: https://iotclass.example becomes: 0x04 + iotclass.example (saves 8 bytes)

Note3. Tag Type Selection

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

889.5 Challenge Exercises

CautionChallenge 1: Add a New Tag Type

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

CautionChallenge 2: Implement Write Simulation

Add a function to simulate writing NDEF data to a tag:

void simulateWriteNDEF(NFCTag* tag, const char* 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
}
CautionChallenge 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 cm
if (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.

CautionChallenge 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.

889.6 Expected Outcomes

After completing this lab, you should be able to:

  1. Explain NDEF structure: Describe the TLV container format, record headers, and payload organization

  2. 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
  3. Identify tag types: Recognize NFC Forum Tag Types 1-5 and their characteristics:

    • Memory capacity ranges
    • Security features (none, password, crypto)
    • Appropriate use cases
  4. Understand security: Explain NFC security concepts including:

    • Authentication mechanisms
    • Encryption levels (none, AES, 3DES)
    • Relay attack vectors and mitigations
    • Distance bounding protocols
  5. 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
TipNext Steps

After mastering this simulation, practice with real hardware:

  1. Arduino + PN532: Use the Adafruit PN532 library with actual NFC tags
  2. Mobile development: Build Android NFC apps using the NfcAdapter API
  3. Security testing: Explore tools like Proxmark3 for NFC security research

889.7 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
  • Security Demonstrations: Relay attack mechanics, distance bounding countermeasures, and authentication requirements

889.8 What’s Next

The next chapter, NFC Real-World Applications, explores practical NFC implementations including mobile payments (Apple Pay/Google Pay), smart home automation with NFC tags, product authentication and anti-counterfeiting, and Python-based security analysis tools.