Merge pull request #1 from pan-/nfc-spec

NFC: Amend ndef parsing design.
pull/7426/head
Donatien Garnier 2018-08-03 10:31:00 +01:00 committed by GitHub
commit 5cb43e6bb3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 515 additions and 160 deletions

View File

@ -7,7 +7,7 @@ NFC offers three modes;
2. NFC reader/writer
3. NFC peer to peer
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.
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.
However the architecture should be future-proofed and should also be extendable to support other NFC modes in the future.
@ -114,7 +114,7 @@ nfc_rf_protocols_bitmask_t get_supported_rf_protocols() const;
Retrieve the list of supported RF protocols.
These are mapped against NFC Forum-defined protocols.
* T1T is based on ISO/IEC 14443A-3 and commonly known as Topaz (Innovision).
* T1T is based on ISO/IEC 14443A-3 and commonly known as Topaz (Innovision).
* T2T is based on ISO/IEC 14443A-3 and commonly known as Mifare Ultralight/NTAG (NXP).
* T3T is based on JIS X6319-4, also known as Felica (Sony).
* 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,72 +261,13 @@ The `NFCRemoteTarget` class derives from `NFCTarget` and additionally from `NFCE
## NDEF API
![ndef_diagram]
The NDEF API is constructed with these requirements in mind:
* Minimizing memory allocation/copies
* NFC Forum compliance
* Ease of use
* Extensibility
#### NDEF Message
A NDEF Message is made of multiple NDEF Records which is reflected by the API:
```cpp
bool parse(const uint8_t* buffer, size_t sz)
size_t count()
NDEFRecord operator[](size_t n)
```
The message can be mapped with a byte array and individual records are decoded/populated on the fly.
#### NDEF Message builder
We're using a builder pattern to encode an NDEF message over a byte array.
```cpp
NDEFMessageBuilder(uint8_t* buffer, size_t max_sz)
bool add_record(const NDEFRecord& record)
NDEFMessage build()
```
A reference to the array is provided in the constructor and records can be appended by the user (within memory limits).
Once done a NDEFMessage instance mapped to a subset of the byte array can be generated.
#### NDEF Record
The NDEF Record class is closely mapped with the NFC NDEF specification.
Each record holds:
* A Type Name Format indicator - indicates which namespace the type field belongs to ('Well-known NDEF types', MIME, Absolute URI, etc.)
* A type field
* An optional ID field
* The record's value
All arrays are passed by reference (no copy made).
```cpp
static bool parse(const uint8_t* buffer, size_t max_sz)
ssize_t build(const uint8_t* buffer, size_t max_sz)
uint8_t tnf()
void set_tnf(uint8_t tnf)
const uint8_t* type() const
size_t type_size() const
void set_type(const uint8_t* type, size_t type_size)
const uint8_t* id() const
size_t id_size() const
void set_id(const uint8_t* id, size_t id_size)
const uint8_t* value() const
size_t value_size() const
void set_value(const uint8_t* type, size_t type_size)
```
**Helpers**
### Common objects
We will provide multiple helpers to make it easy to create/parse common record types:
* URI
@ -334,11 +275,8 @@ We will provide multiple helpers to make it easy to create/parse common record t
* Smart Poster
* MIME data
For instance, the `URIRecord`'s class API is as follows:
For instance, the `URI`'s class API is as follows:
```cpp
static bool is_uri_record(const NDEFRecord& record)
static URIRecord as_uri_record(const NDEFRecord& record)
uri_prefix_t uri_prefix() const
void set_uri_prefix(uri_prefix_t prefix)
@ -351,9 +289,162 @@ size_t full_uri_size() const
void set_full_uri(const char* uri)
```
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`.
**Note:** These types can be replaced by user defined ones if parsing and serialization logic is provided.
In this case buffers are copied to account for the NULL-terminator character that is not present in the underlying byte buffer.
### Parsing
#### ndef::MessageParser
![ndef_message_parser_diagram]
Messages incoming from the peer are parsed by a `MessageParser` which produce
`Record` instances to its client. The parsing operation is event-driven: a
message parser client registers a delegate inside the message parser. This delegate
gets notified whenever an interesting event happens during the parsing.
```cpp
void set_delegate(Delegate* delegate);
void parse(const ac_buffer_t& data_buffer);
```
It is important to note that the data_buffer in entry of the parse function must
contain the entire NDEF message.
##### ndef::MessageParser::Delegate
```cpp
virtual void on_parsing_started() { }
virtual void on_record_parsed(const Record& record) { }
virtual void on_parsing_terminated() { }
virtual void on_parsing_error(error_t error) { }
```
The delegate is notified by the parser when the parsing start or end; when an error
is encountered or when an ndef `Record` has been parsed.
To reduce memory consumption `Record` instances generated by the parser are short
lived. They are only valid during the callback invocation. If a client is interested
by the content of a message parsed and wants to use it after the parsing callback
then it must make a copy of the record object.
#### NDEF Record parsing
![ndef_record_parser_diagram]
NDEF records can contain any type of content. Therefore parsing of records is
specific to the application. To help the developer; an optional ndef record
parsing framework is included. It follows the _chain-of-responsibility_ design
pattern that facilitate the integration of record parsers defined by client code.
##### ndef::RecordParser
Is is the base building block of the record parsing frame working. It parses a
record then return true if the record has been parsed or false otherwise.
```cpp
virtual bool parse(const Record&);
```
##### ndef::RecordParserChain
It aggregate `RecordParser` instances and defer parsing to the instances it contains.
```cpp
bool parse(const Record& record);
void set_next_parser(RecordParser* parser);
```
##### ndef::GenericRecordParser<ParserImplementation, ParsingResult>
This is a partial implementation of the `RecordParser` interface. It exposes a
delegate type that can be implemented and registered by clients of this parser.
This delegate expects objects of the parsing result type.
```cpp
bool parse(const Record&)
void set_delegate(Delegate* delegate)
```
Implementation of this class must expose the following non virtual function:
```c++
bool do_parse(const Record& record, ParsingResult& parsing_result);
```
If the parsing is successful then it should return true and fill `parsing_result`
otherwise it should return false and leave `parsing_result` untouched.
**Note:** The Curiously recurring template pattern (CRTP) is used to implement
the delegation mechanism in a type-safe fashion. This is not achievable with
_regular_ polymorphism.
###### ndef::GenericRecordParser<ParserImplementation, ParsingResult>::Delegate
This delegate must be implemented by clients of this class. It receives the objects
parsed.
```cpp
virtual void on_record_parsed(const ParsingResult& record, const RecordID* id);
```
**Note:** Usually clients are client of an implementation of an
ndef::GenericRecordParser<ParserImplementation, ParsingResult> . They can refer
to the delegate as `ImplementationName::Delegate`.
#### Common parsers
![ndef_common_parsers_diagram]
Parsers for each common record type exists. They inherit from the
`GenericRecordParser` to exposes a common delegate interface:
```cpp
virtual void on_record_parsed(const <ParsedType>& result, const ndef::RecordID* id)
```
#### Simple parser
The API provide a class named `SimpleMessageParser` that glues together a
`MessageParser` and a chain `RecordParser`'s containing the parsers for the common
types.
![ndef_simple_parser_diagram]
Clients of the class can register a delegate, parse a message or add a new
`RecordParser` in the parsing chain.
```cpp
void set_delegate(Delegate* delegate);
void parse(const ac_buffer_t& data_buffer);
void add_record_parser(ndef::RecordParser* parser);
```
##### Delegate
This delegate must be implemented by clients of this class. It receives events
from the parsing process:
```cpp
virtual void on_parsing_error(ndef::MessageParser::error_t error);
virtual void on_parsing_started();
virtual void on_text_parsed(const Text& text, const ndef::RecordID* id);
virtual void on_mime_parsed(const Mime& text, const ndef::RecordID* id);
virtual void on_uri_parsed(const URI& uri, const ndef::RecordID* id);
virtual void on_unknown_record_parsed(const ndef::Record& record);
virtual void on_parsing_terminated();
```
### Serialization
The class `MessageBuilder` is used to map a record into an NDEF message. It
includes a data buffer that contains the _raw_ message. Client code use the
functions `append_record` to append a new record into the message being built.
![ndef_message_builder_diagram]
For convenience, serialization functions for common types are provided as well as
a specialized `MessageBuilder` named `SimpleMessageBuilder` that exposes them
in an object oriented fashion.
## HAL APIs
@ -363,7 +454,7 @@ The one HAL API that will have to be implemented by vendors to make use of the `
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.
Address 0 means the start of the NDEF buffer (not necessarily at address 0 in the EEPROM).
Address 0 means the start of the NDEF buffer (not necessarily at address 0 in the EEPROM).
When a buffer is passed to the backend, the reference remains valid till the corresponding event is called.
@ -376,7 +467,7 @@ void backend_read_bytes(uint32_t address, size_t count)
void backend_write_bytes(uint32_t address, const uint8_t* bytes, size_t count)
void backend_set_size(size_t count)
void backend_get_size()
void backend_erase_bytes(uint32_t address, size_t size)
void backend_erase_bytes(uint32_t address, size_t size)
```
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
* Event Queue
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.
[phase_1_architecture]: phase_1_architecture.png
[phase_2_architecture]: phase_2_architecture.png
[nfc_controller_diagram]: uml_diagram_controller.png
[nfc_endpoints_diagram]: uml_diagram_endpoints.png
[ndef_diagram]: uml_diagram_ndef.png
[interop_test_rig]: interop_test_rig.png
[interop_test_rig]: interop_test_rig.png
[ndef_message_parser_diagram]: uml_diagram_ndef_message_parser.png
[ndef_record_parser_diagram]: uml_diagram_ndef_record_parser.png
[ndef_common_parsers_diagram]: uml_diagram_ndef_common_parsers.png
[ndef_simple_parser_diagram]: uml_diagram_ndef_simple_parser.png
[ndef_message_builder_diagram]: uml_diagram_ndef_message_builder_diagram.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

View File

@ -1,86 +0,0 @@
@startuml
class NDEFMessage {
+NDEFMessage()
+bool parse(const uint8_t* buffer, size_t sz)
+size_t count()
+NDEFRecord operator[](size_t n)
}
class NDEFMessageBuilder {
+NDEFMessageBuilder(uint8_t* buffer, size_t max_sz)
+bool add_record(const NDEFRecord& record)
+NDEFMessage build()
}
class NDEFRecord {
+bool parse(const uint8_t* buffer, size_t max_sz)
+ssize_t build(const uint8_t* buffer, size_t max_sz)
+uint8_t tnf()
+void set_tnf(uint8_t tnf)
+const uint8_t* type() const
+size_t type_size() const
+void set_type(const uint8_t* type, size_t type_size)
+const uint8_t* id() const
+size_t id_size() const
+void set_id(const uint8_t* id, size_t id_size)
+const uint8_t* value() const
+size_t value_size() const
+void set_value(const uint8_t* type, size_t type_size)
}
NDEFMessage *-- NDEFRecord
NDEFMessageBuilder *-- NDEFRecord
class URIRecord {
{static} +bool is_uri_record(const NDEFRecord& record)
{static} +URIRecord as_uri_record(const NDEFRecord& record)
+uri_prefix_t uri_prefix() const
+void set_uri_prefix(uri_prefix_t prefix)
+bool get_uri(char* uri, size_t max_sz) const
+size_t uri_size() const
+void set_uri(const char* uri)
+bool get_full_uri(char* uri, size_t max_sz) const
+size_t full_uri_size() const
+void set_full_uri(const char* uri)
}
NDEFRecord <-- URIRecord
class TextRecord {
{static} +bool is_text_record(const NDEFRecord& record)
{static} +TextRecord as_text_record(const NDEFRecord& record)
+text_encoding_t encoding() const
+void set_encoding(text_encoding_t encoding)
+bool get_text(char* text, size_t max_sz) const
+size_t text_size() const
+void set_text(const char* text)
}
NDEFRecord <-- TextRecord
class MIMERecord {
{static} +bool is_mime_record(const NDEFRecord& record)
{static} +MIMERecord as_mime_record(const NDEFRecord& record)
+const char* mime_type() const
+size_t mime_type_size() const
+void set_mime_type(const char* text)
+const uint8_t* mime_data() const
+size_t mime_data_size() const
+void set_mime_data(const uint8_t* type, size_t type_size)
}
NDEFRecord <-- MIMERecord
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -0,0 +1,58 @@
@startuml
package ndef {
abstract RecordParser {
+RecordParser()
+{abstract} bool parse(const Record&)
#~RecordParser()
}
abstract GenericRecordParser<ParserImplementation, ParsingResult> {
+GenericRecordParser()
+bool parse(const Record&)
+void set_delegate(Delegate* delegate)
#~GenericRecordParser()
}
interface GenericRecordParser::Delegate<ParsingResult> {
+{abstract} void on_record_parsed(const ParsingResult& result, const RecordID* id)
#~Delegate()
}
RecordParser <|- GenericRecordParser
GenericRecordParser +- "0..1" GenericRecordParser::Delegate
}
package common {
class URI {
}
class Mime {
}
class Text {
}
class URIParser {
bool do_parse(const ndef::Record& record, URI& uri)
}
class TextParser {
bool do_parse(const ndef::Record& record, Text& text)
}
class MimeParser {
bool do_parse(const ndef::Record& record, Mime& mime)
}
URI -- URIParser: Produce <
Text -- TextParser: Produce <
Mime -- MimeParser: Produce <
URIParser --|> GenericRecordParser
TextParser --|> GenericRecordParser
MimeParser --|> GenericRecordParser
}
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

View File

@ -0,0 +1,64 @@
@startuml
package ndef {
class MessageBuilder {
+MessageBuilder(uint8_t* buffer, size_t capacity)
+~MessageBuilder()
+bool append_record(const RecordType& type, const RecordPayload* payload, bool is_last_record = false)
+bool append_record(const Record& record)
+const uint8_t* get_data() const;
+size_t get_data_size() const;
}
class Record {
}
class RecordType{
}
class RecordPayload{
}
class RecordID{
}
Record *-- RecordType
Record *-- "0..1" RecordPayload
Record *-- "0..1" RecordID
Record - MessageBuilder: insert >
}
package common {
class SimpleMessageBuilder {
+SimpleMessageBuilder(uint8_t* buffer, size_t capacity)
+~SimpleMessageBuilder()
+bool append_uri(const URI&)
+bool append_text(const Text&)
+bool append_mime(const Mime&)
}
class URI {
+friend bool append_record(ndef::MessageBuilder&, const URI&)
}
class Text {
+friend bool append_record(ndef::MessageBuilder&, const Text&)
}
class Mime {
+friend bool append_record(ndef::MessageBuilder&, const Mime&)
}
SimpleMessageBuilder --|> MessageBuilder
URI -- SimpleMessageBuilder: wrap >
Text -- SimpleMessageBuilder: wrap >
Mime -- SimpleMessageBuilder: wrap >
}
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -0,0 +1,46 @@
@startuml
package ndef {
class MessageParser {
+MessageParser()
+void set_delegate(Delegate* delegate)
+void parse(const ac_buffer_t& data_buffer)
}
interface MessageParser::Delegate {
+{abstract} void on_parsing_started()
+{abstract} void on_record_parsed(const Record& record)
+{abstract} void on_parsing_terminated()
+{abstract} void on_parsing_error(error_t error)
# ~Delegate()
}
enum MessageParser::error_t {
}
MessageParser +-- "0..1" MessageParser::Delegate
MessageParser +-- MessageParser::error_t
note top of "MessageParser::Delegate"
Implemented by the client of the parsing operation.
end note
class Record {
}
class RecordType {
}
class RecordPayload {
}
class RecordID {
}
Record *-- RecordType
Record *-- "0..1" RecordPayload
Record *-- "0..1" RecordID
MessageParser - Record: Produce >
}
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View File

@ -0,0 +1,73 @@
@startuml
package ndef {
class Record {
}
class RecordType {
}
class RecordPayload {
}
class RecordID {
}
Record *-- RecordType
Record *-- "0..1" RecordPayload
Record *-- "0..1" RecordID
abstract RecordParser {
+RecordParser()
+{abstract} bool parse(const Record&)
#~RecordParser()
}
abstract GenericRecordParser<ParserImplementation, ParsingResult> {
+GenericRecordParser()
+bool parse(const Record&)
+void set_delegate(Delegate* delegate)
#~GenericRecordParser()
}
interface GenericRecordParserConcept<ParsingResult> {
+bool do_parse(const Record& record, ParsingResult& parsing_result)
}
interface GenericRecordParser::Delegate<ParsingResult> {
+{abstract} void on_record_parsed(const ParsingResult& record, const RecordID* id)
#~Delegate()
}
RecordParser <|-- GenericRecordParser
GenericRecordParser <|-- GenericRecordParserConcept
GenericRecordParser +-- "0..1" GenericRecordParser::Delegate
note as N1
GenericRecordParserConcept model the concept that must
be implemented by GenericRecordParser childs.
It doesn't exist in the hierarchy.
end note
N1 - GenericRecordParser
N1 - GenericRecordParserConcept
note bottom of "GenericRecordParser::Delegate"
Implemented by the client of the parsing operation.
end note
class RecordParserChain {
+RecordParserChain()
+~RecordParserChain()
+bool parse(const Record& record)
+void set_next_parser(RecordParser* parser)
}
note bottom of "RecordParserChain"
Chain of responsibility pattern.
end note
Record - RecordParserChain: Parse >
RecordParserChain o- "*" RecordParser
}
@enduml

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

View File

@ -0,0 +1,105 @@
@startuml
package ndef {
class RecordParserChain {
}
abstract RecordParser {
}
abstract GenericRecordParser<ParserImplementation, ParsingResult> {
}
interface GenericRecordParser::Delegate<ParsingResult> {
+{abstract} void on_record_parsed(const ParsingResult& record, const RecordID* id)
#~Delegate()
}
class MessageParser {
}
interface MessageParser::Delegate {
+{abstract} void on_parsing_started()
+{abstract} void on_record_parsed(const Record& record)
+{abstract} void on_parsing_terminated()
+{abstract} void on_parsing_error(error_t error)
# ~Delegate()
}
MessageParser +-- "0..1" MessageParser::Delegate
RecordParserChain -o RecordParser
GenericRecordParser --|> RecordParser
GenericRecordParser +- "0..1" GenericRecordParser::Delegate
RecordParserChain -[hidden]- MessageParser
}
package common {
class SimpleMessageParser {
+ SimpleMessageParser()
+ ~SimpleMessageParser()
+ void set_delegate(Delegate* delegate)
+ void parse(const ac_buffer_t& data_buffer)
+ void add_record_parser(ndef::RecordParser* parser)
- void on_parsing_error(ndef::MessageParser::error_t error)
- void on_parsing_started()
- void on_record_parsed(const ndef::Record& record)
- void on_parsing_terminated()
- void on_record_parsed(const URI& uri, const ndef::RecordID* id)
- void on_record_parsed(const Text& uri, const ndef::RecordID* id)
- void on_record_parsed(const Mime& uri, const ndef::RecordID* id)
}
interface SimpleMessageParser::Delegate {
+ {abstract} void on_parsing_error(ndef::MessageParser::error_t error)
+ {abstract} void on_parsing_started()
+ {abstract} void on_text_parsed(const Text& text, const ndef::RecordID* id)
+ {abstract} void on_mime_parsed(const Mime& text, const ndef::RecordID* id)
+ {abstract} void on_uri_parsed(const URI& uri, const ndef::RecordID* id)
+ {abstract} void on_unknown_record_parsed(const ndef::Record& record)
+ {abstract} void on_parsing_terminated()
# ~Delegate()
}
SimpleMessageParser::Delegate +- SimpleMessageParser
class URIParser {
}
class TextParser {
}
class MimeParser {
}
URIParser --|> GenericRecordParser
TextParser --|> GenericRecordParser
MimeParser --|> GenericRecordParser
SimpleMessageParser o-- URIParser
SimpleMessageParser o-- TextParser
SimpleMessageParser o-- MimeParser
SimpleMessageParser o-- RecordParserChain
SimpleMessageParser o-- MessageParser
SimpleMessageParser <|-- MessageParser::Delegate
SimpleMessageParser <|-- GenericRecordParser::Delegate
SimpleMessageParser <|-- GenericRecordParser::Delegate
SimpleMessageParser <|-- GenericRecordParser::Delegate
}
@enduml