Skip to content

Commit 5cb43e6

Browse files
author
Donatien Garnier
authored
Merge pull request #1 from pan-/nfc-spec
NFC: Amend ndef parsing design.
2 parents 33fd8f0 + 876cf06 commit 5cb43e6

13 files changed

+503
-148
lines changed

features/nfc/doc/nfc_design.md

Lines changed: 157 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ NFC offers three modes;
77
2. NFC reader/writer
88
3. NFC peer to peer
99

10-
To support new use cases such as commissioning, BLE pairing and identification/authentication of NFC enabled IoT endpoints, Mbed OS should support the card emulation mode.
10+
To support new use cases such as commissioning, BLE pairing and identification/authentication of NFC enabled IoT endpoints, Mbed OS should support the card emulation mode.
1111

1212
However the architecture should be future-proofed and should also be extendable to support other NFC modes in the future.
1313

@@ -114,7 +114,7 @@ nfc_rf_protocols_bitmask_t get_supported_rf_protocols() const;
114114
Retrieve the list of supported RF protocols.
115115
These are mapped against NFC Forum-defined protocols.
116116

117-
* T1T is based on ISO/IEC 14443A-3 and commonly known as Topaz (Innovision).
117+
* T1T is based on ISO/IEC 14443A-3 and commonly known as Topaz (Innovision).
118118
* T2T is based on ISO/IEC 14443A-3 and commonly known as Mifare Ultralight/NTAG (NXP).
119119
* T3T is based on JIS X6319-4, also known as Felica (Sony).
120120
* ISO-DEP is based on ISO/IEC 14443-4 and is the common interface for contactless smartcards. The underlying radio protocol can either be ISO/IEC 14443A or ISO/IEC 14443B.
@@ -261,99 +261,190 @@ The `NFCRemoteTarget` class derives from `NFCTarget` and additionally from `NFCE
261261

262262
## NDEF API
263263

264-
![ndef_diagram]
265-
266264
The NDEF API is constructed with these requirements in mind:
267265
* Minimizing memory allocation/copies
268266
* NFC Forum compliance
269267
* Ease of use
268+
* Extensibility
270269

271-
#### NDEF Message
270+
### Common objects
272271

273-
A NDEF Message is made of multiple NDEF Records which is reflected by the API:
272+
We will provide multiple helpers to make it easy to create/parse common record types:
273+
* URI
274+
* Text
275+
* Smart Poster
276+
* MIME data
274277

278+
For instance, the `URI`'s class API is as follows:
275279
```cpp
276-
bool parse(const uint8_t* buffer, size_t sz)
277-
size_t count()
278-
NDEFRecord operator[](size_t n)
280+
uri_prefix_t uri_prefix() const
281+
void set_uri_prefix(uri_prefix_t prefix)
282+
283+
bool get_uri(char* uri, size_t max_sz) const
284+
size_t uri_size() const
285+
void set_uri(const char* uri)
286+
287+
bool get_full_uri(char* uri, size_t max_sz) const
288+
size_t full_uri_size() const
289+
void set_full_uri(const char* uri)
279290
```
280291
281-
The message can be mapped with a byte array and individual records are decoded/populated on the fly.
292+
**Note:** These types can be replaced by user defined ones if parsing and serialization logic is provided.
293+
294+
### Parsing
282295
283-
#### NDEF Message builder
296+
#### ndef::MessageParser
284297
285-
We're using a builder pattern to encode an NDEF message over a byte array.
298+
![ndef_message_parser_diagram]
299+
300+
Messages incoming from the peer are parsed by a `MessageParser` which produce
301+
`Record` instances to its client. The parsing operation is event-driven: a
302+
message parser client registers a delegate inside the message parser. This delegate
303+
gets notified whenever an interesting event happens during the parsing.
286304
287305
```cpp
288-
NDEFMessageBuilder(uint8_t* buffer, size_t max_sz)
289-
bool add_record(const NDEFRecord& record)
290-
NDEFMessage build()
306+
void set_delegate(Delegate* delegate);
307+
void parse(const ac_buffer_t& data_buffer);
291308
```
292309

293-
A reference to the array is provided in the constructor and records can be appended by the user (within memory limits).
310+
It is important to note that the data_buffer in entry of the parse function must
311+
contain the entire NDEF message.
312+
313+
##### ndef::MessageParser::Delegate
314+
315+
```cpp
316+
virtual void on_parsing_started() { }
317+
virtual void on_record_parsed(const Record& record) { }
318+
virtual void on_parsing_terminated() { }
319+
virtual void on_parsing_error(error_t error) { }
320+
```
294321
295-
Once done a NDEFMessage instance mapped to a subset of the byte array can be generated.
322+
The delegate is notified by the parser when the parsing start or end; when an error
323+
is encountered or when an ndef `Record` has been parsed.
296324
297-
#### NDEF Record
325+
To reduce memory consumption `Record` instances generated by the parser are short
326+
lived. They are only valid during the callback invocation. If a client is interested
327+
by the content of a message parsed and wants to use it after the parsing callback
328+
then it must make a copy of the record object.
298329
299-
The NDEF Record class is closely mapped with the NFC NDEF specification.
330+
#### NDEF Record parsing
300331
301-
Each record holds:
302-
* A Type Name Format indicator - indicates which namespace the type field belongs to ('Well-known NDEF types', MIME, Absolute URI, etc.)
303-
* A type field
304-
* An optional ID field
305-
* The record's value
332+
![ndef_record_parser_diagram]
306333
307-
All arrays are passed by reference (no copy made).
334+
NDEF records can contain any type of content. Therefore parsing of records is
335+
specific to the application. To help the developer; an optional ndef record
336+
parsing framework is included. It follows the _chain-of-responsibility_ design
337+
pattern that facilitate the integration of record parsers defined by client code.
338+
339+
##### ndef::RecordParser
340+
341+
Is is the base building block of the record parsing frame working. It parses a
342+
record then return true if the record has been parsed or false otherwise.
308343
309344
```cpp
310-
static bool parse(const uint8_t* buffer, size_t max_sz)
311-
ssize_t build(const uint8_t* buffer, size_t max_sz)
345+
virtual bool parse(const Record&);
346+
```
312347

313-
uint8_t tnf()
314-
void set_tnf(uint8_t tnf)
348+
##### ndef::RecordParserChain
315349

316-
const uint8_t* type() const
317-
size_t type_size() const
318-
void set_type(const uint8_t* type, size_t type_size)
350+
It aggregate `RecordParser` instances and defer parsing to the instances it contains.
319351

320-
const uint8_t* id() const
321-
size_t id_size() const
322-
void set_id(const uint8_t* id, size_t id_size)
352+
```cpp
353+
bool parse(const Record& record);
354+
void set_next_parser(RecordParser* parser);
355+
```
356+
357+
##### ndef::GenericRecordParser<ParserImplementation, ParsingResult>
323358
324-
const uint8_t* value() const
325-
size_t value_size() const
326-
void set_value(const uint8_t* type, size_t type_size)
359+
This is a partial implementation of the `RecordParser` interface. It exposes a
360+
delegate type that can be implemented and registered by clients of this parser.
361+
This delegate expects objects of the parsing result type.
362+
363+
```cpp
364+
bool parse(const Record&)
365+
void set_delegate(Delegate* delegate)
327366
```
328367

329-
**Helpers**
368+
Implementation of this class must expose the following non virtual function:
330369

331-
We will provide multiple helpers to make it easy to create/parse common record types:
332-
* URI
333-
* Text
334-
* Smart Poster
335-
* MIME data
370+
```c++
371+
bool do_parse(const Record& record, ParsingResult& parsing_result);
372+
```
373+
374+
If the parsing is successful then it should return true and fill `parsing_result`
375+
otherwise it should return false and leave `parsing_result` untouched.
376+
377+
**Note:** The Curiously recurring template pattern (CRTP) is used to implement
378+
the delegation mechanism in a type-safe fashion. This is not achievable with
379+
_regular_ polymorphism.
380+
381+
###### ndef::GenericRecordParser<ParserImplementation, ParsingResult>::Delegate
382+
383+
This delegate must be implemented by clients of this class. It receives the objects
384+
parsed.
336385
337-
For instance, the `URIRecord`'s class API is as follows:
338386
```cpp
339-
static bool is_uri_record(const NDEFRecord& record)
340-
static URIRecord as_uri_record(const NDEFRecord& record)
387+
virtual void on_record_parsed(const ParsingResult& record, const RecordID* id);
388+
```
341389

342-
uri_prefix_t uri_prefix() const
343-
void set_uri_prefix(uri_prefix_t prefix)
390+
**Note:** Usually clients are client of an implementation of an
391+
ndef::GenericRecordParser<ParserImplementation, ParsingResult> . They can refer
392+
to the delegate as `ImplementationName::Delegate`.
344393

345-
bool get_uri(char* uri, size_t max_sz) const
346-
size_t uri_size() const
347-
void set_uri(const char* uri)
394+
#### Common parsers
348395

349-
bool get_full_uri(char* uri, size_t max_sz) const
350-
size_t full_uri_size() const
351-
void set_full_uri(const char* uri)
396+
![ndef_common_parsers_diagram]
397+
398+
Parsers for each common record type exists. They inherit from the
399+
`GenericRecordParser` to exposes a common delegate interface:
400+
401+
```cpp
402+
virtual void on_record_parsed(const <ParsedType>& result, const ndef::RecordID* id)
403+
```
404+
405+
#### Simple parser
406+
407+
The API provide a class named `SimpleMessageParser` that glues together a
408+
`MessageParser` and a chain `RecordParser`'s containing the parsers for the common
409+
types.
410+
411+
![ndef_simple_parser_diagram]
412+
413+
Clients of the class can register a delegate, parse a message or add a new
414+
`RecordParser` in the parsing chain.
415+
416+
```cpp
417+
void set_delegate(Delegate* delegate);
418+
void parse(const ac_buffer_t& data_buffer);
419+
void add_record_parser(ndef::RecordParser* parser);
352420
```
353421

354-
This includes some helper classes to check whether a record is an URI record, and if so to construct an `URIRecord` instance from a `NDEFRecord`.
422+
##### Delegate
355423

356-
In this case buffers are copied to account for the NULL-terminator character that is not present in the underlying byte buffer.
424+
This delegate must be implemented by clients of this class. It receives events
425+
from the parsing process:
426+
427+
```cpp
428+
virtual void on_parsing_error(ndef::MessageParser::error_t error);
429+
virtual void on_parsing_started();
430+
virtual void on_text_parsed(const Text& text, const ndef::RecordID* id);
431+
virtual void on_mime_parsed(const Mime& text, const ndef::RecordID* id);
432+
virtual void on_uri_parsed(const URI& uri, const ndef::RecordID* id);
433+
virtual void on_unknown_record_parsed(const ndef::Record& record);
434+
virtual void on_parsing_terminated();
435+
```
436+
437+
### Serialization
438+
439+
The class `MessageBuilder` is used to map a record into an NDEF message. It
440+
includes a data buffer that contains the _raw_ message. Client code use the
441+
functions `append_record` to append a new record into the message being built.
442+
443+
![ndef_message_builder_diagram]
444+
445+
For convenience, serialization functions for common types are provided as well as
446+
a specialized `MessageBuilder` named `SimpleMessageBuilder` that exposes them
447+
in an object oriented fashion.
357448
358449
## HAL APIs
359450
@@ -363,7 +454,7 @@ The one HAL API that will have to be implemented by vendors to make use of the `
363454
364455
From the upper layer's point of view, the EEPROM is a byte array that can be read from/written to. Long operations (reads, writes, erasures) must happen asynchronously. Booleans indicate whether a particular operation was succesful. Encoding is handled by the upper layer.
365456
366-
Address 0 means the start of the NDEF buffer (not necessarily at address 0 in the EEPROM).
457+
Address 0 means the start of the NDEF buffer (not necessarily at address 0 in the EEPROM).
367458
368459
When a buffer is passed to the backend, the reference remains valid till the corresponding event is called.
369460
@@ -376,7 +467,7 @@ void backend_read_bytes(uint32_t address, size_t count)
376467
void backend_write_bytes(uint32_t address, const uint8_t* bytes, size_t count)
377468
void backend_set_size(size_t count)
378469
void backend_get_size()
379-
void backend_erase_bytes(uint32_t address, size_t size)
470+
void backend_erase_bytes(uint32_t address, size_t size)
380471
```
381472

382473
The following events must be called to signal completion of long operations:
@@ -415,10 +506,14 @@ GreenTea tests will be provided to partners to ensure compliance with the NFC EE
415506
* Event Queue
416507
417508
There are currently at least four event queues (Plaftorm, BLE, USB, IP) in mbed OS and NFC will also require an event queing mechanism. We should aim at reusing one of these existing queues with the long term goal of unifying these code bases.
418-
509+
419510
[phase_1_architecture]: phase_1_architecture.png
420511
[phase_2_architecture]: phase_2_architecture.png
421512
[nfc_controller_diagram]: uml_diagram_controller.png
422513
[nfc_endpoints_diagram]: uml_diagram_endpoints.png
423-
[ndef_diagram]: uml_diagram_ndef.png
424-
[interop_test_rig]: interop_test_rig.png
514+
[interop_test_rig]: interop_test_rig.png
515+
[ndef_message_parser_diagram]: uml_diagram_ndef_message_parser.png
516+
[ndef_record_parser_diagram]: uml_diagram_ndef_record_parser.png
517+
[ndef_common_parsers_diagram]: uml_diagram_ndef_common_parsers.png
518+
[ndef_simple_parser_diagram]: uml_diagram_ndef_simple_parser.png
519+
[ndef_message_builder_diagram]: uml_diagram_ndef_message_builder_diagram.png

features/nfc/doc/uml_diagram_ndef.png

-52.2 KB
Binary file not shown.

features/nfc/doc/uml_diagram_ndef.txt

Lines changed: 0 additions & 86 deletions
This file was deleted.
Loading

0 commit comments

Comments
 (0)