From 6c4d9ed2dc8b1451d0439a2cb274a7c21ed92b01 Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Tue, 23 Jul 2024 18:22:26 +0200 Subject: [PATCH 01/45] Allow setting a content type for event data --- communication/inc/messages.h | 4 -- communication/inc/protocol.h | 6 +- communication/inc/spark_protocol_functions.h | 2 + communication/src/coap_defs.h | 8 +-- communication/src/messages.cpp | 33 ---------- communication/src/publisher.cpp | 66 +++++++++++++------ communication/src/publisher.h | 2 +- .../src/spark_protocol_functions.cpp | 35 +++++++--- system/inc/system_cloud.h | 2 + system/src/system_cloud.cpp | 12 ++++ wiring/inc/spark_wiring_cloud.h | 21 +++++- wiring/src/spark_wiring_cloud.cpp | 7 +- 12 files changed, 119 insertions(+), 79 deletions(-) diff --git a/communication/inc/messages.h b/communication/inc/messages.h index ab35fbda5f..33144fd015 100644 --- a/communication/inc/messages.h +++ b/communication/inc/messages.h @@ -79,10 +79,6 @@ class Messages unsigned char token, unsigned char code, const unsigned char* payload, unsigned payload_len, bool confirmable); - static size_t event(uint8_t buf[], uint16_t message_id, const char *event_name, - const char *data, size_t data_size, int ttl, EventType::Enum event_type, bool confirmable); - - static inline size_t empty_ack(unsigned char *buf, unsigned char message_id_msb, unsigned char message_id_lsb) { diff --git a/communication/inc/protocol.h b/communication/inc/protocol.h index ced338aac3..c9258b1350 100644 --- a/communication/inc/protocol.h +++ b/communication/inc/protocol.h @@ -500,7 +500,7 @@ class Protocol ProtocolError post_description(int desc_flags, bool force); // Returns true on success, false on sending timeout or rate-limiting failure - bool send_event(const char *event_name, const char *data, int ttl, + bool send_event(const char *event_name, const char *data, size_t data_size, int content_type, int ttl, EventType::Enum event_type, int flags, CompletionHandler handler) { #if HAL_PLATFORM_OTA_PROTOCOL_V3 @@ -513,8 +513,8 @@ class Protocol handler.setError(SYSTEM_ERROR_BUSY); return false; } - const ProtocolError error = publisher.send_event(channel, event_name, data, ttl, event_type, flags, - callbacks.millis(), std::move(handler)); + const ProtocolError error = publisher.send_event(channel, event_name, data, data_size, content_type, ttl, + event_type, flags, callbacks.millis(), std::move(handler)); if (error != NO_ERROR) { handler.setError(toSystemError(error)); diff --git a/communication/inc/spark_protocol_functions.h b/communication/inc/spark_protocol_functions.h index 0078087067..d09bd954fb 100644 --- a/communication/inc/spark_protocol_functions.h +++ b/communication/inc/spark_protocol_functions.h @@ -191,6 +191,8 @@ typedef struct { size_t size; completion_callback handler_callback; void* handler_data; + size_t data_size; + int content_type; } completion_handler_data; typedef completion_handler_data spark_protocol_send_event_data; diff --git a/communication/src/coap_defs.h b/communication/src/coap_defs.h index f3a35005e0..a940470b2a 100644 --- a/communication/src/coap_defs.h +++ b/communication/src/coap_defs.h @@ -109,12 +109,8 @@ PARTICLE_DEFINE_ENUM_COMPARISON_OPERATORS(CoapOption) enum class CoapContentFormat { // RFC 7252 12.3. CoAP Content-Formats Registry - TEXT_PLAIN_CHARSET_UTF8 = 0, - APPLICATION_LINK_FORMAT = 40, - APPLICATION_XML = 41, - APPLICATION_OCTET_STREAM = 42, - APPLICATION_EXI = 47, - APPLICATION_JSON = 50 + TEXT_PLAIN = 0, + APPLICATION_OCTET_STREAM = 42 }; PARTICLE_DEFINE_ENUM_COMPARISON_OPERATORS(CoapContentFormat) diff --git a/communication/src/messages.cpp b/communication/src/messages.cpp index 9b37458f7c..df44ebe220 100644 --- a/communication/src/messages.cpp +++ b/communication/src/messages.cpp @@ -317,39 +317,6 @@ size_t Messages::separate_response_with_payload(unsigned char *buf, uint16_t mes return len; } -size_t Messages::event(uint8_t buf[], uint16_t message_id, const char *event_name, - const char *data, size_t data_size, int ttl, EventType::Enum event_type, bool confirmable) -{ - uint8_t *p = buf; - *p++ = confirmable ? 0x40 : 0x50; // non-confirmable /confirmable, no token - *p++ = 0x02; // code 0.02 POST request - *p++ = message_id >> 8; - *p++ = message_id & 0xff; - *p++ = 0xb1; // one-byte Uri-Path option - *p++ = event_type; - - size_t name_data_len = strnlen(event_name, MAX_EVENT_NAME_LENGTH); - p += event_name_uri_path(p, event_name, name_data_len); - - if (60 != ttl) - { - *p++ = 0x33; - *p++ = (ttl >> 16) & 0xff; - *p++ = (ttl >> 8) & 0xff; - *p++ = ttl & 0xff; - } - - if (NULL != data && data_size > 0) - { - *p++ = 0xff; - - memcpy(p, data, data_size); - p += data_size; - } - - return p - buf; -} - size_t Messages::coded_ack(uint8_t* buf, uint8_t token, uint8_t code, uint8_t message_id_msb, uint8_t message_id_lsb, uint8_t* data, size_t data_len) diff --git a/communication/src/publisher.cpp b/communication/src/publisher.cpp index f6deb9f035..8f109d97b9 100644 --- a/communication/src/publisher.cpp +++ b/communication/src/publisher.cpp @@ -15,9 +15,13 @@ * License along with this library; if not, see . */ +#include +#include + #include "publisher.h" #include "protocol.h" +#include "coap_message_encoder.h" namespace particle { @@ -28,7 +32,7 @@ void Publisher::add_ack_handler(message_id_t msg_id, CompletionHandler handler) } ProtocolError Publisher::send_event(MessageChannel& channel, const char* event_name, - const char* data, int ttl, EventType::Enum event_type, int flags, + const char* data, size_t data_size, int content_type, int ttl, EventType::Enum event_type, int flags, system_tick_t time, CompletionHandler handler) { bool is_system_event = is_system(event_name); bool rate_limited = is_rate_limited(is_system_event, time); @@ -37,8 +41,12 @@ ProtocolError Publisher::send_event(MessageChannel& channel, const char* event_n return BANDWIDTH_EXCEEDED; } - Message message; - channel.create(message); + Message msg; + auto err = channel.create(msg); + if (err != ProtocolError::NO_ERROR) { + return err; + } + bool confirmable = channel.is_unreliable(); if (flags & EventType::NO_ACK) { confirmable = false; @@ -46,24 +54,44 @@ ProtocolError Publisher::send_event(MessageChannel& channel, const char* event_n confirmable = true; } - size_t data_size = 0; - if (data) { - const auto max_data_size = protocol->get_max_event_data_size(); - data_size = strnlen(data, max_data_size); + CoapMessageEncoder e((char*)msg.buf(), msg.capacity()); + e.type(confirmable ? CoapType::CON : CoapType::NON); + e.code(CoapCode::POST); + e.id(0); // Will be assigned by the message channel + // Event messages have an empty token + e.option(CoapOption::URI_PATH, event_type); // "e" or "E" (option number: 11) + size_t name_len = strnlen(event_name, MAX_EVENT_NAME_LENGTH); + e.option(CoapOption::URI_PATH, event_name, name_len); // 11 + if (content_type != CoapContentFormat::TEXT_PLAIN) { + e.option(CoapOption::CONTENT_FORMAT, content_type); // 12 + } + if (ttl != 60) { + // XXX: Max-Age is not supposed to be used in a request message + e.option(CoapOption::MAX_AGE, ttl); // 14 + } + if (data_size > 0) { + auto max_data_size = std::min(e.maxPayloadSize(), protocol->get_max_event_data_size()); + data_size = std::min(data_size, max_data_size); + } + int r = e.encode(); + if (r < 0) { + return ProtocolError::INTERNAL; // Should not happen + } + msg.set_length(r); + + err = channel.send(msg); + if (err != ProtocolError::NO_ERROR) { + return err; } - size_t msglen = Messages::event(message.buf(), 0, event_name, data, data_size, ttl, - event_type, confirmable); - message.set_length(msglen); - const ProtocolError result = channel.send(message); - if (result == NO_ERROR) { - // Register completion handler only if acknowledgement was requested explicitly - if ((flags & EventType::WITH_ACK) && message.has_id()) { - add_ack_handler(message.get_id(), std::move(handler)); - } else { - handler.setResult(); - } + + // Register completion handler only if acknowledgement was requested explicitly + if ((flags & EventType::WITH_ACK) && msg.has_id()) { + add_ack_handler(msg.get_id(), std::move(handler)); + } else { + handler.setResult(); } - return result; + + return ProtocolError::NO_ERROR; } } // protocol diff --git a/communication/src/publisher.h b/communication/src/publisher.h index ffba1cf5e9..837f15ed4c 100644 --- a/communication/src/publisher.h +++ b/communication/src/publisher.h @@ -88,7 +88,7 @@ class Publisher } ProtocolError send_event(MessageChannel& channel, const char* event_name, - const char* data, int ttl, EventType::Enum event_type, int flags, + const char* data, size_t data_size, int content_type, int ttl, EventType::Enum event_type, int flags, system_tick_t time, CompletionHandler handler); private: diff --git a/communication/src/spark_protocol_functions.cpp b/communication/src/spark_protocol_functions.cpp index 7af6b360fc..a31b75e88a 100644 --- a/communication/src/spark_protocol_functions.cpp +++ b/communication/src/spark_protocol_functions.cpp @@ -17,12 +17,16 @@ ****************************************************************************** */ +#include +#include +#include + #include "protocol_defs.h" #include "protocol_selector.h" +#include "coap_defs.h" #include "spark_protocol_functions.h" #include "handshake.h" #include "debug.h" -#include using namespace particle; using namespace particle::protocol; @@ -111,15 +115,28 @@ int spark_protocol_post_description(ProtocolFacade* protocol, int desc_flags, vo } bool spark_protocol_send_event(ProtocolFacade* protocol, const char *event_name, const char *data, - int ttl, uint32_t flags, void* reserved) { + int ttl, uint32_t flags, void* reserved) { ASSERT_ON_SYSTEM_THREAD(); - CompletionHandler handler; - if (reserved) { - auto r = static_cast(reserved); - handler = CompletionHandler(r->handler_callback, r->handler_data); - } - EventType::Enum event_type = EventType::extract_event_type(flags); - return protocol->send_event(event_name, data, ttl, event_type, flags, std::move(handler)); + CompletionHandler handler; + std::optional data_size = 0; + int content_type = static_cast(CoapContentFormat::TEXT_PLAIN); + if (reserved) { + auto r = static_cast(reserved); + handler = CompletionHandler(r->handler_callback, r->handler_data); + // Even though this library is now in the same module with the rest of the system on all + // supported platforms, this function is still exposed via a dynalib so normal checks apply + // when accessing extra parameters + if (r->size >= offsetof(spark_protocol_send_event_data, data_size) + sizeof(spark_protocol_send_event_data::data_size) + + sizeof(spark_protocol_send_event_data::content_type)) { + data_size = r->data_size; + content_type = r->content_type; + } + } + if (!data_size.has_value()) { + data_size = data ? std::strlen(data) : 0; + } + EventType::Enum event_type = EventType::extract_event_type(flags); + return protocol->send_event(event_name, data, data_size.value(), content_type, ttl, event_type, flags, std::move(handler)); } bool spark_protocol_send_subscription_device(ProtocolFacade* protocol, const char *event_name, const char *device_id, void*) { diff --git a/system/inc/system_cloud.h b/system/inc/system_cloud.h index a4fcb0bb9f..102b5ad114 100644 --- a/system/inc/system_cloud.h +++ b/system/inc/system_cloud.h @@ -256,6 +256,8 @@ typedef struct { size_t size; completion_callback handler_callback; void* handler_data; + size_t data_size; + int content_type; } spark_send_event_data; /** diff --git a/system/src/system_cloud.cpp b/system/src/system_cloud.cpp index 21bb45ff40..9baefbcc4a 100644 --- a/system/src/system_cloud.cpp +++ b/system/src/system_cloud.cpp @@ -187,11 +187,23 @@ bool spark_send_event(const char* name, const char* data, int ttl, uint32_t flag spark_protocol_send_event_data d = {}; d.size = sizeof(d); + d.content_type = 0; // text/plain; charset=utf-8 + + bool hasDataSize = false; if (reserved) { // Forward completion callback to the protocol implementation auto r = static_cast(reserved); d.handler_callback = r->handler_callback; d.handler_data = r->handler_data; + if (r->size >= offsetof(spark_send_event_data, data_size) + sizeof(spark_send_event_data::data_size) + + sizeof(spark_send_event_data::content_type)) { + d.data_size = r->data_size; + d.content_type = r->content_type; + hasDataSize = true; + } + } + if (!hasDataSize) { + d.data_size = data ? std::strlen(data) : 0; } return spark_protocol_send_event(sp, name, data, ttl, convert(flags), &d); diff --git a/wiring/inc/spark_wiring_cloud.h b/wiring/inc/spark_wiring_cloud.h index 5e214ca220..c49075854e 100644 --- a/wiring/inc/spark_wiring_cloud.h +++ b/wiring/inc/spark_wiring_cloud.h @@ -23,6 +23,7 @@ #pragma once #include +#include #include "spark_wiring_string.h" #include "events.h" @@ -72,6 +73,18 @@ namespace particle { class Ledger; +/** + * Content type. + */ +enum class ContentType { + TEXT = 0, ///< text/plain; charset=utf-8 + JPEG = 22, ///< image/jpeg + PNG = 23, ///< image/png + BINARY = 42, ///< application/octet-stream + JSON = 50, ///< application/json + CBOR = 60, ///< application/cbor +}; + } // namespace particle class CloudDisconnectOptions { @@ -265,13 +278,16 @@ class CloudClass { inline particle::Future publish(const char *eventName, const char *eventData, int ttl, PublishFlags flags1, PublishFlags flags2 = PublishFlags()) { - return publish_event(eventName, eventData, ttl, flags1 | flags2); + return publish_event(eventName, eventData, eventData ? std::strlen(eventData) : 0, particle::ContentType::TEXT, ttl, flags1 | flags2); } particle::Future publish(const char* name); particle::Future publish(const char* name, const char* data); particle::Future publish(const char* name, const char* data, int ttl); + particle::Future publish(const char* name, const char* data, particle::ContentType type, PublishFlags flags = PublishFlags()); + particle::Future publish(const char* name, const uint8_t* data, size_t size, particle::ContentType type, PublishFlags flags = PublishFlags()); + /** * @brief Publish vitals information * @@ -490,7 +506,8 @@ class CloudClass { static void call_wiring_event_handler(const void* param, const char *event_name, const char *data); - static particle::Future publish_event(const char *eventName, const char *eventData, int ttl, PublishFlags flags); + static particle::Future publish_event(const char* name, const char* data, size_t size, particle::ContentType type, + int ttl, PublishFlags flags); bool subscribe_wiring(const char *eventName, wiring_event_handler_t handler, Spark_Subscription_Scope_TypeDef scope, const char *deviceID = NULL) { diff --git a/wiring/src/spark_wiring_cloud.cpp b/wiring/src/spark_wiring_cloud.cpp index 86609681da..c4f6c8cb2f 100644 --- a/wiring/src/spark_wiring_cloud.cpp +++ b/wiring/src/spark_wiring_cloud.cpp @@ -72,19 +72,22 @@ bool CloudClass::register_function(cloud_function_t fn, void* data, const char* return spark_function(NULL, (user_function_int_str_t*)&desc, NULL); } -Future CloudClass::publish_event(const char *eventName, const char *eventData, int ttl, PublishFlags flags) { +Future CloudClass::publish_event(const char* name, const char* data, size_t size, ContentType type, int ttl, + PublishFlags flags) { if (!connected()) { return Future(Error::INVALID_STATE); } spark_send_event_data d = {}; d.size = sizeof(spark_send_event_data); + d.data_size = size; + d.content_type = static_cast(type); // Completion handler Promise p; d.handler_callback = publishCompletionCallback; d.handler_data = p.dataPtr(); - if (!spark_send_event(eventName, eventData, ttl, flags.value(), &d) && !p.isDone()) { + if (!spark_send_event(name, data, ttl, flags.value(), &d) && !p.isDone()) { // Set generic error code in case completion callback wasn't invoked for some reason p.setError(Error::UNKNOWN); p.fromDataPtr(d.handler_data); // Free wrapper object From 75e31d183065e582cab935720119d1fdb970df2a Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Tue, 23 Jul 2024 18:47:12 +0200 Subject: [PATCH 02/45] Minor fixes --- communication/src/publisher.cpp | 1 + wiring/inc/spark_wiring_cloud.h | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/communication/src/publisher.cpp b/communication/src/publisher.cpp index 8f109d97b9..b4a55e2686 100644 --- a/communication/src/publisher.cpp +++ b/communication/src/publisher.cpp @@ -72,6 +72,7 @@ ProtocolError Publisher::send_event(MessageChannel& channel, const char* event_n if (data_size > 0) { auto max_data_size = std::min(e.maxPayloadSize(), protocol->get_max_event_data_size()); data_size = std::min(data_size, max_data_size); + e.payload(data, data_size); } int r = e.encode(); if (r < 0) { diff --git a/wiring/inc/spark_wiring_cloud.h b/wiring/inc/spark_wiring_cloud.h index c49075854e..3d7881868f 100644 --- a/wiring/inc/spark_wiring_cloud.h +++ b/wiring/inc/spark_wiring_cloud.h @@ -285,8 +285,13 @@ class CloudClass { particle::Future publish(const char* name, const char* data); particle::Future publish(const char* name, const char* data, int ttl); - particle::Future publish(const char* name, const char* data, particle::ContentType type, PublishFlags flags = PublishFlags()); - particle::Future publish(const char* name, const uint8_t* data, size_t size, particle::ContentType type, PublishFlags flags = PublishFlags()); + particle::Future publish(const char* name, const char* data, particle::ContentType type, PublishFlags flags = PublishFlags()) { + return publish_event(name, data, std::strlen(data), type, DEFAULT_CLOUD_EVENT_TTL, flags); + } + + particle::Future publish(const char* name, const uint8_t* data, size_t size, particle::ContentType type, PublishFlags flags = PublishFlags()) { + return publish_event(name, (const char*)data, size, type, DEFAULT_CLOUD_EVENT_TTL, flags); + } /** * @brief Publish vitals information From 25f0ea8a02f00fa35fb7c98c28e9c23adc094282 Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Thu, 25 Jul 2024 19:30:28 +0200 Subject: [PATCH 03/45] Parse Content-Format option when receiving a cloud event (WIP) --- communication/{src => inc}/coap_defs.h | 0 communication/inc/coap_util.h | 27 ++++++ communication/inc/events.h | 6 +- communication/inc/spark_descriptor.h | 5 +- communication/src/build.mk | 1 + communication/src/coap_util.cpp | 48 ++++++++++ communication/src/events.cpp | 2 +- communication/src/subscriptions.cpp | 96 +++++++++++++++++++ communication/src/subscriptions.h | 126 +------------------------ wiring/inc/spark_wiring_cloud.h | 12 +-- 10 files changed, 190 insertions(+), 133 deletions(-) rename communication/{src => inc}/coap_defs.h (100%) create mode 100644 communication/src/subscriptions.cpp diff --git a/communication/src/coap_defs.h b/communication/inc/coap_defs.h similarity index 100% rename from communication/src/coap_defs.h rename to communication/inc/coap_defs.h diff --git a/communication/inc/coap_util.h b/communication/inc/coap_util.h index f3824402fc..95e47b6891 100644 --- a/communication/inc/coap_util.h +++ b/communication/inc/coap_util.h @@ -18,6 +18,7 @@ #pragma once #include "coap_api.h" +#include "coap_defs.h" #include "logging.h" namespace particle { @@ -86,6 +87,32 @@ class CoapMessagePtr { namespace protocol { +class MessageChannel; +class CoapOptionIterator; + +/** + * Send an empty ACK or RST message. + * + * @param channel Message channel. + * @param msg Received message. + * @param type Type of the message to send. + * @return 0 on success, otherwise an error code defined by the `system_error_t` enum. + */ +int sendEmptyAckOrRst(MessageChannel& channel, Message& msg, CoapType type); + +/** + * Append an URI path entry to a string. + * + * The output is null-terminated unless the size of the buffer is 0. + * + * @param buf Destination buffer. + * @param bufSize Buffer size. + * @param pathLen Length of the URI path already stored in the buffer. + * @param it CoAP options iterator. + * @return Length of the URI path entry plus one character for a path separator. + */ +size_t appendUriPath(char* buf, size_t bufSize, size_t pathLen, const CoapOptionIterator& it); + /** * Log the contents of a CoAP message. * diff --git a/communication/inc/events.h b/communication/inc/events.h index fe1dffb5b8..50a5fac8b4 100644 --- a/communication/inc/events.h +++ b/communication/inc/events.h @@ -28,6 +28,7 @@ #include #include #include "platforms.h" +#include "protocol_defs.h" namespace EventType { enum Enum : char { @@ -76,14 +77,15 @@ namespace SubscriptionScope { } typedef void (*EventHandler)(const char *event_name, const char *data); -typedef void (*EventHandlerWithData)(void *handler_data, const char *event_name, const char *data); +typedef void (*EventHandlerWithData)(void *handler_data, const char *event_name, const char *data, size_t data_size, + int content_type); /** * This is used in a callback so only change by adding fields to the end */ struct FilteringEventHandler { - char filter[64]; + char filter[64]; // XXX: Not null-terminated if the length of the filter is 64 characters EventHandler handler; void *handler_data; SubscriptionScope::Enum scope; diff --git a/communication/inc/spark_descriptor.h b/communication/inc/spark_descriptor.h index ccd55502a4..d60cf1b6f0 100644 --- a/communication/inc/spark_descriptor.h +++ b/communication/inc/spark_descriptor.h @@ -68,6 +68,9 @@ namespace SparkAppStateUpdate { struct SparkDescriptor { + typedef void (*CallEventHandlerCallback)(uint16_t size, FilteringEventHandler* handler, const char* event, + const char* data, size_t data_size, int content_type); + typedef std::function FunctionResultCallback; /** @@ -96,7 +99,7 @@ struct SparkDescriptor bool (*append_system_info)(appender_fn appender, void* append, void* reserved); - void (*call_event_handler)(uint16_t size, FilteringEventHandler* handler, const char* event, const char* data, void* reserved); + CallEventHandlerCallback call_event_handler; /** * Optional callback - may be null. diff --git a/communication/src/build.mk b/communication/src/build.mk index c6bc89ecc4..a60c7156d4 100644 --- a/communication/src/build.mk +++ b/communication/src/build.mk @@ -28,6 +28,7 @@ CPPSRC += $(TARGET_SRC_PATH)/messages.cpp CPPSRC += $(TARGET_SRC_PATH)/chunked_transfer.cpp CPPSRC += $(TARGET_SRC_PATH)/coap_channel.cpp CPPSRC += $(TARGET_SRC_PATH)/publisher.cpp +CPPSRC += $(TARGET_SRC_PATH)/subscriptions.cpp CPPSRC += $(TARGET_SRC_PATH)/protocol_defs.cpp CPPSRC += $(TARGET_SRC_PATH)/protocol_util.cpp CPPSRC += $(TARGET_SRC_PATH)/communication_diagnostic.cpp diff --git a/communication/src/coap_util.cpp b/communication/src/coap_util.cpp index 22e120bb7f..9abbd55e26 100644 --- a/communication/src/coap_util.cpp +++ b/communication/src/coap_util.cpp @@ -15,15 +15,63 @@ * License along with this library; if not, see . */ +#if !defined(DEBUG_BUILD) && !defined(UNIT_TEST) +#define NDEBUG // TODO: Define NDEBUG in release builds +#endif + #include #include #include "coap_util.h" + +#include "message_channel.h" +#include "coap_message_encoder.h" #include "coap_message_decoder.h" #include "str_util.h" +#include "check.h" namespace particle::protocol { +int sendEmptyAckOrRst(MessageChannel& channel, Message& msg, CoapType type) { + Message resp; + auto err = channel.response(msg, resp, msg.capacity() - msg.length()); + if (err != ProtocolError::NO_ERROR) { + return toSystemError(err); + } + CoapMessageEncoder e((char*)resp->buf(), resp->capacity()); + e.type(type); + e.code(CoapCode::EMPTY); + e.id(0); // Serialized by the message channel + size_t n = CHECK(e.encode()); + if (n > resp.capacity()) { + return SYSTEM_ERROR_TOO_LARGE; + } + resp->set_length(n); + resp->set_id(msg.get_id()); + err = channel.send(resp); + if (r != ProtocolError::NO_ERROR) { + return toSystemError(err); + } + return 0; +} + +size_t appendUriPath(char* buf, size_t bufSize, size_t pathLen, const CoapOptionIterator& it) { + assert(it.option() == CoapOption::URI_PATH); + auto end = buf + bufSize; + buf += pathLen; + if (buf < end) { + *buf++ = '/'; + size_t n = std::min(iter.size(), end - buf); + std::memcpy(buf, iter.data(), n); + buf += n; + if (buf == end) { + --buf; + } + *buf = '\0'; + } + return iter.size() + 1; +} + void logCoapMessage(LogLevel level, const char* category, const char* data, size_t size, bool logPayload) { CoapMessageDecoder d; const int r = d.decode(data, size); diff --git a/communication/src/events.cpp b/communication/src/events.cpp index 09bba3edbb..c4429ea26d 100644 --- a/communication/src/events.cpp +++ b/communication/src/events.cpp @@ -39,7 +39,7 @@ uint8_t *subscription_prelude(uint8_t buf[], uint16_t message_id, if (NULL != event_name) { - size_t len = strnlen(event_name, 63); + size_t len = strnlen(event_name, 64); p += event_name_uri_path(p, event_name, len); } diff --git a/communication/src/subscriptions.cpp b/communication/src/subscriptions.cpp new file mode 100644 index 0000000000..ad1e5946a4 --- /dev/null +++ b/communication/src/subscriptions.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2024 Particle Industries, Inc. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include "subscriptions.h" + +#include "spark_descriptor.h" +#include "coap_message_decoder.h" +#include "coap_util.h" +#include "logging.h" + +namespace particle::protocol { + +ProtocolError Subscriptions::handle_event(Message& msg, SparkDescriptor::CallEventHandlerCallback callback, MessageChannel& channel) { + CoapMessageDecoder d; + int r = d.decode((const char*)message.buf(), message.length()); + if (r < 0) { + return ProtocolError::MALFORMED_MESSAGE; + } + + if (d.type() == CoapType::CON && channel.is_unreliable()) { + int r = sendEmptyAckOrRst(channel, resp, CoapType::ACK); + if (r < 0) { + LOG(ERROR, "Failed to send ACK: %d", r); + return ProtocolError::COAP_ERROR; + } + } + + char name[MAX_EVENT_NAME_LENGTH + 1]; + size_t nameLen = 0; + int contentFmt = -1; + bool skipUriPrefix = true; + + auto it = d.options(); + while (it.next()) { + switch (it.option()) { + case CoapOption::URI_PATH: { + if (skipUriPrefix) { + skipUriPrefix = false; + continue; // Skip the "e/" or "E/" part + } + nameLen += appendUriPath(name, sizeof(name), nameLen, it); + if (nameLen >= sizeof(name)) { + LOG(ERROR, "Event name is too long"); + return ProtocolError::MALFORMED_MESSAGE; + } + break; + } + case CoapOption::CONTENT_FORMAT: { + contentFmt = it.toUInt(); + } + default: + break; + } + } + + for (size_t i = 0; i < MAX_SUBSCRIPTIONS; ++i) { + if (!event_handlers[i].handler) { + break; + } + size_t filterLen = strnlen(event_handlers[i].filter, sizeof(event_handlers[i].filter)); + if (nameLen < filterLen) { + continue; + } + if (!std::memcmp(event_handlers[i].filter, name, filterLen)) { + const char* data = nullptr; + size_t dataSize = d.payloadSize(); + if (dataSize > 0) { + data = d.payload(); + // Historically, the event handler callback expected a null-terminated string. Keeping that + // behavior for now + if (msg.length() >= msg.capacity()) { + std::memmove(data - 1, data, dataSize); // Overwrites the payload marker + } + data[dataSize] = '\0'; + } + callback(sizeof(FilteringEventHandler), &event_handlers[i], name, data, dataSize, contentFmt); + } + } + return ProtocolError::NO_ERROR; +} + +} // namespace particle::protocol diff --git a/communication/src/subscriptions.h b/communication/src/subscriptions.h index 6f35ea81c2..0f6f76b032 100644 --- a/communication/src/subscriptions.h +++ b/communication/src/subscriptions.h @@ -22,6 +22,7 @@ #include "protocol_defs.h" #include "events.h" #include "message_channel.h" +#include "spark_descriptor.h" #include "spark_wiring_vector.h" @@ -88,128 +89,7 @@ class Subscriptions return checksum; } - ProtocolError handle_event(Message& message, - void (*call_event_handler)(uint16_t size, - FilteringEventHandler* handler, const char* event, - const char* data, void* reserved), - MessageChannel& channel) - { - const unsigned len = message.length(); - uint8_t* queue = message.buf(); - if (CoAP::type(queue)==CoAPType::CON && channel.is_unreliable()) - { - Message response; - if (channel.response(message, response, 5)==NO_ERROR) - { - size_t len = Messages::empty_ack(response.buf(), 0, 0); - response.set_length(len); - response.set_id(message.get_id()); - ProtocolError error = channel.send(response); - if (error) - return error; - } - } - - // end of CoAP message - unsigned char *end = queue + len; - // start of event name option (location path) - 6 bytes - // 4 bytes coap header, 2 bytes for the location path of the message - // plus the size of the token. - unsigned char *event_name = queue + 6 + (queue[0] & 0xF); - size_t event_name_length = CoAP::option_decode(&event_name); - if (0 == event_name_length) - { - // error, malformed CoAP option - return MALFORMED_MESSAGE; - } - - unsigned char *next_src = event_name + event_name_length; - unsigned char *next_dst = next_src; - while (next_src < end && 0x00 == (*next_src & 0xf0)) - { - // there's another Uri-Path option, i.e., event name with slashes - size_t option_len = CoAP::option_decode(&next_src); - *next_dst++ = '/'; - if (next_dst != next_src) - { - // at least one extra byte has been used to encode a CoAP Uri-Path option length - memmove(next_dst, next_src, option_len); - } - next_src += option_len; - next_dst += option_len; - } - event_name_length = next_dst - event_name; - - if (next_src < end && 0x30 == (*next_src & 0xf0)) - { - // Max-Age option is next, which we ignore - size_t next_len = CoAP::option_decode(&next_src); - next_src += next_len; - } - - unsigned char *data = NULL; - if (next_src < end && 0xff == *next_src) - { - // payload is next - data = next_src + 1; - // null terminate data string - *end = 0; - } - // null terminate event name string - event_name[event_name_length] = 0; - - const int NUM_HANDLERS = sizeof(event_handlers) - / sizeof(FilteringEventHandler); - for (int i = 0; i < NUM_HANDLERS; i++) - { - if (NULL == event_handlers[i].handler) - { - break; - } - const size_t MAX_FILTER_LENGTH = sizeof(event_handlers[i].filter); - const size_t filter_length = strnlen(event_handlers[i].filter, - MAX_FILTER_LENGTH); - - if (event_name_length < filter_length) - { - // does not match this filter, try the next event handler - continue; - } - - const int cmp = memcmp(event_handlers[i].filter, event_name, - filter_length); - if (0 == cmp) - { - // don't call the handler directly, use a callback for it. - if (!call_event_handler) - { - if (event_handlers[i].handler_data) - { -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wcast-function-type" - EventHandlerWithData handler = - (EventHandlerWithData) event_handlers[i].handler; -#pragma GCC diagnostic pop - handler(event_handlers[i].handler_data, - (char *) event_name, (char *) data); - } - else - { - event_handlers[i].handler((char *) event_name, - (char *) data); - } - } - else - { - call_event_handler(sizeof(FilteringEventHandler), - &event_handlers[i], (const char*) event_name, - (const char*) data, NULL); - } - } - // else continue the for loop to try the next handler - } - return NO_ERROR; - } + ProtocolError handle_event(Message& message, SparkDescriptor::CallEventHandlerCallback callback, MessageChannel& channel); template ProtocolError for_each(F callback) { @@ -241,7 +121,7 @@ class Subscriptions int dest = 0; for (int i = 0; i < NUM_HANDLERS; i++) { - if (!strcmp(event_name, event_handlers[i].filter)) + if (!strncmp(event_name, event_handlers[i].filter, sizeof(event_handlers[i].filter))) { memset(&event_handlers[i], 0, sizeof(event_handlers[i])); } diff --git a/wiring/inc/spark_wiring_cloud.h b/wiring/inc/spark_wiring_cloud.h index 3d7881868f..c30d91b58e 100644 --- a/wiring/inc/spark_wiring_cloud.h +++ b/wiring/inc/spark_wiring_cloud.h @@ -77,12 +77,12 @@ class Ledger; * Content type. */ enum class ContentType { - TEXT = 0, ///< text/plain; charset=utf-8 - JPEG = 22, ///< image/jpeg - PNG = 23, ///< image/png - BINARY = 42, ///< application/octet-stream - JSON = 50, ///< application/json - CBOR = 60, ///< application/cbor + TEXT = 0, ///< `text/plain; charset=utf-8`. + JPEG = 22, ///< `image/jpeg`. + PNG = 23, ///< `image/png`. + BINARY = 42, ///< `application/octet-stream`. + JSON = 50, ///< `application/json`. + CBOR = 60, ///< `application/cbor`. }; } // namespace particle From 7551d8371724ee68686663553aa2a8e3e068b327 Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Thu, 25 Jul 2024 19:59:39 +0200 Subject: [PATCH 04/45] Bugfixes --- communication/inc/coap_defs.h | 10 +++++--- communication/inc/coap_util.h | 1 + communication/src/coap_util.cpp | 14 +++++------ .../src/spark_protocol_functions.cpp | 2 +- communication/src/subscriptions.cpp | 13 +++++----- communication/src/subscriptions.h | 5 ++-- system/src/system_cloud.cpp | 3 ++- system/src/system_cloud_internal.cpp | 24 +++++++++---------- wiring/inc/spark_wiring_cloud.h | 13 +++++----- 9 files changed, 47 insertions(+), 38 deletions(-) diff --git a/communication/inc/coap_defs.h b/communication/inc/coap_defs.h index a940470b2a..ffb0351421 100644 --- a/communication/inc/coap_defs.h +++ b/communication/inc/coap_defs.h @@ -108,9 +108,13 @@ enum class CoapOption { PARTICLE_DEFINE_ENUM_COMPARISON_OPERATORS(CoapOption) enum class CoapContentFormat { - // RFC 7252 12.3. CoAP Content-Formats Registry - TEXT_PLAIN = 0, - APPLICATION_OCTET_STREAM = 42 + // https://www.iana.org/assignments/core-parameters/core-parameters.xhtml#content-formats + TEXT_PLAIN = 0, // text/plain; charset=utf-8 + IMAGE_JPEG = 22, + IMAGE_PNG = 23, + APPLICATION_OCTET_STREAM = 42, + APPLICATION_JSON = 50, + APPLICATION_CBOR = 60 }; PARTICLE_DEFINE_ENUM_COMPARISON_OPERATORS(CoapContentFormat) diff --git a/communication/inc/coap_util.h b/communication/inc/coap_util.h index 95e47b6891..82993288bf 100644 --- a/communication/inc/coap_util.h +++ b/communication/inc/coap_util.h @@ -87,6 +87,7 @@ class CoapMessagePtr { namespace protocol { +class Message; class MessageChannel; class CoapOptionIterator; diff --git a/communication/src/coap_util.cpp b/communication/src/coap_util.cpp index 9abbd55e26..331bdd1c70 100644 --- a/communication/src/coap_util.cpp +++ b/communication/src/coap_util.cpp @@ -38,7 +38,7 @@ int sendEmptyAckOrRst(MessageChannel& channel, Message& msg, CoapType type) { if (err != ProtocolError::NO_ERROR) { return toSystemError(err); } - CoapMessageEncoder e((char*)resp->buf(), resp->capacity()); + CoapMessageEncoder e((char*)resp.buf(), resp.capacity()); e.type(type); e.code(CoapCode::EMPTY); e.id(0); // Serialized by the message channel @@ -46,10 +46,10 @@ int sendEmptyAckOrRst(MessageChannel& channel, Message& msg, CoapType type) { if (n > resp.capacity()) { return SYSTEM_ERROR_TOO_LARGE; } - resp->set_length(n); - resp->set_id(msg.get_id()); + resp.set_length(n); + resp.set_id(msg.get_id()); err = channel.send(resp); - if (r != ProtocolError::NO_ERROR) { + if (err != ProtocolError::NO_ERROR) { return toSystemError(err); } return 0; @@ -61,15 +61,15 @@ size_t appendUriPath(char* buf, size_t bufSize, size_t pathLen, const CoapOption buf += pathLen; if (buf < end) { *buf++ = '/'; - size_t n = std::min(iter.size(), end - buf); - std::memcpy(buf, iter.data(), n); + size_t n = std::min(it.size(), end - buf); + std::memcpy(buf, it.data(), n); buf += n; if (buf == end) { --buf; } *buf = '\0'; } - return iter.size() + 1; + return it.size() + 1; } void logCoapMessage(LogLevel level, const char* category, const char* data, size_t size, bool logPayload) { diff --git a/communication/src/spark_protocol_functions.cpp b/communication/src/spark_protocol_functions.cpp index a31b75e88a..4de8074b38 100644 --- a/communication/src/spark_protocol_functions.cpp +++ b/communication/src/spark_protocol_functions.cpp @@ -119,7 +119,7 @@ bool spark_protocol_send_event(ProtocolFacade* protocol, const char *event_name, ASSERT_ON_SYSTEM_THREAD(); CompletionHandler handler; std::optional data_size = 0; - int content_type = static_cast(CoapContentFormat::TEXT_PLAIN); + int content_type = (int)CoapContentFormat::TEXT_PLAIN; if (reserved) { auto r = static_cast(reserved); handler = CompletionHandler(r->handler_callback, r->handler_data); diff --git a/communication/src/subscriptions.cpp b/communication/src/subscriptions.cpp index ad1e5946a4..fd7fa33ea5 100644 --- a/communication/src/subscriptions.cpp +++ b/communication/src/subscriptions.cpp @@ -26,13 +26,13 @@ namespace particle::protocol { ProtocolError Subscriptions::handle_event(Message& msg, SparkDescriptor::CallEventHandlerCallback callback, MessageChannel& channel) { CoapMessageDecoder d; - int r = d.decode((const char*)message.buf(), message.length()); + int r = d.decode((const char*)msg.buf(), msg.length()); if (r < 0) { return ProtocolError::MALFORMED_MESSAGE; } if (d.type() == CoapType::CON && channel.is_unreliable()) { - int r = sendEmptyAckOrRst(channel, resp, CoapType::ACK); + int r = sendEmptyAckOrRst(channel, msg, CoapType::ACK); if (r < 0) { LOG(ERROR, "Failed to send ACK: %d", r); return ProtocolError::COAP_ERROR; @@ -47,7 +47,7 @@ ProtocolError Subscriptions::handle_event(Message& msg, SparkDescriptor::CallEve auto it = d.options(); while (it.next()) { switch (it.option()) { - case CoapOption::URI_PATH: { + case (int)CoapOption::URI_PATH: { if (skipUriPrefix) { skipUriPrefix = false; continue; // Skip the "e/" or "E/" part @@ -59,7 +59,7 @@ ProtocolError Subscriptions::handle_event(Message& msg, SparkDescriptor::CallEve } break; } - case CoapOption::CONTENT_FORMAT: { + case (int)CoapOption::CONTENT_FORMAT: { contentFmt = it.toUInt(); } default: @@ -76,14 +76,15 @@ ProtocolError Subscriptions::handle_event(Message& msg, SparkDescriptor::CallEve continue; } if (!std::memcmp(event_handlers[i].filter, name, filterLen)) { - const char* data = nullptr; + char* data = nullptr; size_t dataSize = d.payloadSize(); if (dataSize > 0) { - data = d.payload(); + data = const_cast(d.payload()); // Historically, the event handler callback expected a null-terminated string. Keeping that // behavior for now if (msg.length() >= msg.capacity()) { std::memmove(data - 1, data, dataSize); // Overwrites the payload marker + --data; } data[dataSize] = '\0'; } diff --git a/communication/src/subscriptions.h b/communication/src/subscriptions.h index 0f6f76b032..ed9e826356 100644 --- a/communication/src/subscriptions.h +++ b/communication/src/subscriptions.h @@ -19,6 +19,9 @@ #pragma once +#include +#include + #include "protocol_defs.h" #include "events.h" #include "message_channel.h" @@ -26,8 +29,6 @@ #include "spark_wiring_vector.h" -#include - namespace particle { namespace protocol diff --git a/system/src/system_cloud.cpp b/system/src/system_cloud.cpp index 9baefbcc4a..4be635ef9c 100644 --- a/system/src/system_cloud.cpp +++ b/system/src/system_cloud.cpp @@ -44,6 +44,7 @@ #include "string_convert.h" #include "spark_protocol_functions.h" #include "events.h" +#include "coap_defs.h" #include "deviceid_hal.h" #include "system_mode.h" @@ -187,7 +188,7 @@ bool spark_send_event(const char* name, const char* data, int ttl, uint32_t flag spark_protocol_send_event_data d = {}; d.size = sizeof(d); - d.content_type = 0; // text/plain; charset=utf-8 + d.content_type = (int)protocol::CoapContentFormat::TEXT_PLAIN; bool hasDataSize = false; if (reserved) { diff --git a/system/src/system_cloud_internal.cpp b/system/src/system_cloud_internal.cpp index f68f2a22f9..6b29390e89 100644 --- a/system/src/system_cloud_internal.cpp +++ b/system/src/system_cloud_internal.cpp @@ -453,16 +453,16 @@ bool spark_function_internal(const cloud_function_descriptor* desc, void* reserv } -void invokeEventHandlerInternal(uint16_t handlerInfoSize, FilteringEventHandler* handlerInfo, - const char* event_name, const char* data, void* reserved) +void invokeEventHandlerInternal(uint16_t handlerInfoSize, FilteringEventHandler* handlerInfo, const char* event_name, + const char* data, size_t data_size, int content_type) { if(handlerInfo->handler_data) { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-function-type" - EventHandlerWithData handler = (EventHandlerWithData) handlerInfo->handler; + EventHandlerWithData handler = (EventHandlerWithData)handlerInfo->handler; #pragma GCC diagnostic pop - handler(handlerInfo->handler_data, event_name, data); + handler(handlerInfo->handler_data, event_name, data, data_size, content_type); } else { @@ -470,10 +470,10 @@ void invokeEventHandlerInternal(uint16_t handlerInfoSize, FilteringEventHandler* } } -void invokeEventHandlerString(uint16_t handlerInfoSize, FilteringEventHandler* handlerInfo, - const String& name, const String& data, void* reserved) +void invokeEventHandlerString(uint16_t handlerInfoSize, FilteringEventHandler* handlerInfo, const String& name, + const String& data, int content_type) { - invokeEventHandlerInternal(handlerInfoSize, handlerInfo, name.c_str(), data.c_str(), reserved); + invokeEventHandlerInternal(handlerInfoSize, handlerInfo, name.c_str(), data.c_str(), data.length(), content_type); } bool is_system_handler(uint16_t handlerInfoSize, FilteringEventHandler* handlerInfo) { @@ -481,19 +481,19 @@ bool is_system_handler(uint16_t handlerInfoSize, FilteringEventHandler* handlerI return handlerInfo->handler == systemEventHandler; } -void invokeEventHandler(uint16_t handlerInfoSize, FilteringEventHandler* handlerInfo, - const char* event_name, const char* event_data, void* reserved) +void invokeEventHandler(uint16_t handlerInfoSize, FilteringEventHandler* handlerInfo, const char* event_name, + const char* event_data, size_t data_size, int content_type) { if (is_system_handler(handlerInfoSize, handlerInfo) || system_thread_get_state(NULL)==spark::feature::DISABLED) { - invokeEventHandlerInternal(handlerInfoSize, handlerInfo, event_name, event_data, reserved); + invokeEventHandlerInternal(handlerInfoSize, handlerInfo, event_name, event_data, data_size, content_type); } else { // copy the buffers to dynamically allocated storage. String name(event_name); - String data(event_data); - APPLICATION_THREAD_CONTEXT_ASYNC(invokeEventHandlerString(handlerInfoSize, handlerInfo, name, data, reserved)); + String data(event_data, data_size); + APPLICATION_THREAD_CONTEXT_ASYNC(invokeEventHandlerString(handlerInfoSize, handlerInfo, name, data, content_type)); } } diff --git a/wiring/inc/spark_wiring_cloud.h b/wiring/inc/spark_wiring_cloud.h index c30d91b58e..24068c30b7 100644 --- a/wiring/inc/spark_wiring_cloud.h +++ b/wiring/inc/spark_wiring_cloud.h @@ -27,6 +27,7 @@ #include "spark_wiring_string.h" #include "events.h" +#include "coap_defs.h" #include "system_cloud.h" #include "system_sleep.h" #include "system_tick_hal.h" @@ -77,12 +78,12 @@ class Ledger; * Content type. */ enum class ContentType { - TEXT = 0, ///< `text/plain; charset=utf-8`. - JPEG = 22, ///< `image/jpeg`. - PNG = 23, ///< `image/png`. - BINARY = 42, ///< `application/octet-stream`. - JSON = 50, ///< `application/json`. - CBOR = 60, ///< `application/cbor`. + TEXT = (int)protocol::CoapContentFormat::TEXT_PLAIN, ///< `text/plain; charset=utf-8`. + JPEG = (int)protocol::CoapContentFormat::IMAGE_JPEG, ///< `image/jpeg`. + PNG = (int)protocol::CoapContentFormat::IMAGE_PNG, ///< `image/png`. + BINARY = (int)protocol::CoapContentFormat::APPLICATION_OCTET_STREAM, ///< `application/octet-stream`. + JSON = (int)protocol::CoapContentFormat::APPLICATION_JSON, ///< `application/json`. + CBOR = (int)protocol::CoapContentFormat::APPLICATION_CBOR ///< `application/cbor`. }; } // namespace particle From 66261f5e8adea2fb8153acb55efc7dbd2019593f Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Thu, 25 Jul 2024 20:55:29 +0200 Subject: [PATCH 05/45] Overload Particle.subscribe() to accept a callback taking a content type --- communication/inc/events.h | 2 +- wiring/inc/spark_wiring_cloud.h | 6 ++++++ wiring/src/spark_wiring_cloud.cpp | 24 ++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/communication/inc/events.h b/communication/inc/events.h index 50a5fac8b4..3137c75384 100644 --- a/communication/inc/events.h +++ b/communication/inc/events.h @@ -85,7 +85,7 @@ typedef void (*EventHandlerWithData)(void *handler_data, const char *event_name, */ struct FilteringEventHandler { - char filter[64]; // XXX: Not null-terminated if the length of the filter is 64 characters + char filter[64]; // XXX: Not null-terminated if 64 characters long EventHandler handler; void *handler_data; SubscriptionScope::Enum scope; diff --git a/wiring/inc/spark_wiring_cloud.h b/wiring/inc/spark_wiring_cloud.h index 24068c30b7..9d2f3c8295 100644 --- a/wiring/inc/spark_wiring_cloud.h +++ b/wiring/inc/spark_wiring_cloud.h @@ -86,6 +86,9 @@ enum class ContentType { CBOR = (int)protocol::CoapContentFormat::APPLICATION_CBOR ///< `application/cbor`. }; +typedef void (*EventHandlerWithContentType)(const char* name, const uint8_t* data, size_t size, ContentType type); +typedef std::function EventHandlerWithContentTypeFn; + } // namespace particle class CloudDisconnectOptions { @@ -358,6 +361,9 @@ class CloudClass { template bool subscribe(const char* name, void (T::*handler)(const char*, const char*), T* instance); + bool subscribe(const char* name, particle::EventHandlerWithContentType handler); + bool subscribe(const char* name, particle::EventHandlerWithContentTypeFn handler); + void unsubscribe() { spark_unsubscribe(NULL); diff --git a/wiring/src/spark_wiring_cloud.cpp b/wiring/src/spark_wiring_cloud.cpp index c4f6c8cb2f..ff80a2b26a 100644 --- a/wiring/src/spark_wiring_cloud.cpp +++ b/wiring/src/spark_wiring_cloud.cpp @@ -19,6 +19,16 @@ void publishCompletionCallback(int error, const void* data, void* callbackData, } } +void subscribeWithContentTypeCallbackWrapper(void* arg, const char* name, const char* data, size_t dataSize, int contentType) { + auto cb = (EventHandlerWithContentType)arg; + cb(name, (const uint8_t*)data, dataSize, (ContentType)contentType); +} + +void subscribeWithContentTypeFunctionWrapper(void* arg, const char* name, const char* data, size_t dataSize, int contentType) { + auto fn = (EventHandlerWithContentTypeFn*)arg; + (*fn)(name, (const uint8_t*)data, dataSize, (ContentType)contentType); +} + } // namespace spark_cloud_disconnect_options CloudDisconnectOptions::toSystemOptions() const @@ -166,3 +176,17 @@ int CloudClass::useLedgersImpl(const Vector& usedNames) { } #endif // Wiring_Ledger + +bool CloudClass::subscribe(const char* name, EventHandlerWithContentType handler) { + return spark_subscribe(name, (EventHandler)subscribeWithContentTypeCallbackWrapper, (void*)handler, ALL_DEVICES, + nullptr /* deviceID */, nullptr /* reserved */); +} + +bool CloudClass::subscribe(const char* name, EventHandlerWithContentTypeFn handler) { + auto fnPtr = new(std::nothrow) EventHandlerWithContentTypeFn(std::move(handler)); + if (!fnPtr) { + return false; + } + return spark_subscribe(name, (EventHandler)subscribeWithContentTypeFunctionWrapper, fnPtr, ALL_DEVICES, + nullptr /* deviceID */, nullptr /* reserved */); +} From f3c3f31b23767e5d26112464d597fc401b820437 Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Tue, 30 Jul 2024 13:21:59 +0200 Subject: [PATCH 06/45] Minor fixes --- communication/src/coap_util.cpp | 4 +++- wiring/inc/spark_wiring_cloud.h | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/communication/src/coap_util.cpp b/communication/src/coap_util.cpp index 331bdd1c70..ba73b9f753 100644 --- a/communication/src/coap_util.cpp +++ b/communication/src/coap_util.cpp @@ -60,7 +60,9 @@ size_t appendUriPath(char* buf, size_t bufSize, size_t pathLen, const CoapOption auto end = buf + bufSize; buf += pathLen; if (buf < end) { - *buf++ = '/'; + if (pathLen > 0) { + *buf++ = '/'; + } size_t n = std::min(it.size(), end - buf); std::memcpy(buf, it.data(), n); buf += n; diff --git a/wiring/inc/spark_wiring_cloud.h b/wiring/inc/spark_wiring_cloud.h index 9d2f3c8295..a3782d2df9 100644 --- a/wiring/inc/spark_wiring_cloud.h +++ b/wiring/inc/spark_wiring_cloud.h @@ -77,7 +77,7 @@ class Ledger; /** * Content type. */ -enum class ContentType { +enum class ContentType: int { TEXT = (int)protocol::CoapContentFormat::TEXT_PLAIN, ///< `text/plain; charset=utf-8`. JPEG = (int)protocol::CoapContentFormat::IMAGE_JPEG, ///< `image/jpeg`. PNG = (int)protocol::CoapContentFormat::IMAGE_PNG, ///< `image/png`. From 70ebbae921e7eb911fc63768c741d0fe614a0611 Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Wed, 31 Jul 2024 14:08:56 +0200 Subject: [PATCH 07/45] Send the type of the subscription handler to the server; refactoring --- communication/inc/communication_dynalib.h | 6 +- communication/inc/events.h | 26 ++--- communication/inc/protocol.h | 18 ++- communication/inc/spark_protocol_functions.h | 6 +- communication/src/build.mk | 1 - communication/src/events.cpp | 108 ------------------ communication/src/protocol.cpp | 13 +-- communication/src/publisher.cpp | 3 + .../src/spark_protocol_functions.cpp | 17 ++- communication/src/subscriptions.cpp | 36 ++++++ communication/src/subscriptions.h | 83 +++----------- system/inc/system_cloud.h | 26 ++++- system/inc/system_dynalib_cloud.h | 2 +- system/src/system_cloud.cpp | 39 +++---- wiring/inc/spark_wiring_cloud.h | 6 + wiring/src/spark_wiring_cloud.cpp | 14 ++- 16 files changed, 142 insertions(+), 262 deletions(-) delete mode 100644 communication/src/events.cpp diff --git a/communication/inc/communication_dynalib.h b/communication/inc/communication_dynalib.h index b787fca903..82663503e0 100644 --- a/communication/inc/communication_dynalib.h +++ b/communication/inc/communication_dynalib.h @@ -44,9 +44,9 @@ DYNALIB_FN(7, communication, spark_protocol_event_loop, bool(ProtocolFacade* pro DYNALIB_FN(8, communication, spark_protocol_is_initialized, bool(ProtocolFacade*)) DYNALIB_FN(9, communication, spark_protocol_presence_announcement, int(ProtocolFacade*, uint8_t*, const uint8_t*, void*)) DYNALIB_FN(10, communication, spark_protocol_send_event, bool(ProtocolFacade*, const char*, const char*, int, uint32_t, void*)) -DYNALIB_FN(11, communication, spark_protocol_send_subscription_device, bool(ProtocolFacade*, const char*, const char*, void*)) -DYNALIB_FN(12, communication, spark_protocol_send_subscription_scope, bool(ProtocolFacade*, const char*, SubscriptionScope::Enum, void*)) -DYNALIB_FN(13, communication, spark_protocol_add_event_handler, bool(ProtocolFacade*, const char*, EventHandler, SubscriptionScope::Enum, const char*, void*)) +DYNALIB_FN(11, communication, spark_protocol_send_subscription_device_deprecated, bool(ProtocolFacade*, const char*, const char*, void*)) +DYNALIB_FN(12, communication, spark_protocol_send_subscription, bool(ProtocolFacade*, const char*, uint8_t, void*)) +DYNALIB_FN(13, communication, spark_protocol_add_event_handler, bool(ProtocolFacade*, const char*, EventHandler, uint8_t, const char*, void*)) DYNALIB_FN(14, communication, spark_protocol_send_time_request, bool(ProtocolFacade*, void*)) DYNALIB_FN(15, communication, spark_protocol_send_subscriptions, void(ProtocolFacade*, void*)) diff --git a/communication/inc/events.h b/communication/inc/events.h index 3137c75384..7f5e88e63c 100644 --- a/communication/inc/events.h +++ b/communication/inc/events.h @@ -66,16 +66,21 @@ namespace EventType { } // namespace EventType #if PLATFORM_ID != PLATFORM_GCC -static_assert(sizeof(EventType::Enum)==1, "EventType size is 1"); +static_assert(sizeof(EventType::Enum) == 1, "EventType::Enum size is not 1"); #endif -namespace SubscriptionScope { +namespace SubscriptionFlag { enum Enum { - MY_DEVICES, - FIREHOSE + MY_DEVICES = 0x00, // Deprecated + FIREHOSE = 0x01, // Deprecated + BINARY_DATA = 0x02 // The subscription handler accepts binary data }; } +#if PLATFORM_ID != PLATFORM_GCC +static_assert(sizeof(SubscriptionFlag::Enum) == 1, "SubscriptionFlag::Enum size is not 1"); +#endif + typedef void (*EventHandler)(const char *event_name, const char *data); typedef void (*EventHandlerWithData)(void *handler_data, const char *event_name, const char *data, size_t data_size, int content_type); @@ -88,17 +93,8 @@ struct FilteringEventHandler char filter[64]; // XXX: Not null-terminated if 64 characters long EventHandler handler; void *handler_data; - SubscriptionScope::Enum scope; - char device_id[13]; + uint8_t flags; + char device_id[13]; // XXX: Unused field. Keeping for ABI compatibility for now }; - -size_t subscription(uint8_t buf[], uint16_t message_id, - const char *event_name, const char *device_id); - -size_t subscription(uint8_t buf[], uint16_t message_id, - const char *event_name, SubscriptionScope::Enum scope); - -size_t event_name_uri_path(uint8_t buf[], const char *name, size_t name_len); - #endif // __EVENTS_H diff --git a/communication/inc/protocol.h b/communication/inc/protocol.h index c9258b1350..3ec53805a6 100644 --- a/communication/inc/protocol.h +++ b/communication/inc/protocol.h @@ -525,28 +525,24 @@ class Protocol void build_describe_message(Appender& appender, int desc_flags); - inline bool add_event_handler(const char *event_name, EventHandler handler) + bool add_event_handler(const char *event_name, EventHandler handler) { - return add_event_handler(event_name, handler, NULL, - SubscriptionScope::FIREHOSE, NULL); + return add_event_handler(event_name, handler, nullptr /* handler_data */, 0 /* flags */); } - inline bool add_event_handler(const char *event_name, EventHandler handler, - void *handler_data, SubscriptionScope::Enum scope, - const char* device_id) + bool add_event_handler(const char *event_name, EventHandler handler, void *handler_data, int flags) { - return !subscriptions.add_event_handler(event_name, handler, - handler_data, scope, device_id); + auto err = subscriptions.add_event_handler(event_name, handler, handler_data, flags); + return err == ProtocolError::NO_ERROR; } - inline bool remove_event_handlers(const char* name) + bool remove_event_handlers(const char* name) { subscriptions.remove_event_handlers(name); return true; } - ProtocolError send_subscription(const char *event_name, const char *device_id); - ProtocolError send_subscription(const char *event_name, SubscriptionScope::Enum scope); + ProtocolError send_subscription(const char *event_name, int flags); ProtocolError send_subscriptions(bool force); /** diff --git a/communication/inc/spark_protocol_functions.h b/communication/inc/spark_protocol_functions.h index d09bd954fb..a355a4fcdd 100644 --- a/communication/inc/spark_protocol_functions.h +++ b/communication/inc/spark_protocol_functions.h @@ -199,9 +199,9 @@ typedef completion_handler_data spark_protocol_send_event_data; bool spark_protocol_send_event(ProtocolFacade* protocol, const char *event_name, const char *data, int ttl, uint32_t flags, void* reserved); -bool spark_protocol_send_subscription_device(ProtocolFacade* protocol, const char *event_name, const char *device_id, void* reserved=NULL); -bool spark_protocol_send_subscription_scope(ProtocolFacade* protocol, const char *event_name, SubscriptionScope::Enum scope, void* reserved=NULL); -bool spark_protocol_add_event_handler(ProtocolFacade* protocol, const char *event_name, EventHandler handler, SubscriptionScope::Enum scope, const char* id, void* handler_data=NULL); +bool spark_protocol_send_subscription_device_deprecated(ProtocolFacade* protocol, const char *event_name, const char *device_id, void* reserved=NULL); +bool spark_protocol_send_subscription(ProtocolFacade* protocol, const char *event_name, uint8_t flags, void* reserved=NULL); +bool spark_protocol_add_event_handler(ProtocolFacade* protocol, const char *event_name, EventHandler handler, uint8_t flags, const char* device_id_deprecated, void* handler_data=NULL); bool spark_protocol_send_time_request(ProtocolFacade* protocol, void* reserved=NULL); void spark_protocol_send_subscriptions(ProtocolFacade* protocol, void* reserved=NULL); void spark_protocol_remove_event_handlers(ProtocolFacade* protocol, const char *event_name, void* reserved=NULL); diff --git a/communication/src/build.mk b/communication/src/build.mk index a60c7156d4..9e29fdb7ee 100644 --- a/communication/src/build.mk +++ b/communication/src/build.mk @@ -14,7 +14,6 @@ INCLUDE_DIRS += $(TARGET_SRC_PATH) # C++ source files included in this build. CPPSRC += $(TARGET_SRC_PATH)/coap.cpp CPPSRC += $(TARGET_SRC_PATH)/handshake.cpp -CPPSRC += $(TARGET_SRC_PATH)/events.cpp CPPSRC += $(TARGET_SRC_PATH)/spark_protocol_functions.cpp CPPSRC += $(TARGET_SRC_PATH)/communication_dynalib.cpp CPPSRC += $(TARGET_SRC_PATH)/dsakeygen.cpp diff --git a/communication/src/events.cpp b/communication/src/events.cpp deleted file mode 100644 index c4429ea26d..0000000000 --- a/communication/src/events.cpp +++ /dev/null @@ -1,108 +0,0 @@ -/** - ****************************************************************************** - * @file events.cpp - * @authors Zachary Crockett - * @version V1.0.0 - * @date 26-Feb-2014 - * @brief Internal CoAP event message creation - ****************************************************************************** - Copyright (c) 2014-2015 Particle Industries, Inc. All rights reserved. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation, either - version 3 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see . - ****************************************************************************** - */ -#include "events.h" -#include - -// Private, used by two subscription variants below -uint8_t *subscription_prelude(uint8_t buf[], uint16_t message_id, - const char *event_name) -{ - uint8_t *p = buf; - *p++ = 0x40; // confirmable, no token - *p++ = 0x01; // code 0.01 GET request - *p++ = message_id >> 8; - *p++ = message_id & 0xff; - *p++ = 0xb1; // one-byte Uri-Path option - *p++ = 'e'; - - if (NULL != event_name) - { - size_t len = strnlen(event_name, 64); - p += event_name_uri_path(p, event_name, len); - } - - return p; -} - -size_t subscription(uint8_t buf[], uint16_t message_id, - const char *event_name, const char *device_id) -{ - uint8_t *p = subscription_prelude(buf, message_id, event_name); - - if (NULL != device_id) - { - size_t len = strnlen(device_id, 63); - - *p++ = 0xff; - memcpy(p, device_id, len); - p += len; - } - - return p - buf; -} - -size_t subscription(uint8_t buf[], uint16_t message_id, - const char *event_name, SubscriptionScope::Enum scope) -{ - uint8_t *p = subscription_prelude(buf, message_id, event_name); - - switch (scope) - { - case SubscriptionScope::MY_DEVICES: - *p++ = 0x41; // one-byte Uri-Query option - *p++ = 'u'; - break; - case SubscriptionScope::FIREHOSE: - default: - // unfiltered firehose is not allowed - if (NULL == event_name || 0 == *event_name) - { - return -1; - } - } - - return p - buf; -} - -size_t event_name_uri_path(uint8_t buf[], const char *name, size_t name_len) -{ - if (0 == name_len) - { - return 0; - } - else if (name_len < 13) - { - buf[0] = name_len; - memcpy(buf + 1, name, name_len); - return name_len + 1; - } - else - { - buf[0] = 0x0d; - buf[1] = name_len - 13; - memcpy(buf + 2, name, name_len); - return name_len + 2; - } -} diff --git a/communication/src/protocol.cpp b/communication/src/protocol.cpp index 10cbda99d9..e6e5499551 100644 --- a/communication/src/protocol.cpp +++ b/communication/src/protocol.cpp @@ -697,18 +697,9 @@ ProtocolError Protocol::post_description(int desc_flags, bool force) return description.sendRequest(desc_flags); } -ProtocolError Protocol::send_subscription(const char *event_name, const char *device_id) +ProtocolError Protocol::send_subscription(const char *event_name, int flags) { - const ProtocolError error = subscriptions.send_subscription(channel, event_name, device_id); - if (error == ProtocolError::NO_ERROR && descriptor.app_state_selector_info) { - subscription_msg_ids.append(subscriptions.subscription_message_ids()); - } - return error; -} - -ProtocolError Protocol::send_subscription(const char *event_name, SubscriptionScope::Enum scope) -{ - const ProtocolError error = subscriptions.send_subscription(channel, event_name, scope); + const ProtocolError error = subscriptions.send_subscription(channel, event_name, flags); if (error == ProtocolError::NO_ERROR && descriptor.app_state_selector_info) { subscription_msg_ids.append(subscriptions.subscription_message_ids()); } diff --git a/communication/src/publisher.cpp b/communication/src/publisher.cpp index b4a55e2686..0fded7f8e3 100644 --- a/communication/src/publisher.cpp +++ b/communication/src/publisher.cpp @@ -78,6 +78,9 @@ ProtocolError Publisher::send_event(MessageChannel& channel, const char* event_n if (r < 0) { return ProtocolError::INTERNAL; // Should not happen } + if (r > (int)msg.capacity()) { + return ProtocolError::INSUFFICIENT_STORAGE; + } msg.set_length(r); err = channel.send(msg); diff --git a/communication/src/spark_protocol_functions.cpp b/communication/src/spark_protocol_functions.cpp index 4de8074b38..c16be12b27 100644 --- a/communication/src/spark_protocol_functions.cpp +++ b/communication/src/spark_protocol_functions.cpp @@ -139,22 +139,21 @@ bool spark_protocol_send_event(ProtocolFacade* protocol, const char *event_name, return protocol->send_event(event_name, data, data_size.value(), content_type, ttl, event_type, flags, std::move(handler)); } -bool spark_protocol_send_subscription_device(ProtocolFacade* protocol, const char *event_name, const char *device_id, void*) { - ASSERT_ON_SYSTEM_THREAD(); - const auto error = protocol->send_subscription(event_name, device_id); - return (error == ProtocolError::NO_ERROR); +bool spark_protocol_send_subscription_device_deprecated(ProtocolFacade* protocol, const char* event_name, + const char* /* device_id */, void* /* reserved */) { + return spark_protocol_send_subscription(protocol, event_name, 0 /* flags */, nullptr /* reserved */); } -bool spark_protocol_send_subscription_scope(ProtocolFacade* protocol, const char *event_name, SubscriptionScope::Enum scope, void*) { +bool spark_protocol_send_subscription(ProtocolFacade* protocol, const char* event_name, uint8_t flags, void* /* reserved */) { ASSERT_ON_SYSTEM_THREAD(); - const auto error = protocol->send_subscription(event_name, scope); + const auto error = protocol->send_subscription(event_name, flags); return (error == ProtocolError::NO_ERROR); } -bool spark_protocol_add_event_handler(ProtocolFacade* protocol, const char *event_name, - EventHandler handler, SubscriptionScope::Enum scope, const char* device_id, void* handler_data) { +bool spark_protocol_add_event_handler(ProtocolFacade* protocol, const char* event_name, EventHandler handler, uint8_t flags, + const char* /* device_id_deprecated */, void* handler_data) { ASSERT_ON_SYSTEM_OR_MAIN_THREAD(); - return protocol->add_event_handler(event_name, handler, handler_data, scope, device_id); + return protocol->add_event_handler(event_name, handler, handler_data, flags); } bool spark_protocol_send_time_request(ProtocolFacade* protocol, void* reserved) { diff --git a/communication/src/subscriptions.cpp b/communication/src/subscriptions.cpp index fd7fa33ea5..9f6a49a2ff 100644 --- a/communication/src/subscriptions.cpp +++ b/communication/src/subscriptions.cpp @@ -18,12 +18,48 @@ #include "subscriptions.h" #include "spark_descriptor.h" +#include "coap_message_encoder.h" #include "coap_message_decoder.h" #include "coap_util.h" #include "logging.h" namespace particle::protocol { +ProtocolError Subscriptions::send_subscription(MessageChannel& channel, const char* filter, size_t filterLen, int flags) { + Message msg; + auto err = channel.create(msg); + if (err != ProtocolError::NO_ERROR) { + return err; + } + + CoapMessageEncoder e((char*)msg.buf(), msg.capacity()); + e.type(channel.is_unreliable() ? CoapType::CON : CoapType::NON); + e.code(CoapCode::GET); + e.id(0); // Will be assigned by the message channel + // Subscription messages have an empty token + e.option(CoapOption::URI_PATH, "e"); // 11 + e.option(CoapOption::URI_PATH, filter, filterLen); // 11 + if (flags & SubscriptionFlag::BINARY_DATA) { + e.option(CoapOption::URI_QUERY, "b"); // 15 + } + int r = e.encode(); + if (r < 0) { + return ProtocolError::INTERNAL; // Should not happen + } + if (r > (int)msg.capacity()) { + return ProtocolError::INSUFFICIENT_STORAGE; + } + msg.set_length(r); + + err = channel.send(msg); + if (err != ProtocolError::NO_ERROR) { + return err; + } + subscription_msg_ids.append(msg.get_id()); + + return ProtocolError::NO_ERROR; +} + ProtocolError Subscriptions::handle_event(Message& msg, SparkDescriptor::CallEventHandlerCallback callback, MessageChannel& channel) { CoapMessageDecoder d; int r = d.decode((const char*)msg.buf(), msg.length()); diff --git a/communication/src/subscriptions.h b/communication/src/subscriptions.h index ed9e826356..880bd4f06d 100644 --- a/communication/src/subscriptions.h +++ b/communication/src/subscriptions.h @@ -44,29 +44,7 @@ class Subscriptions Vector subscription_msg_ids; protected: - - ProtocolError send_subscription(MessageChannel& channel, const char* filter, const char* device_id, SubscriptionScope::Enum scope) - { - size_t msglen; - Message message; - channel.create(message); - if (device_id) { - msglen = subscription(message.buf(), 0, filter, device_id); - } else { - msglen = subscription(message.buf(), 0, filter, scope); - } - message.set_length(msglen); - ProtocolError result = channel.send(message); - if (result == ProtocolError::NO_ERROR) { - subscription_msg_ids.append(message.get_id()); - } - return result; - } - - inline ProtocolError send_subscription(MessageChannel& channel, const FilteringEventHandler& handler) - { - return send_subscription(channel, handler.filter, handler.device_id[0] ? handler.device_id : nullptr, handler.scope); - } + ProtocolError send_subscription(MessageChannel& channel, const char* filter, size_t filter_len, int flags); public: @@ -79,11 +57,10 @@ class Subscriptions { uint32_t checksum = 0; for_each([&checksum, calculate_crc](FilteringEventHandler& handler){ - uint32_t chk[4]; + uint32_t chk[3]; chk[0] = checksum; - chk[1] = calculate_crc((const uint8_t*)handler.device_id, sizeof(handler.device_id)); - chk[2] = calculate_crc((const uint8_t*)handler.filter, sizeof(handler.filter)); - chk[3] = calculate_crc((const uint8_t*)&handler.scope, sizeof(handler.scope)); + chk[1] = calculate_crc((const uint8_t*)handler.filter, sizeof(handler.filter)); + chk[2] = calculate_crc((const uint8_t*)&handler.flags, sizeof(handler.flags)); checksum = calculate_crc((const uint8_t*)chk, sizeof(chk)); return NO_ERROR; }); @@ -144,28 +121,19 @@ class Subscriptions /** * Determines if the given handler exists. */ - bool event_handler_exists(const char *event_name, EventHandler handler, - void *handler_data, SubscriptionScope::Enum scope, const char* id) + bool event_handler_exists(const char *event_name, EventHandler handler, void *handler_data) { const int NUM_HANDLERS = sizeof(event_handlers) / sizeof(FilteringEventHandler); for (int i = 0; i < NUM_HANDLERS; i++) { - if (event_handlers[i].handler == handler - && event_handlers[i].handler_data == handler_data - && event_handlers[i].scope == scope) + if (event_handlers[i].handler == handler && event_handlers[i].handler_data == handler_data) { const size_t MAX_FILTER_LEN = sizeof(event_handlers[i].filter); const size_t FILTER_LEN = strnlen(event_name, MAX_FILTER_LEN); if (!strncmp(event_handlers[i].filter, event_name, FILTER_LEN)) { - const size_t MAX_ID_LEN = - sizeof(event_handlers[i].device_id) - 1; - const size_t id_len = id ? strnlen(id, MAX_ID_LEN) : 0; - if (id_len) - return !strncmp(event_handlers[i].device_id, id, id_len); - else - return !event_handlers[i].device_id[0]; + return true; } } } @@ -175,10 +143,9 @@ class Subscriptions /** * Adds the given handler. */ - ProtocolError add_event_handler(const char *event_name, EventHandler handler, - void *handler_data, SubscriptionScope::Enum scope, const char* id) + ProtocolError add_event_handler(const char *event_name, EventHandler handler, void *handler_data, int flags) { - if (event_handler_exists(event_name, handler, handler_data, scope, id)) + if (event_handler_exists(event_name, handler, handler_data)) return NO_ERROR; const int NUM_HANDLERS = sizeof(event_handlers) / sizeof(FilteringEventHandler); @@ -192,46 +159,34 @@ class Subscriptions memset(event_handlers[i].filter + FILTER_LEN, 0, MAX_FILTER_LEN - FILTER_LEN); event_handlers[i].handler = handler; event_handlers[i].handler_data = handler_data; - event_handlers[i].device_id[0] = 0; - const size_t MAX_ID_LEN = sizeof(event_handlers[i].device_id) - 1; - const size_t id_len = id ? strnlen(id, MAX_ID_LEN) : 0; - memcpy(event_handlers[i].device_id, id, id_len); - event_handlers[i].device_id[id_len] = 0; - event_handlers[i].scope = scope; + event_handlers[i].flags = flags; return NO_ERROR; } } return INSUFFICIENT_STORAGE; } - inline ProtocolError send_subscriptions(MessageChannel& channel) + ProtocolError send_subscriptions(MessageChannel& channel) { subscription_msg_ids.clear(); - ProtocolError result = for_each([&](const FilteringEventHandler& handler){return send_subscription(channel, handler);}); - if (result==NO_ERROR) { - // - } + ProtocolError result = for_each([&](const FilteringEventHandler& handler) { + return send_subscription(channel, handler.filter, strnlen(handler.filter, sizeof(handler.filter)), handler.flags); + }); return result; } - inline ProtocolError send_subscription(MessageChannel& channel, const char* filter, const char* device_id) - { - subscription_msg_ids.clear(); - return send_subscription(channel, filter, device_id, SubscriptionScope::MY_DEVICES); - } - - inline ProtocolError send_subscription(MessageChannel& channel, const char* filter, SubscriptionScope::Enum scope) + ProtocolError send_subscription(MessageChannel& channel, const char* filter, int flags) { subscription_msg_ids.clear(); - return send_subscription(channel, filter, nullptr, scope); + return send_subscription(channel, filter, flags); } const Vector& subscription_message_ids() const { return subscription_msg_ids; } - }; -} -} +} // namespace protocol + +} // namespace particle diff --git a/system/inc/system_cloud.h b/system/inc/system_cloud.h index 102b5ad114..31a0ad2556 100644 --- a/system/inc/system_cloud.h +++ b/system/inc/system_cloud.h @@ -189,12 +189,18 @@ PARTICLE_STATIC_ASSERT(publish_no_ack_flag_matches, PUBLISH_EVENT_FLAG_NO_ACK==E typedef void (*EventHandler)(const char* name, const char* data); -typedef enum -{ - MY_DEVICES, - ALL_DEVICES +typedef enum { + MY_DEVICES, // Deprecated + ALL_DEVICES // Deprecated } Spark_Subscription_Scope_TypeDef; +/** + * Subscription flags. + */ +typedef enum { + SUBSCRIBE_FLAG_BINARY_DATA = 0x01 ///< The subscription handler accepts binary data. +} spark_subscribe_flag; + typedef int (*cloud_function_t)(void* data, const char* param, void* reserved); typedef int (user_function_int_str_t)(String paramString); @@ -260,6 +266,14 @@ typedef struct { int content_type; } spark_send_event_data; +/** + * Additional parameters for `spark_subscribe()`. + */ +typedef struct { + size_t size; ///< Size of this structure. + int flags; ///< Subscription flags defined by the `spark_subscribe_flag` enum. +} spark_subscribe_param; + /** * @brief Publish vitals information * @@ -284,8 +298,8 @@ typedef struct { */ int spark_publish_vitals(system_tick_t period_s, void *reserved); bool spark_send_event(const char* name, const char* data, int ttl, uint32_t flags, void* reserved); -bool spark_subscribe(const char *eventName, EventHandler handler, void* handler_data, - Spark_Subscription_Scope_TypeDef scope, const char* deviceID, void* reserved); +bool spark_subscribe(const char* event_name, EventHandler handler, void* handler_data, + Spark_Subscription_Scope_TypeDef scope_deprecated, const char* device_id_deprecated, spark_subscribe_param* param); void spark_unsubscribe(void *reserved); bool spark_sync_time(void *reserved); bool spark_sync_time_pending(void* reserved); diff --git a/system/inc/system_dynalib_cloud.h b/system/inc/system_dynalib_cloud.h index cb34649755..f7e75e6622 100644 --- a/system/inc/system_dynalib_cloud.h +++ b/system/inc/system_dynalib_cloud.h @@ -44,7 +44,7 @@ DYNALIB_FN(5, system_cloud, spark_cloud_flag_connected, bool(void)) DYNALIB_FN(6, system_cloud, system_cloud_protocol_instance, ProtocolFacade*(void)) DYNALIB_FN(7, system_cloud, spark_deviceID, String(void)) DYNALIB_FN(8, system_cloud, spark_send_event, bool(const char*, const char*, int, uint32_t, void*)) -DYNALIB_FN(9, system_cloud, spark_subscribe, bool(const char*, EventHandler, void*, Spark_Subscription_Scope_TypeDef, const char*, void*)) +DYNALIB_FN(9, system_cloud, spark_subscribe, bool(const char*, EventHandler, void*, Spark_Subscription_Scope_TypeDef, const char*, spark_subscribe_param*)) DYNALIB_FN(10, system_cloud, spark_unsubscribe, void(void*)) DYNALIB_FN(11, system_cloud, spark_sync_time, bool(void*)) DYNALIB_FN(12, system_cloud, spark_sync_time_pending, bool(void*)) diff --git a/system/src/system_cloud.cpp b/system/src/system_cloud.cpp index 4be635ef9c..f7deb26cc5 100644 --- a/system/src/system_cloud.cpp +++ b/system/src/system_cloud.cpp @@ -83,21 +83,6 @@ int getConnectionProperty(protocol::Connection::Enum property, void* data, size_ } // namespace -SubscriptionScope::Enum convert(Spark_Subscription_Scope_TypeDef subscription_type) -{ - return(subscription_type==MY_DEVICES) ? SubscriptionScope::MY_DEVICES : SubscriptionScope::FIREHOSE; -} - -bool register_event(const char* eventName, SubscriptionScope::Enum event_scope, const char* deviceID) -{ - bool success; - if (deviceID) - success = spark_protocol_send_subscription_device(sp, eventName, deviceID); - else - success = spark_protocol_send_subscription_scope(sp, eventName, event_scope); - return success; -} - int spark_publish_vitals(system_tick_t period_s_, void* reserved_) { SYSTEM_THREAD_CONTEXT_SYNC(spark_publish_vitals(period_s_, reserved_)); @@ -121,17 +106,19 @@ int spark_publish_vitals(system_tick_t period_s_, void* reserved_) return result; } -bool spark_subscribe(const char *eventName, EventHandler handler, void* handler_data, - Spark_Subscription_Scope_TypeDef scope, const char* deviceID, void* reserved) +bool spark_subscribe(const char* event_name, EventHandler handler, void* handler_data, + Spark_Subscription_Scope_TypeDef scope_deprecated, const char* device_id_deprecated, spark_subscribe_param* param) { - SYSTEM_THREAD_CONTEXT_SYNC(spark_subscribe(eventName, handler, handler_data, scope, deviceID, reserved)); - auto event_scope = convert(scope); - bool success = spark_protocol_add_event_handler(sp, eventName, handler, event_scope, deviceID, handler_data); - if (success && spark_cloud_flag_connected() && (system_mode() != AUTOMATIC || APPLICATION_SETUP_DONE)) - { - register_event(eventName, event_scope, deviceID); + SYSTEM_THREAD_CONTEXT_SYNC(spark_subscribe(event_name, handler, handler_data, scope_deprecated, device_id_deprecated, param)); + int flags = 0; + if (param && param->flags & SUBSCRIBE_FLAG_BINARY_DATA) { + flags |= SubscriptionFlag::BINARY_DATA; + } + bool ok = spark_protocol_add_event_handler(sp, event_name, handler, flags, nullptr /* device_id_deprecated */, handler_data); + if (ok && spark_cloud_flag_connected() && (system_mode() != AUTOMATIC || APPLICATION_SETUP_DONE)) { + ok = spark_protocol_send_subscription(sp, event_name, flags, nullptr /* reserved */); } - return success; + return ok; } void spark_unsubscribe(void *reserved) @@ -170,7 +157,7 @@ system_tick_t spark_sync_time_last(time32_t* tm32, time_t* tm) * Convert from the API flags to the communications lib flags * The event visibility flag (public/private) is encoded differently. The other flags map directly. */ -inline uint32_t convert(uint32_t flags) { +inline uint32_t convert_event_flags(uint32_t flags) { bool priv = flags & PUBLISH_EVENT_FLAG_PRIVATE; flags &= ~PUBLISH_EVENT_FLAG_PRIVATE; flags |= !priv ? EventType::PUBLIC : EventType::PRIVATE; @@ -207,7 +194,7 @@ bool spark_send_event(const char* name, const char* data, int ttl, uint32_t flag d.data_size = data ? std::strlen(data) : 0; } - return spark_protocol_send_event(sp, name, data, ttl, convert(flags), &d); + return spark_protocol_send_event(sp, name, data, ttl, convert_event_flags(flags), &d); } bool spark_variable(const char *varKey, const void *userVar, Spark_Data_TypeDef userVarType, spark_variable_t* extra) diff --git a/wiring/inc/spark_wiring_cloud.h b/wiring/inc/spark_wiring_cloud.h index a3782d2df9..c93e4bb95b 100644 --- a/wiring/inc/spark_wiring_cloud.h +++ b/wiring/inc/spark_wiring_cloud.h @@ -322,27 +322,32 @@ class CloudClass { int publishVitals(system_tick_t period_s = particle::NOW); inline int publishVitals(std::chrono::seconds s) { return publishVitals(s.count()); } + [[deprecated("Subscription scope has no effect")]] inline bool subscribe(const char *eventName, EventHandler handler, Spark_Subscription_Scope_TypeDef scope) { return spark_subscribe(eventName, handler, NULL, scope, NULL, NULL); } + [[deprecated("Subscription scope has no effect")]] inline bool subscribe(const char *eventName, EventHandler handler, const char *deviceID) { return spark_subscribe(eventName, handler, NULL, MY_DEVICES, deviceID, NULL); } + [[deprecated("Subscription scope has no effect")]] bool subscribe(const char *eventName, wiring_event_handler_t handler, Spark_Subscription_Scope_TypeDef scope) { return subscribe_wiring(eventName, handler, scope); } + [[deprecated("Subscription scope has no effect")]] bool subscribe(const char *eventName, wiring_event_handler_t handler, const char *deviceID) { return subscribe_wiring(eventName, handler, MY_DEVICES, deviceID); } template + [[deprecated("Subscription scope has no effect")]] bool subscribe(const char *eventName, void (T::*handler)(const char *, const char *), T *instance, Spark_Subscription_Scope_TypeDef scope) { using namespace std::placeholders; @@ -350,6 +355,7 @@ class CloudClass { } template + [[deprecated("Subscription scope has no effect")]] bool subscribe(const char *eventName, void (T::*handler)(const char *, const char *), T *instance, const char *deviceID) { using namespace std::placeholders; diff --git a/wiring/src/spark_wiring_cloud.cpp b/wiring/src/spark_wiring_cloud.cpp index ff80a2b26a..b8196140df 100644 --- a/wiring/src/spark_wiring_cloud.cpp +++ b/wiring/src/spark_wiring_cloud.cpp @@ -19,6 +19,14 @@ void publishCompletionCallback(int error, const void* data, void* callbackData, } } +inline bool subscribeWithContentType(const char* name, EventHandler handler, void* handlerData) { + spark_subscribe_param param = {}; + param.size = sizeof(param); + param.flags |= SUBSCRIBE_FLAG_BINARY_DATA; + + return spark_subscribe(name, handler, handlerData, MY_DEVICES, nullptr /* device_id_deprecated */, ¶m); +} + void subscribeWithContentTypeCallbackWrapper(void* arg, const char* name, const char* data, size_t dataSize, int contentType) { auto cb = (EventHandlerWithContentType)arg; cb(name, (const uint8_t*)data, dataSize, (ContentType)contentType); @@ -178,8 +186,7 @@ int CloudClass::useLedgersImpl(const Vector& usedNames) { #endif // Wiring_Ledger bool CloudClass::subscribe(const char* name, EventHandlerWithContentType handler) { - return spark_subscribe(name, (EventHandler)subscribeWithContentTypeCallbackWrapper, (void*)handler, ALL_DEVICES, - nullptr /* deviceID */, nullptr /* reserved */); + return subscribeWithContentType(name, (EventHandler)subscribeWithContentTypeCallbackWrapper, (void*)handler); } bool CloudClass::subscribe(const char* name, EventHandlerWithContentTypeFn handler) { @@ -187,6 +194,5 @@ bool CloudClass::subscribe(const char* name, EventHandlerWithContentTypeFn handl if (!fnPtr) { return false; } - return spark_subscribe(name, (EventHandler)subscribeWithContentTypeFunctionWrapper, fnPtr, ALL_DEVICES, - nullptr /* deviceID */, nullptr /* reserved */); + return subscribeWithContentType(name, (EventHandler)subscribeWithContentTypeFunctionWrapper, fnPtr); } From 53044c074472877ae380410070126a2f424d5b3b Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Thu, 1 Aug 2024 13:53:22 +0200 Subject: [PATCH 08/45] When receiving a binary-encoded event, do not invoke a handler that only accepts text data --- communication/inc/coap_defs.h | 12 +++++++++--- communication/src/coap_defs.cpp | 12 ++++++++++++ communication/src/subscriptions.cpp | 3 +++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/communication/inc/coap_defs.h b/communication/inc/coap_defs.h index ffb0351421..86f8c54d70 100644 --- a/communication/inc/coap_defs.h +++ b/communication/inc/coap_defs.h @@ -108,12 +108,16 @@ enum class CoapOption { PARTICLE_DEFINE_ENUM_COMPARISON_OPERATORS(CoapOption) enum class CoapContentFormat { - // https://www.iana.org/assignments/core-parameters/core-parameters.xhtml#content-formats + // RFC 7252 TEXT_PLAIN = 0, // text/plain; charset=utf-8 - IMAGE_JPEG = 22, - IMAGE_PNG = 23, + APPLICATION_LINK_FORMAT = 40, + APPLICATION_XML = 41, APPLICATION_OCTET_STREAM = 42, + APPLICATION_EXI = 47, APPLICATION_JSON = 50, + // https://www.iana.org/assignments/core-parameters/core-parameters.xhtml#content-formats + IMAGE_JPEG = 22, + IMAGE_PNG = 23, APPLICATION_CBOR = 60 }; @@ -188,6 +192,8 @@ inline bool isCoapEmptyAck(CoapType type, unsigned code) { return type == CoapType::ACK && code == CoapCode::EMPTY; } +bool isCoapTextContentFormat(unsigned fmt); + } // namespace protocol } // namespace particle diff --git a/communication/src/coap_defs.cpp b/communication/src/coap_defs.cpp index a2513222cf..369c558dd1 100644 --- a/communication/src/coap_defs.cpp +++ b/communication/src/coap_defs.cpp @@ -60,6 +60,18 @@ CoapCode coapCodeForSystemError(int error) { } } +bool isCoapTextContentFormat(unsigned fmt) { + switch (fmt) { + case (unsigned)CoapContentFormat::TEXT_PLAIN: + case (unsigned)CoapContentFormat::APPLICATION_LINK_FORMAT: + case (unsigned)CoapContentFormat::APPLICATION_XML: + case (unsigned)CoapContentFormat::APPLICATION_JSON: + return true; + default: + return false; + } +} + } // namespace protocol } // namespace particle diff --git a/communication/src/subscriptions.cpp b/communication/src/subscriptions.cpp index 9f6a49a2ff..ae3b86f878 100644 --- a/communication/src/subscriptions.cpp +++ b/communication/src/subscriptions.cpp @@ -112,6 +112,9 @@ ProtocolError Subscriptions::handle_event(Message& msg, SparkDescriptor::CallEve continue; } if (!std::memcmp(event_handlers[i].filter, name, filterLen)) { + if (!(event_handlers[i].flags & SubscriptionFlag::BINARY_DATA) && !isCoapTextContentFormat(contentFmt)) { + continue; // Do not invoke a handler that only accepts text data + } char* data = nullptr; size_t dataSize = d.payloadSize(); if (dataSize > 0) { From 4402960e9a8124b7e08efe9f7a90d269d1ae8f73 Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Thu, 1 Aug 2024 14:31:53 +0200 Subject: [PATCH 09/45] Bugfix --- communication/src/subscriptions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/communication/src/subscriptions.cpp b/communication/src/subscriptions.cpp index ae3b86f878..0a9de7b617 100644 --- a/communication/src/subscriptions.cpp +++ b/communication/src/subscriptions.cpp @@ -77,7 +77,7 @@ ProtocolError Subscriptions::handle_event(Message& msg, SparkDescriptor::CallEve char name[MAX_EVENT_NAME_LENGTH + 1]; size_t nameLen = 0; - int contentFmt = -1; + int contentFmt = (int)CoapContentFormat::TEXT_PLAIN; bool skipUriPrefix = true; auto it = d.options(); From 3b9b0c9443d17292c689a8004cfbd63a11f2f014 Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Mon, 5 Aug 2024 16:29:02 +0200 Subject: [PATCH 10/45] Bugfix --- communication/inc/coap_util.h | 8 ++++++-- communication/src/coap_util.cpp | 2 +- system/src/system_cloud_internal.cpp | 4 ++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/communication/inc/coap_util.h b/communication/inc/coap_util.h index 82993288bf..b0f2f8551e 100644 --- a/communication/inc/coap_util.h +++ b/communication/inc/coap_util.h @@ -104,13 +104,17 @@ int sendEmptyAckOrRst(MessageChannel& channel, Message& msg, CoapType type); /** * Append an URI path entry to a string. * + * If the path in the buffer is not empty (`pathLen > 0`), appends a separator character to it, + * otherwise appends just the value of the URI path option. + * * The output is null-terminated unless the size of the buffer is 0. * * @param buf Destination buffer. * @param bufSize Buffer size. * @param pathLen Length of the URI path already stored in the buffer. - * @param it CoAP options iterator. - * @return Length of the URI path entry plus one character for a path separator. + * @param it Iterator pointing to a URI path CoAP option. + * @return Length of the URI path entry plus one character for a path separator if the path in the + * buffer wasn't empty. */ size_t appendUriPath(char* buf, size_t bufSize, size_t pathLen, const CoapOptionIterator& it); diff --git a/communication/src/coap_util.cpp b/communication/src/coap_util.cpp index ba73b9f753..41c88f76e5 100644 --- a/communication/src/coap_util.cpp +++ b/communication/src/coap_util.cpp @@ -71,7 +71,7 @@ size_t appendUriPath(char* buf, size_t bufSize, size_t pathLen, const CoapOption } *buf = '\0'; } - return it.size() + 1; + return it.size() + (pathLen > 0) ? 1 : 0; } void logCoapMessage(LogLevel level, const char* category, const char* data, size_t size, bool logPayload) { diff --git a/system/src/system_cloud_internal.cpp b/system/src/system_cloud_internal.cpp index 6b29390e89..67b8c72fde 100644 --- a/system/src/system_cloud_internal.cpp +++ b/system/src/system_cloud_internal.cpp @@ -205,8 +205,8 @@ int sendApplicationDescription() { } void registerSystemSubscriptions() { - bool ok = Particle.subscribe("particle", systemEventHandler, MY_DEVICES); - ok = Particle.subscribe("spark", systemEventHandler, MY_DEVICES) && ok; + bool ok = Particle.subscribe("particle", systemEventHandler); + ok = Particle.subscribe("spark", systemEventHandler) && ok; if (!ok) { LOG(ERROR, "Particle.subscribe() failed"); } From 95da3de2ad6916f352ae6289d44f4fb605fe6612 Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Tue, 6 Aug 2024 13:58:06 +0200 Subject: [PATCH 11/45] Minor fixes --- communication/src/publisher.cpp | 2 +- communication/src/subscriptions.cpp | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/communication/src/publisher.cpp b/communication/src/publisher.cpp index 0fded7f8e3..d8730b70ea 100644 --- a/communication/src/publisher.cpp +++ b/communication/src/publisher.cpp @@ -57,7 +57,7 @@ ProtocolError Publisher::send_event(MessageChannel& channel, const char* event_n CoapMessageEncoder e((char*)msg.buf(), msg.capacity()); e.type(confirmable ? CoapType::CON : CoapType::NON); e.code(CoapCode::POST); - e.id(0); // Will be assigned by the message channel + e.id(0); // Will be assigned and serialized by the message channel // Event messages have an empty token e.option(CoapOption::URI_PATH, event_type); // "e" or "E" (option number: 11) size_t name_len = strnlen(event_name, MAX_EVENT_NAME_LENGTH); diff --git a/communication/src/subscriptions.cpp b/communication/src/subscriptions.cpp index 0a9de7b617..4ef3629c78 100644 --- a/communication/src/subscriptions.cpp +++ b/communication/src/subscriptions.cpp @@ -35,7 +35,7 @@ ProtocolError Subscriptions::send_subscription(MessageChannel& channel, const ch CoapMessageEncoder e((char*)msg.buf(), msg.capacity()); e.type(channel.is_unreliable() ? CoapType::CON : CoapType::NON); e.code(CoapCode::GET); - e.id(0); // Will be assigned by the message channel + e.id(0); // Will be assigned and serialized by the message channel // Subscription messages have an empty token e.option(CoapOption::URI_PATH, "e"); // 11 e.option(CoapOption::URI_PATH, filter, filterLen); // 11 @@ -90,8 +90,7 @@ ProtocolError Subscriptions::handle_event(Message& msg, SparkDescriptor::CallEve } nameLen += appendUriPath(name, sizeof(name), nameLen, it); if (nameLen >= sizeof(name)) { - LOG(ERROR, "Event name is too long"); - return ProtocolError::MALFORMED_MESSAGE; + nameLen = sizeof(name) - 1; // Truncate the event name } break; } From 6e168f38d254881dfa0ae3eaa63e5cb5639e520d Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Tue, 6 Aug 2024 14:25:36 +0200 Subject: [PATCH 12/45] Ignore event visibility flags; minor fixes --- communication/inc/events.h | 24 ++++++------------- communication/inc/protocol.h | 6 ++--- communication/src/publisher.cpp | 7 +++--- communication/src/publisher.h | 5 ++-- .../src/spark_protocol_functions.cpp | 3 +-- system/src/system_cloud.cpp | 21 +++++----------- wiring/inc/spark_wiring_cloud.h | 10 ++++---- wiring/src/spark_wiring_cloud.cpp | 4 ++-- 8 files changed, 29 insertions(+), 51 deletions(-) diff --git a/communication/inc/events.h b/communication/inc/events.h index 7f5e88e63c..58527537bd 100644 --- a/communication/inc/events.h +++ b/communication/inc/events.h @@ -32,19 +32,19 @@ namespace EventType { enum Enum : char { - PUBLIC = 'e', // 0x65 - PRIVATE = 'E', // 0x45 + PUBLIC = 'e', // Deprecated (0x65) + PRIVATE = 'E', // Deprecated (0x45) }; /** * These flags are encoded into the same 32-bit integer that already holds EventType::Enum */ enum Flags { - EMPTY_FLAGS = 0, - NO_ACK = 0x2, - WITH_ACK = 0x8, - ASYNC = 0x10, // not used here, but reserved since it's used in the system layer. Makes conversion simpler. - ALL_FLAGS = NO_ACK | WITH_ACK | ASYNC + EMPTY_FLAGS = 0, + NO_ACK = 0x2, + WITH_ACK = 0x8, + ASYNC = 0x10, // not used here, but reserved since it's used in the system layer. Makes conversion simpler. + ALL_FLAGS = NO_ACK | WITH_ACK | ASYNC }; static_assert((PUBLIC & NO_ACK)==0 && @@ -53,16 +53,6 @@ namespace EventType { (PRIVATE & WITH_ACK)==0 && (PRIVATE & ASYNC)==0 && (PUBLIC & ASYNC)==0, "flags should be distinct from event type"); - -/** - * The flags are encoded in with the event type. - */ - inline Enum extract_event_type(uint32_t& value) - { - Enum et = Enum(value & ~ALL_FLAGS); - value = value & ALL_FLAGS; - return et; - } } // namespace EventType #if PLATFORM_ID != PLATFORM_GCC diff --git a/communication/inc/protocol.h b/communication/inc/protocol.h index 3ec53805a6..3bc97dcd8b 100644 --- a/communication/inc/protocol.h +++ b/communication/inc/protocol.h @@ -500,8 +500,8 @@ class Protocol ProtocolError post_description(int desc_flags, bool force); // Returns true on success, false on sending timeout or rate-limiting failure - bool send_event(const char *event_name, const char *data, size_t data_size, int content_type, int ttl, - EventType::Enum event_type, int flags, CompletionHandler handler) + bool send_event(const char *event_name, const char *data, size_t data_size, int content_type, int ttl, int flags, + CompletionHandler handler) { #if HAL_PLATFORM_OTA_PROTOCOL_V3 const bool updating = firmwareUpdate.isRunning(); @@ -514,7 +514,7 @@ class Protocol return false; } const ProtocolError error = publisher.send_event(channel, event_name, data, data_size, content_type, ttl, - event_type, flags, callbacks.millis(), std::move(handler)); + flags, callbacks.millis(), std::move(handler)); if (error != NO_ERROR) { handler.setError(toSystemError(error)); diff --git a/communication/src/publisher.cpp b/communication/src/publisher.cpp index d8730b70ea..7994230f3f 100644 --- a/communication/src/publisher.cpp +++ b/communication/src/publisher.cpp @@ -31,9 +31,8 @@ void Publisher::add_ack_handler(message_id_t msg_id, CompletionHandler handler) protocol->add_ack_handler(msg_id, std::move(handler), SEND_EVENT_ACK_TIMEOUT); } -ProtocolError Publisher::send_event(MessageChannel& channel, const char* event_name, - const char* data, size_t data_size, int content_type, int ttl, EventType::Enum event_type, int flags, - system_tick_t time, CompletionHandler handler) { +ProtocolError Publisher::send_event(MessageChannel& channel, const char* event_name, const char* data, size_t data_size, + int content_type, int ttl, int flags, system_tick_t time, CompletionHandler handler) { bool is_system_event = is_system(event_name); bool rate_limited = is_rate_limited(is_system_event, time); if (rate_limited) { @@ -59,7 +58,7 @@ ProtocolError Publisher::send_event(MessageChannel& channel, const char* event_n e.code(CoapCode::POST); e.id(0); // Will be assigned and serialized by the message channel // Event messages have an empty token - e.option(CoapOption::URI_PATH, event_type); // "e" or "E" (option number: 11) + e.option(CoapOption::URI_PATH, "E"); // 11 size_t name_len = strnlen(event_name, MAX_EVENT_NAME_LENGTH); e.option(CoapOption::URI_PATH, event_name, name_len); // 11 if (content_type != CoapContentFormat::TEXT_PLAIN) { diff --git a/communication/src/publisher.h b/communication/src/publisher.h index 837f15ed4c..35be620423 100644 --- a/communication/src/publisher.h +++ b/communication/src/publisher.h @@ -87,9 +87,8 @@ class Publisher return false; } - ProtocolError send_event(MessageChannel& channel, const char* event_name, - const char* data, size_t data_size, int content_type, int ttl, EventType::Enum event_type, int flags, - system_tick_t time, CompletionHandler handler); + ProtocolError send_event(MessageChannel& channel, const char* event_name, const char* data, size_t data_size, + int content_type, int ttl, int flags, system_tick_t time, CompletionHandler handler); private: Protocol* protocol; diff --git a/communication/src/spark_protocol_functions.cpp b/communication/src/spark_protocol_functions.cpp index c16be12b27..ba56bc4c0c 100644 --- a/communication/src/spark_protocol_functions.cpp +++ b/communication/src/spark_protocol_functions.cpp @@ -135,8 +135,7 @@ bool spark_protocol_send_event(ProtocolFacade* protocol, const char *event_name, if (!data_size.has_value()) { data_size = data ? std::strlen(data) : 0; } - EventType::Enum event_type = EventType::extract_event_type(flags); - return protocol->send_event(event_name, data, data_size.value(), content_type, ttl, event_type, flags, std::move(handler)); + return protocol->send_event(event_name, data, data_size.value(), content_type, ttl, flags, std::move(handler)); } bool spark_protocol_send_subscription_device_deprecated(ProtocolFacade* protocol, const char* event_name, diff --git a/system/src/system_cloud.cpp b/system/src/system_cloud.cpp index f7deb26cc5..3439c08e1a 100644 --- a/system/src/system_cloud.cpp +++ b/system/src/system_cloud.cpp @@ -153,24 +153,12 @@ system_tick_t spark_sync_time_last(time32_t* tm32, time_t* tm) return spark_protocol_time_last_synced(system_cloud_protocol_instance(), tm32, tm); } -/** - * Convert from the API flags to the communications lib flags - * The event visibility flag (public/private) is encoded differently. The other flags map directly. - */ -inline uint32_t convert_event_flags(uint32_t flags) { - bool priv = flags & PUBLISH_EVENT_FLAG_PRIVATE; - flags &= ~PUBLISH_EVENT_FLAG_PRIVATE; - flags |= !priv ? EventType::PUBLIC : EventType::PRIVATE; - return flags; -} - bool spark_send_event(const char* name, const char* data, int ttl, uint32_t flags, void* reserved) { if (flags & PUBLISH_EVENT_FLAG_ASYNC) { SYSTEM_THREAD_CONTEXT_ASYNC_RESULT(spark_send_event(name, data, ttl, flags, reserved), true); - } - else { - SYSTEM_THREAD_CONTEXT_SYNC(spark_send_event(name, data, ttl, flags, reserved)); + } else { + SYSTEM_THREAD_CONTEXT_SYNC(spark_send_event(name, data, ttl, flags, reserved)); } spark_protocol_send_event_data d = {}; @@ -194,7 +182,10 @@ bool spark_send_event(const char* name, const char* data, int ttl, uint32_t flag d.data_size = data ? std::strlen(data) : 0; } - return spark_protocol_send_event(sp, name, data, ttl, convert_event_flags(flags), &d); + // Visibility flags no longer have effect + flags &= ~PUBLISH_EVENT_FLAG_PRIVATE; + + return spark_protocol_send_event(sp, name, data, ttl, flags, &d); } bool spark_variable(const char *varKey, const void *userVar, Spark_Data_TypeDef userVarType, spark_variable_t* extra) diff --git a/wiring/inc/spark_wiring_cloud.h b/wiring/inc/spark_wiring_cloud.h index c93e4bb95b..7ab28aacbc 100644 --- a/wiring/inc/spark_wiring_cloud.h +++ b/wiring/inc/spark_wiring_cloud.h @@ -86,8 +86,8 @@ enum class ContentType: int { CBOR = (int)protocol::CoapContentFormat::APPLICATION_CBOR ///< `application/cbor`. }; -typedef void (*EventHandlerWithContentType)(const char* name, const uint8_t* data, size_t size, ContentType type); -typedef std::function EventHandlerWithContentTypeFn; +typedef void (*EventHandlerWithContentType)(const char* name, const char* data, size_t size, ContentType type); +typedef std::function EventHandlerWithContentTypeFn; } // namespace particle @@ -290,11 +290,11 @@ class CloudClass { particle::Future publish(const char* name, const char* data, int ttl); particle::Future publish(const char* name, const char* data, particle::ContentType type, PublishFlags flags = PublishFlags()) { - return publish_event(name, data, std::strlen(data), type, DEFAULT_CLOUD_EVENT_TTL, flags); + return publish(name, data, std::strlen(data), type, flags); } - particle::Future publish(const char* name, const uint8_t* data, size_t size, particle::ContentType type, PublishFlags flags = PublishFlags()) { - return publish_event(name, (const char*)data, size, type, DEFAULT_CLOUD_EVENT_TTL, flags); + particle::Future publish(const char* name, const char* data, size_t size, particle::ContentType type, PublishFlags flags = PublishFlags()) { + return publish_event(name, data, size, type, DEFAULT_CLOUD_EVENT_TTL, flags); } /** diff --git a/wiring/src/spark_wiring_cloud.cpp b/wiring/src/spark_wiring_cloud.cpp index b8196140df..be57eaeb9c 100644 --- a/wiring/src/spark_wiring_cloud.cpp +++ b/wiring/src/spark_wiring_cloud.cpp @@ -29,12 +29,12 @@ inline bool subscribeWithContentType(const char* name, EventHandler handler, voi void subscribeWithContentTypeCallbackWrapper(void* arg, const char* name, const char* data, size_t dataSize, int contentType) { auto cb = (EventHandlerWithContentType)arg; - cb(name, (const uint8_t*)data, dataSize, (ContentType)contentType); + cb(name, data, dataSize, (ContentType)contentType); } void subscribeWithContentTypeFunctionWrapper(void* arg, const char* name, const char* data, size_t dataSize, int contentType) { auto fn = (EventHandlerWithContentTypeFn*)arg; - (*fn)(name, (const uint8_t*)data, dataSize, (ContentType)contentType); + (*fn)(name, data, dataSize, (ContentType)contentType); } } // namespace From bd2884a873656632bb7063e13d5d6f45e201a367 Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Tue, 6 Aug 2024 15:13:38 +0200 Subject: [PATCH 13/45] Fix tests --- communication/src/coap_util.cpp | 4 +++ test/unit_tests/communication/CMakeLists.txt | 3 +- test/unit_tests/communication/protocol.cpp | 30 ++++++++++++++++++-- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/communication/src/coap_util.cpp b/communication/src/coap_util.cpp index 41c88f76e5..96c555211b 100644 --- a/communication/src/coap_util.cpp +++ b/communication/src/coap_util.cpp @@ -21,6 +21,7 @@ #include #include +#include #include "coap_util.h" @@ -28,6 +29,7 @@ #include "coap_message_encoder.h" #include "coap_message_decoder.h" #include "str_util.h" +#include "logging.h" #include "check.h" namespace particle::protocol { @@ -75,6 +77,7 @@ size_t appendUriPath(char* buf, size_t bufSize, size_t pathLen, const CoapOption } void logCoapMessage(LogLevel level, const char* category, const char* data, size_t size, bool logPayload) { +#ifndef LOG_DISABLE CoapMessageDecoder d; const int r = d.decode(data, size); if (r < 0) { @@ -144,6 +147,7 @@ void logCoapMessage(LogLevel level, const char* category, const char* data, size log_dump(level, category, d.payload(), d.payloadSize(), 0 /* flags */, nullptr /* reserved */); log_write(level, category, "\r\n", 2 /* size */, nullptr /* reserved */); } +#endif // !defined(LOG_DISABLE) } } // namespace particle::protocol diff --git a/test/unit_tests/communication/CMakeLists.txt b/test/unit_tests/communication/CMakeLists.txt index c07130d44b..6658f2e907 100644 --- a/test/unit_tests/communication/CMakeLists.txt +++ b/test/unit_tests/communication/CMakeLists.txt @@ -6,12 +6,13 @@ add_executable( ${target_name} ${DEVICE_OS_DIR}/communication/src/coap.cpp ${DEVICE_OS_DIR}/communication/src/coap_channel.cpp ${DEVICE_OS_DIR}/communication/src/communication_diagnostic.cpp - ${DEVICE_OS_DIR}/communication/src/events.cpp + ${DEVICE_OS_DIR}/communication/src/subscriptions.cpp ${DEVICE_OS_DIR}/communication/src/messages.cpp ${DEVICE_OS_DIR}/communication/src/protocol.cpp ${DEVICE_OS_DIR}/communication/src/publisher.cpp ${DEVICE_OS_DIR}/communication/src/variables.cpp ${DEVICE_OS_DIR}/communication/src/coap_defs.cpp + ${DEVICE_OS_DIR}/communication/src/coap_util.cpp ${DEVICE_OS_DIR}/communication/src/coap_message_encoder.cpp ${DEVICE_OS_DIR}/communication/src/coap_message_decoder.cpp ${DEVICE_OS_DIR}/communication/src/firmware_update.cpp diff --git a/test/unit_tests/communication/protocol.cpp b/test/unit_tests/communication/protocol.cpp index a0c68d4686..bd7a7d01e4 100644 --- a/test/unit_tests/communication/protocol.cpp +++ b/test/unit_tests/communication/protocol.cpp @@ -18,6 +18,7 @@ */ #include "protocol.h" +#include "util/coap_message.h" #include #include "fakeit.hpp" @@ -26,6 +27,8 @@ using namespace fakeit; using namespace particle; using namespace particle::protocol; +namespace { + class AbstractProtocol : public Protocol { public: @@ -55,6 +58,28 @@ class AbstractProtocol : public Protocol } }; +size_t encodeEvent(uint8_t* buf, size_t buf_size, uint16_t msg_id, const char *event_name, const char *data, + size_t data_size, int ttl, EventType::Enum event_type, bool confirmable) +{ + test::CoapMessage m; + m.type(confirmable ? CoapType::CON : CoapType::NON); + m.code(CoapCode::POST); + m.id(msg_id); + m.option(CoapOption::URI_PATH, event_type); // "e" or "E" + m.option(CoapOption::URI_PATH, event_name); + if (ttl != 60) { + m.option(CoapOption::MAX_AGE, ttl); + } + if (data_size > 0) { + m.payload(data, data_size); + } + auto s = m.encode(); + std::memcpy(buf, s.data(), std::min(s.size(), buf_size)); + return s.size(); +} + +} // namespace + SCENARIO("default product co-ordinates are set") { MessageChannel* channel = nullptr; @@ -136,7 +161,7 @@ void event_ack(bool confirmable, bool unreliable) Message event; uint8_t event_buf[50]; event.set_buffer(event_buf, sizeof(event_buf)); - size_t msglen = Messages::event(event_buf, 0x1234, "e", "", 0, 60, EventType::PUBLIC, confirmable); + size_t msglen = encodeEvent(event_buf, sizeof(event_buf), 0x1234, "e", "", 0, 60, EventType::PUBLIC, confirmable); event.set_length(msglen); event.decode_id(); // need this in the test since it's not done by our mock MessageChannel @@ -225,7 +250,8 @@ void verify_event_type_with_flags(int flags, CoAPType::Enum coapType) AbstractProtocol p(channel.get()); Publisher publisher(&p); CompletionHandler completionHandler; - publisher.send_event(channel.get(),"abc","def", 60, EventType::PUBLIC, flags, 0, std::move(completionHandler)); + publisher.send_event(channel.get(), "abc", "def", 3 /* data_size */, 0 /* content_type */, 60 /* ttl */, flags, + 0 /* time */, std::move(completionHandler)); Verify(Method(channel,send)); } From 923716bd8bcbcd1ed7efee9b67c96d959dde6262 Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Tue, 6 Aug 2024 17:28:19 +0200 Subject: [PATCH 14/45] Fix build for nRF52 platforms --- system/src/system_cloud_internal.h | 5 ++--- system/src/system_task.cpp | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/system/src/system_cloud_internal.h b/system/src/system_cloud_internal.h index 0304cd744a..4a6318825f 100644 --- a/system/src/system_cloud_internal.h +++ b/system/src/system_cloud_internal.h @@ -243,9 +243,8 @@ extern "C" { size_t system_interpolate_cloud_server_hostname(const char* var, size_t var_len, char* buf, size_t buf_len); -void invokeEventHandler(uint16_t handlerInfoSize, FilteringEventHandler* handlerInfo, - const char* event_name, const char* event_data, void* reserved); - +void invokeEventHandler(uint16_t handlerInfoSize, FilteringEventHandler* handlerInfo, const char* event_name, + const char* event_data, size_t data_size, int content_type); #ifdef __cplusplus } diff --git a/system/src/system_task.cpp b/system/src/system_task.cpp index 9d08e46f67..7ddf9c76d2 100644 --- a/system/src/system_task.cpp +++ b/system/src/system_task.cpp @@ -770,7 +770,8 @@ int system_invoke_event_handler(uint16_t handlerInfoSize, FilteringEventHandler* const char* event_name, const char* event_data, void* reserved) { #if HAL_PLATFORM_NRF52840 - invokeEventHandler(handlerInfoSize, handlerInfo, event_name, event_data, reserved); + invokeEventHandler(handlerInfoSize, handlerInfo, event_name, event_data, event_data ? std::strlen(event_data) : 0, + (int)ContentType::TEXT); return SYSTEM_ERROR_NONE; #else return SYSTEM_ERROR_NOT_SUPPORTED; From aa1d5db4d507d849cde385f0803153f6f0806dfd Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Tue, 6 Aug 2024 17:58:44 +0200 Subject: [PATCH 15/45] Bugfix --- communication/src/coap_util.cpp | 2 +- communication/src/subscriptions.cpp | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/communication/src/coap_util.cpp b/communication/src/coap_util.cpp index 96c555211b..54e62c402f 100644 --- a/communication/src/coap_util.cpp +++ b/communication/src/coap_util.cpp @@ -73,7 +73,7 @@ size_t appendUriPath(char* buf, size_t bufSize, size_t pathLen, const CoapOption } *buf = '\0'; } - return it.size() + (pathLen > 0) ? 1 : 0; + return it.size() + ((pathLen > 0) ? 1 : 0); } void logCoapMessage(LogLevel level, const char* category, const char* data, size_t size, bool logPayload) { diff --git a/communication/src/subscriptions.cpp b/communication/src/subscriptions.cpp index 4ef3629c78..cc223802d2 100644 --- a/communication/src/subscriptions.cpp +++ b/communication/src/subscriptions.cpp @@ -101,6 +101,9 @@ ProtocolError Subscriptions::handle_event(Message& msg, SparkDescriptor::CallEve break; } } + if (!nameLen) { + return ProtocolError::NO_ERROR; // Ignore an event without a name + } for (size_t i = 0; i < MAX_SUBSCRIPTIONS; ++i) { if (!event_handlers[i].handler) { From 00334f8a575211b5b68dfb5ce0f3c609bef37bdc Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Tue, 6 Aug 2024 18:10:52 +0200 Subject: [PATCH 16/45] Add tests; minor fixes --- .../communication/events/events.cpp | 51 ++++++++++++++++++- .../communication/events/events.spec.js | 48 +++++++++++++++++ user/tests/wiring/api/cloud.cpp | 10 ++++ wiring/inc/spark_wiring_cloud.h | 4 ++ 4 files changed, 111 insertions(+), 2 deletions(-) diff --git a/user/tests/integration/communication/events/events.cpp b/user/tests/integration/communication/events/events.cpp index 606545e23e..f583e20c5a 100644 --- a/user/tests/integration/communication/events/events.cpp +++ b/user/tests/integration/communication/events/events.cpp @@ -1,9 +1,36 @@ #include "application.h" #include "test.h" -test(particle_publish_publishes_an_event) { +namespace { + +String eventName; +String eventData; +ContentType eventContentType = ContentType(); +bool eventReceived = false; + +void eventHandler(const char* name, const char* data, size_t size, ContentType type) { + eventName = name; + eventData = String(data, size); + eventContentType = type; + eventReceived = true; +} + +void clearReceivedEvent() { + eventName = String(); + eventData = String(); + eventContentType = ContentType(); + eventReceived = false; +} + +} // namespace + +test(connect) { + Particle.subscribe(System.deviceID() + "/my_event", eventHandler); Particle.connect(); - waitUntil(Particle.connected); + assertTrue(waitFor(Particle.connected, 60000)); +} + +test(particle_publish_publishes_an_event) { assertTrue((bool)Particle.publish("my_event", "event data", PRIVATE | WITH_ACK)); } @@ -18,3 +45,23 @@ test(verify_max_event_data_size) { auto data = "eI568Df9nXQmUyaDeNE7A4pZnrcdaAxetam6QYQe3lXFwzN3A6ZO2VGutxVBbIWc8EyrqFMtzKByspno2vL1bGB9H6btc5GWysJZ3XLa3paAmAG4P3UZcbg4NuSRTSEr2YsDMTIEF2lSdd51YR0BPsbcEiQN29ufOpfEHXqK7LfJ3lfEMySnl0iX3ajaQ9rlLsKF4vhSoLFQDp3SRAmzfHhLCDHqVFDT9o8I4Ac5ER6cPl5k8wucWJqxQVWCHB2jdrtSX3WNX8Uq14mAuS4L4s2SeP6UlCcWXrzV9AAuBeTON9Jw7Lbe09F7Ijz0KxIPlwnVZDqXV09GbxKXIOA41E1ZeR9Cg23vozKZZzn2cWeeYtJmRi5Evmwmjus72XQM1W7KGZABrQbzSZawK0pRk9Cp7kl2uy39IjxL6ev3nlC8EA2DE7zi1DJHW7bJceUvFevQcHjWHU5FNKx7m48SG2046PDxxl0vnkXQ6hompl04RFmjUnIgEfIT9XZCkes5lPa8T2V8Ueo7aDfPBYSZOX35XBCczj6nXZ9oxVqn9zxH5NrLcmeDsLop77PVmdJles0CWEAAr5zNVOxIETN2jJcksLXRfQ1pESo9YLaBTyjSuDRQqMenYwuv2qFFnEbaZCMqBQRvE4ql0Oo6K9rXKdfO5G8b9c9jSI4g56f1DAiv7iWU99NdMUMVFt2LmYZsT0azi6MztjRsbtVRG2thZUqAhaPuhvZd0Efbd5H01oUN2CIsh9NiMdEkG5ouSMVaLGjIuvfDeFnlKjL7wSvmNauWYQY021dCKfpJCx0Q7XRB9kFDWZLcew61CmCHsEctM4JldvVhKLdWcnKFDttz3CfbFgtkGBVPWSW0hOwA2e5SLNwHyyJyJXNsicFxMpelYlVAhFjSR8nXe0cJqylvmKYUQ85H2Qet4kehs4boQLIqTHeDoDy1ITDbNVnv3PWzbna5kmEiBhyRw4kn6Di1a6r7uamd5fgFAGURi9LYCp3wAuw6PbYpq8rFXFFzkOUniI3q5c1bLDFxRS4zxNOuH31W819DZGM57zimuZ8YeEfAljxmSOeUWQQdlJjZbjgvERF1Dlexe4nROXyDOadc4qlznOKL0u2ttG0hCVPHMXG4s4uP8YLXJMhyNZod6mkdW9R42aWAsJgDMZZnuU7J7HJL9OpOZXPDCl1l2wOlPCyUtVQzG7PD1Db0dIaTMe9YnFtNAPPxAD4JQXNKMkmWRrhVE2VuJlNvokoCZp9pBDYBFJEPHOYWZI93gsR2tdSIa7YQslZRykJRAF90xlBfNvljN9yR64g7Q1IKCbGwr59H2I5WFEHruiIFJpPs9QQOYxlgq9juAJ9GyfmpEwuvF6n49Bi34v9dQGwt5ZMRFB6HgoRTb9PaLCp4e0Ns7zYYY2rWwESeZnPsqADsFFG3pxsisIn8pjLdAlJrAdMiyUGaIvi7Vj6uFmClZMI8i39pnWXfJbUSJtofdeCthZD2awxZJMjC"; assertTrue((bool)Particle.publish("my_event", data, PRIVATE | WITH_ACK)); } + +test(device_to_cloud_event_with_content_type) { + uint8_t data[] = { 0x92, 0xb2, 0xd1, 0x00, 0x23, 0x8c, 0x49, 0x0b, 0xd6, 0xe7, 0xa8, 0x90, 0x50, 0xdb, 0xc5, 0xbb, 0xe5, 0x3f, 0xf3, 0x29, 0xb2, 0x4c, 0xef, 0xad, 0xad, 0xd8, 0x10, 0xd9, 0xc3, 0xa4, 0xf1, 0xb0, 0xe7, 0x74, 0x8e, 0x7e, 0x2e, 0xcf, 0x48, 0xbe, 0x4d, 0xd3, 0xae, 0x08, 0x36, 0x8f, 0x76, 0xa8, 0xd5, 0x50, 0xec, 0x13, 0x9d, 0x5b, 0xca, 0x62, 0x4e, 0x3c, 0x6b, 0x3c, 0xbc, 0x75, 0x85, 0x65, 0x35, 0x6c, 0x00, 0xaf, 0xee, 0x12, 0xf2, 0xbd, 0x3f, 0xf2, 0x27, 0x00, 0xdc, 0x4c, 0xc6, 0xfa, 0x02, 0x16, 0x8e, 0xe5, 0xa1, 0xe6, 0xe9, 0x40, 0x4a, 0x71, 0xd1, 0x7d, 0xa5, 0xa6, 0xb7, 0x8d, 0x7d, 0x47, 0x5f, 0xf2 }; + assertTrue((bool)Particle.publish("my_event", (const char*)data, sizeof(data), ContentType::BINARY, WITH_ACK)); +} + +test(publish_cloud_to_device_event_with_content_type) { + clearReceivedEvent(); +} + +test(validate_cloud_to_device_event_with_content_type) { + assertTrue(waitFor([]() { + return eventReceived; + }, 10000)); + uint8_t expectedData[] = { 0xcb, 0xdf, 0x43, 0x83, 0x00, 0x86, 0x31, 0x72, 0x8b, 0xaf, 0x35, 0xc2, 0xaa, 0xae, 0x5d, 0x2e, 0x77, 0x76, 0x91, 0xb1, 0x31, 0xa5, 0xf1, 0x05, 0x1d, 0x7f, 0xc1, 0x47, 0xa8, 0x1f, 0x2a, 0x90, 0xe5, 0x75, 0x30, 0x9d, 0xdc, 0x28, 0x90, 0x68, 0x8b, 0xb8, 0x6e, 0x6e, 0x85, 0x14, 0x0d, 0x95, 0xc0, 0x64, 0xfd, 0xf3, 0xce, 0x3d, 0xfb, 0x45, 0xa3, 0xa7, 0xfe, 0x3d, 0xcf, 0x94, 0xd8, 0x69, 0xcb, 0x21, 0x39, 0x2c, 0x9a, 0xc9, 0xbb, 0x5b, 0xcb, 0x2d, 0xd9, 0x43, 0xb5, 0xbe, 0x21, 0xdd, 0x3b, 0xe1, 0x8c, 0x87, 0x64, 0x68, 0x00, 0x4d, 0x98, 0x1e, 0x6a, 0xe0, 0x2a, 0x42, 0xe8, 0x05, 0xb9, 0x89, 0xef, 0xcd }; + assertTrue(eventName == System.deviceID() + "/my_event"); + assertTrue(eventData.length() == sizeof(expectedData)); + assertTrue(std::memcmp(eventData.c_str(), expectedData, sizeof(expectedData)) == 0); + assertTrue(eventContentType == ContentType::BINARY); +} diff --git a/user/tests/integration/communication/events/events.spec.js b/user/tests/integration/communication/events/events.spec.js index bbd40183aa..2a667db27d 100644 --- a/user/tests/integration/communication/events/events.spec.js +++ b/user/tests/integration/communication/events/events.spec.js @@ -2,8 +2,35 @@ suite('Cloud events'); platform('gen3', 'gen4'); +let deviceId; let maxEventDataSize = 0; +function parseDataUri(str) { + const m = /^data:(.+?)(;base64)?,(.*)$/.exec(str); + if (!m) { + throw new Error('Failed to parse data URI'); + } + let data; + const base64 = !!m[2]; + if (base64) { + data = Buffer.from(m[3], 'base64'); + } else { + data = decodeURI(m[3]); + } + return { + type: m[1], + data + }; +} + +before(function() { + deviceId = this.particle.devices[0].id; +}); + +test('connect', async function() { + // See events.cpp +}); + test('particle_publish_publishes_an_event', async function() { const data = await this.particle.receiveEvent('my_event'); expect(data).to.equal('event data'); @@ -22,3 +49,24 @@ test('verify_max_event_data_size', async function() { const data = await this.particle.receiveEvent('my_event'); expect(data).to.equal(str.slice(0, maxEventDataSize)); }); + +test('device_to_cloud_event_with_content_type', async function() { + const expectedData = Buffer.from([0x92, 0xb2, 0xd1, 0x00, 0x23, 0x8c, 0x49, 0x0b, 0xd6, 0xe7, 0xa8, 0x90, 0x50, 0xdb, 0xc5, 0xbb, 0xe5, 0x3f, 0xf3, 0x29, 0xb2, 0x4c, 0xef, 0xad, 0xad, 0xd8, 0x10, 0xd9, 0xc3, 0xa4, 0xf1, 0xb0, 0xe7, 0x74, 0x8e, 0x7e, 0x2e, 0xcf, 0x48, 0xbe, 0x4d, 0xd3, 0xae, 0x08, 0x36, 0x8f, 0x76, 0xa8, 0xd5, 0x50, 0xec, 0x13, 0x9d, 0x5b, 0xca, 0x62, 0x4e, 0x3c, 0x6b, 0x3c, 0xbc, 0x75, 0x85, 0x65, 0x35, 0x6c, 0x00, 0xaf, 0xee, 0x12, 0xf2, 0xbd, 0x3f, 0xf2, 0x27, 0x00, 0xdc, 0x4c, 0xc6, 0xfa, 0x02, 0x16, 0x8e, 0xe5, 0xa1, 0xe6, 0xe9, 0x40, 0x4a, 0x71, 0xd1, 0x7d, 0xa5, 0xa6, 0xb7, 0x8d, 0x7d, 0x47, 0x5f, 0xf2]); + let d = await this.particle.receiveEvent('my_event'); + d = parseDataUri(d); + expect(d.type).to.equal('application/octet-stream'); + expect(d.data.equals(expectedData)).to.be.true; +}); + +test('publish_cloud_to_device_event_with_content_type', async function() { + const data = Buffer.from([0xcb, 0xdf, 0x43, 0x83, 0x00, 0x86, 0x31, 0x72, 0x8b, 0xaf, 0x35, 0xc2, 0xaa, 0xae, 0x5d, 0x2e, 0x77, 0x76, 0x91, 0xb1, 0x31, 0xa5, 0xf1, 0x05, 0x1d, 0x7f, 0xc1, 0x47, 0xa8, 0x1f, 0x2a, 0x90, 0xe5, 0x75, 0x30, 0x9d, 0xdc, 0x28, 0x90, 0x68, 0x8b, 0xb8, 0x6e, 0x6e, 0x85, 0x14, 0x0d, 0x95, 0xc0, 0x64, 0xfd, 0xf3, 0xce, 0x3d, 0xfb, 0x45, 0xa3, 0xa7, 0xfe, 0x3d, 0xcf, 0x94, 0xd8, 0x69, 0xcb, 0x21, 0x39, 0x2c, 0x9a, 0xc9, 0xbb, 0x5b, 0xcb, 0x2d, 0xd9, 0x43, 0xb5, 0xbe, 0x21, 0xdd, 0x3b, 0xe1, 0x8c, 0x87, 0x64, 0x68, 0x00, 0x4d, 0x98, 0x1e, 0x6a, 0xe0, 0x2a, 0x42, 0xe8, 0x05, 0xb9, 0x89, 0xef, 0xcd]); + await this.particle.apiClient.instance.publishEvent({ + name: `${deviceId}/my_event`, + data: `data:application/octet-stream;base64,${data.toString('base64')}`, + auth: this.particle.apiClient.token + }); +}); + +test('validate_cloud_to_device_event_with_content_type', async function() { + // See events.cpp +}); diff --git a/user/tests/wiring/api/cloud.cpp b/user/tests/wiring/api/cloud.cpp index ccd6f0b189..4c89a81f43 100644 --- a/user/tests/wiring/api/cloud.cpp +++ b/user/tests/wiring/api/cloud.cpp @@ -145,6 +145,11 @@ test(api_spark_publish) { API_COMPILE(Particle.publish("event", "data", 60, PUBLIC, NO_ACK)); API_COMPILE(Particle.publish("event", "data", 60, PUBLIC | NO_ACK)); + API_COMPILE(Particle.publish("event", "data", ContentType::TEXT)); + API_COMPILE(Particle.publish("event", "data", ContentType::TEXT, NO_ACK)); + API_COMPILE(Particle.publish("event", "data", 4 /* size */, ContentType::TEXT)); + API_COMPILE(Particle.publish("event", "data", 4 /* size */, ContentType::TEXT, NO_ACK)); + // Particle.publish(String, String, ...) API_COMPILE(Particle.publish(String("event"))); API_COMPILE(Particle.publish(String("event"), PUBLIC)); @@ -160,6 +165,9 @@ test(api_spark_publish) { API_COMPILE(Particle.publish(String("event"), String("data"), 60, PUBLIC)); API_COMPILE(Particle.publish(String("event"), String("data"), 60, PUBLIC, NO_ACK)); API_COMPILE(Particle.publish(String("event"), String("data"), 60, PUBLIC | NO_ACK)); + + API_COMPILE(Particle.publish("event", String("data"), ContentType::TEXT)); + API_COMPILE(Particle.publish("event", String("data"), ContentType::TEXT, NO_ACK)); } test(api_spark_publish_vitals) { @@ -198,6 +206,8 @@ test(api_spark_subscribe) { API_COMPILE(Particle.subscribe("name", &MyClass::handler, &myObj, "1234")); + void (*handlerWithContentType)(const char* name, const char* data, size_t size, ContentType type) = nullptr; + API_COMPILE(Particle.subscribe("name", handlerWithContentType)); } test(api_spark_sleep) { diff --git a/wiring/inc/spark_wiring_cloud.h b/wiring/inc/spark_wiring_cloud.h index 7ab28aacbc..73651234a5 100644 --- a/wiring/inc/spark_wiring_cloud.h +++ b/wiring/inc/spark_wiring_cloud.h @@ -293,6 +293,10 @@ class CloudClass { return publish(name, data, std::strlen(data), type, flags); } + particle::Future publish(const char* name, const String& data, particle::ContentType type, PublishFlags flags = PublishFlags()) { + return publish(name, data.c_str(), data.length(), type, flags); + } + particle::Future publish(const char* name, const char* data, size_t size, particle::ContentType type, PublishFlags flags = PublishFlags()) { return publish_event(name, data, size, type, DEFAULT_CLOUD_EVENT_TTL, flags); } From 987d1cc5d52d620d994936e3ca7bd33b4fb0ec5e Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Tue, 6 Aug 2024 18:58:22 +0200 Subject: [PATCH 17/45] Fix GCC build --- communication/src/subscriptions.cpp | 2 +- communication/src/subscriptions.h | 6 +++--- wiring/src/spark_wiring_cloud.cpp | 12 ++++++++++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/communication/src/subscriptions.cpp b/communication/src/subscriptions.cpp index cc223802d2..40fc796379 100644 --- a/communication/src/subscriptions.cpp +++ b/communication/src/subscriptions.cpp @@ -25,7 +25,7 @@ namespace particle::protocol { -ProtocolError Subscriptions::send_subscription(MessageChannel& channel, const char* filter, size_t filterLen, int flags) { +ProtocolError Subscriptions::send_subscription_impl(MessageChannel& channel, const char* filter, size_t filterLen, int flags) { Message msg; auto err = channel.create(msg); if (err != ProtocolError::NO_ERROR) { diff --git a/communication/src/subscriptions.h b/communication/src/subscriptions.h index 880bd4f06d..ff9f6ddf0c 100644 --- a/communication/src/subscriptions.h +++ b/communication/src/subscriptions.h @@ -44,7 +44,7 @@ class Subscriptions Vector subscription_msg_ids; protected: - ProtocolError send_subscription(MessageChannel& channel, const char* filter, size_t filter_len, int flags); + ProtocolError send_subscription_impl(MessageChannel& channel, const char* filter, size_t filter_len, int flags); public: @@ -170,7 +170,7 @@ class Subscriptions { subscription_msg_ids.clear(); ProtocolError result = for_each([&](const FilteringEventHandler& handler) { - return send_subscription(channel, handler.filter, strnlen(handler.filter, sizeof(handler.filter)), handler.flags); + return send_subscription_impl(channel, handler.filter, strnlen(handler.filter, sizeof(handler.filter)), handler.flags); }); return result; } @@ -178,7 +178,7 @@ class Subscriptions ProtocolError send_subscription(MessageChannel& channel, const char* filter, int flags) { subscription_msg_ids.clear(); - return send_subscription(channel, filter, flags); + return send_subscription_impl(channel, filter, std::strlen(filter), flags); } const Vector& subscription_message_ids() const diff --git a/wiring/src/spark_wiring_cloud.cpp b/wiring/src/spark_wiring_cloud.cpp index be57eaeb9c..4b4bac9903 100644 --- a/wiring/src/spark_wiring_cloud.cpp +++ b/wiring/src/spark_wiring_cloud.cpp @@ -186,7 +186,11 @@ int CloudClass::useLedgersImpl(const Vector& usedNames) { #endif // Wiring_Ledger bool CloudClass::subscribe(const char* name, EventHandlerWithContentType handler) { - return subscribeWithContentType(name, (EventHandler)subscribeWithContentTypeCallbackWrapper, (void*)handler); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-function-type" + auto h = (EventHandler)subscribeWithContentTypeCallbackWrapper; +#pragma GCC diagnostic pop + return subscribeWithContentType(name, h, (void*)handler); } bool CloudClass::subscribe(const char* name, EventHandlerWithContentTypeFn handler) { @@ -194,5 +198,9 @@ bool CloudClass::subscribe(const char* name, EventHandlerWithContentTypeFn handl if (!fnPtr) { return false; } - return subscribeWithContentType(name, (EventHandler)subscribeWithContentTypeFunctionWrapper, fnPtr); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-function-type" + auto h = (EventHandler)subscribeWithContentTypeFunctionWrapper; +#pragma GCC diagnostic pop + return subscribeWithContentType(name, h, fnPtr); } From de48abe8fcc74066120a5b7bc05ecfde93dafb84 Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Thu, 8 Aug 2024 10:05:49 +0200 Subject: [PATCH 18/45] Minor fixes --- communication/src/spark_protocol_functions.cpp | 2 +- system/src/system_cloud.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/communication/src/spark_protocol_functions.cpp b/communication/src/spark_protocol_functions.cpp index ba56bc4c0c..397bf2f0e8 100644 --- a/communication/src/spark_protocol_functions.cpp +++ b/communication/src/spark_protocol_functions.cpp @@ -118,7 +118,7 @@ bool spark_protocol_send_event(ProtocolFacade* protocol, const char *event_name, int ttl, uint32_t flags, void* reserved) { ASSERT_ON_SYSTEM_THREAD(); CompletionHandler handler; - std::optional data_size = 0; + std::optional data_size; int content_type = (int)CoapContentFormat::TEXT_PLAIN; if (reserved) { auto r = static_cast(reserved); diff --git a/system/src/system_cloud.cpp b/system/src/system_cloud.cpp index 3439c08e1a..771216e648 100644 --- a/system/src/system_cloud.cpp +++ b/system/src/system_cloud.cpp @@ -111,7 +111,7 @@ bool spark_subscribe(const char* event_name, EventHandler handler, void* handler { SYSTEM_THREAD_CONTEXT_SYNC(spark_subscribe(event_name, handler, handler_data, scope_deprecated, device_id_deprecated, param)); int flags = 0; - if (param && param->flags & SUBSCRIBE_FLAG_BINARY_DATA) { + if (param && (param->flags & SUBSCRIBE_FLAG_BINARY_DATA)) { flags |= SubscriptionFlag::BINARY_DATA; } bool ok = spark_protocol_add_event_handler(sp, event_name, handler, flags, nullptr /* device_id_deprecated */, handler_data); From f695b9462d0770de3feb01f8ea78a99263f54f62 Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Fri, 9 Aug 2024 11:01:43 +0200 Subject: [PATCH 19/45] Bugfix --- communication/src/coap_util.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/communication/src/coap_util.cpp b/communication/src/coap_util.cpp index 54e62c402f..116ec38990 100644 --- a/communication/src/coap_util.cpp +++ b/communication/src/coap_util.cpp @@ -30,16 +30,25 @@ #include "coap_message_decoder.h" #include "str_util.h" #include "logging.h" +#include "scope_guard.h" #include "check.h" namespace particle::protocol { int sendEmptyAckOrRst(MessageChannel& channel, Message& msg, CoapType type) { + auto msgLen = msg.length(); + auto msgCapacity = msg.capacity(); Message resp; - auto err = channel.response(msg, resp, msg.capacity() - msg.length()); + auto err = channel.response(msg, resp, msgCapacity - msgLen); if (err != ProtocolError::NO_ERROR) { return toSystemError(err); } + SCOPE_GUARD({ + // BufferMessageChannel::response() alters the capacity of the original message. Restore it + // so that the calling code could still use the extra space available in the message + msg.set_buffer(msg.buf(), msgCapacity); + msg.set_length(msgLen); + }); CoapMessageEncoder e((char*)resp.buf(), resp.capacity()); e.type(type); e.code(CoapCode::EMPTY); From 5cb5d4c0daa8f242aa8b8b1fbea725e1ccd95ad4 Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Thu, 19 Sep 2024 16:27:54 +0200 Subject: [PATCH 20/45] Remove JSON and CBOR content types --- wiring/inc/spark_wiring_cloud.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/wiring/inc/spark_wiring_cloud.h b/wiring/inc/spark_wiring_cloud.h index 73651234a5..4dd2a5b774 100644 --- a/wiring/inc/spark_wiring_cloud.h +++ b/wiring/inc/spark_wiring_cloud.h @@ -81,9 +81,7 @@ enum class ContentType: int { TEXT = (int)protocol::CoapContentFormat::TEXT_PLAIN, ///< `text/plain; charset=utf-8`. JPEG = (int)protocol::CoapContentFormat::IMAGE_JPEG, ///< `image/jpeg`. PNG = (int)protocol::CoapContentFormat::IMAGE_PNG, ///< `image/png`. - BINARY = (int)protocol::CoapContentFormat::APPLICATION_OCTET_STREAM, ///< `application/octet-stream`. - JSON = (int)protocol::CoapContentFormat::APPLICATION_JSON, ///< `application/json`. - CBOR = (int)protocol::CoapContentFormat::APPLICATION_CBOR ///< `application/cbor`. + BINARY = (int)protocol::CoapContentFormat::APPLICATION_OCTET_STREAM ///< `application/octet-stream`. }; typedef void (*EventHandlerWithContentType)(const char* name, const char* data, size_t size, ContentType type); From ab560ffa83ca4be8452d23fccde0531d33cdc72c Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Wed, 21 Aug 2024 15:55:07 +0200 Subject: [PATCH 21/45] Add a method for publishing a Variant --- wiring/inc/spark_wiring_cloud.h | 3 +++ wiring/src/spark_wiring_cloud.cpp | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/wiring/inc/spark_wiring_cloud.h b/wiring/inc/spark_wiring_cloud.h index 4dd2a5b774..46827fc347 100644 --- a/wiring/inc/spark_wiring_cloud.h +++ b/wiring/inc/spark_wiring_cloud.h @@ -73,6 +73,7 @@ struct is_string_literal { namespace particle { class Ledger; +class Variant; /** * Content type. @@ -299,6 +300,8 @@ class CloudClass { return publish_event(name, data, size, type, DEFAULT_CLOUD_EVENT_TTL, flags); } + particle::Future publish(const char* name, const particle::Variant& data, PublishFlags flags = PublishFlags()); + /** * @brief Publish vitals information * diff --git a/wiring/src/spark_wiring_cloud.cpp b/wiring/src/spark_wiring_cloud.cpp index 4b4bac9903..85d60180e1 100644 --- a/wiring/src/spark_wiring_cloud.cpp +++ b/wiring/src/spark_wiring_cloud.cpp @@ -1,6 +1,8 @@ #include "spark_wiring_cloud.h" #include "spark_wiring_ledger.h" +#include "spark_wiring_variant.h" +#include "spark_wiring_print.h" #include #include "system_cloud.h" @@ -114,6 +116,16 @@ Future CloudClass::publish_event(const char* name, const char* data, size_ return p.future(); } +Future CloudClass::publish(const char* name, const Variant& data, PublishFlags flags) { + String s; + OutputStringStream stream(s); + int r = encodeToCBOR(data, stream); + if (r < 0) { + return Future((Error::Type)r); + } + return publish(name, s.c_str(), s.length(), ContentType::CBOR, flags); +} + int CloudClass::publishVitals(system_tick_t period_s_) { return spark_publish_vitals(period_s_, nullptr); } From 41dd61c972b5efdbfc4189046773a83e9f8ae141 Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Wed, 21 Aug 2024 16:53:56 +0200 Subject: [PATCH 22/45] Add InputBufferStream and InputStringStream utility classes --- wiring/inc/spark_wiring_stream.h | 52 ++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/wiring/inc/spark_wiring_stream.h b/wiring/inc/spark_wiring_stream.h index c821e1cdd1..bf0e8f013a 100644 --- a/wiring/inc/spark_wiring_stream.h +++ b/wiring/inc/spark_wiring_stream.h @@ -29,6 +29,7 @@ #include "spark_wiring_string.h" #include "spark_wiring_print.h" +#include "spark_wiring_error.h" #include "system_tick_hal.h" // compatability macros for testing @@ -99,4 +100,55 @@ class Stream : public Print float parseFloat(char skipChar); // as above but the given skipChar is ignored }; +namespace particle { + +class InputBufferStream: public Stream { +public: + InputBufferStream(const char* data, size_t size) : + p_(data), + end_(data + size) { + } + + // Stream + int available() override { + return end_ - p_; + } + + int read() override { + if (p_ == end_) { + return Error::END_OF_STREAM; + } + return (uint8_t)*p_++; + } + + int peek() override { + if (p_ == end_) { + return Error::END_OF_STREAM; + } + return (uint8_t)*p_; + } + + // Print + size_t write(uint8_t b) override { + setWriteError(Error::NOT_SUPPORTED); + return 0; + } + + void flush() override { + } + +private: + const char* p_; + const char* end_; +}; + +class InputStringStream: public InputBufferStream { +public: + explicit InputStringStream(const String& str) : + InputBufferStream(str.c_str(), str.length()) { + } +}; + +} // namespace particle + #endif From 8f3f6e187a6ab223bce83b27947bd35ce1452f55 Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Wed, 21 Aug 2024 16:55:48 +0200 Subject: [PATCH 23/45] Add an API for subscribing and decoding events as a Variant --- communication/inc/events.h | 3 +- communication/src/subscriptions.cpp | 4 +- system/inc/system_cloud.h | 3 +- system/src/system_cloud.cpp | 9 +++- wiring/inc/spark_wiring_cloud.h | 6 +++ wiring/src/spark_wiring_cloud.cpp | 68 ++++++++++++++++++++++++----- 6 files changed, 76 insertions(+), 17 deletions(-) diff --git a/communication/inc/events.h b/communication/inc/events.h index 58527537bd..4f5871c604 100644 --- a/communication/inc/events.h +++ b/communication/inc/events.h @@ -63,7 +63,8 @@ namespace SubscriptionFlag { enum Enum { MY_DEVICES = 0x00, // Deprecated FIREHOSE = 0x01, // Deprecated - BINARY_DATA = 0x02 // The subscription handler accepts binary data + BINARY_DATA = 0x02, // The subscription handler accepts binary data + CBOR_DATA = 0x04 // The subscription handler accepts CBOR data }; } diff --git a/communication/src/subscriptions.cpp b/communication/src/subscriptions.cpp index 40fc796379..9e0d18de44 100644 --- a/communication/src/subscriptions.cpp +++ b/communication/src/subscriptions.cpp @@ -39,7 +39,9 @@ ProtocolError Subscriptions::send_subscription_impl(MessageChannel& channel, con // Subscription messages have an empty token e.option(CoapOption::URI_PATH, "e"); // 11 e.option(CoapOption::URI_PATH, filter, filterLen); // 11 - if (flags & SubscriptionFlag::BINARY_DATA) { + if (flags & SubscriptionFlag::CBOR_DATA) { + e.option(CoapOption::URI_QUERY, "c"); // 15 + } else if (flags & SubscriptionFlag::BINARY_DATA) { e.option(CoapOption::URI_QUERY, "b"); // 15 } int r = e.encode(); diff --git a/system/inc/system_cloud.h b/system/inc/system_cloud.h index 31a0ad2556..7cf701125d 100644 --- a/system/inc/system_cloud.h +++ b/system/inc/system_cloud.h @@ -198,7 +198,8 @@ typedef enum { * Subscription flags. */ typedef enum { - SUBSCRIBE_FLAG_BINARY_DATA = 0x01 ///< The subscription handler accepts binary data. + SUBSCRIBE_FLAG_BINARY_DATA = 0x01, ///< The subscription handler accepts binary data. + SUBSCRIBE_FLAG_CBOR_DATA = 0x02 ///< The subscription handler accepts CBOR data. } spark_subscribe_flag; typedef int (*cloud_function_t)(void* data, const char* param, void* reserved); diff --git a/system/src/system_cloud.cpp b/system/src/system_cloud.cpp index 771216e648..6531f6e980 100644 --- a/system/src/system_cloud.cpp +++ b/system/src/system_cloud.cpp @@ -111,8 +111,13 @@ bool spark_subscribe(const char* event_name, EventHandler handler, void* handler { SYSTEM_THREAD_CONTEXT_SYNC(spark_subscribe(event_name, handler, handler_data, scope_deprecated, device_id_deprecated, param)); int flags = 0; - if (param && (param->flags & SUBSCRIBE_FLAG_BINARY_DATA)) { - flags |= SubscriptionFlag::BINARY_DATA; + if (param) { + if (param->flags & SUBSCRIBE_FLAG_CBOR_DATA) { + flags |= SubscriptionFlag::CBOR_DATA; + } + if (param->flags & SUBSCRIBE_FLAG_BINARY_DATA) { + flags |= SubscriptionFlag::BINARY_DATA; + } } bool ok = spark_protocol_add_event_handler(sp, event_name, handler, flags, nullptr /* device_id_deprecated */, handler_data); if (ok && spark_cloud_flag_connected() && (system_mode() != AUTOMATIC || APPLICATION_SETUP_DONE)) { diff --git a/wiring/inc/spark_wiring_cloud.h b/wiring/inc/spark_wiring_cloud.h index 46827fc347..506263bb19 100644 --- a/wiring/inc/spark_wiring_cloud.h +++ b/wiring/inc/spark_wiring_cloud.h @@ -88,6 +88,9 @@ enum class ContentType: int { typedef void (*EventHandlerWithContentType)(const char* name, const char* data, size_t size, ContentType type); typedef std::function EventHandlerWithContentTypeFn; +typedef void (*EventHandlerWithVariant)(const char* name, Variant data); +typedef std::function EventHandlerWithVariantFn; + } // namespace particle class CloudDisconnectOptions { @@ -375,6 +378,9 @@ class CloudClass { bool subscribe(const char* name, particle::EventHandlerWithContentType handler); bool subscribe(const char* name, particle::EventHandlerWithContentTypeFn handler); + bool subscribe(const char* name, particle::EventHandlerWithVariant handler); + bool subscribe(const char* name, particle::EventHandlerWithVariantFn handler); + void unsubscribe() { spark_unsubscribe(NULL); diff --git a/wiring/src/spark_wiring_cloud.cpp b/wiring/src/spark_wiring_cloud.cpp index 85d60180e1..3f553ab15e 100644 --- a/wiring/src/spark_wiring_cloud.cpp +++ b/wiring/src/spark_wiring_cloud.cpp @@ -12,6 +12,16 @@ namespace { using namespace particle; +bool parseVariantFromCbor(Variant& v, const char* data, size_t size) { + InputBufferStream stream(data, size); + int r = decodeFromCBOR(v, stream); + if (r < 0) { + LOG(ERROR, "Failed to parse CBOR: %d", r); + return false; + } + return true; +} + void publishCompletionCallback(int error, const void* data, void* callbackData, void* reserved) { auto p = Promise::fromDataPtr(callbackData); if (error != Error::NONE) { @@ -21,10 +31,10 @@ void publishCompletionCallback(int error, const void* data, void* callbackData, } } -inline bool subscribeWithContentType(const char* name, EventHandler handler, void* handlerData) { +inline bool subscribeWithFlags(const char* name, EventHandler handler, void* handlerData, int flags) { spark_subscribe_param param = {}; param.size = sizeof(param); - param.flags |= SUBSCRIBE_FLAG_BINARY_DATA; + param.flags = flags; return spark_subscribe(name, handler, handlerData, MY_DEVICES, nullptr /* device_id_deprecated */, ¶m); } @@ -39,6 +49,32 @@ void subscribeWithContentTypeFunctionWrapper(void* arg, const char* name, const (*fn)(name, data, dataSize, (ContentType)contentType); } +void subscribeWithVariantCallbackWrapper(void* arg, const char* name, const char* data, size_t dataSize, int contentType) { + Variant v; + if (!parseVariantFromCbor(v, data, dataSize)) { + return; + } + auto cb = (EventHandlerWithVariant)arg; + cb(name, std::move(v)); +} + +void subscribeWithVariantFunctionWrapper(void* arg, const char* name, const char* data, size_t dataSize, int contentType) { + Variant v; + if (!parseVariantFromCbor(v, data, dataSize)) { + return; + } + auto fn = (EventHandlerWithVariantFn*)arg; + (*fn)(name, std::move(v)); +} + +template +inline EventHandler eventHandlerCast(T* fn) { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-function-type" + return (EventHandler)fn; +#pragma GCC diagnostic pop +} + } // namespace spark_cloud_disconnect_options CloudDisconnectOptions::toSystemOptions() const @@ -198,11 +234,8 @@ int CloudClass::useLedgersImpl(const Vector& usedNames) { #endif // Wiring_Ledger bool CloudClass::subscribe(const char* name, EventHandlerWithContentType handler) { -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wcast-function-type" - auto h = (EventHandler)subscribeWithContentTypeCallbackWrapper; -#pragma GCC diagnostic pop - return subscribeWithContentType(name, h, (void*)handler); + auto h = eventHandlerCast(subscribeWithContentTypeCallbackWrapper); + return subscribeWithFlags(name, h, (void*)handler, SUBSCRIBE_FLAG_BINARY_DATA); } bool CloudClass::subscribe(const char* name, EventHandlerWithContentTypeFn handler) { @@ -210,9 +243,20 @@ bool CloudClass::subscribe(const char* name, EventHandlerWithContentTypeFn handl if (!fnPtr) { return false; } -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wcast-function-type" - auto h = (EventHandler)subscribeWithContentTypeFunctionWrapper; -#pragma GCC diagnostic pop - return subscribeWithContentType(name, h, fnPtr); + auto h = eventHandlerCast(subscribeWithContentTypeFunctionWrapper); + return subscribeWithFlags(name, h, fnPtr, SUBSCRIBE_FLAG_BINARY_DATA); +} + +bool subscribe(const char* name, particle::EventHandlerWithVariant handler) { + auto h = eventHandlerCast(subscribeWithVariantCallbackWrapper); + return subscribeWithFlags(name, h, (void*)handler, SUBSCRIBE_FLAG_CBOR_DATA); +} + +bool subscribe(const char* name, particle::EventHandlerWithVariantFn handler) { + auto fnPtr = new(std::nothrow) EventHandlerWithVariantFn(std::move(handler)); + if (!fnPtr) { + return false; + } + auto h = eventHandlerCast(subscribeWithVariantFunctionWrapper); + return subscribeWithFlags(name, h, fnPtr, SUBSCRIBE_FLAG_CBOR_DATA); } From 47bee703c7e1a2373cbfe5073d146dfbb1bdda7d Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Thu, 22 Aug 2024 16:44:00 +0200 Subject: [PATCH 24/45] Bugfixes --- communication/src/subscriptions.cpp | 6 ++++-- wiring/src/spark_wiring_cloud.cpp | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/communication/src/subscriptions.cpp b/communication/src/subscriptions.cpp index 9e0d18de44..84aad96034 100644 --- a/communication/src/subscriptions.cpp +++ b/communication/src/subscriptions.cpp @@ -40,7 +40,7 @@ ProtocolError Subscriptions::send_subscription_impl(MessageChannel& channel, con e.option(CoapOption::URI_PATH, "e"); // 11 e.option(CoapOption::URI_PATH, filter, filterLen); // 11 if (flags & SubscriptionFlag::CBOR_DATA) { - e.option(CoapOption::URI_QUERY, "c"); // 15 + e.option(CoapOption::URI_QUERY, "b"); // 15 FIXME } else if (flags & SubscriptionFlag::BINARY_DATA) { e.option(CoapOption::URI_QUERY, "b"); // 15 } @@ -116,7 +116,9 @@ ProtocolError Subscriptions::handle_event(Message& msg, SparkDescriptor::CallEve continue; } if (!std::memcmp(event_handlers[i].filter, name, filterLen)) { - if (!(event_handlers[i].flags & SubscriptionFlag::BINARY_DATA) && !isCoapTextContentFormat(contentFmt)) { + if (!(event_handlers[i].flags & SubscriptionFlag::BINARY_DATA) && + !(event_handlers[i].flags & SubscriptionFlag::CBOR_DATA) && + !isCoapTextContentFormat(contentFmt)) { continue; // Do not invoke a handler that only accepts text data } char* data = nullptr; diff --git a/wiring/src/spark_wiring_cloud.cpp b/wiring/src/spark_wiring_cloud.cpp index 3f553ab15e..76d5144b60 100644 --- a/wiring/src/spark_wiring_cloud.cpp +++ b/wiring/src/spark_wiring_cloud.cpp @@ -247,12 +247,12 @@ bool CloudClass::subscribe(const char* name, EventHandlerWithContentTypeFn handl return subscribeWithFlags(name, h, fnPtr, SUBSCRIBE_FLAG_BINARY_DATA); } -bool subscribe(const char* name, particle::EventHandlerWithVariant handler) { +bool CloudClass::subscribe(const char* name, particle::EventHandlerWithVariant handler) { auto h = eventHandlerCast(subscribeWithVariantCallbackWrapper); return subscribeWithFlags(name, h, (void*)handler, SUBSCRIBE_FLAG_CBOR_DATA); } -bool subscribe(const char* name, particle::EventHandlerWithVariantFn handler) { +bool CloudClass::subscribe(const char* name, particle::EventHandlerWithVariantFn handler) { auto fnPtr = new(std::nothrow) EventHandlerWithVariantFn(std::move(handler)); if (!fnPtr) { return false; From 5ea7f64ed79a398652c4d63e98199d8d623599af Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Fri, 23 Aug 2024 16:06:12 +0200 Subject: [PATCH 25/45] Bugfixes --- communication/src/subscriptions.cpp | 44 ++++++++++++++--------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/communication/src/subscriptions.cpp b/communication/src/subscriptions.cpp index 84aad96034..3a7bc4e730 100644 --- a/communication/src/subscriptions.cpp +++ b/communication/src/subscriptions.cpp @@ -40,7 +40,7 @@ ProtocolError Subscriptions::send_subscription_impl(MessageChannel& channel, con e.option(CoapOption::URI_PATH, "e"); // 11 e.option(CoapOption::URI_PATH, filter, filterLen); // 11 if (flags & SubscriptionFlag::CBOR_DATA) { - e.option(CoapOption::URI_QUERY, "b"); // 15 FIXME + e.option(CoapOption::URI_QUERY, "c"); // 15 } else if (flags & SubscriptionFlag::BINARY_DATA) { e.option(CoapOption::URI_QUERY, "b"); // 15 } @@ -108,33 +108,31 @@ ProtocolError Subscriptions::handle_event(Message& msg, SparkDescriptor::CallEve } for (size_t i = 0; i < MAX_SUBSCRIPTIONS; ++i) { - if (!event_handlers[i].handler) { + auto& eventHandler = event_handlers[i]; + if (!eventHandler.handler) { break; } - size_t filterLen = strnlen(event_handlers[i].filter, sizeof(event_handlers[i].filter)); - if (nameLen < filterLen) { - continue; + size_t filterLen = strnlen(eventHandler.filter, sizeof(eventHandler.filter)); + if (nameLen < filterLen || std::memcmp(eventHandler.filter, name, filterLen) != 0) { + continue; // Event name mismatch } - if (!std::memcmp(event_handlers[i].filter, name, filterLen)) { - if (!(event_handlers[i].flags & SubscriptionFlag::BINARY_DATA) && - !(event_handlers[i].flags & SubscriptionFlag::CBOR_DATA) && - !isCoapTextContentFormat(contentFmt)) { - continue; // Do not invoke a handler that only accepts text data - } - char* data = nullptr; - size_t dataSize = d.payloadSize(); - if (dataSize > 0) { - data = const_cast(d.payload()); - // Historically, the event handler callback expected a null-terminated string. Keeping that - // behavior for now - if (msg.length() >= msg.capacity()) { - std::memmove(data - 1, data, dataSize); // Overwrites the payload marker - --data; - } - data[dataSize] = '\0'; + if (((eventHandler.flags & SubscriptionFlag::CBOR_DATA) && contentFmt != CoapContentFormat::APPLICATION_CBOR) || + (!(eventHandler.flags & (SubscriptionFlag::BINARY_DATA | SubscriptionFlag::CBOR_DATA)) && !isCoapTextContentFormat(contentFmt))) { + continue; // Encoding mismatch + } + char* data = nullptr; + size_t dataSize = d.payloadSize(); + if (dataSize > 0) { + data = const_cast(d.payload()); + // Historically, the event handler callback expected a null-terminated string. Keeping that + // behavior for now + if (msg.length() >= msg.capacity()) { + std::memmove(data - 1, data, dataSize); // Overwrites the payload marker + --data; } - callback(sizeof(FilteringEventHandler), &event_handlers[i], name, data, dataSize, contentFmt); + data[dataSize] = '\0'; } + callback(sizeof(FilteringEventHandler), &eventHandler, name, data, dataSize, contentFmt); } return ProtocolError::NO_ERROR; } From 8a221cb748fef12209d2f66f56ed9377d4cf531d Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Mon, 26 Aug 2024 13:42:07 +0200 Subject: [PATCH 26/45] Move the Buffer class to the Wiring library --- services/inc/buffer.h | 134 ----------------------------- system/inc/asset_manager.h | 4 +- system/inc/asset_manager_api.h | 2 +- wiring/inc/spark_wiring_buffer.h | 103 ++++++++++++++++++++++ wiring/src/spark_wiring_buffer.cpp | 43 +++++++++ 5 files changed, 150 insertions(+), 136 deletions(-) delete mode 100644 services/inc/buffer.h create mode 100644 wiring/inc/spark_wiring_buffer.h create mode 100644 wiring/src/spark_wiring_buffer.cpp diff --git a/services/inc/buffer.h b/services/inc/buffer.h deleted file mode 100644 index bf709c21f0..0000000000 --- a/services/inc/buffer.h +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (c) 2023 Particle Industries, Inc. All rights reserved. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation, either - * version 3 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, see . - */ - -#pragma once - -#include -#include -#include -#include -#include -#include "system_error.h" - -namespace particle { - -class Buffer { -public: - explicit Buffer(size_t size = 0); - Buffer(const char* data, size_t size); - Buffer(const uint8_t* data, size_t size); - Buffer(const Buffer& other); - Buffer(Buffer&& other); - - char* data(); - const char* data() const; - - size_t size() const; - - operator char*(); - operator const char*() const; - - Buffer& operator=(Buffer other); - - bool operator==(const Buffer& other) const; - bool operator!=(const Buffer& other) const; - -private: - friend void swap(Buffer& lhs, Buffer& rhs) { - using std::swap; - swap(lhs.buffer_, rhs.buffer_); - swap(lhs.size_, rhs.size_); - } - -private: - std::unique_ptr buffer_; - size_t size_; -}; - -inline Buffer::Buffer(size_t size) - : size_(size) { - if (size_ > 0) { - buffer_ = std::make_unique(size_); - if (!buffer_) { - size_ = 0; - } - } -} - -inline Buffer::Buffer(const char* data, size_t size) - : Buffer(size) { - if (buffer_ && data && size > 0) { - memcpy(buffer_.get(), data, size); - } -} - -inline Buffer::Buffer(const uint8_t* data, size_t size) - : Buffer((const char*)data, size) { - -} -inline Buffer::Buffer(const Buffer& other) - : Buffer(other.size()) { - if (size_ > 0 && other.size() > 0) { - memcpy(buffer_.get(), other.buffer_.get(), size_); - } -} - -inline Buffer::Buffer(Buffer&& other) - : Buffer() { - swap(*this, other); -} - -inline char* Buffer::data() { - return buffer_.get(); -} - -inline const char* Buffer::data() const { - return buffer_.get(); -} - -inline size_t Buffer::size() const { - return size_; -} - -inline Buffer::operator char*() { - return data(); -} - -inline Buffer::operator const char*() const { - return data(); -} - -inline Buffer& Buffer::operator=(Buffer other) { - swap(*this, other); - return *this; -} - -inline bool Buffer::operator==(const Buffer& other) const { - if (size() != other.size()) { - return false; - } - if (size() > 0 && data() && other.data()) { - return !memcmp(buffer_.get(), other.buffer_.get(), size()); - } - return size() == 0 && other.size() == 0; -} - -inline bool Buffer::operator!=(const Buffer& other) const { - return !(*this == other); -} - -} // particle diff --git a/system/inc/asset_manager.h b/system/inc/asset_manager.h index 1fca8a59f1..8fcc3b7cda 100644 --- a/system/inc/asset_manager.h +++ b/system/inc/asset_manager.h @@ -17,9 +17,11 @@ #pragma once +#include #include + #include "spark_wiring_vector.h" -#include "buffer.h" +#include "spark_wiring_buffer.h" #include "spark_wiring_string.h" #include "ota_flash_hal.h" #include "stream.h" diff --git a/system/inc/asset_manager_api.h b/system/inc/asset_manager_api.h index dccbfc7125..847582b487 100644 --- a/system/inc/asset_manager_api.h +++ b/system/inc/asset_manager_api.h @@ -23,7 +23,7 @@ #ifdef __cplusplus #include "spark_wiring_string.h" -#include "buffer.h" +#include "spark_wiring_buffer.h" #include "str_util.h" namespace particle { diff --git a/wiring/inc/spark_wiring_buffer.h b/wiring/inc/spark_wiring_buffer.h new file mode 100644 index 0000000000..91633bed40 --- /dev/null +++ b/wiring/inc/spark_wiring_buffer.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2024 Particle Industries, Inc. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#pragma once + +#include +#include + +#include "spark_wiring_string.h" +#include "spark_wiring_vector.h" + +namespace particle { + +class Buffer { +public: + Buffer() = default; + + explicit Buffer(size_t size) : + d_(size) { + } + + Buffer(const char* data, size_t size) : + d_(data, size) { + } + + Buffer(const uint8_t* data, size_t size) : + d_((const char*)data, size) { + } + + char* data() { + return d_.data(); + } + + const char* data() const { + return d_.data(); + } + + size_t size() const { + return d_.size(); + } + + bool isEmpty() const { + return d_.isEmpty(); + } + + bool resize(size_t size) { + return d_.resize(size); + } + + size_t capacity() const { + return d_.capacity(); + } + + bool reserve(size_t size) { + return d_.reserve(size); + } + + bool trimToSize() { + return d_.trimToSize(); + } + + String toHex() const; + + bool operator==(const Buffer& buf) const { + if (d_.size() != buf.d_.size()) { + return false; + } + return std::memcmp(d_.data(), buf.d_.data(), d_.size()) == 0; + } + + bool operator!=(const Buffer& buf) const { + return !(operator==(buf)); + } + + static Buffer fromHex(const String& str) { + return fromHex(str.c_str(), str.length()); + } + + static Buffer fromHex(const char* str) { + return fromHex(str, std::strlen(str)); + } + + static Buffer fromHex(const char* str, size_t len); + +private: + Vector d_; +}; + +} // namespace particle diff --git a/wiring/src/spark_wiring_buffer.cpp b/wiring/src/spark_wiring_buffer.cpp new file mode 100644 index 0000000000..ad255cc57d --- /dev/null +++ b/wiring/src/spark_wiring_buffer.cpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024 Particle Industries, Inc. All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + */ + +#include "spark_wiring_buffer.h" + +#include "str_util.h" + +namespace particle { + +String Buffer::toHex() const { + String s; + if (!s.reserve(d_.size() * 2)) { + return String(); + } + particle::toHex(d_.data(), d_.size(), &s.operator[](0), s.length() + 1); + return s; +} + +Buffer Buffer::fromHex(const char* str, size_t len) { + Buffer buf; + if (!buf.reserve(len / 2)) { + return Buffer(); + } + size_t n = particle::fromHex(str, len, buf.d_.data(), buf.d_.capacity()); + buf.resize(n); + return buf; +} + +} // namespace particle From f1a665901188979339e3e1ff18821a7a299eb298 Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Mon, 26 Aug 2024 13:42:37 +0200 Subject: [PATCH 27/45] Add fromHex() --- services/inc/str_util.h | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/services/inc/str_util.h b/services/inc/str_util.h index ecf66a2dd1..d21355076d 100644 --- a/services/inc/str_util.h +++ b/services/inc/str_util.h @@ -133,6 +133,42 @@ inline size_t toHex(const void* src, size_t srcSize, char* dest, size_t destSize return n; } +/** + * Parse a hex-encoded string. + * + * @param src Source string. + * @param srcSize Size of the source string. + * @param dest Destination buffer. + * @param destSize Size of the destination buffer. + * @return Number of bytes written to the destination buffer. + */ +inline size_t fromHex(const char* src, size_t srcSize, char* dest, size_t destSize) { + size_t n = 0; + uint8_t b = 0; + for (size_t i = 0; i < srcSize; ++i) { + if (n >= destSize) { + break; + } + char c = src[i]; + if (c >= '0' && c <= '9') { + b |= c - '0'; + } else if (c >= 'A' && c <= 'F') { + b |= c - 'A' + 10; + } else if (c >= 'a' && c <= 'f') { + b |= c - 'a' + 10; + } else { + break; + } + if (i % 2) { + dest[n++] = b; + b = 0; + } else { + b <<= 4; + } + } + return n; +} + /** * Converts binary data to a printable string. The output is null-terminated unless the size of the * destination buffer is 0. From ad07b32d0788086190e00c48e39ea3bf23ed51d4 Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Mon, 26 Aug 2024 14:21:11 +0200 Subject: [PATCH 28/45] Make Buffer one of the alternative types of Variant --- wiring/inc/spark_wiring_variant.h | 52 ++++++++++++++++++++++++++--- wiring/src/spark_wiring_variant.cpp | 4 +++ 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/wiring/inc/spark_wiring_variant.h b/wiring/inc/spark_wiring_variant.h index cfaa30c325..0c2c54b9e5 100644 --- a/wiring/inc/spark_wiring_variant.h +++ b/wiring/inc/spark_wiring_variant.h @@ -24,6 +24,7 @@ #include #include "spark_wiring_string.h" +#include "spark_wiring_buffer.h" #include "spark_wiring_vector.h" #include "spark_wiring_map.h" @@ -61,7 +62,7 @@ struct IsComparableWithVariant { // TODO: This is not ideal as we'd like Variant to be comparable with any type as long as it's // comparable with one of the Variant's alternative types static constexpr bool value = std::is_same_v || std::is_arithmetic_v || std::is_same_v || - std::is_same_v || std::is_same_v || std::is_same_v; + std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v; }; } // namespace detail @@ -83,6 +84,7 @@ class Variant { UINT64, ///< `uint64_t`. DOUBLE, ///< `double`. STRING, ///< `String`. + BUFFER, ///< `Buffer`. ARRAY, ///< `VariantArray`. MAP ///< `VariantMap`. }; @@ -150,6 +152,10 @@ class Variant { v_(std::move(val)) { } + Variant(Buffer val) : + v_(std::move(val)) { + } + Variant(VariantArray val) : v_(std::move(val)) { } @@ -230,6 +236,10 @@ class Variant { return is(); } + bool isBuffer() const { + return is(); + } + bool isArray() const { return is(); } @@ -289,6 +299,10 @@ class Variant { return to(); } + Buffer toBuffer() const { + return to(); + } + VariantArray toArray() const { return to(); } @@ -336,6 +350,10 @@ class Variant { return to(ok); } + Buffer toBuffer(bool& ok) const { + return to(ok); + } + VariantArray toArray(bool& ok) const { return to(ok); } @@ -427,6 +445,10 @@ class Variant { return as(); } + Buffer& asBuffer() { + return as(); + } + VariantArray& asArray() { return as(); } @@ -795,7 +817,7 @@ class Variant { static constexpr bool value = false; }; - typedef std::variant VariantType; + typedef std::variant VariantType; VariantType v_; @@ -887,7 +909,7 @@ class Variant::ConvertToVisitor { if constexpr (std::is_arithmetic_v) { ok = true; return static_cast(val); - } else { // std::monostate, VariantArray, VariantMap + } else { // std::monostate, Buffer, VariantArray, VariantMap return false; } } @@ -913,7 +935,7 @@ struct Variant::ConvertToVisitor) { ok = true; return static_cast(val); - } else { // std::monostate, VariantArray, VariantMap + } else { // std::monostate, Buffer, VariantArray, VariantMap return TargetT(); } } @@ -941,12 +963,27 @@ struct Variant::ConvertToVisitor { SPARK_ASSERT(r.ec == std::errc()); ok = true; return String(buf, r.ptr - buf); - } else { // std::monostate, VariantArray, VariantMap + } else { // std::monostate, Buffer, VariantArray, VariantMap return String(); } } }; +template<> +struct Variant::ConvertToVisitor { + bool ok = false; + + Buffer operator()(const String& val) { + ok = true; + return Buffer(val.c_str(), val.length()); + } + + template + Buffer operator()(const SourceT& val) { + return Buffer(); + } +}; + template<> struct Variant::ConvertToVisitor { bool ok = false; @@ -1017,6 +1054,11 @@ struct Variant::IsAlternativeType { static const bool value = true; }; +template<> +struct Variant::IsAlternativeType { + static const bool value = true; +}; + template<> struct Variant::IsAlternativeType { static const bool value = true; diff --git a/wiring/src/spark_wiring_variant.cpp b/wiring/src/spark_wiring_variant.cpp index 4e1737a853..69bb572a8e 100644 --- a/wiring/src/spark_wiring_variant.cpp +++ b/wiring/src/spark_wiring_variant.cpp @@ -759,6 +759,8 @@ int Variant::size() const { switch (type()) { case Type::STRING: return value().length(); + case Type::BUFFER: + return value().size(); case Type::ARRAY: return value().size(); case Type::MAP: @@ -774,6 +776,8 @@ bool Variant::isEmpty() const { return true; // A default-constructed Variant is empty case Type::STRING: return value().length() == 0; + case Type::BUFFER: + return value().size() == 0; case Type::ARRAY: return value().isEmpty(); case Type::MAP: From 67d1f83d5368a95e993cf9e1edc5a70826900f5c Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Mon, 26 Aug 2024 15:56:33 +0200 Subject: [PATCH 29/45] Support serializing/deserializing Buffer as CBOR --- wiring/src/spark_wiring_variant.cpp | 57 +++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 12 deletions(-) diff --git a/wiring/src/spark_wiring_variant.cpp b/wiring/src/spark_wiring_variant.cpp index 69bb572a8e..acc4e1f8d7 100644 --- a/wiring/src/spark_wiring_variant.cpp +++ b/wiring/src/spark_wiring_variant.cpp @@ -157,6 +157,15 @@ int readAndAppendToString(DecodingStream& stream, size_t size, String& str) { return 0; } +int readAndAppendToBuffer(DecodingStream& stream, size_t size, Buffer& buf) { + auto oldSize = buf.size(); + if (!buf.resize(oldSize + size)) { + return Error::NO_MEMORY; + } + CHECK(stream.read(buf.data() + oldSize, size)); + return 0; +} + int readCborHead(DecodingStream& stream, CborHead& head) { uint8_t b; CHECK(stream.readUint8(b)); @@ -237,8 +246,9 @@ int writeCborSignedInteger(EncodingStream& stream, int64_t val) { return 0; } -int readCborString(DecodingStream& stream, const CborHead& head, String& str) { - String s; +template +int readCborString(DecodingStream& stream, const CborHead& head, T& output, const F& read) { + T out; if (head.detail == 31 /* Indefinite length */) { for (;;) { CborHead h; @@ -246,30 +256,46 @@ int readCborString(DecodingStream& stream, const CborHead& head, String& str) { if (h.type == 7 /* Misc. items */ && h.detail == 31 /* Stop code */) { break; } - if (h.type != 3 /* Text string */ || h.detail == 31 /* Indefinite length */) { // Chunks of indefinite length are not permitted + if (h.type != head.type || h.detail == 31 /* Indefinite length */) { // Chunks of indefinite length are not permitted return Error::BAD_DATA; } if (h.arg > std::numeric_limits::max()) { return Error::OUT_OF_RANGE; } - CHECK(readAndAppendToString(stream, h.arg, s)); + CHECK(read(stream, h.arg, out)); } } else { if (head.arg > std::numeric_limits::max()) { return Error::OUT_OF_RANGE; } - CHECK(readAndAppendToString(stream, head.arg, s)); + CHECK(read(stream, head.arg, out)); } - str = std::move(s); + output = std::move(out); + return 0; +} + +int readCborTextString(DecodingStream& stream, const CborHead& head, String& str) { + CHECK(readCborString(stream, head, str, readAndAppendToString)); return 0; } -int writeCborString(EncodingStream& stream, const String& str) { +int writeCborTextString(EncodingStream& stream, const String& str) { CHECK(writeCborHeadWithArgument(stream, 3 /* Text string */, str.length())); CHECK(stream.write(str.c_str(), str.length())); return 0; } +int readCborByteString(DecodingStream& stream, const CborHead& head, Buffer& buf) { + CHECK(readCborString(stream, head, buf, readAndAppendToBuffer)); + return 0; +} + +int writeCborByteString(EncodingStream& stream, const Buffer& buf) { + CHECK(writeCborHeadWithArgument(stream, 2 /* Byte string */, buf.size())); + CHECK(stream.write(buf.data(), buf.size())); + return 0; +} + int encodeToCbor(EncodingStream& stream, const Variant& var) { switch (var.type()) { case Variant::NULL_: { @@ -311,7 +337,11 @@ int encodeToCbor(EncodingStream& stream, const Variant& var) { break; } case Variant::STRING: { - CHECK(writeCborString(stream, var.value())); + CHECK(writeCborTextString(stream, var.value())); + break; + } + case Variant::BUFFER: { + CHECK(writeCborByteString(stream, var.value())); break; } case Variant::ARRAY: { @@ -326,7 +356,7 @@ int encodeToCbor(EncodingStream& stream, const Variant& var) { auto& entries = var.value().entries(); CHECK(writeCborHeadWithArgument(stream, 5 /* Map */, entries.size())); for (auto& e: entries) { - CHECK(writeCborString(stream, e.first)); + CHECK(writeCborTextString(stream, e.first)); CHECK(encodeToCbor(stream, e.second)); } break; @@ -360,11 +390,14 @@ int decodeFromCbor(DecodingStream& stream, const CborHead& head, Variant& var) { break; } case 2: { // Byte string - return Error::NOT_SUPPORTED; // Not supported + Buffer b; + CHECK(readCborByteString(stream, head, b)); + var = std::move(b); + break; } case 3: { // Text string String s; - CHECK(readCborString(stream, head, s)); + CHECK(readCborTextString(stream, head, s)); var = std::move(s); break; } @@ -429,7 +462,7 @@ int decodeFromCbor(DecodingStream& stream, const CborHead& head, Variant& var) { return Error::NOT_SUPPORTED; // Non-string keys are not supported } String k; - CHECK(readCborString(stream, h, k)); + CHECK(readCborTextString(stream, h, k)); Variant v; CHECK(readCborHead(stream, h)); CHECK(decodeFromCbor(stream, h, v)); From cf6f34a77d7c7048040f8d68721c290a942e2b7d Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Mon, 26 Aug 2024 17:20:00 +0200 Subject: [PATCH 30/45] Do not fail decoding if the CBOR map contains non-string keys --- wiring/src/spark_wiring_variant.cpp | 61 ++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 19 deletions(-) diff --git a/wiring/src/spark_wiring_variant.cpp b/wiring/src/spark_wiring_variant.cpp index acc4e1f8d7..6cb5bc2ad4 100644 --- a/wiring/src/spark_wiring_variant.cpp +++ b/wiring/src/spark_wiring_variant.cpp @@ -143,6 +143,15 @@ struct CborHead { int detail; }; +int appendKeyValue(VariantArray& arr, Variant key, Variant val) { + if (!arr.reserve(arr.size() + 2)) { + return Error::NO_MEMORY; + } + arr.append(std::move(key)); + arr.append(std::move(val)); + return 0; +} + int readAndAppendToString(DecodingStream& stream, size_t size, String& str) { if (!str.reserve(str.length() + size)) { return Error::NO_MEMORY; @@ -211,7 +220,7 @@ int readCborHead(DecodingStream& stream, CborHead& head) { return 0; } -int writeCborHeadWithArgument(EncodingStream& stream, int type, uint64_t arg) { +int writeCborHead(EncodingStream& stream, int type, uint64_t arg) { type <<= 5; if (arg < 24) { CHECK(stream.writeUint8(arg | type)); @@ -232,16 +241,16 @@ int writeCborHeadWithArgument(EncodingStream& stream, int type, uint64_t arg) { } int writeCborUnsignedInteger(EncodingStream& stream, uint64_t val) { - CHECK(writeCborHeadWithArgument(stream, 0 /* Unsigned integer */, val)); + CHECK(writeCborHead(stream, 0 /* Unsigned integer */, val)); return 0; } int writeCborSignedInteger(EncodingStream& stream, int64_t val) { if (val < 0) { val = -(val + 1); - CHECK(writeCborHeadWithArgument(stream, 1 /* Negative integer */, val)); + CHECK(writeCborHead(stream, 1 /* Negative integer */, val)); } else { - CHECK(writeCborHeadWithArgument(stream, 0 /* Unsigned integer */, val)); + CHECK(writeCborHead(stream, 0 /* Unsigned integer */, val)); } return 0; } @@ -280,7 +289,7 @@ int readCborTextString(DecodingStream& stream, const CborHead& head, String& str } int writeCborTextString(EncodingStream& stream, const String& str) { - CHECK(writeCborHeadWithArgument(stream, 3 /* Text string */, str.length())); + CHECK(writeCborHead(stream, 3 /* Text string */, str.length())); CHECK(stream.write(str.c_str(), str.length())); return 0; } @@ -291,7 +300,7 @@ int readCborByteString(DecodingStream& stream, const CborHead& head, Buffer& buf } int writeCborByteString(EncodingStream& stream, const Buffer& buf) { - CHECK(writeCborHeadWithArgument(stream, 2 /* Byte string */, buf.size())); + CHECK(writeCborHead(stream, 2 /* Byte string */, buf.size())); CHECK(stream.write(buf.data(), buf.size())); return 0; } @@ -346,7 +355,7 @@ int encodeToCbor(EncodingStream& stream, const Variant& var) { } case Variant::ARRAY: { auto& arr = var.value(); - CHECK(writeCborHeadWithArgument(stream, 4 /* Array */, arr.size())); + CHECK(writeCborHead(stream, 4 /* Array */, arr.size())); for (auto& v: arr) { CHECK(encodeToCbor(stream, v)); } @@ -354,7 +363,7 @@ int encodeToCbor(EncodingStream& stream, const Variant& var) { } case Variant::MAP: { auto& entries = var.value().entries(); - CHECK(writeCborHeadWithArgument(stream, 5 /* Map */, entries.size())); + CHECK(writeCborHead(stream, 5 /* Map */, entries.size())); for (auto& e: entries) { CHECK(writeCborTextString(stream, e.first)); CHECK(encodeToCbor(stream, e.second)); @@ -435,19 +444,19 @@ int decodeFromCbor(DecodingStream& stream, const CborHead& head, Variant& var) { break; } case 5: { // Map - VariantMap map; + Variant cont = VariantMap(); // Initially a map but can be an array int len = -1; if (head.detail != 31 /* Indefinite length */) { if (head.arg > (uint64_t)std::numeric_limits::max()) { return Error::OUT_OF_RANGE; } len = head.arg; - if (!map.reserve(len)) { + if (!cont.asMap().reserve(len)) { return Error::NO_MEMORY; } } for (;;) { - if (len >= 0 && map.size() == len) { + if (len >= 0 && cont.size() == len) { break; } CborHead h; @@ -458,19 +467,33 @@ int decodeFromCbor(DecodingStream& stream, const CborHead& head, Variant& var) { } break; } - if (h.type != 3 /* Text string */) { - return Error::NOT_SUPPORTED; // Non-string keys are not supported - } - String k; - CHECK(readCborTextString(stream, h, k)); + Variant k; + CHECK(decodeFromCbor(stream, h, k)); Variant v; CHECK(readCborHead(stream, h)); CHECK(decodeFromCbor(stream, h, v)); - if (!map.set(std::move(k), std::move(v))) { - return Error::NO_MEMORY; + if (cont.isMap()) { + if (!k.isString()) { + // VariantMap can only contain string keys. If any of the keys is not a string, + // the map is parsed as an array of key-value pairs + VariantArray arr; + int capacity = (len < 0) ? (cont.size() + 1) : len; + if (!arr.reserve(capacity)) { + return Error::NO_MEMORY; + } + for (auto& entry: cont.asMap()) { + CHECK(appendKeyValue(arr, std::move(entry.first), std::move(entry.second))); + } + cont = std::move(arr); + } else if (!cont.asMap().set(std::move(k.asString()), std::move(v))) { + return Error::NO_MEMORY; + } + } + if (cont.isArray()) { + CHECK(appendKeyValue(cont.asArray(), std::move(k), std::move(v))); } } - var = std::move(map); + var = std::move(cont); break; } case 6: { // Tagged item From 05f5d2c5d4a6131141675c39a7bdc8a29d9c3831 Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Tue, 27 Aug 2024 18:42:18 +0200 Subject: [PATCH 31/45] Add a method for resizing the string --- test/unit_tests/wiring/string.cpp | 28 ++++++++++++++++++++++++++++ wiring/inc/spark_wiring_string.h | 1 + wiring/src/spark_wiring_string.cpp | 13 +++++++++++++ 3 files changed, 42 insertions(+) diff --git a/test/unit_tests/wiring/string.cpp b/test/unit_tests/wiring/string.cpp index ada301d806..39c3671caf 100644 --- a/test/unit_tests/wiring/string.cpp +++ b/test/unit_tests/wiring/string.cpp @@ -1,6 +1,7 @@ #include #include +#include #include "util/catch.h" #include "spark_wiring_string.h" @@ -209,3 +210,30 @@ TEST_CASE("String concat", "[ParticleString]") { REQUIRE(str1 == "Hello 18446744073709551615"); } } + +TEST_CASE("String resize") { + SECTION("can increase the string length") { + String s; + CHECK(s.resize(5)); + CHECK(s.capacity() == 5); + CHECK(s.length() == 5); + CHECK(std::memcmp(s.c_str(), "\0\0\0\0\0\0", 6) == 0); + s.setCharAt(4, 'a'); + CHECK(s.resize(6)); + CHECK(s.capacity() == 6); + CHECK(s.length() == 6); + CHECK(std::memcmp(s.c_str(), "\0\0\0\0a\0\0", 7) == 0); + } + + SECTION("can decrease the string length") { + String s("abcde"); + CHECK(s.resize(4)); + CHECK(s.capacity() == 5); + CHECK(s.length() == 4); + CHECK(std::memcmp(s.c_str(), "abcd\0", 5) == 0); + CHECK(s.resize(0)); + CHECK(s.capacity() == 5); + CHECK(s.length() == 0); + CHECK(std::memcmp(s.c_str(), "\0", 1) == 0); + } +} diff --git a/wiring/inc/spark_wiring_string.h b/wiring/inc/spark_wiring_string.h index 91602aa550..438178a32a 100644 --- a/wiring/inc/spark_wiring_string.h +++ b/wiring/inc/spark_wiring_string.h @@ -92,6 +92,7 @@ class String // is left unchanged). reserve(0), if successful, will validate an // invalid string (i.e., "if (s)" will be true afterwards) unsigned char reserve(unsigned int size); + bool resize(size_t size); inline unsigned int length(void) const {return len;} unsigned int capacity() const { diff --git a/wiring/src/spark_wiring_string.cpp b/wiring/src/spark_wiring_string.cpp index 2265a78ecc..8dc37193aa 100644 --- a/wiring/src/spark_wiring_string.cpp +++ b/wiring/src/spark_wiring_string.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include "string_convert.h" using namespace particle; @@ -233,6 +234,18 @@ unsigned char String::reserve(unsigned int size) return 0; } +bool String::resize(size_t size) { + if (size > capacity_ && !changeBuffer(size)) { + return false; + } + if (size > len) { + std::memset(buffer + len, 0, size - len); + } + buffer[size] = '\0'; + len = size; + return true; +} + unsigned char String::changeBuffer(unsigned int maxStrLen) { char *newbuffer = (char *)realloc(buffer, maxStrLen + 1); From 5795e5b079e4473573a0a258ddb86cc5041ed0be Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Tue, 27 Aug 2024 18:43:22 +0200 Subject: [PATCH 32/45] Add tests; minor fixes --- system/inc/asset_manager_api.h | 2 +- test/unit_tests/util/dump.h | 21 ++ test/unit_tests/wiring/CMakeLists.txt | 2 + test/unit_tests/wiring/buffer.cpp | 325 ++++++++++++++++++++++++++ test/unit_tests/wiring/variant.cpp | 4 +- wiring/inc/spark_wiring_buffer.h | 115 ++++++++- wiring/src/spark_wiring_buffer.cpp | 12 +- 7 files changed, 466 insertions(+), 15 deletions(-) create mode 100644 test/unit_tests/util/dump.h create mode 100644 test/unit_tests/wiring/buffer.cpp diff --git a/system/inc/asset_manager_api.h b/system/inc/asset_manager_api.h index 847582b487..a6dd4505a5 100644 --- a/system/inc/asset_manager_api.h +++ b/system/inc/asset_manager_api.h @@ -158,7 +158,7 @@ inline AssetHash::AssetHash(const char* hash, size_t length, Type type) } inline AssetHash::AssetHash(const uint8_t* hash, size_t length, Type type) - : AssetHash(Buffer(hash, length), type) { + : AssetHash(Buffer((const char*)hash, length), type) { } inline AssetHash::AssetHash(const Buffer& hash, Type type) diff --git a/test/unit_tests/util/dump.h b/test/unit_tests/util/dump.h new file mode 100644 index 0000000000..2f2ea8500a --- /dev/null +++ b/test/unit_tests/util/dump.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include +#include +#include + +namespace particle::test { + +inline void dump(const void* data, size_t size) { + std::ostringstream s; + s << std::hex << std::setfill('0'); + auto d = static_cast(data); + for (size_t i = 0; i < size; ++i) { + s << std::setw(2) << (int)d[i]; + } + std::cerr << "*******************************************************************************" << std::endl; + std::cerr << "dump(): " << s.str() << " (" << size << " bytes)" << std::endl; +} + +} // namespace particle::test diff --git a/test/unit_tests/wiring/CMakeLists.txt b/test/unit_tests/wiring/CMakeLists.txt index e8bf8b883d..ae0182f523 100644 --- a/test/unit_tests/wiring/CMakeLists.txt +++ b/test/unit_tests/wiring/CMakeLists.txt @@ -19,6 +19,7 @@ add_executable( ${target_name} ${DEVICE_OS_DIR}/wiring/src/spark_wiring_i2c.cpp ${DEVICE_OS_DIR}/wiring/src/spark_wiring_ipaddress.cpp ${DEVICE_OS_DIR}/wiring/src/spark_wiring_variant.cpp + ${DEVICE_OS_DIR}/wiring/src/spark_wiring_buffer.cpp ${DEVICE_OS_DIR}/wiring_globals/src/wiring_globals_i2c.cpp ${DEVICE_OS_DIR}/hal/src/template/i2c_hal.cpp ${DEVICE_OS_DIR}/wiring/src/string_convert.cpp @@ -44,6 +45,7 @@ add_executable( ${target_name} wlan.cpp map.cpp variant.cpp + buffer.cpp ) # Set defines specific to target diff --git a/test/unit_tests/wiring/buffer.cpp b/test/unit_tests/wiring/buffer.cpp new file mode 100644 index 0000000000..c710fc7e0e --- /dev/null +++ b/test/unit_tests/wiring/buffer.cpp @@ -0,0 +1,325 @@ +#include + +#include "spark_wiring_buffer.h" + +#include "util/catch.h" + +using namespace particle; + +TEST_CASE("Buffer") { + SECTION("Buffer()") { + SECTION("constructs an empty buffer") { + Buffer b; + CHECK(b.size() == 0); + CHECK(b.capacity() == 0); + CHECK(b.isEmpty()); + } + } + + SECTION("Buffer(size_t)") { + SECTION("constructs a buffer of a specific size") { + { + Buffer b(0); + CHECK(b.size() == 0); + CHECK(b.capacity() == 0); + CHECK(b.isEmpty()); + } + { + Buffer b(100); + CHECK(b.size() == 100); + CHECK(b.capacity() == 100); + CHECK(!b.isEmpty()); + } + } + } + + SECTION("Buffer(const char*, size_t)") { + SECTION("constructs a buffer by copying existing data") { + { + Buffer b(nullptr, 0); + CHECK(b.size() == 0); + CHECK(b.capacity() == 0); + CHECK(b.isEmpty()); + } + { + char d[] = { 0x00, 0x11, 0x22, 0x33 }; + Buffer b(d, 4); + CHECK(b.size() == 4); + CHECK(b.capacity() == 4); + CHECK(!b.isEmpty()); + CHECK(std::memcmp(b.data(), d, 4) == 0); + } + } + } + + SECTION("Buffer(const Buffer&)") { + SECTION("copy-constructs a buffer") { + Buffer b; + REQUIRE(b.reserve(10)); + b.resize(3); + std::memcpy(b.data(), "abc", 3); + + Buffer b2(b); + CHECK(b2.size() == 3); + CHECK(b2.capacity() == 3); // Not the same capacity as the original buffer had + CHECK(!b2.isEmpty()); + CHECK(std::memcmp(b2.data(), "abc", 3) == 0); + } + } + + SECTION("Buffer(Buffer&&)") { + SECTION("move-constructs a buffer") { + Buffer b("abc", 3); + Buffer b2(std::move(b)); + CHECK(b2.size() == 3); + CHECK(b2.capacity() == 3); + CHECK(!b2.isEmpty()); + CHECK(std::memcmp(b2.data(), "abc", 3) == 0); + CHECK(!b.data()); // The underlying buffer was moved to the other instance + } + } + + SECTION("data()") { + SECTION("returns a pointer to the buffer data") { + Buffer b("abc", 3); + CHECK(std::memcmp(b.data(), "abc", 3) == 0); + const Buffer b2("abc", 3); + CHECK(std::memcmp(b2.data(), "abc", 3) == 0); + } + } + + SECTION("size()") { + SECTION("returns the size of the buffer data") { + Buffer b("abc", 3); + CHECK(b.size() == 3); + } + } + + SECTION("isEmpty()") { + SECTION("returns true if the buffer is empty") { + Buffer b; + CHECK(b.isEmpty()); + } + SECTION("returns false if the buffer is not empty") { + Buffer b("abc", 3); + CHECK(!b.isEmpty()); + } + } + + SECTION("resize()") { + SECTION("can increase the buffer size") { + Buffer b; + CHECK(b.resize(3)); + CHECK(b.capacity() == 3); + CHECK(b.size() == 3); + std::memcpy(b.data(), "abc", 3); + CHECK(b.resize(4)); + CHECK(b.capacity() == 4); + CHECK(b.size() == 4); + CHECK(std::memcmp(b.data(), "abc", 3) == 0); + } + SECTION("can decrease the buffer size") { + Buffer b("abcd", 4); + CHECK(b.resize(3)); + CHECK(b.capacity() == 4); + CHECK(b.size() == 3); + CHECK(std::memcmp(b.data(), "abc", 3) == 0); + CHECK(b.resize(0)); + CHECK(b.capacity() == 4); + CHECK(b.size() == 0); + } + } + + SECTION("reserve()") { + SECTION("can increase the buffer capacity") { + Buffer b; + CHECK(b.reserve(3)); + CHECK(b.capacity() == 3); + CHECK(b.size() == 0); + CHECK(b.resize(3)); + std::memcpy(b.data(), "abc", 3); + CHECK(b.reserve(4)); + CHECK(b.capacity() == 4); + CHECK(b.size() == 3); + CHECK(std::memcmp(b.data(), "abc", 3) == 0); + CHECK(b.reserve(0)); + CHECK(b.capacity() == 4); + CHECK(b.size() == 3); + } + } + + SECTION("capacity()") { + SECTION("returns the capacity of the buffer") { + Buffer b("abc", 3); + CHECK(b.capacity() == 3); + CHECK(b.resize(4)); + CHECK(b.capacity() == 4); + CHECK(b.resize(2)); + CHECK(b.capacity() == 4); + } + } + + SECTION("trimToSize()") { + SECTION("frees the unused buffer capacity") { + Buffer b("abc", 3); + CHECK(b.capacity() == 3); + CHECK(b.size() == 3); + CHECK(b.resize(2)); + CHECK(b.trimToSize()); + CHECK(b.capacity() == 2); + CHECK(b.size() == 2); + CHECK(std::memcmp(b.data(), "ab", 2) == 0); + } + } + + SECTION("toHex()") { + SECTION("converts the buffer data to a hex-encoded string") { + { + Buffer b; + CHECK(b.toHex() == String("")); + } + { + Buffer b("\x01\x23\x45\x67\x89\xab\xcd\xef", 8); + CHECK(b.toHex() == String("0123456789abcdef")); + } + } + } + + SECTION("toHex(char*, size_t)") { + SECTION("converts the buffer data to a hex-encoded string") { + { + Buffer b; + char out[2]; + std::memset(out, 0xff, sizeof(out)); + CHECK(b.toHex(out, sizeof(out)) == 0); + CHECK(std::memcmp(out, "\x00\xff", 2) == 0); + } + { + Buffer b("\x01\x23\x45\x67\x89\xab\xcd\xef", 8); + char out[17]; + std::memset(out, 0xff, sizeof(out)); + CHECK(b.toHex(out, sizeof(out)) == 16); + CHECK(std::memcmp(out, "0123456789abcdef\0", 17) == 0); + } + { + Buffer b("\x01\x23", 2); + char out[4]; + std::memset(out, 0xff, sizeof(out)); + CHECK(b.toHex(out, sizeof(out)) == 3); + CHECK(std::memcmp(out, "012\0", 4) == 0); + } + } + } + + SECTION("fromHex(const char*)") { + SECTION("constructs a buffer from a hex-encoded string") { + { + Buffer b = Buffer::fromHex(""); + CHECK(b.isEmpty()); + } + { + Buffer b = Buffer::fromHex("0123456789abcdef"); + CHECK(b.size() == 8); + CHECK(std::memcmp(b.data(), "\x01\x23\x45\x67\x89\xab\xcd\xef", 8) == 0); + } + { + Buffer b = Buffer::fromHex("01234"); + CHECK(b.size() == 2); + CHECK(std::memcmp(b.data(), "\x01\x23", 2) == 0); + } + { + Buffer b = Buffer::fromHex("0123.567"); + CHECK(b.size() == 2); + CHECK(std::memcmp(b.data(), "\x01\x23", 2) == 0); + } + { + Buffer b = Buffer::fromHex("01234.67"); + CHECK(b.size() == 2); + CHECK(std::memcmp(b.data(), "\x01\x23", 2) == 0); + } + } + } + + SECTION("fromHex(const char*, size_t)") { + SECTION("constructs a buffer from a hex-encoded string") { + { + Buffer b = Buffer::fromHex(nullptr, 0); + CHECK(b.isEmpty()); + } + { + Buffer b = Buffer::fromHex("0123456789abcdef", 16); + CHECK(b.size() == 8); + CHECK(std::memcmp(b.data(), "\x01\x23\x45\x67\x89\xab\xcd\xef", 8) == 0); + } + { + Buffer b = Buffer::fromHex("0123456789abcdef", 15); + CHECK(b.size() == 7); + CHECK(std::memcmp(b.data(), "\x01\x23\x45\x67\x89\xab\xcd", 7) == 0); + } + } + } + + SECTION("fromHex(const String&)") { + SECTION("constructs a buffer from a hex-encoded string") { + { + Buffer b = Buffer::fromHex(String()); + CHECK(b.isEmpty()); + } + { + Buffer b = Buffer::fromHex(String("0123456789abcdef")); + CHECK(b.size() == 8); + CHECK(std::memcmp(b.data(), "\x01\x23\x45\x67\x89\xab\xcd\xef", 8) == 0); + } + } + } + + SECTION("operator==") { + SECTION("compares the buffer with another buffer") { + { + Buffer b; + Buffer b2; + CHECK(b == b2); + } + { + Buffer b("abc", 3); + Buffer b2("abc", 3); + CHECK(b == b2); + } + { + Buffer b("abc", 3); + Buffer b2("abcd", 4); + CHECK(!(b == b2)); + } + { + Buffer b("abc", 3); + Buffer b2("abd", 3); + CHECK(!(b == b2)); + } + } + } + + SECTION("operator!=") { + SECTION("compares the buffer with another buffer") { + { + Buffer b; + Buffer b2; + CHECK(!(b != b2)); + } + { + Buffer b("abc", 3); + Buffer b2("abc", 3); + CHECK(!(b != b2)); + } + { + Buffer b("abc", 3); + Buffer b2("abcd", 4); + CHECK(b != b2); + } + { + Buffer b("abc", 3); + Buffer b2("abd", 3); + CHECK(b != b2); + } + } + } +} diff --git a/test/unit_tests/wiring/variant.cpp b/test/unit_tests/wiring/variant.cpp index 05ac4fc264..2a444dafb2 100644 --- a/test/unit_tests/wiring/variant.cpp +++ b/test/unit_tests/wiring/variant.cpp @@ -346,7 +346,7 @@ TEST_CASE("Variant") { checkVariant(v, VariantMap{ { "a", 1 }, { "b", 2 }, { "c", 3 } }); } - SECTION("encodeVariantToCBOR()") { + SECTION("encodeToCBOR()") { using test::toHex; CHECK(toHex(toCbor(0)) == "00"); CHECK(toHex(toCbor(1)) == "01"); @@ -399,7 +399,7 @@ TEST_CASE("Variant") { CHECK(toHex(toCbor(VariantMap{{"a", "A"}, {"b", "B"}, {"c", "C"}, {"d", "D"}, {"e", "E"}})) == "a56161614161626142616361436164614461656145"); } - SECTION("decodeVariantFromCBOR") { + SECTION("decodeFromCBOR()") { using test::fromHex; CHECK(fromCbor(fromHex("00")) == Variant(0)); CHECK(fromCbor(fromHex("01")) == Variant(1)); diff --git a/wiring/inc/spark_wiring_buffer.h b/wiring/inc/spark_wiring_buffer.h index 91633bed40..96a0fed20b 100644 --- a/wiring/inc/spark_wiring_buffer.h +++ b/wiring/inc/spark_wiring_buffer.h @@ -25,75 +25,174 @@ namespace particle { +/** + * A dynamically allocated buffer. + */ class Buffer { public: + /** + * Construct an empty buffer. + */ Buffer() = default; + /** + * Construct a buffer of a specific size. + * + * The buffer is filled with zero bytes. + * + * @param size Buffer size. + */ explicit Buffer(size_t size) : d_(size) { } + /** + * Construct a buffer by copying existing data. + * + * @param data Data to copy. + * @param size Data size. + */ Buffer(const char* data, size_t size) : d_(data, size) { } - Buffer(const uint8_t* data, size_t size) : - d_((const char*)data, size) { - } - + /** + * Get a pointer to the buffer data. + * + * @return Buffer data. + */ char* data() { return d_.data(); } + /** + * Get a pointer to the buffer data. + * + * @return Buffer data. + */ const char* data() const { return d_.data(); } + /** + * Get the size of the buffer data. + * + * @return Data size. + */ size_t size() const { return d_.size(); } + /** + * Check if the buffer is empty. + * + * @return `true` if the buffer is empty, otherwise `false`. + */ bool isEmpty() const { return d_.isEmpty(); } + /** + * Resize the buffer. + * + * @param size New size. + * @return `true` if the buffer was resized successfully, or `false` if a memory allocation error occured. + */ bool resize(size_t size) { return d_.resize(size); } + /** + * Get the capacity of the buffer. + * + * @return Buffer capacity. + */ size_t capacity() const { return d_.capacity(); } + /** + * Ensure the buffer has enough capacity. + * + * @param size Desired capacity. + * @return `true` if the capacity was changed successfully, or `false` if a memory allocation error occured. + */ bool reserve(size_t size) { return d_.reserve(size); } + /** + * Free the unused buffer capacity. + * + * @return `true` if the capacity was changed successfully, or `false` if a memory allocation error occured. + */ bool trimToSize() { return d_.trimToSize(); } + /** + * Convert the contents of the buffer to a hex-encoded string. + * + * @return Hex-encoded string. + */ String toHex() const; + /** + * Convert the contents of the buffer to a hex-encoded string. + * + * The output is always null-terminated, unless the size of the output buffer is 0. + * + * @param out Output buffer. + * @param size Size of the output buffer. + * @return Number of characters written to the output buffer, not including the trailing `\0`. + */ + size_t toHex(char* out, size_t size) const; + + /** + * Compare this buffer with another buffer. + * + * @return `true` if the buffers contain the same bytes, otherwise `false`. + */ bool operator==(const Buffer& buf) const { - if (d_.size() != buf.d_.size()) { - return false; - } - return std::memcmp(d_.data(), buf.d_.data(), d_.size()) == 0; + return d_.size() == buf.d_.size() && std::memcmp(d_.data(), buf.d_.data(), d_.size()) == 0; } + /** + * Compare this buffer with another buffer. + * + * @return `true` if the buffers contain different bytes, otherwise `false`. + */ bool operator!=(const Buffer& buf) const { return !(operator==(buf)); } + /** + * Construct a buffer from a hex-encoded string. + * + * @param str Hex-encoded string. + * @return A buffer. + */ static Buffer fromHex(const String& str) { return fromHex(str.c_str(), str.length()); } + /** + * Construct a buffer from a hex-encoded string. + * + * @param str Hex-encoded string. + * @return A buffer. + */ static Buffer fromHex(const char* str) { return fromHex(str, std::strlen(str)); } + /** + * Construct a buffer from a hex-encoded string. + * + * @param str Hex-encoded string. + * @param len String length. + * @return A buffer. + */ static Buffer fromHex(const char* str, size_t len); private: diff --git a/wiring/src/spark_wiring_buffer.cpp b/wiring/src/spark_wiring_buffer.cpp index ad255cc57d..a7ed43c4b2 100644 --- a/wiring/src/spark_wiring_buffer.cpp +++ b/wiring/src/spark_wiring_buffer.cpp @@ -23,19 +23,23 @@ namespace particle { String Buffer::toHex() const { String s; - if (!s.reserve(d_.size() * 2)) { + if (!s.resize(d_.size() * 2)) { return String(); } - particle::toHex(d_.data(), d_.size(), &s.operator[](0), s.length() + 1); + toHex(&s.operator[](0), s.length() + 1); // Overwrites the term. null return s; } +size_t Buffer::toHex(char* out, size_t size) const { + return particle::toHex(d_.data(), d_.size(), out, size); +} + Buffer Buffer::fromHex(const char* str, size_t len) { Buffer buf; - if (!buf.reserve(len / 2)) { + if (!buf.resize(len / 2)) { return Buffer(); } - size_t n = particle::fromHex(str, len, buf.d_.data(), buf.d_.capacity()); + size_t n = particle::fromHex(str, len, buf.d_.data(), buf.d_.size()); buf.resize(n); return buf; } From 0b0b35003a31a5d6f07593b3558f9b67f93323f9 Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Tue, 27 Aug 2024 23:21:31 +0200 Subject: [PATCH 33/45] Add tests; bugfixes --- test/unit_tests/wiring/buffer.cpp | 5 + test/unit_tests/wiring/variant.cpp | 292 ++++++++++++++++------------ wiring/inc/spark_wiring_variant.h | 5 + wiring/src/spark_wiring_variant.cpp | 22 ++- 4 files changed, 187 insertions(+), 137 deletions(-) diff --git a/test/unit_tests/wiring/buffer.cpp b/test/unit_tests/wiring/buffer.cpp index c710fc7e0e..8ccab319d5 100644 --- a/test/unit_tests/wiring/buffer.cpp +++ b/test/unit_tests/wiring/buffer.cpp @@ -222,6 +222,11 @@ TEST_CASE("Buffer") { CHECK(b.size() == 8); CHECK(std::memcmp(b.data(), "\x01\x23\x45\x67\x89\xab\xcd\xef", 8) == 0); } + { + Buffer b = Buffer::fromHex("0123456789ABCDEF"); + CHECK(b.size() == 8); + CHECK(std::memcmp(b.data(), "\x01\x23\x45\x67\x89\xab\xcd\xef", 8) == 0); + } { Buffer b = Buffer::fromHex("01234"); CHECK(b.size() == 2); diff --git a/test/unit_tests/wiring/variant.cpp b/test/unit_tests/wiring/variant.cpp index 2a444dafb2..a6d4ffdd07 100644 --- a/test/unit_tests/wiring/variant.cpp +++ b/test/unit_tests/wiring/variant.cpp @@ -25,66 +25,73 @@ void checkVariant(const Variant& v, const T& expectedValue = T()) { CHECK_FALSE(expectedValue != v); if constexpr (std::is_same_v) { CHECK(v.type() == Variant::NULL_); - CHECK((v.isNull() && !v.isBool() && !v.isInt() && !v.isUInt() && !v.isInt64() && !v.isUInt64() && !v.isDouble() && !v.isNumber() && !v.isString() && !v.isArray() && !v.isMap())); + CHECK((v.isNull() && !v.isBool() && !v.isInt() && !v.isUInt() && !v.isInt64() && !v.isUInt64() && !v.isDouble() && !v.isNumber() && !v.isString() && !v.isBuffer() && !v.isArray() && !v.isMap())); } else if constexpr (std::is_same_v) { CHECK(v.type() == Variant::BOOL); - CHECK((!v.isNull() && v.isBool() && !v.isInt() && !v.isUInt() && !v.isInt64() && !v.isUInt64() && !v.isDouble() && !v.isNumber() && !v.isString() && !v.isArray() && !v.isMap())); + CHECK((!v.isNull() && v.isBool() && !v.isInt() && !v.isUInt() && !v.isInt64() && !v.isUInt64() && !v.isDouble() && !v.isNumber() && !v.isString() && !v.isBuffer() && !v.isArray() && !v.isMap())); CHECK(v.toBool() == expectedValue); bool ok = false; CHECK(v.toBool(ok) == expectedValue); CHECK(ok); } else if constexpr (std::is_same_v) { CHECK(v.type() == Variant::INT); - CHECK((!v.isNull() && !v.isBool() && v.isInt() && !v.isUInt() && !v.isInt64() && !v.isUInt64() && !v.isDouble() && v.isNumber() && !v.isString() && !v.isArray() && !v.isMap())); + CHECK((!v.isNull() && !v.isBool() && v.isInt() && !v.isUInt() && !v.isInt64() && !v.isUInt64() && !v.isDouble() && v.isNumber() && !v.isString() && !v.isBuffer() && !v.isArray() && !v.isMap())); CHECK(v.toInt() == expectedValue); bool ok = false; CHECK(v.toInt(ok) == expectedValue); CHECK(ok); } else if constexpr (std::is_same_v) { CHECK(v.type() == Variant::UINT); - CHECK((!v.isNull() && !v.isBool() && !v.isInt() && v.isUInt() && !v.isInt64() && !v.isUInt64() && !v.isDouble() && v.isNumber() && !v.isString() && !v.isArray() && !v.isMap())); + CHECK((!v.isNull() && !v.isBool() && !v.isInt() && v.isUInt() && !v.isInt64() && !v.isUInt64() && !v.isDouble() && v.isNumber() && !v.isString() && !v.isBuffer() && !v.isArray() && !v.isMap())); CHECK(v.toUInt() == expectedValue); bool ok = false; CHECK(v.toUInt(ok) == expectedValue); CHECK(ok); } else if constexpr (std::is_same_v) { CHECK(v.type() == Variant::INT64); - CHECK((!v.isNull() && !v.isBool() && !v.isInt() && !v.isUInt() && v.isInt64() && !v.isUInt64() && !v.isDouble() && v.isNumber() && !v.isString() && !v.isArray() && !v.isMap())); + CHECK((!v.isNull() && !v.isBool() && !v.isInt() && !v.isUInt() && v.isInt64() && !v.isUInt64() && !v.isDouble() && v.isNumber() && !v.isString() && !v.isBuffer() && !v.isArray() && !v.isMap())); CHECK(v.toInt64() == expectedValue); bool ok = false; CHECK(v.toInt64(ok) == expectedValue); CHECK(ok); } else if constexpr (std::is_same_v) { CHECK(v.type() == Variant::UINT64); - CHECK((!v.isNull() && !v.isBool() && !v.isInt() && !v.isUInt() && !v.isInt64() && v.isUInt64() && !v.isDouble() && v.isNumber() && !v.isString() && !v.isArray() && !v.isMap())); + CHECK((!v.isNull() && !v.isBool() && !v.isInt() && !v.isUInt() && !v.isInt64() && v.isUInt64() && !v.isDouble() && v.isNumber() && !v.isString() && !v.isBuffer() && !v.isArray() && !v.isMap())); CHECK(v.toUInt64() == expectedValue); bool ok = false; CHECK(v.toUInt64(ok) == expectedValue); CHECK(ok); } else if constexpr (std::is_same_v) { CHECK(v.type() == Variant::DOUBLE); - CHECK((!v.isNull() && !v.isBool() && !v.isInt() && !v.isUInt() && !v.isInt64() && !v.isUInt64() && v.isDouble() && v.isNumber() && !v.isString() && !v.isArray() && !v.isMap())); + CHECK((!v.isNull() && !v.isBool() && !v.isInt() && !v.isUInt() && !v.isInt64() && !v.isUInt64() && v.isDouble() && v.isNumber() && !v.isString() && !v.isBuffer() && !v.isArray() && !v.isMap())); CHECK(v.toDouble() == expectedValue); bool ok = false; CHECK(v.toDouble(ok) == expectedValue); CHECK(ok); } else if constexpr (std::is_same_v) { CHECK(v.type() == Variant::STRING); - CHECK((!v.isNull() && !v.isBool() && !v.isInt() && !v.isUInt() && !v.isInt64() && !v.isUInt64() && !v.isDouble() && !v.isNumber() && v.isString() && !v.isArray() && !v.isMap())); + CHECK((!v.isNull() && !v.isBool() && !v.isInt() && !v.isUInt() && !v.isInt64() && !v.isUInt64() && !v.isDouble() && !v.isNumber() && v.isString() && !v.isBuffer() && !v.isArray() && !v.isMap())); CHECK(v.toString() == expectedValue); bool ok = false; CHECK(v.toString(ok) == expectedValue); CHECK(ok); + } else if constexpr (std::is_same_v) { + CHECK(v.type() == Variant::BUFFER); + CHECK((!v.isNull() && !v.isBool() && !v.isInt() && !v.isUInt() && !v.isInt64() && !v.isUInt64() && !v.isDouble() && !v.isNumber() && !v.isString() && v.isBuffer() && !v.isArray() && !v.isMap())); + CHECK(v.toBuffer() == expectedValue); + bool ok = false; + CHECK(v.toBuffer(ok) == expectedValue); + CHECK(ok); } else if constexpr (std::is_same_v) { CHECK(v.type() == Variant::ARRAY); - CHECK((!v.isNull() && !v.isBool() && !v.isInt() && !v.isUInt() && !v.isInt64() && !v.isUInt64() && !v.isDouble() && !v.isNumber() && !v.isString() && v.isArray() && !v.isMap())); + CHECK((!v.isNull() && !v.isBool() && !v.isInt() && !v.isUInt() && !v.isInt64() && !v.isUInt64() && !v.isDouble() && !v.isNumber() && !v.isString() && !v.isBuffer() && v.isArray() && !v.isMap())); CHECK(v.toArray() == expectedValue); bool ok = false; CHECK(v.toArray(ok) == expectedValue); CHECK(ok); } else if constexpr (std::is_same_v) { CHECK(v.type() == Variant::MAP); - CHECK((!v.isNull() && !v.isBool() && !v.isInt() && !v.isUInt() && !v.isInt64() && !v.isUInt64() && !v.isDouble() && !v.isNumber() && !v.isString() && !v.isArray() && v.isMap())); + CHECK((!v.isNull() && !v.isBool() && !v.isInt() && !v.isUInt() && !v.isInt64() && !v.isUInt64() && !v.isDouble() && !v.isNumber() && !v.isString() && !v.isBuffer() && !v.isArray() && v.isMap())); CHECK(v.toMap() == expectedValue); bool ok = false; CHECK(v.toMap(ok) == expectedValue); @@ -114,6 +121,8 @@ void checkVariant(Variant& v, const T& expectedValue = T()) { CHECK(v.asDouble() == expectedValue); } else if constexpr (std::is_same_v) { CHECK(v.asString() == expectedValue); + } else if constexpr (std::is_same_v) { + CHECK(v.asBuffer() == expectedValue); } else if constexpr (std::is_same_v) { CHECK(v.asArray() == expectedValue); } else if constexpr (std::is_same_v) { @@ -219,6 +228,11 @@ TEST_CASE("Variant") { Variant v(String("abc")); checkVariant(v, "abc"); } + { + Buffer buf("abc", 3); + Variant v(buf); + checkVariant(v, buf); + } { VariantArray arr({ 1, 2, 3 }); Variant v(arr); @@ -288,6 +302,10 @@ TEST_CASE("Variant") { v = String("abc"); checkVariant(v, "abc"); + Buffer buf("abc", 3); + v = buf; + checkVariant(v, buf); + VariantArray arr({ 1, 2, 3 }); v = arr; checkVariant(v, arr); @@ -348,127 +366,145 @@ TEST_CASE("Variant") { SECTION("encodeToCBOR()") { using test::toHex; - CHECK(toHex(toCbor(0)) == "00"); - CHECK(toHex(toCbor(1)) == "01"); - CHECK(toHex(toCbor(10)) == "0a"); - CHECK(toHex(toCbor(23)) == "17"); - CHECK(toHex(toCbor(24)) == "1818"); - CHECK(toHex(toCbor(25)) == "1819"); - CHECK(toHex(toCbor(100)) == "1864"); - CHECK(toHex(toCbor(1000)) == "1903e8"); - CHECK(toHex(toCbor(1000000)) == "1a000f4240"); - CHECK(toHex(toCbor(1000000000000ull)) == "1b000000e8d4a51000"); - CHECK(toHex(toCbor(18446744073709551615ull)) == "1bffffffffffffffff"); - CHECK(toHex(toCbor(-9223372036854775807ll - 1)) == "3b7fffffffffffffff"); - CHECK(toHex(toCbor(-1)) == "20"); - CHECK(toHex(toCbor(-10)) == "29"); - CHECK(toHex(toCbor(-100)) == "3863"); - CHECK(toHex(toCbor(-1000)) == "3903e7"); - CHECK(toHex(toCbor(0.0)) == "fa00000000"); // Encoding half-precision floats is not supported - CHECK(toHex(toCbor(-0.0)) == "fa80000000"); // ditto - CHECK(toHex(toCbor(1.0)) == "fa3f800000"); // ditto - CHECK(toHex(toCbor(1.1)) == "fb3ff199999999999a"); - CHECK(toHex(toCbor(1.5)) == "fa3fc00000"); // ditto - CHECK(toHex(toCbor(100000.0)) == "fa47c35000"); - CHECK(toHex(toCbor(16777216.0)) == "fa4b800000"); - CHECK(toHex(toCbor(3.4028234663852886e+38)) == "fa7f7fffff"); - CHECK(toHex(toCbor(1.0e+300)) == "fb7e37e43c8800759c"); - CHECK(toHex(toCbor(1.401298464324817e-45)) == "fa00000001"); - CHECK(toHex(toCbor(1.1754943508222875e-38)) == "fa00800000"); - CHECK(toHex(toCbor(-4.0)) == "fac0800000"); // ditto - CHECK(toHex(toCbor(-4.1)) == "fbc010666666666666"); - CHECK(toHex(toCbor(INFINITY)) == "fa7f800000"); // ditto - CHECK(toHex(toCbor(-INFINITY)) == "faff800000"); // ditto - CHECK(toHex(toCbor(NAN)) == "fb7ff8000000000000"); // For simplicity, NaN is always encoded as a double - CHECK(toHex(toCbor(false)) == "f4"); - CHECK(toHex(toCbor(true)) == "f5"); - CHECK(toHex(toCbor(Variant())) == "f6"); - CHECK(toHex(toCbor("")) == "60"); - CHECK(toHex(toCbor("a")) == "6161"); - CHECK(toHex(toCbor("IETF")) == "6449455446"); - CHECK(toHex(toCbor("\"\\")) == "62225c"); - CHECK(toHex(toCbor("\u00fc")) == "62c3bc"); - CHECK(toHex(toCbor("\u6c34")) == "63e6b0b4"); - CHECK(toHex(toCbor(VariantArray{})) == "80"); - CHECK(toHex(toCbor(VariantArray{1, 2, 3})) == "83010203"); - CHECK(toHex(toCbor(VariantArray{1, VariantArray{2, 3}, VariantArray{4, 5}})) == "8301820203820405"); - CHECK(toHex(toCbor(VariantArray{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25})) == "98190102030405060708090a0b0c0d0e0f101112131415161718181819"); - CHECK(toHex(toCbor(VariantMap{})) == "a0"); - CHECK(toHex(toCbor(VariantMap{{"a", 1}, {"b", VariantArray{2, 3}}})) == "a26161016162820203"); - CHECK(toHex(toCbor(VariantArray{"a", VariantMap{{"b", "c"}}})) == "826161a161626163"); - CHECK(toHex(toCbor(VariantMap{{"a", "A"}, {"b", "B"}, {"c", "C"}, {"d", "D"}, {"e", "E"}})) == "a56161614161626142616361436164614461656145"); + + SECTION("encodes a Variant to CBOR") { + // See https://datatracker.ietf.org/doc/html/rfc8949#section-appendix.a + CHECK(toHex(toCbor(0)) == "00"); + CHECK(toHex(toCbor(1)) == "01"); + CHECK(toHex(toCbor(10)) == "0a"); + CHECK(toHex(toCbor(23)) == "17"); + CHECK(toHex(toCbor(24)) == "1818"); + CHECK(toHex(toCbor(25)) == "1819"); + CHECK(toHex(toCbor(100)) == "1864"); + CHECK(toHex(toCbor(1000)) == "1903e8"); + CHECK(toHex(toCbor(1000000)) == "1a000f4240"); + CHECK(toHex(toCbor(1000000000000ull)) == "1b000000e8d4a51000"); + CHECK(toHex(toCbor(18446744073709551615ull)) == "1bffffffffffffffff"); + CHECK(toHex(toCbor(-9223372036854775807ll - 1)) == "3b7fffffffffffffff"); + CHECK(toHex(toCbor(-1)) == "20"); + CHECK(toHex(toCbor(-10)) == "29"); + CHECK(toHex(toCbor(-100)) == "3863"); + CHECK(toHex(toCbor(-1000)) == "3903e7"); + CHECK(toHex(toCbor(0.0)) == "fa00000000"); // Encoding half-precision floats is not supported + CHECK(toHex(toCbor(-0.0)) == "fa80000000"); // ditto + CHECK(toHex(toCbor(1.0)) == "fa3f800000"); // ditto + CHECK(toHex(toCbor(1.1)) == "fb3ff199999999999a"); + CHECK(toHex(toCbor(1.5)) == "fa3fc00000"); // ditto + CHECK(toHex(toCbor(100000.0)) == "fa47c35000"); + CHECK(toHex(toCbor(16777216.0)) == "fa4b800000"); + CHECK(toHex(toCbor(3.4028234663852886e+38)) == "fa7f7fffff"); + CHECK(toHex(toCbor(1.0e+300)) == "fb7e37e43c8800759c"); + CHECK(toHex(toCbor(1.401298464324817e-45)) == "fa00000001"); + CHECK(toHex(toCbor(1.1754943508222875e-38)) == "fa00800000"); + CHECK(toHex(toCbor(-4.0)) == "fac0800000"); // ditto + CHECK(toHex(toCbor(-4.1)) == "fbc010666666666666"); + CHECK(toHex(toCbor(INFINITY)) == "fa7f800000"); // ditto + CHECK(toHex(toCbor(-INFINITY)) == "faff800000"); // ditto + CHECK(toHex(toCbor(NAN)) == "fb7ff8000000000000"); // For simplicity, NaN is always encoded as a double + CHECK(toHex(toCbor(false)) == "f4"); + CHECK(toHex(toCbor(true)) == "f5"); + CHECK(toHex(toCbor(Variant())) == "f6"); + CHECK(toHex(toCbor(Buffer())) == "40"); + CHECK(toHex(toCbor(Buffer("\x01\x02\x03\x04", 4))) == "4401020304"); + CHECK(toHex(toCbor("")) == "60"); + CHECK(toHex(toCbor("a")) == "6161"); + CHECK(toHex(toCbor("IETF")) == "6449455446"); + CHECK(toHex(toCbor("\"\\")) == "62225c"); + CHECK(toHex(toCbor("\u00fc")) == "62c3bc"); + CHECK(toHex(toCbor("\u6c34")) == "63e6b0b4"); + CHECK(toHex(toCbor(VariantArray{})) == "80"); + CHECK(toHex(toCbor(VariantArray{1, 2, 3})) == "83010203"); + CHECK(toHex(toCbor(VariantArray{1, VariantArray{2, 3}, VariantArray{4, 5}})) == "8301820203820405"); + CHECK(toHex(toCbor(VariantArray{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25})) == "98190102030405060708090a0b0c0d0e0f101112131415161718181819"); + CHECK(toHex(toCbor(VariantMap{})) == "a0"); + CHECK(toHex(toCbor(VariantMap{{"a", 1}, {"b", VariantArray{2, 3}}})) == "a26161016162820203"); + CHECK(toHex(toCbor(VariantArray{"a", VariantMap{{"b", "c"}}})) == "826161a161626163"); + CHECK(toHex(toCbor(VariantMap{{"a", "A"}, {"b", "B"}, {"c", "C"}, {"d", "D"}, {"e", "E"}})) == "a56161614161626142616361436164614461656145"); + } } SECTION("decodeFromCBOR()") { using test::fromHex; - CHECK(fromCbor(fromHex("00")) == Variant(0)); - CHECK(fromCbor(fromHex("01")) == Variant(1)); - CHECK(fromCbor(fromHex("0a")) == Variant(10)); - CHECK(fromCbor(fromHex("17")) == Variant(23)); - CHECK(fromCbor(fromHex("1818")) == Variant(24)); - CHECK(fromCbor(fromHex("1819")) == Variant(25)); - CHECK(fromCbor(fromHex("1864")) == Variant(100)); - CHECK(fromCbor(fromHex("1903e8")) == Variant(1000)); - CHECK(fromCbor(fromHex("1a000f4240")) == Variant(1000000)); - CHECK(fromCbor(fromHex("1b000000e8d4a51000")) == Variant(1000000000000ull)); - CHECK(fromCbor(fromHex("1bffffffffffffffff")) == Variant(18446744073709551615ull)); - CHECK(fromCbor(fromHex("3b7fffffffffffffff")) == Variant(-9223372036854775807ll - 1)); - CHECK(fromCbor(fromHex("20")) == Variant(-1)); - CHECK(fromCbor(fromHex("29")) == Variant(-10)); - CHECK(fromCbor(fromHex("3863")) == Variant(-100)); - CHECK(fromCbor(fromHex("3903e7")) == Variant(-1000)); - CHECK(fromCbor(fromHex("f90000")) == Variant(0.0)); - CHECK(fromCbor(fromHex("f98000")) == Variant(-0.0)); - CHECK(fromCbor(fromHex("f93c00")) == Variant(1.0)); - CHECK(fromCbor(fromHex("fb3ff199999999999a")) == Variant(1.1)); - CHECK(fromCbor(fromHex("f93e00")) == Variant(1.5)); - CHECK(fromCbor(fromHex("f97bff")) == Variant(65504.0)); - CHECK(fromCbor(fromHex("fa47c35000")) == Variant(100000.0)); - CHECK(fromCbor(fromHex("fa7f7fffff")) == Variant(3.4028234663852886e+38)); - CHECK(fromCbor(fromHex("fb7e37e43c8800759c")) == Variant(1.0e+300)); - CHECK(fromCbor(fromHex("f90001")) == Variant(5.960464477539063e-8)); - CHECK(fromCbor(fromHex("f90400")) == Variant(0.00006103515625)); - CHECK(fromCbor(fromHex("f9c400")) == Variant(-4.0)); - CHECK(fromCbor(fromHex("fbc010666666666666")) == Variant(-4.1)); - CHECK(fromCbor(fromHex("f97c00")) == Variant(INFINITY)); - CHECK(std::isnan(fromCbor(fromHex("f97e00")).asDouble())); - CHECK(fromCbor(fromHex("f9fc00")) == Variant(-INFINITY)); - CHECK(fromCbor(fromHex("fa7f800000")) == Variant(INFINITY)); - CHECK(std::isnan(fromCbor(fromHex("fa7fc00000")).asDouble())); - CHECK(fromCbor(fromHex("faff800000")) == Variant(-INFINITY)); - CHECK(fromCbor(fromHex("fb7ff0000000000000")) == Variant(INFINITY)); - CHECK(std::isnan(fromCbor(fromHex("fb7ff8000000000000")).asDouble())); - CHECK(fromCbor(fromHex("fbfff0000000000000")) == Variant(-INFINITY)); - CHECK(fromCbor(fromHex("f4")) == Variant(false)); - CHECK(fromCbor(fromHex("f5")) == Variant(true)); - CHECK(fromCbor(fromHex("f6")) == Variant()); - CHECK(fromCbor(fromHex("c074323031332d30332d32315432303a30343a30305a")) == Variant("2013-03-21T20:04:00Z")); - CHECK(fromCbor(fromHex("c11a514b67b0")) == Variant(1363896240)); - CHECK(fromCbor(fromHex("c1fb41d452d9ec200000")) == Variant(1363896240.5)); - CHECK(fromCbor(fromHex("d82076687474703a2f2f7777772e6578616d706c652e636f6d")) == Variant("http://www.example.com")); - CHECK(fromCbor(fromHex("60")) == Variant("")); - CHECK(fromCbor(fromHex("6161")) == Variant("a")); - CHECK(fromCbor(fromHex("6449455446")) == Variant("IETF")); - CHECK(fromCbor(fromHex("62225c")) == Variant("\"\\")); - CHECK(fromCbor(fromHex("62c3bc")) == Variant("\u00fc")); - CHECK(fromCbor(fromHex("63e6b0b4")) == Variant("\u6c34")); - CHECK(fromCbor(fromHex("80")) == VariantArray{}); - CHECK(fromCbor(fromHex("83010203")) == VariantArray{1, 2, 3}); - CHECK(fromCbor(fromHex("8301820203820405")) == VariantArray{1, VariantArray{2, 3}, VariantArray{4, 5}}); - CHECK(fromCbor(fromHex("98190102030405060708090a0b0c0d0e0f101112131415161718181819")) == VariantArray{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25}); - CHECK(fromCbor(fromHex("a0")) == VariantMap{}); - CHECK(fromCbor(fromHex("a26161016162820203")) == VariantMap{{"a", 1}, {"b", VariantArray{2, 3}}}); - CHECK(fromCbor(fromHex("826161a161626163")) == VariantArray{"a", VariantMap{{"b", "c"}}}); - CHECK(fromCbor(fromHex("a56161614161626142616361436164614461656145")) == VariantMap{{"a", "A"}, {"b", "B"}, {"c", "C"}, {"d", "D"}, {"e", "E"}}); - CHECK(fromCbor(fromHex("7f657374726561646d696e67ff")) == Variant("streaming")); - CHECK(fromCbor(fromHex("9fff")) == VariantArray{}); - CHECK(fromCbor(fromHex("9f018202039f0405ffff")) == VariantArray{1, VariantArray{2, 3}, VariantArray{4, 5}}); - CHECK(fromCbor(fromHex("9f01820203820405ff")) == VariantArray{1, VariantArray{2, 3}, VariantArray{4, 5}}); - CHECK(fromCbor(fromHex("83018202039f0405ff")) == VariantArray{1, VariantArray{2, 3}, VariantArray{4, 5}}); - CHECK(fromCbor(fromHex("83019f0203ff820405")) == VariantArray{1, VariantArray{2, 3}, VariantArray{4, 5}}); - CHECK(fromCbor(fromHex("9f0102030405060708090a0b0c0d0e0f101112131415161718181819ff")) == VariantArray{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25}); - CHECK(fromCbor(fromHex("bf61610161629f0203ffff")) == VariantMap{{"a", 1}, {"b", VariantArray{2, 3}}}); - CHECK(fromCbor(fromHex("826161bf61626163ff")) == VariantArray{"a", VariantMap{{"b", "c"}}}); - CHECK(fromCbor(fromHex("bf6346756ef563416d7421ff")) == VariantMap{{"Fun", true}, {"Amt", -2}}); + + SECTION("decodes a Variant from CBOR") { + // See https://datatracker.ietf.org/doc/html/rfc8949#section-appendix.a + CHECK(fromCbor(fromHex("00")) == Variant(0)); + CHECK(fromCbor(fromHex("01")) == Variant(1)); + CHECK(fromCbor(fromHex("0a")) == Variant(10)); + CHECK(fromCbor(fromHex("17")) == Variant(23)); + CHECK(fromCbor(fromHex("1818")) == Variant(24)); + CHECK(fromCbor(fromHex("1819")) == Variant(25)); + CHECK(fromCbor(fromHex("1864")) == Variant(100)); + CHECK(fromCbor(fromHex("1903e8")) == Variant(1000)); + CHECK(fromCbor(fromHex("1a000f4240")) == Variant(1000000)); + CHECK(fromCbor(fromHex("1b000000e8d4a51000")) == Variant(1000000000000ull)); + CHECK(fromCbor(fromHex("1bffffffffffffffff")) == Variant(18446744073709551615ull)); + CHECK(fromCbor(fromHex("3b7fffffffffffffff")) == Variant(-9223372036854775807ll - 1)); + CHECK(fromCbor(fromHex("20")) == Variant(-1)); + CHECK(fromCbor(fromHex("29")) == Variant(-10)); + CHECK(fromCbor(fromHex("3863")) == Variant(-100)); + CHECK(fromCbor(fromHex("3903e7")) == Variant(-1000)); + CHECK(fromCbor(fromHex("f90000")) == Variant(0.0)); + CHECK(fromCbor(fromHex("f98000")) == Variant(-0.0)); + CHECK(fromCbor(fromHex("f93c00")) == Variant(1.0)); + CHECK(fromCbor(fromHex("fb3ff199999999999a")) == Variant(1.1)); + CHECK(fromCbor(fromHex("f93e00")) == Variant(1.5)); + CHECK(fromCbor(fromHex("f97bff")) == Variant(65504.0)); + CHECK(fromCbor(fromHex("fa47c35000")) == Variant(100000.0)); + CHECK(fromCbor(fromHex("fa7f7fffff")) == Variant(3.4028234663852886e+38)); + CHECK(fromCbor(fromHex("fb7e37e43c8800759c")) == Variant(1.0e+300)); + CHECK(fromCbor(fromHex("f90001")) == Variant(5.960464477539063e-8)); + CHECK(fromCbor(fromHex("f90400")) == Variant(0.00006103515625)); + CHECK(fromCbor(fromHex("f9c400")) == Variant(-4.0)); + CHECK(fromCbor(fromHex("fbc010666666666666")) == Variant(-4.1)); + CHECK(fromCbor(fromHex("f97c00")) == Variant(INFINITY)); + CHECK(std::isnan(fromCbor(fromHex("f97e00")).asDouble())); + CHECK(fromCbor(fromHex("f9fc00")) == Variant(-INFINITY)); + CHECK(fromCbor(fromHex("fa7f800000")) == Variant(INFINITY)); + CHECK(std::isnan(fromCbor(fromHex("fa7fc00000")).asDouble())); + CHECK(fromCbor(fromHex("faff800000")) == Variant(-INFINITY)); + CHECK(fromCbor(fromHex("fb7ff0000000000000")) == Variant(INFINITY)); + CHECK(std::isnan(fromCbor(fromHex("fb7ff8000000000000")).asDouble())); + CHECK(fromCbor(fromHex("fbfff0000000000000")) == Variant(-INFINITY)); + CHECK(fromCbor(fromHex("f4")) == Variant(false)); + CHECK(fromCbor(fromHex("f5")) == Variant(true)); + CHECK(fromCbor(fromHex("f6")) == Variant()); + CHECK(fromCbor(fromHex("c074323031332d30332d32315432303a30343a30305a")) == Variant("2013-03-21T20:04:00Z")); + CHECK(fromCbor(fromHex("c11a514b67b0")) == Variant(1363896240)); + CHECK(fromCbor(fromHex("c1fb41d452d9ec200000")) == Variant(1363896240.5)); + CHECK(fromCbor(fromHex("d82076687474703a2f2f7777772e6578616d706c652e636f6d")) == Variant("http://www.example.com")); + CHECK(fromCbor(fromHex("40")) == Variant(Buffer())); + CHECK(fromCbor(fromHex("4401020304")) == Variant(Buffer("\x01\x02\x03\x04", 4))); + CHECK(fromCbor(fromHex("60")) == Variant("")); + CHECK(fromCbor(fromHex("6161")) == Variant("a")); + CHECK(fromCbor(fromHex("6449455446")) == Variant("IETF")); + CHECK(fromCbor(fromHex("62225c")) == Variant("\"\\")); + CHECK(fromCbor(fromHex("62c3bc")) == Variant("\u00fc")); + CHECK(fromCbor(fromHex("63e6b0b4")) == Variant("\u6c34")); + CHECK(fromCbor(fromHex("80")) == VariantArray{}); + CHECK(fromCbor(fromHex("83010203")) == VariantArray{1, 2, 3}); + CHECK(fromCbor(fromHex("8301820203820405")) == VariantArray{1, VariantArray{2, 3}, VariantArray{4, 5}}); + CHECK(fromCbor(fromHex("98190102030405060708090a0b0c0d0e0f101112131415161718181819")) == VariantArray{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25}); + CHECK(fromCbor(fromHex("a0")) == VariantMap{}); + CHECK(fromCbor(fromHex("a26161016162820203")) == VariantMap{{"a", 1}, {"b", VariantArray{2, 3}}}); + CHECK(fromCbor(fromHex("826161a161626163")) == VariantArray{"a", VariantMap{{"b", "c"}}}); + CHECK(fromCbor(fromHex("a56161614161626142616361436164614461656145")) == VariantMap{{"a", "A"}, {"b", "B"}, {"c", "C"}, {"d", "D"}, {"e", "E"}}); + CHECK(fromCbor(fromHex("5f42010243030405ff")) == Variant(Buffer("\x01\x02\x03\x04\x05", 5))); + CHECK(fromCbor(fromHex("7f657374726561646d696e67ff")) == Variant("streaming")); + CHECK(fromCbor(fromHex("9fff")) == VariantArray{}); + CHECK(fromCbor(fromHex("9f018202039f0405ffff")) == VariantArray{1, VariantArray{2, 3}, VariantArray{4, 5}}); + CHECK(fromCbor(fromHex("9f01820203820405ff")) == VariantArray{1, VariantArray{2, 3}, VariantArray{4, 5}}); + CHECK(fromCbor(fromHex("83018202039f0405ff")) == VariantArray{1, VariantArray{2, 3}, VariantArray{4, 5}}); + CHECK(fromCbor(fromHex("83019f0203ff820405")) == VariantArray{1, VariantArray{2, 3}, VariantArray{4, 5}}); + CHECK(fromCbor(fromHex("9f0102030405060708090a0b0c0d0e0f101112131415161718181819ff")) == VariantArray{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25}); + CHECK(fromCbor(fromHex("bf61610161629f0203ffff")) == VariantMap{{"a", 1}, {"b", VariantArray{2, 3}}}); + CHECK(fromCbor(fromHex("826161bf61626163ff")) == VariantArray{"a", VariantMap{{"b", "c"}}}); + CHECK(fromCbor(fromHex("bf6346756ef563416d7421ff")) == VariantMap{{"Fun", true}, {"Amt", -2}}); + } + + SECTION("parses a map with non-string keys as an array of key-value pairs") { + CHECK(fromCbor(fromHex("a2616101026162")) == VariantArray{VariantArray{"a", 1}, VariantArray{2, "b"}}); // {"a": 1, 2: "b"} + CHECK(fromCbor(fromHex("a181018102")) == VariantArray{VariantArray{VariantArray{1}, VariantArray{2}}}); // {[1]: [2]} + } } } diff --git a/wiring/inc/spark_wiring_variant.h b/wiring/inc/spark_wiring_variant.h index 0c2c54b9e5..7f759d6fe1 100644 --- a/wiring/inc/spark_wiring_variant.h +++ b/wiring/inc/spark_wiring_variant.h @@ -978,6 +978,11 @@ struct Variant::ConvertToVisitor { return Buffer(val.c_str(), val.length()); } + Buffer operator()(const Buffer& val) { + ok = true; + return val; + } + template Buffer operator()(const SourceT& val) { return Buffer(); diff --git a/wiring/src/spark_wiring_variant.cpp b/wiring/src/spark_wiring_variant.cpp index 6cb5bc2ad4..17f717dbe1 100644 --- a/wiring/src/spark_wiring_variant.cpp +++ b/wiring/src/spark_wiring_variant.cpp @@ -68,7 +68,7 @@ class DecodingStream { int read(char* data, size_t size) { size_t n = stream_.readBytes(data, size); if (n != size) { - return Error::IO; + return Error::END_OF_STREAM; } return 0; } @@ -143,12 +143,16 @@ struct CborHead { int detail; }; -int appendKeyValue(VariantArray& arr, Variant key, Variant val) { - if (!arr.reserve(arr.size() + 2)) { +int appendKeyValueArray(VariantArray& arr, Variant key, Variant val) { + VariantArray arr2; + if (!arr2.reserve(2)) { + return Error::NO_MEMORY; + } + arr2.append(std::move(key)); + arr2.append(std::move(val)); + if (!arr.append(Variant(std::move(arr2)))) { return Error::NO_MEMORY; } - arr.append(std::move(key)); - arr.append(std::move(val)); return 0; } @@ -474,15 +478,15 @@ int decodeFromCbor(DecodingStream& stream, const CborHead& head, Variant& var) { CHECK(decodeFromCbor(stream, h, v)); if (cont.isMap()) { if (!k.isString()) { - // VariantMap can only contain string keys. If any of the keys is not a string, - // the map is parsed as an array of key-value pairs + // VariantMap can only contain string keys. Convert the map to an array of + // key-value pairs VariantArray arr; int capacity = (len < 0) ? (cont.size() + 1) : len; if (!arr.reserve(capacity)) { return Error::NO_MEMORY; } for (auto& entry: cont.asMap()) { - CHECK(appendKeyValue(arr, std::move(entry.first), std::move(entry.second))); + CHECK(appendKeyValueArray(arr, entry.first, std::move(entry.second))); // Can't move the key } cont = std::move(arr); } else if (!cont.asMap().set(std::move(k.asString()), std::move(v))) { @@ -490,7 +494,7 @@ int decodeFromCbor(DecodingStream& stream, const CborHead& head, Variant& var) { } } if (cont.isArray()) { - CHECK(appendKeyValue(cont.asArray(), std::move(k), std::move(v))); + CHECK(appendKeyValueArray(cont.asArray(), std::move(k), std::move(v))); } } var = std::move(cont); From 5ac9dc29ebf1d4ff4209f14c4efe4e637baef8a5 Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Tue, 27 Aug 2024 23:41:23 +0200 Subject: [PATCH 34/45] Convert the buffer to a hex string when printing a Variant --- test/unit_tests/cellular/CMakeLists.txt | 1 + test/unit_tests/services/logging/CMakeLists.txt | 1 + test/unit_tests/system/CMakeLists.txt | 1 + test/unit_tests/wiring/variant.cpp | 3 +++ wiring/src/spark_wiring_print.cpp | 4 ++++ 5 files changed, 10 insertions(+) diff --git a/test/unit_tests/cellular/CMakeLists.txt b/test/unit_tests/cellular/CMakeLists.txt index 26af446db2..95bafe7e99 100644 --- a/test/unit_tests/cellular/CMakeLists.txt +++ b/test/unit_tests/cellular/CMakeLists.txt @@ -17,6 +17,7 @@ add_executable( ${target_name} ${DEVICE_OS_DIR}/wiring/src/spark_wiring_variant.cpp ${DEVICE_OS_DIR}/wiring/src/spark_wiring_json.cpp ${DEVICE_OS_DIR}/wiring/src/spark_wiring_string.cpp + ${DEVICE_OS_DIR}/wiring/src/spark_wiring_buffer.cpp ${DEVICE_OS_DIR}/wiring/src/string_convert.cpp ${DEVICE_OS_DIR}/hal/network/ncp/cellular/network_config_db.cpp ${DEVICE_OS_DIR}/hal/shared/cellular_sig_perc_mapping.cpp diff --git a/test/unit_tests/services/logging/CMakeLists.txt b/test/unit_tests/services/logging/CMakeLists.txt index 266605955d..52c4d7b699 100644 --- a/test/unit_tests/services/logging/CMakeLists.txt +++ b/test/unit_tests/services/logging/CMakeLists.txt @@ -5,6 +5,7 @@ add_executable( ${target_name} ${DEVICE_OS_DIR}/wiring/src/spark_wiring_logging.cpp ${DEVICE_OS_DIR}/wiring/src/spark_wiring_json.cpp ${DEVICE_OS_DIR}/wiring/src/spark_wiring_string.cpp + ${DEVICE_OS_DIR}/wiring/src/spark_wiring_buffer.cpp ${DEVICE_OS_DIR}/wiring/src/spark_wiring_print.cpp ${DEVICE_OS_DIR}/wiring/src/string_convert.cpp ${DEVICE_OS_DIR}/services/src/logging.cpp diff --git a/test/unit_tests/system/CMakeLists.txt b/test/unit_tests/system/CMakeLists.txt index dada777c89..e999088cc4 100644 --- a/test/unit_tests/system/CMakeLists.txt +++ b/test/unit_tests/system/CMakeLists.txt @@ -10,6 +10,7 @@ add_executable( ${target_name} ${DEVICE_OS_DIR}/hal/src/gcc/interrupts_hal.cpp ${DEVICE_OS_DIR}/services/src/jsmn.c ${DEVICE_OS_DIR}/wiring/src/spark_wiring_string.cpp + ${DEVICE_OS_DIR}/wiring/src/spark_wiring_buffer.cpp ${DEVICE_OS_DIR}/wiring/src/spark_wiring_print.cpp ${DEVICE_OS_DIR}/wiring/src/string_convert.cpp ${DEVICE_OS_DIR}/system/src/system_utilities.cpp diff --git a/test/unit_tests/wiring/variant.cpp b/test/unit_tests/wiring/variant.cpp index a6d4ffdd07..0a3e4e5275 100644 --- a/test/unit_tests/wiring/variant.cpp +++ b/test/unit_tests/wiring/variant.cpp @@ -331,6 +331,9 @@ TEST_CASE("Variant") { v = "abc"; CHECK(v.toJSON() == "\"abc\""); + v = Buffer("\x01\x02\x03", 3); + CHECK(v.toJSON() == "\"010203\""); + v.append(123); v.append("abc"); CHECK(v.toJSON() == "[123,\"abc\"]"); diff --git a/wiring/src/spark_wiring_print.cpp b/wiring/src/spark_wiring_print.cpp index 1944cffac2..c560220831 100644 --- a/wiring/src/spark_wiring_print.cpp +++ b/wiring/src/spark_wiring_print.cpp @@ -71,6 +71,10 @@ void writeVariant(const Variant& var, JSONStreamWriter& writer) { writer.value(var.value()); break; } + case Variant::BUFFER: { + writer.value(var.value().toHex()); + break; + } case Variant::ARRAY: { writer.beginArray(); for (auto& v: var.value()) { From 0bbb4a9054017b344045f6035d498fdd4dd65d59 Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Wed, 28 Aug 2024 12:20:19 +0200 Subject: [PATCH 35/45] Update docs --- wiring/inc/spark_wiring_variant.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/wiring/inc/spark_wiring_variant.h b/wiring/inc/spark_wiring_variant.h index 7f759d6fe1..876dbbbebe 100644 --- a/wiring/inc/spark_wiring_variant.h +++ b/wiring/inc/spark_wiring_variant.h @@ -393,7 +393,11 @@ class Variant { * to or from string is performed. When converted to a string, boolean values are represented * as "true" or "false". * - * - For the null, array and map types, only a trivial conversion to the same type is defined. + * - A `String` can be converted to a `Buffer` but only a trivial conversion to a value of its + * own type is defined for `Buffer`. + * + * - For the null, array and map types, only a trivial conversion to the respective type is + * defined. * * - If no conversion is defined for the current and target types, a default-constructed value * of the target type is returned. @@ -668,6 +672,11 @@ class Variant { /** * Convert the variant to JSON. * + * Note that values of certain types supported by `Variant`, such as `Buffer`, cannot normally be + * represented in JSON. If the variant contains a value of such a type, it is not guaranteed that a + * backward conversion from a JSON string returned by this method would produce a variant equal to + * the original variant. + * * @return JSON document. */ String toJSON() const; From 6052681dfaaf0c6f9b739c87659ecc374ce162d0 Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Thu, 29 Aug 2024 18:59:19 +0200 Subject: [PATCH 36/45] Add integration tests --- .../communication/events/events.cpp | 56 ++++++++++++++----- .../communication/events/events.spec.js | 23 +++++++- .../communication/ledger/ledger.cpp | 36 ++++++++++++ .../communication/ledger/ledger.spec.js | 25 +++++++++ 4 files changed, 124 insertions(+), 16 deletions(-) diff --git a/user/tests/integration/communication/events/events.cpp b/user/tests/integration/communication/events/events.cpp index f583e20c5a..3dac555a9d 100644 --- a/user/tests/integration/communication/events/events.cpp +++ b/user/tests/integration/communication/events/events.cpp @@ -4,28 +4,37 @@ namespace { String eventName; -String eventData; +Variant eventData; ContentType eventContentType = ContentType(); bool eventReceived = false; +void clearReceivedEvent() { + eventName = String(); + eventData = Variant(); + eventContentType = ContentType(); + eventReceived = false; +} + void eventHandler(const char* name, const char* data, size_t size, ContentType type) { + clearReceivedEvent(); eventName = name; - eventData = String(data, size); + eventData = Buffer(data, size); eventContentType = type; eventReceived = true; } -void clearReceivedEvent() { - eventName = String(); - eventData = String(); - eventContentType = ContentType(); - eventReceived = false; +void eventHandler2(const char* name, Variant data) { + clearReceivedEvent(); + eventName = name; + eventData = std::move(data); + eventReceived = true; } } // namespace test(connect) { Particle.subscribe(System.deviceID() + "/my_event", eventHandler); + Particle.subscribe(System.deviceID() + "/my_event2", eventHandler2); Particle.connect(); assertTrue(waitFor(Particle.connected, 60000)); } @@ -46,9 +55,16 @@ test(verify_max_event_data_size) { assertTrue((bool)Particle.publish("my_event", data, PRIVATE | WITH_ACK)); } -test(device_to_cloud_event_with_content_type) { - uint8_t data[] = { 0x92, 0xb2, 0xd1, 0x00, 0x23, 0x8c, 0x49, 0x0b, 0xd6, 0xe7, 0xa8, 0x90, 0x50, 0xdb, 0xc5, 0xbb, 0xe5, 0x3f, 0xf3, 0x29, 0xb2, 0x4c, 0xef, 0xad, 0xad, 0xd8, 0x10, 0xd9, 0xc3, 0xa4, 0xf1, 0xb0, 0xe7, 0x74, 0x8e, 0x7e, 0x2e, 0xcf, 0x48, 0xbe, 0x4d, 0xd3, 0xae, 0x08, 0x36, 0x8f, 0x76, 0xa8, 0xd5, 0x50, 0xec, 0x13, 0x9d, 0x5b, 0xca, 0x62, 0x4e, 0x3c, 0x6b, 0x3c, 0xbc, 0x75, 0x85, 0x65, 0x35, 0x6c, 0x00, 0xaf, 0xee, 0x12, 0xf2, 0xbd, 0x3f, 0xf2, 0x27, 0x00, 0xdc, 0x4c, 0xc6, 0xfa, 0x02, 0x16, 0x8e, 0xe5, 0xa1, 0xe6, 0xe9, 0x40, 0x4a, 0x71, 0xd1, 0x7d, 0xa5, 0xa6, 0xb7, 0x8d, 0x7d, 0x47, 0x5f, 0xf2 }; - assertTrue((bool)Particle.publish("my_event", (const char*)data, sizeof(data), ContentType::BINARY, WITH_ACK)); +test(publish_device_to_cloud_event_with_content_type) { + auto data = Buffer::fromHex("92b2d100238c490bd6e7a89050dbc5bbe53ff329b24cefadadd810d9c3a4f1b0e7748e7e2ecf48be4dd3ae08368f76a8d550ec139d5bca624e3c6b3cbc758565356c00afee12f2bd3ff22700dc4cc6fa02168ee5a1e6e9404a71d17da5a6b78d7d475ff2"); + assertTrue((bool)Particle.publish("my_event", data.data(), data.size(), ContentType::BINARY, WITH_ACK)); +} + +test(publish_device_to_cloud_event_as_variant) { + Variant v; + v["a"] = 123; + v["b"] = Buffer::fromHex("6e7200463f1bb774472f"); + assertTrue((bool)Particle.publish("my_event", v, WITH_ACK)); } test(publish_cloud_to_device_event_with_content_type) { @@ -59,9 +75,23 @@ test(validate_cloud_to_device_event_with_content_type) { assertTrue(waitFor([]() { return eventReceived; }, 10000)); - uint8_t expectedData[] = { 0xcb, 0xdf, 0x43, 0x83, 0x00, 0x86, 0x31, 0x72, 0x8b, 0xaf, 0x35, 0xc2, 0xaa, 0xae, 0x5d, 0x2e, 0x77, 0x76, 0x91, 0xb1, 0x31, 0xa5, 0xf1, 0x05, 0x1d, 0x7f, 0xc1, 0x47, 0xa8, 0x1f, 0x2a, 0x90, 0xe5, 0x75, 0x30, 0x9d, 0xdc, 0x28, 0x90, 0x68, 0x8b, 0xb8, 0x6e, 0x6e, 0x85, 0x14, 0x0d, 0x95, 0xc0, 0x64, 0xfd, 0xf3, 0xce, 0x3d, 0xfb, 0x45, 0xa3, 0xa7, 0xfe, 0x3d, 0xcf, 0x94, 0xd8, 0x69, 0xcb, 0x21, 0x39, 0x2c, 0x9a, 0xc9, 0xbb, 0x5b, 0xcb, 0x2d, 0xd9, 0x43, 0xb5, 0xbe, 0x21, 0xdd, 0x3b, 0xe1, 0x8c, 0x87, 0x64, 0x68, 0x00, 0x4d, 0x98, 0x1e, 0x6a, 0xe0, 0x2a, 0x42, 0xe8, 0x05, 0xb9, 0x89, 0xef, 0xcd }; + auto expectedData = Buffer::fromHex("cbdf4383008631728baf35c2aaae5d2e777691b131a5f1051d7fc147a81f2a90e575309ddc2890688bb86e6e85140d95c064fdf3ce3dfb45a3a7fe3dcf94d869cb21392c9ac9bb5bcb2dd943b5be21dd3be18c876468004d981e6ae02a42e805b989efcd"); assertTrue(eventName == System.deviceID() + "/my_event"); - assertTrue(eventData.length() == sizeof(expectedData)); - assertTrue(std::memcmp(eventData.c_str(), expectedData, sizeof(expectedData)) == 0); + assertTrue(eventData.asBuffer() == expectedData); assertTrue(eventContentType == ContentType::BINARY); } + +test(publish_cloud_to_device_event_with_cbor_data) { + clearReceivedEvent(); +} + +test(validate_cloud_to_device_event_with_cbor_data) { + assertTrue(waitFor([]() { + return eventReceived; + }, 10000)); + assertTrue(eventName == System.deviceID() + "/my_event2"); + Variant v; + v["c"] = 456; + v["d"] = Buffer::fromHex("921bff008d91814e789b"); + assertTrue(eventData == v); +} diff --git a/user/tests/integration/communication/events/events.spec.js b/user/tests/integration/communication/events/events.spec.js index 2a667db27d..00cd1eaafc 100644 --- a/user/tests/integration/communication/events/events.spec.js +++ b/user/tests/integration/communication/events/events.spec.js @@ -50,16 +50,24 @@ test('verify_max_event_data_size', async function() { expect(data).to.equal(str.slice(0, maxEventDataSize)); }); -test('device_to_cloud_event_with_content_type', async function() { - const expectedData = Buffer.from([0x92, 0xb2, 0xd1, 0x00, 0x23, 0x8c, 0x49, 0x0b, 0xd6, 0xe7, 0xa8, 0x90, 0x50, 0xdb, 0xc5, 0xbb, 0xe5, 0x3f, 0xf3, 0x29, 0xb2, 0x4c, 0xef, 0xad, 0xad, 0xd8, 0x10, 0xd9, 0xc3, 0xa4, 0xf1, 0xb0, 0xe7, 0x74, 0x8e, 0x7e, 0x2e, 0xcf, 0x48, 0xbe, 0x4d, 0xd3, 0xae, 0x08, 0x36, 0x8f, 0x76, 0xa8, 0xd5, 0x50, 0xec, 0x13, 0x9d, 0x5b, 0xca, 0x62, 0x4e, 0x3c, 0x6b, 0x3c, 0xbc, 0x75, 0x85, 0x65, 0x35, 0x6c, 0x00, 0xaf, 0xee, 0x12, 0xf2, 0xbd, 0x3f, 0xf2, 0x27, 0x00, 0xdc, 0x4c, 0xc6, 0xfa, 0x02, 0x16, 0x8e, 0xe5, 0xa1, 0xe6, 0xe9, 0x40, 0x4a, 0x71, 0xd1, 0x7d, 0xa5, 0xa6, 0xb7, 0x8d, 0x7d, 0x47, 0x5f, 0xf2]); +test('publish_device_to_cloud_event_with_content_type', async function() { + const expectedData = Buffer.from('92b2d100238c490bd6e7a89050dbc5bbe53ff329b24cefadadd810d9c3a4f1b0e7748e7e2ecf48be4dd3ae08368f76a8d550ec139d5bca624e3c6b3cbc758565356c00afee12f2bd3ff22700dc4cc6fa02168ee5a1e6e9404a71d17da5a6b78d7d475ff2', 'hex'); let d = await this.particle.receiveEvent('my_event'); d = parseDataUri(d); expect(d.type).to.equal('application/octet-stream'); expect(d.data.equals(expectedData)).to.be.true; }); +test('publish_device_to_cloud_event_as_variant', async function() { + const expectedData = Buffer.from('a26161187b61624a6e7200463f1bb774472f', 'hex'); + let d = await this.particle.receiveEvent('my_event'); + d = parseDataUri(d); + expect(d.type).to.equal('application/cbor'); + expect(d.data.equals(expectedData)).to.be.true; +}); + test('publish_cloud_to_device_event_with_content_type', async function() { - const data = Buffer.from([0xcb, 0xdf, 0x43, 0x83, 0x00, 0x86, 0x31, 0x72, 0x8b, 0xaf, 0x35, 0xc2, 0xaa, 0xae, 0x5d, 0x2e, 0x77, 0x76, 0x91, 0xb1, 0x31, 0xa5, 0xf1, 0x05, 0x1d, 0x7f, 0xc1, 0x47, 0xa8, 0x1f, 0x2a, 0x90, 0xe5, 0x75, 0x30, 0x9d, 0xdc, 0x28, 0x90, 0x68, 0x8b, 0xb8, 0x6e, 0x6e, 0x85, 0x14, 0x0d, 0x95, 0xc0, 0x64, 0xfd, 0xf3, 0xce, 0x3d, 0xfb, 0x45, 0xa3, 0xa7, 0xfe, 0x3d, 0xcf, 0x94, 0xd8, 0x69, 0xcb, 0x21, 0x39, 0x2c, 0x9a, 0xc9, 0xbb, 0x5b, 0xcb, 0x2d, 0xd9, 0x43, 0xb5, 0xbe, 0x21, 0xdd, 0x3b, 0xe1, 0x8c, 0x87, 0x64, 0x68, 0x00, 0x4d, 0x98, 0x1e, 0x6a, 0xe0, 0x2a, 0x42, 0xe8, 0x05, 0xb9, 0x89, 0xef, 0xcd]); + const data = Buffer.from('cbdf4383008631728baf35c2aaae5d2e777691b131a5f1051d7fc147a81f2a90e575309ddc2890688bb86e6e85140d95c064fdf3ce3dfb45a3a7fe3dcf94d869cb21392c9ac9bb5bcb2dd943b5be21dd3be18c876468004d981e6ae02a42e805b989efcd', 'hex'); await this.particle.apiClient.instance.publishEvent({ name: `${deviceId}/my_event`, data: `data:application/octet-stream;base64,${data.toString('base64')}`, @@ -70,3 +78,12 @@ test('publish_cloud_to_device_event_with_content_type', async function() { test('validate_cloud_to_device_event_with_content_type', async function() { // See events.cpp }); + +test('publish_cloud_to_device_event_with_cbor_data', async function() { + const data = Buffer.from('a261631901c861644a921bff008d91814e789b', 'hex'); + await this.particle.apiClient.instance.publishEvent({ + name: `${deviceId}/my_event2`, + data: `data:application/octet-stream;base64,${data.toString('base64')}`, + auth: this.particle.apiClient.token + }); +}); diff --git a/user/tests/integration/communication/ledger/ledger.cpp b/user/tests/integration/communication/ledger/ledger.cpp index d486180506..ef56388e15 100644 --- a/user/tests/integration/communication/ledger/ledger.cpp +++ b/user/tests/integration/communication/ledger/ledger.cpp @@ -180,4 +180,40 @@ test(07_validate_cloud_to_device_sync_large_size) { } } +test(08_sync_device_to_cloud_binary_data) { + g_synced = false; + auto ledger = Particle.ledger(DEVICE_TO_CLOUD_LEDGER); + ledger.onSync([](Ledger /* ledger */) { + g_synced = true; + }); + SCOPE_GUARD({ + ledger.onSync(nullptr); + }); + LedgerData d = { { "b", Buffer::fromHex("92b3409f87002be21dd272b5a4a5c9f4a960cdf08994dc50b4ab3b2ba27727abcfdde8a6e81566b711b912f2f4256716224d") } }; + ledger.set(d); + waitFor([]() { + return g_synced; + }, 60000); + assertTrue(g_synced); +} + +test(09_update_cloud_to_device_binary_data) { + g_synced = false; + auto ledger = Particle.ledger(CLOUD_TO_DEVICE_LEDGER); + ledger.onSync([](Ledger /* ledger */) { + g_synced = true; + }); +} + +test(10_validate_cloud_to_device_sync_binary_data) { + waitFor([]() { + return g_synced; + }, 60000); + auto ledger = Particle.ledger(CLOUD_TO_DEVICE_LEDGER); + ledger.onSync(nullptr); + assertTrue(g_synced); + auto d = ledger.get(); + assertTrue((d == LedgerData{ { "c", Buffer::fromHex("b7b5e8af68d10051c80f6e401d78aeea3421abac61983ab267f40534ae21fe6699347124afa89e1b3887ec71f5d9042acd28") } })); +} + #endif // Wiring_Ledger diff --git a/user/tests/integration/communication/ledger/ledger.spec.js b/user/tests/integration/communication/ledger/ledger.spec.js index 8b5344b85a..d8b1c99a8f 100644 --- a/user/tests/integration/communication/ledger/ledger.spec.js +++ b/user/tests/integration/communication/ledger/ledger.spec.js @@ -62,3 +62,28 @@ test('06_update_cloud_to_device_large_size', async function() { test('07_validate_cloud_to_device_sync_large_size', async function() { // See ledger.cpp }); + +test('08_sync_device_to_cloud_binary_data', async function() { + await delay(1000); + const { body: { instance } } = await api.getLedgerInstance({ ledgerName: DEVICE_TO_CLOUD_LEDGER, scopeValue: deviceId, org: ORG_ID, auth }); + expect(instance.data).to.deep.equal({ + b: { + _type: 'buffer', + _data: 'krNAn4cAK+Id0nK1pKXJ9KlgzfCJlNxQtKs7K6J3J6vP3eim6BVmtxG5EvL0JWcWIk0=' + } + }); +}); + +test('09_update_cloud_to_device_binary_data', async function() { + const data = { + c: { + _type: 'buffer', + _data: 't7Xor2jRAFHID25AHXiu6jQhq6xhmDqyZ/QFNK4h/maZNHEkr6ieGziH7HH12QQqzSg=' + } + }; + await api.setLedgerInstance({ ledgerName: CLOUD_TO_DEVICE_LEDGER, instance: { data }, scopeValue: deviceId, org: ORG_ID, auth }); +}); + +test('10_validate_cloud_to_device_sync_binary_data', async function() { + // See ledger.cpp +}); From 704920fc3f393e87dfe5dbc3f9346503fb331947 Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Fri, 30 Aug 2024 12:10:44 +0200 Subject: [PATCH 37/45] Minor bugfix; add API tests --- user/tests/wiring/api/cloud.cpp | 6 ++++++ wiring/inc/spark_wiring_cloud.h | 17 ++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/user/tests/wiring/api/cloud.cpp b/user/tests/wiring/api/cloud.cpp index 4c89a81f43..ce908e15ce 100644 --- a/user/tests/wiring/api/cloud.cpp +++ b/user/tests/wiring/api/cloud.cpp @@ -168,6 +168,9 @@ test(api_spark_publish) { API_COMPILE(Particle.publish("event", String("data"), ContentType::TEXT)); API_COMPILE(Particle.publish("event", String("data"), ContentType::TEXT, NO_ACK)); + + API_COMPILE(Particle.publish("event", Variant("data"))); + API_COMPILE(Particle.publish("event", Variant("data"), NO_ACK)); } test(api_spark_publish_vitals) { @@ -208,6 +211,9 @@ test(api_spark_subscribe) { void (*handlerWithContentType)(const char* name, const char* data, size_t size, ContentType type) = nullptr; API_COMPILE(Particle.subscribe("name", handlerWithContentType)); + + void (*handlerWithVariant)(const char* name, Variant data) = nullptr; + API_COMPILE(Particle.subscribe("name", handlerWithVariant)); } test(api_spark_sleep) { diff --git a/wiring/inc/spark_wiring_cloud.h b/wiring/inc/spark_wiring_cloud.h index 506263bb19..65647c707c 100644 --- a/wiring/inc/spark_wiring_cloud.h +++ b/wiring/inc/spark_wiring_cloud.h @@ -274,7 +274,7 @@ class CloudClass { inline particle::Future publish(const char *eventName, PublishFlags flags1, PublishFlags flags2 = PublishFlags()) { - return publish(eventName, NULL, flags1, flags2); + return publish(eventName, nullptr, DEFAULT_CLOUD_EVENT_TTL, flags1, flags2); } inline particle::Future publish(const char *eventName, const char *eventData, PublishFlags flags1, PublishFlags flags2 = PublishFlags()) @@ -282,6 +282,11 @@ class CloudClass { return publish(eventName, eventData, DEFAULT_CLOUD_EVENT_TTL, flags1, flags2); } + inline particle::Future publish(const char *eventName, const String& eventData, PublishFlags flags1, PublishFlags flags2 = PublishFlags()) + { + return publish(eventName, eventData.c_str(), DEFAULT_CLOUD_EVENT_TTL, flags1, flags2); + } + inline particle::Future publish(const char *eventName, const char *eventData, int ttl, PublishFlags flags1, PublishFlags flags2 = PublishFlags()) { return publish_event(eventName, eventData, eventData ? std::strlen(eventData) : 0, particle::ContentType::TEXT, ttl, flags1 | flags2); @@ -289,7 +294,9 @@ class CloudClass { particle::Future publish(const char* name); particle::Future publish(const char* name, const char* data); + particle::Future publish(const char* name, const String& data); particle::Future publish(const char* name, const char* data, int ttl); + particle::Future publish(const char* name, const String& data, int ttl); particle::Future publish(const char* name, const char* data, particle::ContentType type, PublishFlags flags = PublishFlags()) { return publish(name, data, std::strlen(data), type, flags); @@ -735,10 +742,18 @@ inline particle::Future CloudClass::publish(const char* name, const char* return publish(name, data, PUBLIC); } +inline particle::Future CloudClass::publish(const char* name, const String& data) { + return publish(name, data.c_str(), PUBLIC); +} + inline particle::Future CloudClass::publish(const char* name, const char* data, int ttl) { return publish(name, data, ttl, PUBLIC); } +inline particle::Future CloudClass::publish(const char* name, const String& data, int ttl) { + return publish(name, data.c_str(), ttl, PUBLIC); +} + inline bool CloudClass::subscribe(const char* name, EventHandler handler) { return subscribe(name, handler, ALL_DEVICES); } From 6dc30e87c0e0898e509c340851e6c42df46968bc Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Fri, 30 Aug 2024 15:04:15 +0200 Subject: [PATCH 38/45] Bugfixes --- user/tests/integration/communication/events/events.spec.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/user/tests/integration/communication/events/events.spec.js b/user/tests/integration/communication/events/events.spec.js index 00cd1eaafc..c74c42616d 100644 --- a/user/tests/integration/communication/events/events.spec.js +++ b/user/tests/integration/communication/events/events.spec.js @@ -83,7 +83,11 @@ test('publish_cloud_to_device_event_with_cbor_data', async function() { const data = Buffer.from('a261631901c861644a921bff008d91814e789b', 'hex'); await this.particle.apiClient.instance.publishEvent({ name: `${deviceId}/my_event2`, - data: `data:application/octet-stream;base64,${data.toString('base64')}`, + data: `data:application/cbor;base64,${data.toString('base64')}`, auth: this.particle.apiClient.token }); }); + +test('validate_cloud_to_device_event_with_cbor_data', async function() { + // See events.cpp +}); From b3b723b0e4827765d824136f2761edfec287dbc1 Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Fri, 30 Aug 2024 16:36:24 +0200 Subject: [PATCH 39/45] Minor fixes --- user/tests/integration/communication/events/events.cpp | 6 +++--- user/tests/integration/communication/events/events.spec.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/user/tests/integration/communication/events/events.cpp b/user/tests/integration/communication/events/events.cpp index 3dac555a9d..78b16df59a 100644 --- a/user/tests/integration/communication/events/events.cpp +++ b/user/tests/integration/communication/events/events.cpp @@ -15,7 +15,7 @@ void clearReceivedEvent() { eventReceived = false; } -void eventHandler(const char* name, const char* data, size_t size, ContentType type) { +void eventHandler1(const char* name, const char* data, size_t size, ContentType type) { clearReceivedEvent(); eventName = name; eventData = Buffer(data, size); @@ -33,7 +33,7 @@ void eventHandler2(const char* name, Variant data) { } // namespace test(connect) { - Particle.subscribe(System.deviceID() + "/my_event", eventHandler); + Particle.subscribe(System.deviceID() + "/my_event1", eventHandler1); Particle.subscribe(System.deviceID() + "/my_event2", eventHandler2); Particle.connect(); assertTrue(waitFor(Particle.connected, 60000)); @@ -76,7 +76,7 @@ test(validate_cloud_to_device_event_with_content_type) { return eventReceived; }, 10000)); auto expectedData = Buffer::fromHex("cbdf4383008631728baf35c2aaae5d2e777691b131a5f1051d7fc147a81f2a90e575309ddc2890688bb86e6e85140d95c064fdf3ce3dfb45a3a7fe3dcf94d869cb21392c9ac9bb5bcb2dd943b5be21dd3be18c876468004d981e6ae02a42e805b989efcd"); - assertTrue(eventName == System.deviceID() + "/my_event"); + assertTrue(eventName == System.deviceID() + "/my_event1"); assertTrue(eventData.asBuffer() == expectedData); assertTrue(eventContentType == ContentType::BINARY); } diff --git a/user/tests/integration/communication/events/events.spec.js b/user/tests/integration/communication/events/events.spec.js index c74c42616d..3b834dd76b 100644 --- a/user/tests/integration/communication/events/events.spec.js +++ b/user/tests/integration/communication/events/events.spec.js @@ -69,7 +69,7 @@ test('publish_device_to_cloud_event_as_variant', async function() { test('publish_cloud_to_device_event_with_content_type', async function() { const data = Buffer.from('cbdf4383008631728baf35c2aaae5d2e777691b131a5f1051d7fc147a81f2a90e575309ddc2890688bb86e6e85140d95c064fdf3ce3dfb45a3a7fe3dcf94d869cb21392c9ac9bb5bcb2dd943b5be21dd3be18c876468004d981e6ae02a42e805b989efcd', 'hex'); await this.particle.apiClient.instance.publishEvent({ - name: `${deviceId}/my_event`, + name: `${deviceId}/my_event1`, data: `data:application/octet-stream;base64,${data.toString('base64')}`, auth: this.particle.apiClient.token }); From 0cd195b844ba2843215656459a6fb563ac982043 Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Tue, 17 Sep 2024 14:49:20 +0200 Subject: [PATCH 40/45] Use a custom content type when publishing a Variant --- communication/inc/coap_defs.h | 6 ++++-- wiring/inc/spark_wiring_cloud.h | 3 ++- wiring/src/spark_wiring_cloud.cpp | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/communication/inc/coap_defs.h b/communication/inc/coap_defs.h index 86f8c54d70..1a570f8d74 100644 --- a/communication/inc/coap_defs.h +++ b/communication/inc/coap_defs.h @@ -109,7 +109,7 @@ PARTICLE_DEFINE_ENUM_COMPARISON_OPERATORS(CoapOption) enum class CoapContentFormat { // RFC 7252 - TEXT_PLAIN = 0, // text/plain; charset=utf-8 + TEXT_PLAIN = 0, // text/plain;charset=utf-8 APPLICATION_LINK_FORMAT = 40, APPLICATION_XML = 41, APPLICATION_OCTET_STREAM = 42, @@ -118,7 +118,9 @@ enum class CoapContentFormat { // https://www.iana.org/assignments/core-parameters/core-parameters.xhtml#content-formats IMAGE_JPEG = 22, IMAGE_PNG = 23, - APPLICATION_CBOR = 60 + APPLICATION_CBOR = 60, + // Vendor-specific formats + PARTICLE_JSON_AS_CBOR = 65001 // application/vnd.particle.json+cbor }; PARTICLE_DEFINE_ENUM_COMPARISON_OPERATORS(CoapContentFormat) diff --git a/wiring/inc/spark_wiring_cloud.h b/wiring/inc/spark_wiring_cloud.h index 65647c707c..10b1d66341 100644 --- a/wiring/inc/spark_wiring_cloud.h +++ b/wiring/inc/spark_wiring_cloud.h @@ -82,7 +82,8 @@ enum class ContentType: int { TEXT = (int)protocol::CoapContentFormat::TEXT_PLAIN, ///< `text/plain; charset=utf-8`. JPEG = (int)protocol::CoapContentFormat::IMAGE_JPEG, ///< `image/jpeg`. PNG = (int)protocol::CoapContentFormat::IMAGE_PNG, ///< `image/png`. - BINARY = (int)protocol::CoapContentFormat::APPLICATION_OCTET_STREAM ///< `application/octet-stream`. + BINARY = (int)protocol::CoapContentFormat::APPLICATION_OCTET_STREAM, ///< `application/octet-stream`. + JSON_AS_CBOR = (int)protocol::CoapContentFormat::PARTICLE_JSON_AS_CBOR ///< `application/vnd.particle.json+cbor` (vendor-specific). }; typedef void (*EventHandlerWithContentType)(const char* name, const char* data, size_t size, ContentType type); diff --git a/wiring/src/spark_wiring_cloud.cpp b/wiring/src/spark_wiring_cloud.cpp index 76d5144b60..bb8e85c370 100644 --- a/wiring/src/spark_wiring_cloud.cpp +++ b/wiring/src/spark_wiring_cloud.cpp @@ -159,7 +159,7 @@ Future CloudClass::publish(const char* name, const Variant& data, PublishF if (r < 0) { return Future((Error::Type)r); } - return publish(name, s.c_str(), s.length(), ContentType::CBOR, flags); + return publish(name, s.c_str(), s.length(), ContentType::JSON_AS_CBOR, flags); } int CloudClass::publishVitals(system_tick_t period_s_) { From 0eab19ea3696c4328215b7894702e0e32114ea95 Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Thu, 19 Sep 2024 16:14:26 +0200 Subject: [PATCH 41/45] Add a function for calculating the size of a Variant in CBOR format --- test/unit_tests/wiring/variant.cpp | 13 ++++++++++++ wiring/inc/spark_wiring_variant.h | 8 ++++++++ wiring/src/spark_wiring_variant.cpp | 32 +++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+) diff --git a/test/unit_tests/wiring/variant.cpp b/test/unit_tests/wiring/variant.cpp index 0a3e4e5275..c1d26251ca 100644 --- a/test/unit_tests/wiring/variant.cpp +++ b/test/unit_tests/wiring/variant.cpp @@ -510,4 +510,17 @@ TEST_CASE("Variant") { CHECK(fromCbor(fromHex("a181018102")) == VariantArray{VariantArray{VariantArray{1}, VariantArray{2}}}); // {[1]: [2]} } } + + SECTION("getCBORSize()") { + SECTION("returns the size of a Variant in CBOR format") { + Variant v = VariantMap{ + {"a", 1234}, + {"b", Buffer::fromHex("1234")}, + {"c", VariantArray{1, 2, 3, 4}} + }; + auto s = toCbor(v); + CHECK(test::toHex(s) == "a361611904d2616242123461638401020304"); + CHECK(getCBORSize(v) == s.size()); + } + } } diff --git a/wiring/inc/spark_wiring_variant.h b/wiring/inc/spark_wiring_variant.h index 876dbbbebe..2278fe82b4 100644 --- a/wiring/inc/spark_wiring_variant.h +++ b/wiring/inc/spark_wiring_variant.h @@ -1111,4 +1111,12 @@ int encodeToCBOR(const Variant& var, Print& stream); */ int decodeFromCBOR(Variant& var, Stream& stream); +/** + * Calculate the size of a Variant in CBOR format. + * + * @param var Variant. + * @return Size of CBOR data. + */ +size_t getCBORSize(const Variant& var); + } // namespace particle diff --git a/wiring/src/spark_wiring_variant.cpp b/wiring/src/spark_wiring_variant.cpp index 17f717dbe1..8e9fd3ceb5 100644 --- a/wiring/src/spark_wiring_variant.cpp +++ b/wiring/src/spark_wiring_variant.cpp @@ -36,6 +36,29 @@ namespace particle { namespace { +class NullOutputStream: public Print { +public: + explicit NullOutputStream() : + size_(0) { + } + + size_t write(uint8_t b) override { + return write(&b, 1); + } + + size_t write(const uint8_t* data, size_t size) override { + size_ += size; + return size; + } + + size_t size() const { + return size_; + } + +private: + size_t size_; +}; + class DecodingStream { public: explicit DecodingStream(Stream& stream) : @@ -884,4 +907,13 @@ int decodeFromCBOR(Variant& var, Stream& stream) { return 0; } +size_t getCBORSize(const Variant& var) { + NullOutputStream s; + int r = encodeToCBOR(var, s); + if (r < 0) { + return 0; // Shouldn't happen + } + return s.size(); +} + } // namespace particle From acc97dca9514dbafadd189c7fd661d1a425a2bef Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Thu, 19 Sep 2024 16:26:56 +0200 Subject: [PATCH 42/45] Add an alias for Variant --- wiring/inc/spark_wiring_cloud.h | 10 +++++++--- wiring/src/spark_wiring_cloud.cpp | 8 ++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/wiring/inc/spark_wiring_cloud.h b/wiring/inc/spark_wiring_cloud.h index 10b1d66341..764320d6ca 100644 --- a/wiring/inc/spark_wiring_cloud.h +++ b/wiring/inc/spark_wiring_cloud.h @@ -75,6 +75,8 @@ namespace particle { class Ledger; class Variant; +typedef Variant EventData; + /** * Content type. */ @@ -89,8 +91,10 @@ enum class ContentType: int { typedef void (*EventHandlerWithContentType)(const char* name, const char* data, size_t size, ContentType type); typedef std::function EventHandlerWithContentTypeFn; -typedef void (*EventHandlerWithVariant)(const char* name, Variant data); -typedef std::function EventHandlerWithVariantFn; +typedef void (*EventHandlerWithVariant)(const char* name, EventData data); +typedef std::function EventHandlerWithVariantFn; + +size_t getEventDataSize(const EventData& data); } // namespace particle @@ -311,7 +315,7 @@ class CloudClass { return publish_event(name, data, size, type, DEFAULT_CLOUD_EVENT_TTL, flags); } - particle::Future publish(const char* name, const particle::Variant& data, PublishFlags flags = PublishFlags()); + particle::Future publish(const char* name, const particle::EventData& data, PublishFlags flags = PublishFlags()); /** * @brief Publish vitals information diff --git a/wiring/src/spark_wiring_cloud.cpp b/wiring/src/spark_wiring_cloud.cpp index bb8e85c370..45e2a8909c 100644 --- a/wiring/src/spark_wiring_cloud.cpp +++ b/wiring/src/spark_wiring_cloud.cpp @@ -260,3 +260,11 @@ bool CloudClass::subscribe(const char* name, particle::EventHandlerWithVariantFn auto h = eventHandlerCast(subscribeWithVariantFunctionWrapper); return subscribeWithFlags(name, h, fnPtr, SUBSCRIBE_FLAG_CBOR_DATA); } + +namespace particle { + +size_t getEventDataSize(const EventData& data) { + return getCBORSize(data); +} + +} // namespace particle From 154c474bbe6b68f5770c0922f99a6c7efc6b8798 Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Thu, 19 Sep 2024 16:45:17 +0200 Subject: [PATCH 43/45] Minor refactoring --- wiring/inc/spark_wiring_cloud.h | 11 +++++------ wiring/src/spark_wiring_cloud.cpp | 5 +++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/wiring/inc/spark_wiring_cloud.h b/wiring/inc/spark_wiring_cloud.h index 764320d6ca..d6c520cfe0 100644 --- a/wiring/inc/spark_wiring_cloud.h +++ b/wiring/inc/spark_wiring_cloud.h @@ -84,8 +84,7 @@ enum class ContentType: int { TEXT = (int)protocol::CoapContentFormat::TEXT_PLAIN, ///< `text/plain; charset=utf-8`. JPEG = (int)protocol::CoapContentFormat::IMAGE_JPEG, ///< `image/jpeg`. PNG = (int)protocol::CoapContentFormat::IMAGE_PNG, ///< `image/png`. - BINARY = (int)protocol::CoapContentFormat::APPLICATION_OCTET_STREAM, ///< `application/octet-stream`. - JSON_AS_CBOR = (int)protocol::CoapContentFormat::PARTICLE_JSON_AS_CBOR ///< `application/vnd.particle.json+cbor` (vendor-specific). + BINARY = (int)protocol::CoapContentFormat::APPLICATION_OCTET_STREAM ///< `application/octet-stream`. }; typedef void (*EventHandlerWithContentType)(const char* name, const char* data, size_t size, ContentType type); @@ -294,7 +293,7 @@ class CloudClass { inline particle::Future publish(const char *eventName, const char *eventData, int ttl, PublishFlags flags1, PublishFlags flags2 = PublishFlags()) { - return publish_event(eventName, eventData, eventData ? std::strlen(eventData) : 0, particle::ContentType::TEXT, ttl, flags1 | flags2); + return publish_event(eventName, eventData, eventData ? std::strlen(eventData) : 0, static_cast(particle::ContentType::TEXT), ttl, flags1 | flags2); } particle::Future publish(const char* name); @@ -312,7 +311,7 @@ class CloudClass { } particle::Future publish(const char* name, const char* data, size_t size, particle::ContentType type, PublishFlags flags = PublishFlags()) { - return publish_event(name, data, size, type, DEFAULT_CLOUD_EVENT_TTL, flags); + return publish_event(name, data, size, static_cast(type), DEFAULT_CLOUD_EVENT_TTL, flags); } particle::Future publish(const char* name, const particle::EventData& data, PublishFlags flags = PublishFlags()); @@ -547,8 +546,8 @@ class CloudClass { static void call_wiring_event_handler(const void* param, const char *event_name, const char *data); - static particle::Future publish_event(const char* name, const char* data, size_t size, particle::ContentType type, - int ttl, PublishFlags flags); + static particle::Future publish_event(const char* name, const char* data, size_t size, int type, int ttl, + PublishFlags flags); bool subscribe_wiring(const char *eventName, wiring_event_handler_t handler, Spark_Subscription_Scope_TypeDef scope, const char *deviceID = NULL) { diff --git a/wiring/src/spark_wiring_cloud.cpp b/wiring/src/spark_wiring_cloud.cpp index 45e2a8909c..4db2fc0a48 100644 --- a/wiring/src/spark_wiring_cloud.cpp +++ b/wiring/src/spark_wiring_cloud.cpp @@ -128,7 +128,7 @@ bool CloudClass::register_function(cloud_function_t fn, void* data, const char* return spark_function(NULL, (user_function_int_str_t*)&desc, NULL); } -Future CloudClass::publish_event(const char* name, const char* data, size_t size, ContentType type, int ttl, +Future CloudClass::publish_event(const char* name, const char* data, size_t size, int type, int ttl, PublishFlags flags) { if (!connected()) { return Future(Error::INVALID_STATE); @@ -159,7 +159,8 @@ Future CloudClass::publish(const char* name, const Variant& data, PublishF if (r < 0) { return Future((Error::Type)r); } - return publish(name, s.c_str(), s.length(), ContentType::JSON_AS_CBOR, flags); + return publish_event(name, s.c_str(), s.length(), static_cast(protocol::CoapContentFormat::PARTICLE_JSON_AS_CBOR), + DEFAULT_CLOUD_EVENT_TTL, flags); } int CloudClass::publishVitals(system_tick_t period_s_) { From 47f82c50634cab0f0d617a5c82d84b0b1e144c91 Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Fri, 20 Sep 2024 12:32:24 +0200 Subject: [PATCH 44/45] Fix review comments --- user/tests/integration/communication/events/events.cpp | 10 +++++----- user/tests/wiring/api/cloud.cpp | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/user/tests/integration/communication/events/events.cpp b/user/tests/integration/communication/events/events.cpp index 78b16df59a..c0cc4bd677 100644 --- a/user/tests/integration/communication/events/events.cpp +++ b/user/tests/integration/communication/events/events.cpp @@ -4,13 +4,13 @@ namespace { String eventName; -Variant eventData; +EventData eventData; ContentType eventContentType = ContentType(); bool eventReceived = false; void clearReceivedEvent() { eventName = String(); - eventData = Variant(); + eventData = EventData(); eventContentType = ContentType(); eventReceived = false; } @@ -23,7 +23,7 @@ void eventHandler1(const char* name, const char* data, size_t size, ContentType eventReceived = true; } -void eventHandler2(const char* name, Variant data) { +void eventHandler2(const char* name, EventData data) { clearReceivedEvent(); eventName = name; eventData = std::move(data); @@ -61,7 +61,7 @@ test(publish_device_to_cloud_event_with_content_type) { } test(publish_device_to_cloud_event_as_variant) { - Variant v; + EventData v; v["a"] = 123; v["b"] = Buffer::fromHex("6e7200463f1bb774472f"); assertTrue((bool)Particle.publish("my_event", v, WITH_ACK)); @@ -90,7 +90,7 @@ test(validate_cloud_to_device_event_with_cbor_data) { return eventReceived; }, 10000)); assertTrue(eventName == System.deviceID() + "/my_event2"); - Variant v; + EventData v; v["c"] = 456; v["d"] = Buffer::fromHex("921bff008d91814e789b"); assertTrue(eventData == v); diff --git a/user/tests/wiring/api/cloud.cpp b/user/tests/wiring/api/cloud.cpp index ce908e15ce..13af316ce3 100644 --- a/user/tests/wiring/api/cloud.cpp +++ b/user/tests/wiring/api/cloud.cpp @@ -169,8 +169,8 @@ test(api_spark_publish) { API_COMPILE(Particle.publish("event", String("data"), ContentType::TEXT)); API_COMPILE(Particle.publish("event", String("data"), ContentType::TEXT, NO_ACK)); - API_COMPILE(Particle.publish("event", Variant("data"))); - API_COMPILE(Particle.publish("event", Variant("data"), NO_ACK)); + API_COMPILE(Particle.publish("event", EventData("data"))); + API_COMPILE(Particle.publish("event", EventData("data"), NO_ACK)); } test(api_spark_publish_vitals) { @@ -212,7 +212,7 @@ test(api_spark_subscribe) { void (*handlerWithContentType)(const char* name, const char* data, size_t size, ContentType type) = nullptr; API_COMPILE(Particle.subscribe("name", handlerWithContentType)); - void (*handlerWithVariant)(const char* name, Variant data) = nullptr; + void (*handlerWithVariant)(const char* name, EventData data) = nullptr; API_COMPILE(Particle.subscribe("name", handlerWithVariant)); } From 6eaf70c73811fab3ac688935fef0ddb0e3b8e034 Mon Sep 17 00:00:00 2001 From: Sergey Polyakov Date: Fri, 20 Sep 2024 17:54:47 +0200 Subject: [PATCH 45/45] Fix tests --- .../integration/communication/events/events.spec.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/user/tests/integration/communication/events/events.spec.js b/user/tests/integration/communication/events/events.spec.js index 3b834dd76b..f51dc70c5d 100644 --- a/user/tests/integration/communication/events/events.spec.js +++ b/user/tests/integration/communication/events/events.spec.js @@ -59,11 +59,15 @@ test('publish_device_to_cloud_event_with_content_type', async function() { }); test('publish_device_to_cloud_event_as_variant', async function() { - const expectedData = Buffer.from('a26161187b61624a6e7200463f1bb774472f', 'hex'); let d = await this.particle.receiveEvent('my_event'); - d = parseDataUri(d); - expect(d.type).to.equal('application/cbor'); - expect(d.data.equals(expectedData)).to.be.true; + d = JSON.parse(d); + expect(d).to.deep.equal({ + a: 123, + b: { + _type: 'buffer', + _data: Buffer.from('6e7200463f1bb774472f', 'hex').toString('base64') + } + }); }); test('publish_cloud_to_device_event_with_content_type', async function() {