NFC P2P NDEF Basics

I’ve been playing around with NFC P2P a lot recently and found the information to be rather fragmented and distributed so I’m writing this article both to help myself remember and as a reference for anyone getting started with NFC.

Background

First a little background on NFC, there are 3 essential types of NFC connections. Note that all NFC transactions are powered.

  1. Reader Mode: the NFC device is reading a tag, it actively controls the data stream and control signals.
  2. Tag Mode: the NFC device supplies data to a reader as requested.
  3. Peer To Peer (P2P): the NFC device switches back and forth between reader and tag mode in order to allow free flowing data back and forth between the devices. This is commonly used for sharing photos, contacts, pairing bluetooth devices, etc.

It should also be noted that NFC is essentially a specialization of RFID, namely it shares the same spectrum as HF RFID (13.56Mhz) and some common ISO protocols. For this reason you will commonly see transceivers such as the TRF7970A that can do both protocols, most of the infrastructure is in place so why not.

NFC NDEF Transactions

This post will focus on NFC P2P. All of the relevant standards documents for NFC can be found at http://www.nfc-forum.org/specs/spec_list/. NFC actually has several layers to the protocol. In general it goes Raw Data -> LLCP -> SNEP -> NDEF, this post will be focusing on the NDEF layer, because this is the layer where meaningful data can be abstracted with the least amount of pain to the end user.

NDEF Message Abstract:
All NDEF messages are constructed of two basic components, a Message Header and a Message Payload. The header comes before the payload. The header contains essential meta information about the transaction that determines the size, type, language, and ID of the payload. The message header is well defined by the NDEF spec and can vary between 5 – 12 bytes depending on which fields are present. There is a type field in the header that determines the payload type. This field will tell you the type of the payload ( ie URI, Text, SmartPoster, etc… ). Alternatively you can define your own record types and insert your own type identifiers in the header, just don’t expect any one else to understand them. The payload contains the actual data being transmitted by the message. The payload can either be a well defined type or a custom type. For the sake of sanity this article will focus on well known well defined NFC structures.

 _____________________
|                     |
|    Message Header   |
|_____________________|
|                     |
|    Message Payload  |
|_____________________|

NDEF Message Details: Message Header
The NDEF message header has several sub-fields, some can vary in length. The length of the fields is defined before the field, so at no point is there any ambiguity as to what information is present. The fields are as follows, in order

The fields in an NDEF Message header are as follows:
 ______________________________
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0|  
|------------------------------|
| MB| ME| CF| SR| IL|    TNF   |  NDEF StatusByte, 1 byte
|------------------------------|
|        TYPE_LENGTH           |  1 byte, hex value
|------------------------------|
|        PAYLOAD_LENGTH        |  1 or 4 bytes (determined by SR) (LSB first)
|------------------------------|
|        ID_LENGTH             |  0 or 1 bytes (determined by IL)
|------------------------------|
|        TYPE                  |  2 or 5 bytes (determined by TYPE_LENGTH)
|------------------------------|
|        ID                    |  0 or 1 byte  (determined by IL & ID_LENGTH)
|------------------------------|
|        PAYLOAD               |  X bytes (determined by PAYLOAD_LENGTH)
|------------------------------|
  • NDEF Status Byte : size = 1byte : has multiple bit fields that contain meta data bout the rest of the header fields.
    • MB : Message Begin flag
    • ME : Message End flag
    • CF : Chunk Flag (1 = Record is chunked up across multiple messages)
    • SR : Short Record flag ( 1 = Record is contained in 1 message)
    • IL : ID Length present flag ( 0 = ID field not present, 1 = present)
    • TNF: Type Name Format code – one of the following
      • 0x00 : Empty
      • 0x01 : NFC Well Known Type [NFC RTD] (Use This One)
      • 0x02 : Media Type [RFC 2046]
      • 0x03 : Absolute URI [RFC 3986]
      • 0x04 : External Type [NFC RTD]
      • 0x05 : UnKnown
      • 0x06 : UnChanged
      • 0x07 : Reserved
  • TYPE_LENGTH : size = 1byte : contains the length in bytes of the TYPE field. Current NDEF standard dictates TYPE to be 2 or 5 bytes long.
  • PAYLOAD_LENGTH : size = 1 or 4 bytes (determined by StatusByte.SR field, if SR=1 then PAYLOAD_LENGTH is 1 byte, else 4 bytes) : contains the length in bytes of the NDEF message payload section.
  • ID_LENGTH : size = 1 byte : determines the size in bytes of the ID field. Typically 1 or 0. If 0 then there is no ID field.
  • TYPE : size =determined by TYPE_LENGTH : contains ASCII characters that determine the type of the message, used with the StatusByte.TNF section to determine the message type (ie a TNF of 0x01 for Well Known Type and a TYPE field of ‘T’ would tell us that the NDEF message payload is a Text record. A Type of “U” means URI, and a Type of “Sp” means SmartPoster).
  • ID : size = determined by ID_LENGTH field : holds unique identifier for the message. Usually used with message chunking to identify sections of data, or for custom implementations.
  • PAYLOAD : size = determined by PAYLOAD_LENGTH : contains the payload for the message. The payload is where the actual data transfer happens.

Example : A header for a Short Record (not chunked) message of a Text Record that is 8 bytes in length, no ID necessary
0x11,0x01,0x08,0x84, …<8 bytes of Text Record>…

 ______________________________
|       Message Header         |
|         0x11                 | StatusByte:MB=0,ME=0,CF=0,SR=1, IL=0, TNF=0x01
|         0x01                 | Type Length = 1
|         0x08                 | Payload Length = 8
|                              | ID Len not given (IL=0 in Status Byte)
|         0x84                 | Type = 'T'
|                              | ID field not present 
|------------------------------|
|       Message Payload        |
|         .                    | Payload of 8 bytes
|         .                    |
|         .                    |
|------------------------------|

NDEF Message Details: Message Payload
The Message Payload contains information in an arbitrary format. This format is determined by the meta data in the Message Header. For simplicity all messages in this example will be short records (ie not chunked) so all the information will be contained in one message and not spread out across multiple payloads. Each TNF has its own payload types and formatting. TNF 0x05 Unknown even lets you specify your own formatting for personal use. In the rest of this article I will be using TNF 0x01 NFC Well Known Type because it is well defined and provides a good example base.

TNF 0x01 Well Known Type records have the following payload types:

  • ‘T’ = Text Record
  • ‘U’ = URI (used for webpages, links, all kinds of stuff)
  • ‘Sp’ = Smart Poster – smart posters are special, they are meta wrappers that contain multiple messages each with its own payload, more on that in a second.

Text Record (‘T’) Payload Layout:
Text records are composed of some meta data about the Text encoding and the text itself.

 ______________________________
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0|  
|------------------------------|
|UTF| 0 | Length of Lang Code  |  1 byte Text Record StatusByte
|------------------------------|
|          Lang Code           |  2 or 5 byte, multi-byte language code
|------------------------------|
|             Text             |  Multiple Bytes encoded in UTF-8 or UTF-16
|------------------------------|
  • Text Record Status Byte: size = 1 byte: specifies the encoding type (UTF8 or UTF16) and the length of the language code
    • UTF : 0=UTF8, 1=UTF16
    • Bit6 : bit 6 is reserved for future use and must always be 0
    • Bit5-0: Length Language Code: specifies the size of the Lang Code field in bytes
  • Lang Code : size = 2 or 5 bytes (may vary in future) : this is the langauge code for the document(ISO/IANA), common codes are ‘en’ for english, ‘en-US’ for United States English, ‘jp’ for japanese, … etc
  • Text : size = remainder of Payload Size : this is the area that contains the text, the format and language are known from the UTF bit and the Lang Code.

Example: A Text record encoded in UTF8 English that says ‘helloworld’ 0x82,0x65,0x6e,0x68,0x65,0x6c,0x6c,0x6f,0x77,0x6f,0x72,0x6c,0x64

 ______________________________
|       Message Header         |
|         Type = 'T'           |
|------------------------------|
|       Message Payload        |
|         0x82 = Status Byte   |  UTF = 1 (thus UTF8), len lang code = 2
|         0x65 = 'e'           |  start of  lang code 
|         0x6e = 'n'           |  end of lang code -  'en' for english 
|         0x68 = 'h'           |  Start of the Text field
|         0x65 = 'e'           |
|         0x6c = 'l'           |
|         0x6c = 'l'           |
|         0x6f = 'o'           |
|         0x77 = 'w'           |
|         0x6f = 'o'           |
|         0x72 = 'r'           |
|         0x6c = 'l'           |
|         0x64 = 'd'           |  End of the Text field
|------------------------------|

URI Record (‘U’) Payload Layout:
URI Records consist of a ID code and the URI string, the ID code is used as an abbreviated for commonly used addresses in order to reduce the size of the URI Record.

 ______________________________
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0|
|------------------------------|
|------------------------------|
|         ID Code              |  1 byte ID Code
|------------------------------|
|      UTF-8 String            |  Multiple Bytes UTF-8 string
|------------------------------|
  • ID Code : size = 1 byte : Valid values are 0x00 – 0x23, 0x24 and above are RFU (reserved for future use). The ID Code is pre-pended to the UTF-8 String to make the URI being transmitted.
    • 0x00 N/A. No prepending is done
    • 0x01 http://www.
    • 0x02 https://www.
    • 0x03 http://
    • 0x04 https://
    • 0x05 tel:
    • 0x06 mailto:
    • 0x07 ftp://anonymous:anonymous@
    • 0x08 ftp://ftp.
    • 0x09 ftps://
    • 0x0A sftp://
    • 0x0B smb://
    • 0x0C nfs://
    • 0x0D ftp://
    • 0x0E dav://
    • 0x0F news:
    • 0x10 telnet://
    • 0x11 imap:
    • 0x12 rtsp://
    • 0x13 urn:
    • 0x14 pop:
    • 0x15 sip:
    • 0x16 sips:
    • 0x17 tftp:
    • 0x18 btspp://
    • 0x19 btl2cap://
    • 0x1A btgoep://
    • 0x1B tcpobex://
    • 0x1C irdaobex://
    • 0x1D file://
    • 0x1E urn:epc:id:
    • 0x1F urn:epc:tag:
    • 0x20 urn:epc:pat:
    • 0x21 urn:epc:raw:
    • 0x22 urn:epc:
    • 0x23 urn:nfc:
    • 0x24-0xFF RFU Reserved for Future Use, Not Valid Inputs
  • UTF-8 String : multibyte : contains the actual string for the URI

Example: A URI record that links to http://www.google.com
0x01,0x67,0x6f,0x6f,0x67,0x6c,0x65,0x2e,0x63,0x6f,0x6d

 ______________________________
|       Message Header         |
|         Type = 'U'           | Type = 'U' for URI record
|------------------------------|
|       Message Payload        |
|         0x01 = 'http://www.' |  URI ID Code
|         0x67 = 'g'           |  URI Begin
|         0x6f = 'o'           |
|         0x6f = 'o'           |
|         0x67 = 'g'           |
|         0x6c = 'l'           |
|         0x65 = 'e'           |
|         0x2e = '.'           |
|         0x63 = 'c'           |
|         0x6f = 'o'           |
|         0x6d = 'm'           |  URI End
|------------------------------|

Smart Poster Record (‘Sp’) Payload Layout:
A smart poster is a special kind of NDEF Message, it is a wrapper for other message types. Smart Poster records were initially meant to be used as a hub for information, think put a smart poster tag on a movie poster and it will give you a title for the tag, a link to the movie website, a small image for the movie and maybe some other data. In practice Smart Posters are rarely used, most people prefer to simply use a URI record to send people off to do stuff (the majority of Google Android NFC messages are implemented this way with custom TNF tags).
A smart poster must contain:

    • 1+ Text records (there can be multiple in multiple languages)
    • 1 URI record (this is the main record, everything else is metadata
    • 1 Action Record – Specifies Action to do on URI, (Open, Save, Edit)

A smart poster may optionally contain:

  • 1+ Icon Records – MIME type image record
  • a size record – used to tell how large referenced external entity is (ie size of pdf or mp3 the URI points to)
  • a type record – declares Mime type of external entity
  • Multiple other record types (it literally can be anything you want)

There is no special layout for a Smart Poster record type, the Message Payload is just a series of other messages. You know you have reached the end of a smart poster when you have read in a number of bytes = Payload Length. This is also how you distinguish sub-messages from each other, a whole lot of basic math.

 ______________________________
|       Message Header         |  
|         Type = 'Sp'          |
|------------------------------|
|       Message Payload        |
|    ______________________    |
|   | sub-message1 header  |   | Could be a Text Record
|   |----------------------|   |
|   | sub-message1 payload |   |
|   |----------------------|   |
|                              |
|    ______________________    |
|   | sub-message2 header  |   | Could be a URI record
|   |----------------------|   |
|   | sub-message2 payload |   |
|   |----------------------|   |
|                              |
|    ______________________    |
|   | sub-message3 header  |   | Could be an Action record
|   |----------------------|   |
|   | sub-message3 payload |   |
|   |----------------------|   |
|                              |
|------------------------------|

Because Smart Poster records are almost never used any more I’m not going to dive further into them. At their core they are simple, you just need to recursively process message headers and payloads until you reach the end of the length of the outside most message payload wrapper.

 

Examples:

Example 1: Text Message with the message ‘Hello World’
Data: 0x11,0x01,0x0E,0x54,0x65,0x6e,0x48,0x65,0x6c,0x6c,0x6f,0x20,0x57,0x6f,0x72,0x6c,0x64

 ______________________________
|       Message Header         |
|           0x11               | Status Byte, 
|                              |   SR = 1, ShortRecord, thus the size of 
|                              |       the Payload length field is 1 Byte
|                              |   TNF = 0x01 (NFC Well Known Type), 
|                              |   IL = 0, thus no ID Length of ID field. 
|           0x01               | Type Length = 1
|           0x0E               | PayloadLength = 14Bytes (0xE == 14)
|           0x54               | Type 'T' for Text Record
|------------------------------|
|       Message Payload        | (14 bytes long)
|           0x02               | Text Record Status Byte - 
|                              |    UTF flag  = 0 (message is UTF-8), 
|                              |    Length of language code = 2 bytes
|           0x65               | = 'e' Language Code (2 Bytes) 'en' 
|           0x6e               | = 'n'     for English
|           0x48               | = 'H' (begin multibyte payload)
|           0x65               | = 'e'
|           0x6c               | = 'l'
|           0x6c               | = 'l'
|           0x6f               | = 'o'
|           0x20               | = ' '
|           0x57               | = 'W'
|           0x6f               | = 'o'
|           0x72               | = 'r'
|           0x6c               | = 'l'
|           0x64               | = 'd'
|------------------------------|

 

Example 2: URI Message with web address of  ‘http://www.google.com’
Note that the URI ID Code could easily be changed to something else to change the link, instead of 0x01 for ‘http://www.’ it could have been oxo0 to prepend nothing, so the link would have been ‘google.com’, or we could have prepended 0x06 ‘mailto:’ to make it an email link.

Data: 0x11,0x01,0x0B,0x55,0x01,0x67,0x6f,0x6f,0x67,0x6c,0x65,0x2e,0x63,0x6f,0x6d

 ______________________________
|       Message Header         |
|           0x11               | Status Byte, 
|                              |   SR=1 (Short Record, payload len = 1 byte),
|                              |   TNF = 0x01 (NFC Well Known Type), 
|                              |   IL = 0, thus no ID Length of ID field. 
|           0x01               | Type Length = 1
|           0x0B               | PayloadLength=11Bytes (hex 0xB = decimal 11)
|           0x55               | Type 'U' for URI Record
|------------------------------|
|       Message Payload        |
|         0x01                 |  URI ID Code, 'http://www.'
|         0x67 = 'g'           |  URI Begin
|         0x6f = 'o'           |
|         0x6f = 'o'           |
|         0x67 = 'g'           |
|         0x6c = 'l'           |
|         0x65 = 'e'           |
|         0x2e = '.'           |
|         0x63 = 'c'           |
|         0x6f = 'o'           |
|         0x6d = 'm'           |  URI End
|------------------------------|

 

One Comment