diff --git a/features/nfc/nfc/common/Mime.h b/features/nfc/nfc/common/Mime.h new file mode 100644 index 0000000000..67e8aebef6 --- /dev/null +++ b/features/nfc/nfc/common/Mime.h @@ -0,0 +1,136 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef NFC_COMMON_MIME_H_ +#define NFC_COMMON_MIME_H_ + +#include "platform/Span.h" +#include +#include +#include + +namespace mbed { +namespace nfc { +namespace common { + +/** + * @addtogroup nfc + * @{ + */ + +/** + * Represent a mime object. + */ +class Mime { +public: + /** + * Construct an empty Mime object. + */ + Mime(); + + /** + * Construct a mime object from its type and content + * + * @param mime_type The mime type of the object + * @param content The content of the object. + */ + Mime( + const Span &mime_type, + const Span &content + ); + + + /** + * Copy construct a Mime object. + * @param other The Mime object copied. + */ + Mime(const Mime &other); + + /** + * Destroy a Mime object. + */ + ~Mime(); + + /** + * Copy asign a Mime object. + * @param to_copy The Mime object to copy. + * @return a reference to this object + */ + Mime& operator=(const Mime &other); + + /** + * Set all attributes of a mime object. + * @param mime_type Type of the mime object. + * @param content Content of the mime object. + */ + void set_mime( + const Span &mime_type, + const Span &content + ); + + /** + * Return the mime type. + * @return The mime type. + */ + Span get_mime_type() const; + + /** + * Return the content of thr mime object. + * @return the content of the mime object. + */ + Span get_mime_content() const; + + /** + * Append into a message builder + */ + bool append_as_record( + ndef::MessageBuilder &message_builder, + bool is_last_record = false + ); + +private: + friend class MimeParser; + + void move_data( + uint8_t *mime_record, + size_t mime_type_size, + size_t mime_content_size + ); + + size_t mime_size() const; + + uint8_t *_mime; + size_t _type_size; + size_t _content_size; +}; + +/** + * Parse a Mime payload. + */ +class MimeParser : public ndef::GenericRecordParser { +public: + bool do_parse(const ndef::Record &record, Mime &mime); +}; + +/** + * @} + */ + +} // namespace common +} // namespace nfc +} // namespace mbed + +#endif /* NFC_COMMON_MIME_H_ */ diff --git a/features/nfc/nfc/common/SimpleMessageParser.h b/features/nfc/nfc/common/SimpleMessageParser.h new file mode 100644 index 0000000000..003f581a8e --- /dev/null +++ b/features/nfc/nfc/common/SimpleMessageParser.h @@ -0,0 +1,167 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef NFC_COMMON_SIMPLEMESSAGEPARSER_H_ +#define NFC_COMMON_SIMPLEMESSAGEPARSER_H_ + +#include "platform/Span.h" +#include "nfc/ndef/MessageParser.h" +#include "nfc/ndef/RecordParser.h" +#include "URI.h" +#include "Text.h" +#include "Mime.h" + +namespace mbed { +namespace nfc { +namespace common { + +/** + * @addtogroup nfc + * @{ + */ + +/** + * Basic message parser that aggregates URIParser, TextParser and MimeParser. + * + * Custom parsers can be added at runtime as well. + */ +class SimpleMessageParser : + ndef::MessageParser::Delegate, + URIParser::Delegate, + TextParser::Delegate, + MimeParser::Delegate +{ +public: + /** + * Delegate invoked when the parser raise an event. + */ + struct Delegate { + /** + * Invoked when an error is present in the message. + * @param error The error present in the message. + */ + virtual void on_parsing_error(ndef::MessageParser::error_t error) { } + + /** + * Invoked when parsing as started. + */ + virtual void on_parsing_started() { } + + /** + * Invoked when a text element has been parsed. + * @param text The text parsed. + */ + virtual void on_text_parsed(const Text &text, const ndef::RecordID &id) { } + + /** + * Invoked when a text element has been parsed. + * @param text The text parsed. + */ + virtual void on_uri_parsed(const URI &uri, const ndef::RecordID &id) { } + + /** + * Invoked when a mime element has been parsed. + * @param mime The mime object parsed. + */ + virtual void on_mime_parsed(const Mime &mime, const ndef::RecordID &id) { } + + /** + * Invoked when an unknown record has been parsed. + * @param The record freshly parsed. + */ + virtual void on_unknown_record_parsed(const ndef::Record &record) { } + + /** + * Invoked when parsing is over. + */ + virtual void on_parsing_terminated() { } + + protected: + ~Delegate() { } + }; + + /** + * Construct a new CommonMessageParser. + */ + SimpleMessageParser(); + + /** + * Set the handler that processes parsing events. + * @param delegate The parsing event handler. + */ + void set_delegate(Delegate *delegate); + + /** + * Parse an NDEF Message. + * + * Records and errors are reported to the handler registered with + * set_event_handler. + * + * @param data_buffer The data buffer that contains the NDEF message. + */ + void parse(const Span &data_buffer); + + /** + * Insert a new parser in the parser chain. + * @param parser The parser to add in the parsing chain. + */ + void add_record_parser(ndef::RecordParser *parser); + +private: + //////////////////////////////////////////////////////////////////////////// + /// Implementation of MessageParser::EventHandler + + virtual void on_parsing_error(ndef::MessageParser::error_t error); + + virtual void on_parsing_started(); + + virtual void on_record_parsed(const ndef::Record &record); + + virtual void on_parsing_terminated(); + + //////////////////////////////////////////////////////////////////////////// + /// Implementation of URIParser::EventHandler + + virtual void on_record_parsed(const URI &uri, const ndef::RecordID &id); + + //////////////////////////////////////////////////////////////////////////// + /// Implementation of TextParser::EventHandler + + virtual void on_record_parsed(const Text &text, const ndef::RecordID &id); + + //////////////////////////////////////////////////////////////////////////// + /// Implementation of MimeParser::EventHandler + + virtual void on_record_parsed(const Mime &mime, const ndef::RecordID &id); + + ndef::MessageParser _message_parser; + ndef::RecordParserChain _record_parser_chain; + URIParser _uri_parser; + TextParser _text_parser; + MimeParser _mime_parser; + Delegate *_delegate; +}; + + +/** + * @} + */ + +} // namespace common +} // namespace nfc +} // namespace mbed + +#endif /* NFC_COMMON_SIMPLEMESSAGEPARSER_H_ */ diff --git a/features/nfc/nfc/common/Text.h b/features/nfc/nfc/common/Text.h new file mode 100644 index 0000000000..b42d16b96f --- /dev/null +++ b/features/nfc/nfc/common/Text.h @@ -0,0 +1,145 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef NFC_COMMON_TEXT_H_ +#define NFC_COMMON_TEXT_H_ + +#include "platform/Span.h" +#include +#include +#include + +namespace mbed { +namespace nfc { +namespace common { + +/** + * @addtogroup nfc + * @{ + */ + +/** + * Represent the well known type text. + */ +class Text { +public: + /** + * Encoding of the text. + */ + enum encoding_t { + UTF8 = 0,//!< UTF8 + UTF16 = 1//!< UTF16 + }; + + /** + * Construct an empty text element. + */ + Text(); + + /** + * Construct a text element from a data buffer and an encoding. + * @param text_encoding The encoding of the text. + * @param language_code The string of the language code. + * @param text The text buffer. + */ + Text( + encoding_t text_encoding, + const Span &language_code, + const Span &text + ); + + /** + * Copy construct a text element. + * @param to_copy + */ + Text(const Text &to_copy); + + /** + * Destroy a text element. + */ + ~Text(); + + /** + * Copy assignment of another text element. + * @param to_copy The Text instance to copy + * @return a reference to this object. + */ + Text& operator=(const Text &to_copy); + + /** + * Copy a text from an external buffer. + * @param language_code The language code of the text. + * @param text The text to copy. + */ + void set_text( + encoding_t text_encoding, + const Span &language_code, + const Span &text + ); + + /** + * Get the encoding of the text. + * @return The encoding of the text. + */ + encoding_t get_encoding() const; + + /** + * Return the language code. + * @return The language code. + */ + Span get_language_code() const; + + /** + * Return the text contained in this object. + * @return The text contained in this object. + */ + Span get_text() const; + + /** + * Append into a message builder + */ + bool append_as_record( + ndef::MessageBuilder &message_builder, + bool is_last_record = false + ); + +private: + friend class TextParser; + + void move_data(uint8_t *text, size_t size); + + uint8_t *_text_record; + size_t _text_record_size; +}; + +/** + * Parse a Text. + */ +class TextParser : public ndef::GenericRecordParser { +public: + virtual bool do_parse(const ndef::Record &record, Text &text); +}; + +/** + * @} + */ + +} // namespace common +} // namespace nfc +} // namespace mbed + + +#endif /* NFC_COMMON_TEXT_H_ */ diff --git a/features/nfc/nfc/common/URI.h b/features/nfc/nfc/common/URI.h new file mode 100644 index 0000000000..94867bed62 --- /dev/null +++ b/features/nfc/nfc/common/URI.h @@ -0,0 +1,189 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef NFC_COMMON_URI_H_ +#define NFC_COMMON_URI_H_ + +#include +#include +#include +#include + +#include + +namespace mbed { +namespace nfc { +namespace common { + +/** + * @addtogroup nfc + * @{ + */ + +/** + * Model the well known type URI. + */ +class URI { +public: + /** + * Identifier codes + */ + enum uri_identifier_code_t { + NA = 0x00, /// Not applicable + HTTP_WWW = 0x01, /// http://www. + HTTPS_WWW = 0x02, /// https://www. + HTTP = 0x03, /// http:// + HTTPS = 0x04, /// https:// + TEL = 0x05, /// tel: + MAILTO = 0x06, /// mailto: + FTP_ANONYMOUS = 0x07, /// ftp://anonymous:anonymous@ + FTP_FTP = 0x08, /// ftp://ftp. + FTPS = 0x09, /// ftps:// + SFTP = 0x0A, /// sftp:// + SMB = 0x0B, /// smb:// + NFS = 0x0C, /// nfs:// + FTP = 0x0D, /// ftp:// + DAV = 0x0E, /// dav:// + NEWS = 0x0F, /// news: + TELNET = 0x10, /// telnet:// + IMAP = 0x11, /// imap: + RSTP = 0x12, /// rstp:// + URN = 0x13, /// urn: + POP = 0x14, /// pop: + SIP = 0x15, /// sip: + SIPS = 0x16, /// sips: + TFTP = 0x17, /// tftp: + BTSPP = 0x18, /// btspp:// + BTL2CAP = 0x19, /// btl2cap:// + BTGOEP = 0x1A, /// btgoep:// + TCPOBEX = 0x1B, /// tcpobex:// + IRDAOBEX = 0x1C, /// irdaobex:// + FILE = 0x1D, /// file:// + URN_EPC_ID = 0x1E, /// urn:epc:id: + URN_EPC_TAG = 0x1F, /// urn:epc:tag: + URN_EPC_PAT = 0x20, /// urn:epc:pat: + URN_EPC_RAW = 0x21, /// urn:epc:raw: + URN_EPC = 0x22, /// urn:epc: + URN_NFC = 0x23, /// urn:nfc: + }; + + /** + * Construct an empty URI object. + */ + URI(); + + /** + * Construct a URI from an id and a uri field. + * @param id The code of the URI prefix. + * @param uri_field The URI itself. + * @param uri_field_size The size of the URI. + */ + URI(uri_identifier_code_t id, const Span &uri_field); + + /** + * Construct a URI from another URI. + * @param to_copy The uri copied. + */ + URI(const URI &to_copy); + + /** + * Destroy a URI object. + */ + ~URI(); + + /** + * Replace the content by the one of an existing URI. + * @param to_copy The URI to copy. + * @return a reference to this object + */ + URI& operator=(const URI &to_copy); + + /** + * Replace the value of the URI. + * @param id The ID of the URI + * @param uri_field A buffer containing the value of the URI field. + */ + void set_uri( + uri_identifier_code_t id, + const Span &uri_field + ); + + /** + * Return the id of the uri. + * @return The id of the uri. + */ + uri_identifier_code_t get_id() const; + + /** + * Return the current value of the uri field. + * @return The value of the uri field. + */ + Span get_uri_field() const; + + /** + * Append into a message builder + */ + bool append_as_record( + ndef::MessageBuilder &message_builder, + bool is_last_record = false + ); + + /** + * Equal operator between two URIs + * @param lhs The URI on the left hand side + * @param rhs The URI on the right hand side + * @return true if lhs equals rhs or false. + */ + friend bool operator==(const URI &lhs, const URI &rhs) + { + if (lhs._uri_size != rhs._uri_size) { + return false; + } + + return memcmp(lhs._uri, rhs._uri, lhs._uri_size) == 0; + } + + friend bool operator!=(const URI &lhs, const URI &rhs) + { + return !(lhs == rhs); + } + +private: + friend class URIParser; + + void move_data(uint8_t *text, size_t size); + + uint8_t *_uri; + size_t _uri_size; +}; + +/** + * Parser of a URI. + */ +class URIParser : public ndef::GenericRecordParser { +public: + bool do_parse(const ndef::Record &record, URI &uri); +}; + +/** + * @} + */ + +} // namespace common +} // namespace nfc +} // namespace mbed + +#endif /* NFC_COMMON_URI_H_ */ diff --git a/features/nfc/nfc/ndef/MessageBuilder.h b/features/nfc/nfc/ndef/MessageBuilder.h new file mode 100644 index 0000000000..3ddb4a5723 --- /dev/null +++ b/features/nfc/nfc/ndef/MessageBuilder.h @@ -0,0 +1,138 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef NFC_NDEF_MESSAGEBUILDER_H_ +#define NFC_NDEF_MESSAGEBUILDER_H_ + +#include + +#include "Record.h" + +#include "platform/Span.h" + +namespace mbed { +namespace nfc { +namespace ndef { + +/** + * @addtogroup nfc + * @{ + */ + +/** + * Construct a NDEF Message. + */ +class MessageBuilder { + +public: + /** + * Create a new MessageBuilder that can be used to construct valid NDEF + * messages. + * + * @param buffer The data buffer that will contain the NDEF message. + */ + MessageBuilder(const Span &buffer); + + /** + * Append a new record to the message being built. + * + * @param type The type of the record to insert. + * @param payload The payload of the record (optional). + * @param is_last_record true if the record to insert is the last record of + * the payload or false otherwise. + * + * @return true if the record has been successfully inserted or false + * otherwise. + * + * @note insertion can fail if the message is already complete or if the + * size remaining in the message buffer is not large enough to makes the + * record inserted fit. + */ + bool append_record( + const RecordType &type, + const RecordPayload &payload = RecordPayload(), + bool is_last_record = false + ); + + /** + * Append a new record to the message being built. + * + * @param record The record to insert. + * + * @return true if the record has been successfully inserted or false otherwise. + * + * @note insertion can fail if the message is already complete or if the + * size remaining in the message buffer is not large enough to makes the + * record inserted fit. + */ + bool append_record(const Record &record); + + /** + * Reset the builder state. + */ + void reset(); + + /** + * Reset the builder state and assign a new buffer to it. + */ + void reset(const Span &buffer); + + /** + * Return true if the message stored is complete and false otherwise. + * + * @return true if the message is complete or false. + */ + bool is_message_complete() const; + + /** + * Return the buffer storing the data if the message is complete or an empty + * buffer if the message is not complete. + * + * @return The message built. + */ + Span get_message() const; + +private: + // append fields + void append_header(const Record &record); + void append_type_length(const Record &record); + void append_payload_length(const Record&); + void append_id_length(const Record&); + void append_type(const Record&); + void append_id(const Record&); + void append_payload(const Record&); + + // helpers + static size_t compute_record_size(const Record &record); + static bool is_short_payload(const Record &record); + + // builder state. + Span _message_buffer; + size_t _position; + bool _message_started; + bool _message_ended; + bool _in_chunk; +}; + +/** + * @} + */ + +} // namespace ndef +} // namespace nfc +} // namespace mbed + +#endif /* NFC_NDEF_MESSAGEBUILDER_H_ */ diff --git a/features/nfc/nfc/ndef/MessageParser.h b/features/nfc/nfc/ndef/MessageParser.h new file mode 100644 index 0000000000..f08aa77913 --- /dev/null +++ b/features/nfc/nfc/ndef/MessageParser.h @@ -0,0 +1,178 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef NFC_NDEF_MESSAGEPARSER_H_ +#define NFC_NDEF_MESSAGEPARSER_H_ + +#include + +#include "platform/Span.h" + +namespace mbed { +namespace nfc { +namespace ndef { + +/** + * @addtogroup nfc + * @{ + */ + + +// Forward declaration +class Record; + +/** + * Event driven NDEF Message parser + */ +class MessageParser { +public: + /** + * Error that can be reported by the parsing operation. + */ + enum error_t { + /** + * The message doesn't start with a message start tag. + */ + INVALID_MESSAGE_START, + + /** + * There is not enough data left to pursue parsing of the message. + */ + INSUFICIENT_DATA, + + /** + * The type name of a record is invalid. + */ + INVALID_TYPE_NAME_FORMAT, + + /** + * An empty record is malformed. + */ + INVALID_EMPTY_RECORD, + + /** + * Record of unknown type embed a type length different than 0. + */ + INVALID_UNKNOWN_TYPE_LENGTH, + + /** + * Record of unchanged type contains a type. + */ + INVALID_UNCHANGED_TYPE, + + /** + * Chunk record encountered. + */ + CHUNK_RECORD_NOT_SUPPORTED, + + /** + * Message is not properly closed. + */ + MISSING_MESSAGE_END, + + /** + * Type is missing in a record expecting a type (well known type, media + * type, absolute uri or external type). + */ + MISSING_TYPE_VALUE + }; + + /** + * Report parsing event to the application. + */ + struct Delegate { + /** + * Invoked when parsing as started. + */ + virtual void on_parsing_started() { } + + /** + * Invoked when a record has been parsed. + * + * @param The record obtained from parsing. + */ + virtual void on_record_parsed(const Record &record) { } + + /** + * Invoked when parsing is over. + */ + virtual void on_parsing_terminated() { } + + /** + * Invoked when an error is present in the message. + * @param error The error present in the message. + */ + virtual void on_parsing_error(error_t error) { } + + protected: + /** + * Protected non virtual destructor. + * Delegate is not meant to be destroyed in a polymorphic manner. + */ + ~Delegate() { } + }; + + /** + * Construct a message parser. + */ + MessageParser(); + + /** + * Set the handler that processes parsing events. + * @param delegate The parsing event handler. + */ + void set_delegate(Delegate *delegate); + + /** + * Parse an NDEF Message. + * + * Records and errors are reported to the handler registered with + * set_event_handler. + * + * @param data_buffer The data buffer that contains the NDEF message. + */ + void parse(const Span &data_buffer); + +private: + struct parsing_state_t; + + // parser + bool parse_record(parsing_state_t &it); + + static uint8_t compute_lengths_size(uint8_t header); + static uint8_t extract_type_length(parsing_state_t &s); + static uint32_t extract_payload_length(parsing_state_t &s, uint8_t header); + static uint8_t extract_id_length(parsing_state_t &s, uint8_t header); + + // reporting + void report_parsing_started(); + void report_record_parsed(const Record &record); + void report_parsing_terminated(); + void report_parsing_error(error_t error, parsing_state_t& parsing_state); + + Delegate *_delegate; +}; + +/** + * @} + */ + +} // namespace ndef +} // namespace nfc +} // namespace mbed + + +#endif /* NFC_NDEF_MESSAGEPARSER_H_ */ diff --git a/features/nfc/nfc/ndef/Record.h b/features/nfc/nfc/ndef/Record.h new file mode 100644 index 0000000000..7c5d2d690f --- /dev/null +++ b/features/nfc/nfc/ndef/Record.h @@ -0,0 +1,213 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef NFC_NDEF_RECORD_H_ +#define NFC_NDEF_RECORD_H_ + +#include + +#include "platform/Span.h" + +namespace mbed { +namespace nfc { +namespace ndef { + +/** + * @addtogroup nfc + * @{ + */ + + +/** + * Set of constants of a record header + */ +struct Header { + static const uint8_t message_begin_bit = (1 << 7); + static const uint8_t message_end_bit = (1 << 6); + static const uint8_t chunk_flag_bit = (1 << 5); + static const uint8_t short_record_bit = (1 << 4); + static const uint8_t id_length_bit = (1 << 3); + static const uint8_t tnf_bits = (1 << 0) | (1 << 1) | (1 << 2); +}; + +/** + * Encode a record type. + * + * A RecordType is composed of a type name format flag and an optional type + * value. + */ +struct RecordType { + enum tnf_t { + /** + * empty type; value must be empty. + */ + empty = 0x00, + + /** + * Type defined by the NFC forum; value must be defined. + */ + well_known_type = 0x01, + + /** + * Mime type; value must be defined. + */ + media_type = 0x02, + + /** + * Absolute URI; value must be defined. + */ + absolute_uri = 0x03, + + /** + * Type defined by vendors; value must be defined. + */ + external_type = 0x04, + + /** + * Unknown type; value must be empty. + */ + unknown = 0x05, + + /** + * Use for middle and terminating chunk record. + * value must be empty. + */ + unchanged = 0x06 + }; + + /** + * Construct an unknown type. + */ + RecordType() : tnf(unknown), value() { } + + /** + * Construct a type with no value. + * + * @note Valid tnf are: empty, unknown and unchanged. + * + * @param tnf The type name format of the type. + */ + RecordType(tnf_t tnf) : + tnf(tnf), value() + { } + + /** + * Construct a RecordType from a type name format and its associated value. + * + * @param tnf The type name format of the record type. + * @param value The value associated with the tnf. + */ + RecordType(tnf_t tnf, const Span &value) : + tnf(tnf), value(value) + { } + + /** + * Type name format of the record type. + */ + tnf_t tnf; + + /** + * Value associated with the record type. It can be empty. + */ + Span value; +}; + +/** + * Definition of a Record payload. + * + * @note A payload can be empty. + */ +typedef Span RecordPayload; + +/** + * Definition of a Record IR. + * + * @note ID's are optional and therefore it can be empty. + */ +typedef Span RecordID; + +/** + * Represent a record. + */ +struct Record { + /** + * Construct an empty record. + */ + Record() : type(), payload(), id(), chunk(false), last_record(false) { } + + /** + * Construct a record from its type, payload and id. + * + * The flags chunk and last_record can be added to indicate if the record + * is aprt of a chunk or the last one in a message. + * + * @param type The type of the record. + * @param payload The payload of the record. + * @param id The id associated with the record. + * @param chunk If true then this record is a chunk of a bigger record. + * @param last_record If true then this record is the last of the message + * containing it. + */ + Record( + RecordType type, + const RecordPayload &payload, + const RecordID &id, + bool chunk, + bool last_record + ) : + type(type), + payload(payload), + id(id), + chunk(chunk), + last_record(last_record) + { } + + /** + * Type of the record. + */ + RecordType type; + + /** + * Value of the payload. + */ + RecordPayload payload; + + /** + * ID of the record. + */ + RecordID id; + + /** + * If true, this record is a chunked record. + */ + bool chunk:1; + + /** + * If true, this record is the last one of the payload containing it. + */ + bool last_record:1; +}; + + +/** + * @} + */ + +} // namespace ndef +} // namespace nfc +} // namespace mbed + +#endif /* NFC_NDEF_RECORD_H_ */ diff --git a/features/nfc/nfc/ndef/RecordParser.h b/features/nfc/nfc/ndef/RecordParser.h new file mode 100644 index 0000000000..3f64cc72aa --- /dev/null +++ b/features/nfc/nfc/ndef/RecordParser.h @@ -0,0 +1,168 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef NFC_NDEF_RECORDPARSER_H_ +#define NFC_NDEF_RECORDPARSER_H_ + +#include +#include "Record.h" + +namespace mbed { +namespace nfc { +namespace ndef { + +/** + * @addtogroup nfc + * @{ + */ + +/** + * Parse a record. + */ +struct RecordParser { + /** + * Construct a record parser. + */ + RecordParser() : _next_parser(NULL) { } + + /** + * Parse the record in input. + * @param record The NDEF record to parse. + * @return true if decoding has succeeded and false otherwise. + */ + virtual bool parse(const Record &record) = 0; + +protected: + /** + * Protected non virtual destructor. + * RecordParser subclasses are not meant to be destroyed as RecordParser's. + */ + ~RecordParser() { } + +private: + friend class RecordParserChain; + RecordParser *_next_parser; +}; + + +/** + * GenericRecordParser. + * + * @tparam ParserImplementation the implementation type of the parser. + * It must provides A decoding function named do_parse that accept a const + * reference to a record and a reference to the type parsed and return a boolean + * status that indicates the result of the parsing operation. + * + * @tparam ParsingResult Type produced by the parsing operation when successful. + */ +template +struct GenericRecordParser : public RecordParser { + + /** + * Handle that receives parsed values. + */ + struct Delegate { + /** + * Called when a record has been parsed and converted into a value_type. + * @param record The record in its parsed form. + */ + virtual void on_record_parsed(const ParsingResult &record, const RecordID &id) = 0; + + protected: + ~Delegate() { } + }; + + /** + * Construct a record parser. + */ + GenericRecordParser() : _delegate(NULL) { } + + /** + * @see RecordParser::parse + */ + virtual bool parse(const Record &record) + { + ParsingResult parsed_value; + if (static_cast(this)->do_parse(record, parsed_value)) { + if (_delegate) { + _delegate->on_record_parsed(parsed_value, record.id); + } + return true; + } + return false; + } + + /** + * Set the handler that processes record parser. + * @param handler + */ + void set_delegate(Delegate *delegate) + { + _delegate = delegate; + } + +protected: + /** + * Protected non virtual destructor. + */ + ~GenericRecordParser() { } + +private: + Delegate *_delegate; +}; + + +/** + * Record parser chain. + */ +struct RecordParserChain { + /** + * Construct a parser chain. + */ + RecordParserChain() : _parsers(NULL) { } + + /** + * Destroy a parser chain. + */ + ~RecordParserChain() { } + + /** + * Parse a record. + * @param record The record to parse. + * @return true if the record has been parsed and false otherwise. + */ + bool parse(const Record &record); + + /** + * Add a parser at the end of the parser list. + * @param parser The parser to add. + */ + void set_next_parser(RecordParser *parser); + +private: + RecordParser *_parsers; +}; + +/** + * @} + */ + +} // namespace ndef +} // namespace nfc +} // namespace mbed + + +#endif /* NFC_NDEF_RECORDPARSER_H_ */ diff --git a/features/nfc/source/nfc/common/Mime.cpp b/features/nfc/source/nfc/common/Mime.cpp new file mode 100644 index 0000000000..712e881956 --- /dev/null +++ b/features/nfc/source/nfc/common/Mime.cpp @@ -0,0 +1,153 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "Mime.h" + +namespace mbed { +namespace nfc { +namespace common { + +Mime::Mime() : + _mime(NULL), + _type_size(0), + _content_size(0) +{ } + +Mime::Mime( + const Span &mime_type, + const Span &content +) : _mime(new uint8_t[mime_type.size() + content.size()]), + _type_size(mime_type.size()), + _content_size(content.size()) +{ + memcpy(_mime, mime_type.data(), mime_type.size()); + memcpy(_mime + mime_type.size(), content.data(), content.size()); +} + +Mime::Mime(const Mime &to_copy) : + _mime(new uint8_t[to_copy.mime_size()]), + _type_size(to_copy._type_size), + _content_size(to_copy._content_size) +{ + memcpy(_mime, to_copy._mime, to_copy.mime_size()); +} + +Mime::~Mime() +{ + delete[] _mime; +} + +Mime& Mime::operator=(const Mime &to_copy) +{ + if (this == &to_copy) { + return * this; + } + + delete[] _mime; + + _mime = new uint8_t[to_copy.mime_size()]; + memcpy(_mime, to_copy._mime, to_copy.mime_size()); + _type_size = to_copy._type_size; + _content_size = to_copy._content_size; + + return *this; +} + +void Mime::set_mime( + const Span &mime_type, + const Span &content +) { + delete[] _mime; + + _mime = new uint8_t[mime_type.size() + content.size()]; + memcpy(_mime, mime_type.data(), mime_type.size()); + memcpy(_mime + mime_type.size(), content.data(), content.size()); + _type_size = mime_type.size(); + _content_size = content.size(); +} + +Span Mime::get_mime_type() const +{ + return make_const_Span(_mime, _type_size); +} + +Span Mime::get_mime_content() const +{ + return make_const_Span(_mime + _type_size, _content_size); +} + +bool Mime::append_as_record( + ndef::MessageBuilder &message_builder, + bool is_last_record +) { + return message_builder.append_record( + ndef::RecordType( + ndef::RecordType::media_type, + get_mime_type() + ), + get_mime_content(), + is_last_record + ); +} + +void Mime::move_data( + uint8_t *mime_record, + size_t mime_type_size, + size_t mime_content_size +) { + delete[] _mime; + _mime = mime_record; + _type_size = mime_type_size; + _content_size = mime_content_size; +} + +size_t Mime::mime_size() const +{ + return _type_size + _content_size; +} + +bool MimeParser::do_parse(const ndef::Record &record, Mime &mime) +{ + if (record.type.tnf != ndef::RecordType::media_type) { + return false; + } + + // A type and a payload should be present + if (record.type.value.empty() || record.payload.empty()) { + return false; + } + + // create the buffer + size_t type_size = record.type.value.size(); + size_t content_size = record.payload.size(); + uint8_t *mime_buffer = new uint8_t[type_size + content_size]; + + // copy type + memcpy(mime_buffer, record.type.value.data(), type_size); + + // copy content + memcpy(mime_buffer + type_size, record.payload.data(), content_size); + + mime.move_data(mime_buffer, type_size, content_size); + + return true; +} + +} // namespace common +} // namespace nfc +} // namespace mbed diff --git a/features/nfc/source/nfc/common/SimpleMessageParser.cpp b/features/nfc/source/nfc/common/SimpleMessageParser.cpp new file mode 100644 index 0000000000..62b6b4a77c --- /dev/null +++ b/features/nfc/source/nfc/common/SimpleMessageParser.cpp @@ -0,0 +1,119 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "SimpleMessageParser.h" + +namespace mbed { +namespace nfc { +namespace common { + +SimpleMessageParser::SimpleMessageParser() : + _message_parser(), + _record_parser_chain(), + _uri_parser(), + _text_parser(), + _mime_parser(), + _delegate(NULL) +{ + // setup the parser chain + _record_parser_chain.set_next_parser(&_uri_parser); + _record_parser_chain.set_next_parser(&_text_parser); + _record_parser_chain.set_next_parser(&_mime_parser); + + // wire event handling + _message_parser.set_delegate(this); + _uri_parser.set_delegate(this); + _text_parser.set_delegate(this); + _mime_parser.set_delegate(this); +} + +void SimpleMessageParser::set_delegate(Delegate *delegate) +{ + _delegate = delegate; +} + +void SimpleMessageParser::parse(const Span &data_buffer) +{ + _message_parser.parse(data_buffer); +} + +void SimpleMessageParser::add_record_parser(ndef::RecordParser *parser) +{ + _record_parser_chain.set_next_parser(parser); +} + +void SimpleMessageParser::on_parsing_error(ndef::MessageParser::error_t error) +{ + if (_delegate) { + _delegate->on_parsing_error(error); + } +} + +void SimpleMessageParser::on_parsing_started() +{ + if (_delegate) { + _delegate->on_parsing_started(); + } +} + +void SimpleMessageParser::on_record_parsed(const ndef::Record &record) +{ + bool parsed = _record_parser_chain.parse(record); + + if (!parsed && _delegate) { + _delegate->on_unknown_record_parsed(record); + } +} + +void SimpleMessageParser::on_parsing_terminated() +{ + if (_delegate) { + _delegate->on_parsing_terminated(); + } +} + +void SimpleMessageParser::on_record_parsed( + const URI &uri, + const ndef::RecordID &id +) { + if (_delegate) { + _delegate->on_uri_parsed(uri, id); + } +} + +void SimpleMessageParser::on_record_parsed( + const Text &text, + const ndef::RecordID &id +) { + if (_delegate) { + _delegate->on_text_parsed(text, id); + } +} + +void SimpleMessageParser::on_record_parsed( + const Mime &mime, + const ndef::RecordID &id +) { + if (_delegate) { + _delegate->on_mime_parsed(mime, id); + } +} + +} // namespace common +} // namespace nfc +} // namespace mbed + + diff --git a/features/nfc/source/nfc/common/Text.cpp b/features/nfc/source/nfc/common/Text.cpp new file mode 100644 index 0000000000..384b0273d6 --- /dev/null +++ b/features/nfc/source/nfc/common/Text.cpp @@ -0,0 +1,171 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "Text.h" + +namespace { +static const uint8_t utf16_encoding_bit = (1 << 7); +static const uint8_t language_code_size_mask = 0x3F; +static const uint8_t header_index = 0; +static const uint8_t language_code_index = 1; +static const uint8_t header_size = 1; +static const uint8_t text_record_type_value[] = { 'T' }; +} + +namespace mbed { +namespace nfc { +namespace common { + +Text::Text() : + _text_record(NULL), + _text_record_size(0) +{ } + +Text::Text(const Text &other) : + _text_record(other._text_record ? new uint8_t[other._text_record_size] : NULL), + _text_record_size(other._text_record_size) +{ + memcpy(_text_record, other._text_record, _text_record_size); +} + +Text::Text( + encoding_t text_encoding, + const Span &language_code, + const Span &text +) : _text_record(NULL), + _text_record_size(0) +{ + set_text(text_encoding, language_code, text); +} + +Text::~Text() +{ + delete[] _text_record; +} + +Text& Text::operator=(const Text &other) +{ + if (this == &other) { + return *this; + } + + _text_record_size = other._text_record_size; + + delete[] _text_record; + if (!other._text_record) { + _text_record = NULL; + } else { + _text_record = new uint8_t[_text_record_size]; + memcpy(_text_record, other._text_record, _text_record_size); + } + + return *this; +} + +void Text::set_text( + encoding_t text_encoding, + const Span &language_code, + const Span &text +) { + delete[] _text_record; + + _text_record_size = header_size + language_code.size() + text.size(); + _text_record = new uint8_t[_text_record_size]; + + // build the header + _text_record[header_index] = 0; + if (text_encoding == UTF16) { + _text_record[header_index] |= utf16_encoding_bit; + } + _text_record[header_index] |= language_code.size(); + + // language code + memcpy(_text_record + language_code_index, language_code.data(), language_code.size()); + + // actual text + memcpy(_text_record + language_code_index + language_code.size(), text.data(), text.size()); +} + +Text::encoding_t Text::get_encoding() const +{ + return (_text_record[header_index] & utf16_encoding_bit) ? UTF16 : UTF8; +} + +Span Text::get_language_code() const { + return make_const_Span( + _text_record + language_code_index, + _text_record[header_index] & language_code_size_mask + ); +} + +Span Text::get_text() const { + size_t language_code_size = get_language_code().size(); + + return make_const_Span( + _text_record + header_size + language_code_size, + _text_record_size - header_size - language_code_size + ); +} + +void Text::move_data(uint8_t *text, size_t size) { + delete[] _text_record; + _text_record = text; + _text_record_size = size; +} + +bool Text::append_as_record( + ndef::MessageBuilder &message_builder, + bool is_last_record +) { + // Build the record type + ndef::RecordType type( + ndef::RecordType::well_known_type, + text_record_type_value + ); + + // build the record payload + ndef::RecordPayload payload(_text_record, _text_record_size); + return message_builder.append_record(type, payload, is_last_record); +} + +bool TextParser::do_parse(const ndef::Record &record, Text &text) +{ + if (record.type.tnf != ndef::RecordType::well_known_type) { + return false; + } + + // the record type value should be equal to `T` + if (record.type.value != make_const_Span(text_record_type_value) || + record.payload.empty() + ) { + return false; + } + + // create the buffer + size_t text_record_size = record.payload.size(); + uint8_t *text_record = new uint8_t[text_record_size]; + memcpy(text_record, record.payload.data(), text_record_size); + + text.move_data(text_record, text_record_size); + + return true; +} + +} // namespace common +} // namespace nfc +} // namespace mbed diff --git a/features/nfc/source/nfc/common/URI.cpp b/features/nfc/source/nfc/common/URI.cpp new file mode 100644 index 0000000000..b13e03f0ef --- /dev/null +++ b/features/nfc/source/nfc/common/URI.cpp @@ -0,0 +1,162 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "URI.h" + +namespace { +static const uint8_t uri_id_code_size = 1; +static const uint8_t uri_id_index = 0; +static const uint8_t uri_field_index = 1; +static const uint8_t uri_record_type_value[] = { 'U' } ; +} + +namespace mbed { +namespace nfc { +namespace common { + +URI::URI() : + _uri(NULL), + _uri_size(0) { +} + +URI::URI(uri_identifier_code_t id, const Span &uri_field) : + _uri(uri_field.size() ? new uint8_t[uri_id_code_size + uri_field.size()] : NULL), + _uri_size(uri_id_code_size + uri_field.size()) +{ + _uri[uri_id_index] = id; + memcpy(_uri + uri_field_index, uri_field.data(), uri_field.size()); +} + +URI::URI(const URI &other) : + _uri(other._uri ? new uint8_t[other._uri_size] : NULL), + _uri_size(other._uri_size) +{ + memcpy(_uri, other._uri, other._uri_size); +} + +URI::~URI() +{ + delete[] _uri; +} + +URI& URI::operator=(const URI &other) +{ + delete[] _uri; + + if (!other._uri) { + _uri = NULL; + _uri_size = 0; + } else { + _uri = new uint8_t[other._uri_size]; + _uri_size = other._uri_size; + memcpy(_uri, other._uri, other._uri_size); + } + + return *this; +} + +void URI::set_uri( + uri_identifier_code_t id, + const Span &uri_field +) { + delete[] _uri; + + if (uri_field.empty()) { + _uri = NULL; + _uri_size = 0; + return; + } + + _uri = new uint8_t[uri_id_code_size + uri_field.size()]; + _uri_size = uri_id_code_size + uri_field.size(); + _uri[uri_id_index] = id; + memcpy(_uri + uri_field_index, uri_field.data(), uri_field.size()); +} + +URI::uri_identifier_code_t URI::get_id() const +{ + if (!_uri) { + return NA; + } + + return static_cast(_uri[uri_id_index]); +} + +Span URI::get_uri_field() const +{ + if (!_uri) { + return Span(); + } + return make_const_Span( + _uri + uri_field_index, + _uri_size - uri_id_code_size + ); +} + +bool URI::append_as_record(ndef::MessageBuilder &message_builder, bool is_last_record) +{ + if (!_uri) { + return false; + } + + // Build the record type + ndef::RecordType type( + ndef::RecordType::well_known_type, + uri_record_type_value + ); + + // build the record payload + ndef::RecordPayload payload(_uri, _uri_size); + + return message_builder.append_record(type, payload, is_last_record); +} + +void URI::move_data(uint8_t *new_uri, size_t new_uri_size) +{ + delete[] _uri; + _uri = new_uri; + _uri_size = new_uri_size; +} + +bool URIParser::do_parse(const ndef::Record &record, URI &uri) +{ + if (record.type.tnf != ndef::RecordType::well_known_type) { + return false; + } + + // the record type value should be equal to `U` + if (record.type.value != make_const_Span(uri_record_type_value) || + record.payload.empty() + ) { + return false; + } + + // create the buffer + size_t uri_record_size = record.payload.size(); + uint8_t *uri_record = new uint8_t[uri_record_size]; + memcpy(uri_record, record.payload.data(), uri_record_size); + + uri.move_data(uri_record, uri_record_size); + + return true; +} + +} // namespace common +} // namespace nfc +} // namespace mbed + + diff --git a/features/nfc/source/nfc/ndef/MessageBuilder.cpp b/features/nfc/source/nfc/ndef/MessageBuilder.cpp new file mode 100644 index 0000000000..46fa01025d --- /dev/null +++ b/features/nfc/source/nfc/ndef/MessageBuilder.cpp @@ -0,0 +1,296 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "MessageBuilder.h" +#include + +namespace mbed { +namespace nfc { +namespace ndef { + +MessageBuilder::MessageBuilder(const Span &buffer) : + _message_buffer(buffer), + _position(0), + _message_started(false), + _message_ended(false), + _in_chunk(false) +{ } + +bool MessageBuilder::append_record( + const RecordType &type, + const RecordPayload &payload, + bool is_last_record +) { + Record record( + type, + payload, + /* id */ RecordID(), + /* chunk */ false, + is_last_record + ); + + return append_record(record); +} + +bool MessageBuilder::append_record(const Record &record) +{ + if (_message_ended) { + return false; + } + + if (record.type.value.size() > 255) { + return false; + } + + if (record.id.size() > 255) { + return false; + } + + if (!record.id.empty() && _in_chunk) { + if (record.chunk) { + // middle chunk + return false; + } else if (record.type.tnf == RecordType::unchanged) { + // terminating chunk + return false; + } + } + + if (_in_chunk && record.type.tnf != RecordType::unchanged) { + return false; + } + + if (!_in_chunk && record.chunk && record.type.tnf == RecordType::unchanged) { + return false; + } + + if (record.type.tnf == RecordType::empty) { + if (!record.type.value.empty()) { + return false; + } + + if (!record.id.empty()) { + return false; + } + + if (!record.payload.empty()) { + return false; + } + } + + if (record.type.tnf == RecordType::well_known_type || + record.type.tnf == RecordType::media_type || + record.type.tnf == RecordType::absolute_uri || + record.type.tnf == RecordType::external_type + ) { + if (record.type.value.empty()) { + return false; + } + } + + if (record.type.tnf == RecordType::unknown && !record.type.value.empty()) { + return false; + } + + size_t record_size = compute_record_size(record); + if (record_size > (_message_buffer.size() - _position)) { + return false; + } + + append_header(record); + append_type_length(record); + append_payload_length(record); + append_id_length(record); + append_type(record); + append_id(record); + append_payload(record); + + if (record.chunk) { + _in_chunk = true; + } else if (record.type.tnf == RecordType::unchanged) { + // last chunk reached + _in_chunk = false; + } + + return true; +} + +void MessageBuilder::reset() +{ + _position = 0; + _message_started = false; + _message_ended = false; + _in_chunk = false; +} + +void MessageBuilder::reset(const Span &buffer) +{ + _message_buffer = buffer; + _position = 0; + _message_started = false; + _message_ended = false; + _in_chunk = false; +} + +bool MessageBuilder::is_message_complete() const +{ + return _message_ended; +} + +Span MessageBuilder::get_message() const +{ + if (is_message_complete()) { + return _message_buffer.first(_position); + } else { + return Span(); + } +} + +size_t MessageBuilder::compute_record_size(const Record &record) +{ + size_t record_size = 0; + record_size = 1; /* header */ + record_size += 1; /* type length */ + record_size += is_short_payload(record) ? 1 : 4; + + if (!record.id.empty()) { + record_size += 1; + } + + record_size += record.type.value.size(); + record_size += record.id.size(); + record_size += record.payload.size(); + + return record_size; +} + +void MessageBuilder::append_header(const Record &record) +{ + uint8_t header = 0; + if (!_message_started) { + header |= Header::message_begin_bit; + _message_started = true; + } + + if (record.last_record) { + header |= Header::message_end_bit; + _message_ended = true; + } + + if (record.chunk) { + header |= Header::chunk_flag_bit; + } + + if (is_short_payload(record)) { + header |= Header::short_record_bit; + } + + if (record.id.size()) { + header |= Header::id_length_bit; + } + + header |= record.type.tnf; + _message_buffer[_position++] = header; +} + +void MessageBuilder::append_type_length(const Record &record) +{ + _message_buffer[_position++] = record.type.value.size(); +} + +void MessageBuilder::append_payload_length(const Record &record) +{ + if (record.payload.empty()) { + _message_buffer[_position++] = 0; + return; + } + + if (is_short_payload(record)) { + _message_buffer[_position++] = record.payload.size(); + } else { + // TODO: proper host to network + uint32_t size = record.payload.size(); + _message_buffer[_position++] = (size >> 24) & 0xFF; + _message_buffer[_position++] = (size >> 16) & 0xFF; + _message_buffer[_position++] = (size >> 8) & 0xFF; + _message_buffer[_position++] = size & 0xFF; + } +} + +void MessageBuilder::append_id_length(const Record &record) +{ + if (record.id.empty()) { + return; + } + + _message_buffer[_position++] = record.id.size(); +} + +void MessageBuilder::append_type(const Record &record) +{ + if (record.type.value.empty()) { + return; + } + + memcpy( + _message_buffer.data() + _position, + record.type.value.data(), + record.type.value.size() + ); + _position += record.type.value.size(); +} + +void MessageBuilder::append_id(const Record &record) +{ + if (record.id.empty()) { + return; + } + + memcpy( + _message_buffer.data() + _position, + record.id.data(), + record.id.size() + ); + _position += record.id.size(); +} + +void MessageBuilder::append_payload(const Record &record) +{ + if (record.payload.empty()) { + return; + } + + memcpy( + _message_buffer.data() + _position, + record.payload.data(), + record.payload.size() + ); + _position += record.payload.size(); +} + +bool MessageBuilder::is_short_payload(const Record &record) +{ + if (record.payload.size() <= 255) { + return true; + } else { + return false; + } +} + +} // namespace ndef +} // namespace nfc +} // namespace mbed + diff --git a/features/nfc/source/nfc/ndef/MessageParser.cpp b/features/nfc/source/nfc/ndef/MessageParser.cpp new file mode 100644 index 0000000000..ef264ef4bf --- /dev/null +++ b/features/nfc/source/nfc/ndef/MessageParser.cpp @@ -0,0 +1,313 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "MessageParser.h" +#include "Record.h" + +namespace { +struct buffer_iterator_t { + buffer_iterator_t(const mbed::Span &buffer) : + buffer(buffer), + position(0) + { } + + uint8_t operator*() + { + return buffer[position]; + } + + buffer_iterator_t &operator++() + { + ++position; + return *this; + } + + buffer_iterator_t operator++(int) + { + buffer_iterator_t previous = *this; + ++*this; + return previous; + } + + buffer_iterator_t &operator+=(size_t increment) + { + position += increment; + return *this; + } + + operator bool() const + { + return (position >= buffer.size()) ? false : true; + } + + size_t remaining_size() const + { + return buffer.size() - position; + } + + void read_le(uint8_t *dest, size_t size) + { + memcpy(dest, buffer.data() + position, size); + position += size; + } + + void read_be(uint8_t *dest, size_t size) + { + // TODO: Needs proper network to host function + std::reverse_copy( + buffer.data() + position, + buffer.data() + position + size, + dest + ); + position += size; + } + + mbed::Span get_underlying_buffer() const + { + return buffer.last(buffer.size() - position); + } + +private: + mbed::Span buffer; + mbed::Span::index_type position; +}; + +} // end of anonymous namespace + +namespace mbed { +namespace nfc { +namespace ndef { + +struct MessageParser::parsing_state_t { + parsing_state_t(const Span &data_buffer) : + it(data_buffer), + first_record_parsed(false), + last_record_parsed(false), + error(false) + { } + + buffer_iterator_t it; + bool first_record_parsed:1; + bool last_record_parsed:1; + bool error:1; +}; + +MessageParser::MessageParser() : + _delegate(NULL) +{ } + +void MessageParser::set_delegate(Delegate *delegate) +{ + _delegate = delegate; +} + +void MessageParser::parse(const Span &data_buffer) +{ + parsing_state_t parsing_state(data_buffer); + report_parsing_started(); + while(parsing_state.it && parse_record(parsing_state)); + if (!parsing_state.error && !parsing_state.last_record_parsed) { + report_parsing_error(MISSING_MESSAGE_END, parsing_state); + } + report_parsing_terminated(); +} + +bool MessageParser::parse_record(parsing_state_t &s) +{ + if (s.error || s.last_record_parsed) { + return false; + } + + // ensure that the header can be extracted + if (s.it.remaining_size() < 1) { + report_parsing_error(INSUFICIENT_DATA, s); + return false; + } + + uint8_t header = *s.it++; + + // NOTE: report an error until the chunk parsing design is sorted out + if (header & Header::chunk_flag_bit) { + report_parsing_error(CHUNK_RECORD_NOT_SUPPORTED, s); + return false; + } + + // handle first record cases + if (s.first_record_parsed == false) { + if (header & Header::message_begin_bit) { + s.first_record_parsed = true; + } else { + report_parsing_error(INVALID_MESSAGE_START, s); + return false; + } + } else if (header & Header::message_begin_bit) { + report_parsing_error(INVALID_MESSAGE_START, s); + return false; + } + + // handle last record + if (header & Header::message_end_bit) { + s.last_record_parsed = true; + } + + // ensure their is enough space to contain the type length, payload + // length and id length + uint8_t lengths_size = compute_lengths_size(header); + if (s.it.remaining_size() < lengths_size) { + report_parsing_error(INSUFICIENT_DATA, s); + return false; + } + + // extract the various length from the message + uint8_t type_length = extract_type_length(s); + uint32_t payload_length = extract_payload_length(s, header); + uint8_t id_length = extract_id_length(s, header); + + // there should be enough bytes left in the buffer + if (s.it.remaining_size() < (type_length + id_length + payload_length)) { + report_parsing_error(INSUFICIENT_DATA, s); + return false; + } + + // validate the Type Name Format of the header + switch (header & Header::tnf_bits) { + case RecordType::empty: + if (type_length || payload_length || id_length) { + report_parsing_error(INVALID_EMPTY_RECORD, s); + return false; + } + break; + case RecordType::well_known_type: + case RecordType::media_type: + case RecordType::absolute_uri: + case RecordType::external_type: + if (!type_length) { + report_parsing_error(MISSING_TYPE_VALUE, s); + return false; + } + break; + case RecordType::unknown: + if (type_length) { + report_parsing_error(INVALID_UNKNOWN_TYPE_LENGTH, s); + return false; + } + break; + case RecordType::unchanged: + // shouldn't be handled outside of chunk handling + report_parsing_error(INVALID_UNCHANGED_TYPE, s); + return false; + default: + report_parsing_error(INVALID_TYPE_NAME_FORMAT, s); + return false; + } + + // build the record + Record record; + + // flags + record.last_record = header & Header::message_end_bit; + + // type + record.type.tnf = static_cast(header & Header::tnf_bits); + if (type_length) { + record.type.value = s.it.get_underlying_buffer().first(type_length); + s.it += type_length; + } + + // id + if (id_length) { + record.id = s.it.get_underlying_buffer().first(id_length); + s.it += id_length; + } + + // payload + if (payload_length) { + record.payload = s.it.get_underlying_buffer().first(payload_length); + s.it += payload_length; + } + + s.it += payload_length; + + report_record_parsed(record); + + return true; +} + +uint8_t MessageParser::compute_lengths_size(uint8_t header) +{ + return 1 /* type_length size */ + + ((header & Header::short_record_bit) ? 1 : 4) /* payload length */ + + ((header & Header::id_length_bit) ? 1 : 0); +} + +uint8_t MessageParser::extract_type_length(parsing_state_t &s) +{ + return *s.it++; +} + +uint32_t MessageParser::extract_payload_length(parsing_state_t &s, uint8_t header) +{ + uint32_t payload_length = 0; + if (header & Header::short_record_bit) { + payload_length = *s.it++; + } else { + s.it.read_be( + reinterpret_cast(&payload_length), + sizeof(payload_length) + ); + } + return payload_length; +} + +uint8_t MessageParser::extract_id_length(parsing_state_t &s, uint8_t header) +{ + return (header & Header::id_length_bit) ? *s.it++ : 0; +} + +void MessageParser::report_parsing_started() { + if (_delegate) { + _delegate->on_parsing_started(); + } +} + +void MessageParser::report_record_parsed(const Record &record) +{ + if (_delegate) { + _delegate->on_record_parsed(record); + } +} + +void MessageParser::report_parsing_terminated() +{ + if (_delegate) { + _delegate->on_parsing_terminated(); + } +} + +void MessageParser::report_parsing_error(error_t error, parsing_state_t &parsing_state) +{ + parsing_state.error = true; + if (_delegate) { + _delegate->on_parsing_error(error); + } +} + +} // namespace ndef +} // namespace nfc +} // namespace mbed + diff --git a/features/nfc/source/nfc/ndef/RecordParser.cpp b/features/nfc/source/nfc/ndef/RecordParser.cpp new file mode 100644 index 0000000000..426ce9bb22 --- /dev/null +++ b/features/nfc/source/nfc/ndef/RecordParser.cpp @@ -0,0 +1,50 @@ +/* mbed Microcontroller Library + * Copyright (c) 2018 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "RecordParser.h" + +namespace mbed { +namespace nfc { +namespace ndef { + +bool RecordParserChain::parse(const Record &record) +{ + RecordParser *current_parser = _parsers; + while (current_parser) { + if (current_parser->parse(record)) { + return true; + } + current_parser = current_parser->_next_parser; + } + return false; +} + +void RecordParserChain::set_next_parser(RecordParser *parser) +{ + if (!_parsers) { + _parsers = parser; + } else { + RecordParser *current_parser = _parsers; + while (current_parser->_next_parser) { + current_parser = current_parser->_next_parser; + } + current_parser->_next_parser = parser; + } +} + +} // namespace ndef +} // namespace nfc +} // namespace mbed