NFC: Amend ndef parsing design.

* Decouple Message parsing from Record parsing
* Decouple record parsing from Common objects
* Provide an easy to use parser that parse the most common objects.
pull/7426/head
Vincent Coubard 2018-07-30 18:05:19 +01:00
parent 33fd8f0071
commit 7ed2a7802b
11 changed files with 442 additions and 159 deletions

View File

@ -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,154 @@ 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 register a delegate inside the message parser. This delegate
get notified whenever an interesting event happen 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
**TBD**
## HAL APIs
@ -422,3 +505,7 @@ There are currently at least four event queues (Plaftorm, BLE, USB, IP) in mbed
[nfc_endpoints_diagram]: uml_diagram_endpoints.png
[ndef_diagram]: uml_diagram_ndef.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

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: 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